25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-10 15:47:30 +01:00
xmpp.chapril.org-ejabberd/src/ejabberd_options.erl
2020-01-28 15:49:23 +01:00

767 lines
23 KiB
Erlang

%%%----------------------------------------------------------------------
%%% ejabberd, Copyright (C) 2002-2020 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).
-behaviour(ejabberd_config).
-export([opt_type/1, options/0, globals/0, doc/0]).
-ifdef(NEW_SQL_SCHEMA).
-define(USE_NEW_SQL_SCHEMA_DEFAULT, true).
-else.
-define(USE_NEW_SQL_SCHEMA_DEFAULT, false).
-endif.
-include_lib("kernel/include/inet.hrl").
%%%===================================================================
%%% API
%%%===================================================================
-spec opt_type(atom()) -> econf:validator().
opt_type(access_rules) ->
acl:validator(access_rules);
opt_type(acl) ->
acl:validator(acl);
opt_type(acme) ->
econf:options(
#{ca_url => econf:url(),
contact => econf:list_or_single(econf:binary("^[a-zA-Z]+:[^:]+$")),
auto => econf:bool(),
cert_type => econf:enum([ec, rsa])},
[unique, {return, map}]);
opt_type(allow_contrib_modules) ->
econf:bool();
opt_type(allow_multiple_connections) ->
econf:bool();
opt_type(anonymous_protocol) ->
econf:enum([sasl_anon, login_anon, both]);
opt_type(api_permissions) ->
ejabberd_access_permissions:validator();
opt_type(append_host_config) ->
opt_type(host_config);
opt_type(auth_cache_life_time) ->
econf:timeout(second, infinity);
opt_type(auth_cache_missed) ->
econf:bool();
opt_type(auth_cache_size) ->
econf:pos_int(infinity);
opt_type(auth_method) ->
econf:list_or_single(econf:db_type(ejabberd_auth));
opt_type(auth_password_format) ->
econf:enum([plain, scram]);
opt_type(auth_use_cache) ->
econf:bool();
opt_type(c2s_cafile) ->
econf:file();
opt_type(c2s_ciphers) ->
fun(L) when is_list(L) ->
(econf:and_then(
econf:list(econf:binary(), [unique]),
concat_binary($:)))(L);
(B) ->
(econf:binary())(B)
end;
opt_type(c2s_dhfile) ->
econf:file();
opt_type(c2s_protocol_options) ->
econf:and_then(
econf:list(econf:binary(), [unique]),
concat_binary($|));
opt_type(c2s_tls_compression) ->
econf:bool();
opt_type(ca_file) ->
econf:pem();
opt_type(cache_life_time) ->
econf:timeout(second, infinity);
opt_type(cache_missed) ->
econf:bool();
opt_type(cache_size) ->
econf:pos_int(infinity);
opt_type(captcha_cmd) ->
econf:file();
opt_type(captcha_host) ->
econf:binary();
opt_type(captcha_limit) ->
econf:pos_int(infinity);
opt_type(captcha_url) ->
econf:url();
opt_type(certfiles) ->
econf:list(econf:binary());
opt_type(cluster_backend) ->
econf:db_type(ejabberd_cluster);
opt_type(cluster_nodes) ->
econf:list(econf:atom(), [unique]);
opt_type(default_db) ->
econf:enum([mnesia, sql]);
opt_type(default_ram_db) ->
econf:enum([mnesia, sql, redis]);
opt_type(define_macro) ->
econf:any();
opt_type(disable_sasl_mechanisms) ->
econf:list_or_single(
econf:and_then(
econf:binary(),
fun str:to_upper/1));
opt_type(domain_balancing) ->
econf:map(
econf:domain(),
econf:options(
#{component_number => econf:int(2, 1000),
type => econf:enum([random, source, destination,
bare_source, bare_destination])},
[{required, [component_number]}, {return, map}, unique]),
[{return, map}]);
opt_type(ext_api_path_oauth) ->
econf:binary();
opt_type(ext_api_http_pool_size) ->
econf:pos_int();
opt_type(ext_api_url) ->
econf:url();
opt_type(ext_api_headers) ->
econf:binary();
opt_type(extauth_pool_name) ->
econf:binary();
opt_type(extauth_pool_size) ->
econf:pos_int();
opt_type(extauth_program) ->
econf:string();
opt_type(fqdn) ->
econf:list_or_single(econf:domain());
opt_type(hide_sensitive_log_data) ->
econf:bool();
opt_type(host_config) ->
econf:and_then(
econf:and_then(
econf:map(econf:domain(), econf:list(econf:any())),
fun econf:group_dups/1),
econf:map(
econf:enum(ejabberd_config:get_option(hosts)),
validator(),
[unique]));
opt_type(hosts) ->
econf:non_empty(econf:list(econf:domain(), [unique]));
opt_type(include_config_file) ->
econf:any();
opt_type(language) ->
econf:lang();
opt_type(ldap_backups) ->
econf:list(econf:domain(), [unique]);
opt_type(ldap_base) ->
econf:binary();
opt_type(ldap_deref_aliases) ->
econf:enum([never, searching, finding, always]);
opt_type(ldap_dn_filter) ->
econf:and_then(
econf:non_empty(
econf:map(
econf:ldap_filter(),
econf:list(econf:binary()))),
fun hd/1);
opt_type(ldap_encrypt) ->
econf:enum([tls, starttls, none]);
opt_type(ldap_filter) ->
econf:ldap_filter();
opt_type(ldap_password) ->
econf:binary();
opt_type(ldap_port) ->
econf:port();
opt_type(ldap_rootdn) ->
econf:binary();
opt_type(ldap_servers) ->
econf:list(econf:domain(), [unique]);
opt_type(ldap_tls_cacertfile) ->
econf:pem();
opt_type(ldap_tls_certfile) ->
econf:pem();
opt_type(ldap_tls_depth) ->
econf:non_neg_int();
opt_type(ldap_tls_verify) ->
econf:enum([hard, soft, false]);
opt_type(ldap_uids) ->
econf:either(
econf:list(
econf:and_then(
econf:binary(),
fun(U) -> {U, <<"%u">>} end)),
econf:map(econf:binary(), econf:binary(), [unique]));
opt_type(listen) ->
ejabberd_listener:validator();
opt_type(log_rotate_count) ->
econf:non_neg_int();
opt_type(log_rotate_size) ->
econf:pos_int(infinity);
opt_type(loglevel) ->
fun(N) when is_integer(N) ->
(econf:and_then(
econf:int(0, 5),
fun ejabberd_logger:convert_loglevel/1))(N);
(Level) ->
(econf:enum([none, emergency, alert, critical,
error, warning, notice, info, debug]))(Level)
end;
opt_type(max_fsm_queue) ->
econf:pos_int();
opt_type(modules) ->
econf:map(econf:atom(), econf:any());
opt_type(negotiation_timeout) ->
econf:timeout(second);
opt_type(net_ticktime) ->
econf:timeout(second);
opt_type(new_sql_schema) ->
econf:bool();
opt_type(oauth_access) ->
econf:acl();
opt_type(oauth_cache_life_time) ->
econf:timeout(second, infinity);
opt_type(oauth_cache_missed) ->
econf:bool();
opt_type(oauth_cache_size) ->
econf:pos_int(infinity);
opt_type(oauth_db_type) ->
econf:db_type(ejabberd_oauth);
opt_type(oauth_expire) ->
econf:timeout(second);
opt_type(oauth_use_cache) ->
econf:bool();
opt_type(oauth_client_id_check) ->
econf:enum([allow, deny, db]);
opt_type(oom_killer) ->
econf:bool();
opt_type(oom_queue) ->
econf:pos_int();
opt_type(oom_watermark) ->
econf:int(1, 99);
opt_type(outgoing_s2s_families) ->
econf:and_then(
econf:non_empty(
econf:list(econf:enum([ipv4, ipv6]), [unique])),
fun(L) ->
lists:map(
fun(ipv4) -> inet;
(ipv6) -> inet6
end, L)
end);
opt_type(outgoing_s2s_port) ->
econf:port();
opt_type(outgoing_s2s_timeout) ->
econf:timeout(second, infinity);
opt_type(pam_service) ->
econf:binary();
opt_type(pam_userinfotype) ->
econf:enum([username, jid]);
opt_type(pgsql_users_number_estimate) ->
econf:bool();
opt_type(queue_dir) ->
econf:directory(write);
opt_type(queue_type) ->
econf:enum([ram, file]);
opt_type(redis_connect_timeout) ->
econf:timeout(second);
opt_type(redis_db) ->
econf:non_neg_int();
opt_type(redis_password) ->
econf:string();
opt_type(redis_pool_size) ->
econf:pos_int();
opt_type(redis_port) ->
econf:port();
opt_type(redis_queue_type) ->
econf:enum([ram, file]);
opt_type(redis_server) ->
econf:string();
opt_type(registration_timeout) ->
econf:timeout(second, infinity);
opt_type(resource_conflict) ->
econf:enum([setresource, closeold, closenew, acceptnew]);
opt_type(router_cache_life_time) ->
econf:timeout(second, infinity);
opt_type(router_cache_missed) ->
econf:bool();
opt_type(router_cache_size) ->
econf:pos_int(infinity);
opt_type(router_db_type) ->
econf:db_type(ejabberd_router);
opt_type(router_use_cache) ->
econf:bool();
opt_type(rpc_timeout) ->
econf:timeout(second);
opt_type(s2s_access) ->
econf:acl();
opt_type(s2s_cafile) ->
econf:pem();
opt_type(s2s_ciphers) ->
opt_type(c2s_ciphers);
opt_type(s2s_dhfile) ->
econf:file();
opt_type(s2s_dns_retries) ->
econf:non_neg_int();
opt_type(s2s_dns_timeout) ->
econf:timeout(second, infinity);
opt_type(s2s_max_retry_delay) ->
econf:timeout(second);
opt_type(s2s_protocol_options) ->
opt_type(c2s_protocol_options);
opt_type(s2s_queue_type) ->
econf:enum([ram, file]);
opt_type(s2s_timeout) ->
econf:timeout(second, infinity);
opt_type(s2s_tls_compression) ->
econf:bool();
opt_type(s2s_use_starttls) ->
econf:either(
econf:bool(),
econf:enum([optional, required]));
opt_type(s2s_zlib) ->
econf:and_then(
econf:bool(),
fun(false) -> false;
(true) ->
ejabberd:start_app(ezlib),
true
end);
opt_type(shaper) ->
ejabberd_shaper:validator(shaper);
opt_type(shaper_rules) ->
ejabberd_shaper:validator(shaper_rules);
opt_type(sm_cache_life_time) ->
econf:timeout(second, infinity);
opt_type(sm_cache_missed) ->
econf:bool();
opt_type(sm_cache_size) ->
econf:pos_int(infinity);
opt_type(sm_db_type) ->
econf:db_type(ejabberd_sm);
opt_type(sm_use_cache) ->
econf:bool();
opt_type(sql_connect_timeout) ->
econf:timeout(second);
opt_type(sql_database) ->
econf:binary();
opt_type(sql_keepalive_interval) ->
econf:timeout(second);
opt_type(sql_password) ->
econf:binary();
opt_type(sql_pool_size) ->
econf:pos_int();
opt_type(sql_port) ->
econf:port();
opt_type(sql_query_timeout) ->
econf:timeout(second);
opt_type(sql_queue_type) ->
econf:enum([ram, file]);
opt_type(sql_server) ->
econf:binary();
opt_type(sql_ssl) ->
econf:bool();
opt_type(sql_ssl_cafile) ->
econf:pem();
opt_type(sql_ssl_certfile) ->
econf:pem();
opt_type(sql_ssl_verify) ->
econf:bool();
opt_type(sql_start_interval) ->
econf:timeout(second);
opt_type(sql_type) ->
econf:enum([mysql, pgsql, sqlite, mssql, odbc]);
opt_type(sql_username) ->
econf:binary();
opt_type(sql_prepared_statements) ->
econf:bool();
opt_type(trusted_proxies) ->
econf:either(all, econf:list(econf:ip_mask()));
opt_type(use_cache) ->
econf:bool();
opt_type(validate_stream) ->
econf:bool();
opt_type(version) ->
econf:binary();
opt_type(websocket_origin) ->
econf:list(
econf:and_then(
econf:and_then(
econf:binary_sep("\\s+"),
econf:list(econf:url(), [unique])),
fun(L) -> str:join(L, <<" ">>) end),
[unique]);
opt_type(websocket_ping_interval) ->
econf:timeout(second);
opt_type(websocket_timeout) ->
econf:timeout(second);
opt_type(jwt_key) ->
econf:and_then(
econf:path(),
fun(Path) ->
case file:read_file(Path) of
{ok, Data} ->
try jose_jwk:from_binary(Data) of
{error, _} -> econf:fail({bad_jwt_key, Path});
JWK ->
case jose_jwk:to_map(JWK) of
{_, #{<<"keys">> := [Key]}} ->
jose_jwk:from_map(Key);
{_, #{<<"keys">> := [_|_]}} ->
econf:fail({bad_jwt_key_set, Path});
{_, #{<<"keys">> := _}} ->
econf:fail({bad_jwt_key, Path});
_ ->
JWK
end
catch _:_ ->
econf:fail({bad_jwt_key, Path})
end;
{error, Reason} ->
econf:fail({read_file, Reason, Path})
end
end);
opt_type(jwt_jid_field) ->
econf:binary();
opt_type(jwt_auth_only_rule) ->
econf:atom().
%% We only define the types of options that cannot be derived
%% automatically by tools/opt_type.sh script
-spec options() -> [{s2s_protocol_options, undefined | binary()} |
{c2s_protocol_options, undefined | binary()} |
{s2s_ciphers, undefined | binary()} |
{c2s_ciphers, undefined | binary()} |
{websocket_origin, [binary()]} |
{disable_sasl_mechanisms, [binary()]} |
{s2s_zlib, boolean()} |
{loglevel, ejabberd_logger:loglevel()} |
{listen, [ejabberd_listener:listener()]} |
{modules, [{module(), gen_mod:opts(), integer()}]} |
{ldap_uids, [{binary(), binary()}]} |
{ldap_dn_filter, {binary(), [binary()]}} |
{outgoing_s2s_families, [inet | inet6, ...]} |
{acl, [{atom(), [acl:acl_rule()]}]} |
{access_rules, [{atom(), acl:access()}]} |
{shaper, #{atom() => ejabberd_shaper:shaper_rate()}} |
{shaper_rules, [{atom(), [ejabberd_shaper:shaper_rule()]}]} |
{api_permissions, [ejabberd_access_permissions:permission()]} |
{jwt_key, jose_jwk:key() | undefined} |
{append_host_config, [{binary(), any()}]} |
{host_config, [{binary(), any()}]} |
{define_macro, any()} |
{include_config_file, any()} |
{atom(), any()}].
options() ->
[%% Top-priority options
hosts,
{loglevel, info},
{cache_life_time, timer:seconds(3600)},
{cache_missed, true},
{cache_size, 1000},
{use_cache, true},
{default_db, mnesia},
{default_ram_db, mnesia},
{queue_type, ram},
{version, ejabberd_config:version()},
%% Other options
{acl, []},
{access_rules, []},
{acme, #{}},
{allow_contrib_modules, true},
{allow_multiple_connections, false},
{anonymous_protocol, sasl_anon},
{api_permissions,
[{<<"admin access">>,
{[],
[{acl, admin},
{oauth, {[<<"ejabberd:admin">>], [{acl, admin}]}}],
{all, [start, stop]}}}]},
{append_host_config, []},
{auth_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{auth_cache_missed,
fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end},
{auth_cache_size,
fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end},
{auth_method,
fun(Host) -> [ejabberd_config:default_db(Host, ejabberd_auth)] end},
{auth_password_format, plain},
{auth_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{c2s_cafile, undefined},
{c2s_ciphers, undefined},
{c2s_dhfile, undefined},
{c2s_protocol_options, undefined},
{c2s_tls_compression, undefined},
{ca_file, iolist_to_binary(pkix:get_cafile())},
{captcha_cmd, undefined},
{captcha_host, <<"">>},
{captcha_limit, infinity},
{captcha_url, undefined},
{certfiles, undefined},
{cluster_backend, mnesia},
{cluster_nodes, []},
{define_macro, []},
{disable_sasl_mechanisms, []},
{domain_balancing, #{}},
{ext_api_headers, <<>>},
{ext_api_http_pool_size, 100},
{ext_api_path_oauth, <<"/oauth">>},
{ext_api_url, <<"http://localhost/api">>},
{extauth_pool_name, undefined},
{extauth_pool_size, undefined},
{extauth_program, undefined},
{fqdn, fun fqdn/1},
{hide_sensitive_log_data, false},
{host_config, []},
{include_config_file, []},
{language, <<"en">>},
{ldap_backups, []},
{ldap_base, <<"">>},
{ldap_deref_aliases, never},
{ldap_dn_filter, {undefined, []}},
{ldap_encrypt, none},
{ldap_filter, <<"">>},
{ldap_password, <<"">>},
{ldap_port,
fun(Host) ->
case ejabberd_config:get_option({ldap_encrypt, Host}) of
tls -> 636;
_ -> 389
end
end},
{ldap_rootdn, <<"">>},
{ldap_servers, [<<"localhost">>]},
{ldap_tls_cacertfile, undefined},
{ldap_tls_certfile, undefined},
{ldap_tls_depth, undefined},
{ldap_tls_verify, false},
{ldap_uids, [{<<"uid">>, <<"%u">>}]},
{listen, []},
{log_rotate_count, 1},
{log_rotate_size, 10*1024*1024},
{max_fsm_queue, undefined},
{modules, []},
{negotiation_timeout, timer:seconds(30)},
{net_ticktime, timer:seconds(60)},
{new_sql_schema, ?USE_NEW_SQL_SCHEMA_DEFAULT},
{oauth_access, none},
{oauth_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{oauth_cache_missed,
fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end},
{oauth_cache_size,
fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end},
{oauth_db_type,
fun(Host) -> ejabberd_config:default_db(Host, ejabberd_oauth) end},
{oauth_expire, 4294967},
{oauth_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{oauth_client_id_check, allow},
{oom_killer, true},
{oom_queue, 10000},
{oom_watermark, 80},
{outgoing_s2s_families, [inet, inet6]},
{outgoing_s2s_port, 5269},
{outgoing_s2s_timeout, timer:seconds(10)},
{pam_service, <<"ejabberd">>},
{pam_userinfotype, username},
{pgsql_users_number_estimate, false},
{queue_dir, undefined},
{redis_connect_timeout, timer:seconds(1)},
{redis_db, 0},
{redis_password, ""},
{redis_pool_size, 10},
{redis_port, 6379},
{redis_queue_type,
fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end},
{redis_server, "localhost"},
{registration_timeout, timer:seconds(600)},
{resource_conflict, acceptnew},
{router_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{router_cache_missed,
fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end},
{router_cache_size,
fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end},
{router_db_type,
fun(Host) -> ejabberd_config:default_ram_db(Host, ejabberd_router) end},
{router_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{rpc_timeout, timer:seconds(5)},
{s2s_access, all},
{s2s_cafile, undefined},
{s2s_ciphers, undefined},
{s2s_dhfile, undefined},
{s2s_dns_retries, 2},
{s2s_dns_timeout, timer:seconds(10)},
{s2s_max_retry_delay, timer:seconds(300)},
{s2s_protocol_options, undefined},
{s2s_queue_type,
fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end},
{s2s_timeout, timer:minutes(10)},
{s2s_tls_compression, undefined},
{s2s_use_starttls, false},
{s2s_zlib, false},
{shaper, #{}},
{shaper_rules, []},
{sm_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{sm_cache_missed,
fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end},
{sm_cache_size,
fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end},
{sm_db_type,
fun(Host) -> ejabberd_config:default_ram_db(Host, ejabberd_sm) end},
{sm_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{sql_type, odbc},
{sql_connect_timeout, timer:seconds(5)},
{sql_database, undefined},
{sql_keepalive_interval, undefined},
{sql_password, <<"">>},
{sql_pool_size,
fun(Host) ->
case ejabberd_config:get_option({sql_type, Host}) of
sqlite -> 1;
_ -> 10
end
end},
{sql_port,
fun(Host) ->
case ejabberd_config:get_option({sql_type, Host}) of
mssql -> 1433;
mysql -> 3306;
pgsql -> 5432;
_ -> undefined
end
end},
{sql_query_timeout, timer:seconds(60)},
{sql_queue_type,
fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end},
{sql_server, <<"localhost">>},
{sql_ssl, false},
{sql_ssl_cafile, undefined},
{sql_ssl_certfile, undefined},
{sql_ssl_verify, false},
{sql_start_interval, timer:seconds(30)},
{sql_username, <<"ejabberd">>},
{sql_prepared_statements, true},
{trusted_proxies, []},
{validate_stream, false},
{websocket_origin, []},
{websocket_ping_interval, timer:seconds(60)},
{websocket_timeout, timer:minutes(5)},
{jwt_key, undefined},
{jwt_jid_field, <<"jid">>},
{jwt_auth_only_rule, none}].
-spec globals() -> [atom()].
globals() ->
[acme,
allow_contrib_modules,
api_permissions,
append_host_config,
auth_cache_life_time,
auth_cache_missed,
auth_cache_size,
ca_file,
captcha_cmd,
captcha_host,
captcha_limit,
captcha_url,
certfiles,
cluster_backend,
cluster_nodes,
domain_balancing,
ext_api_path_oauth,
fqdn,
hosts,
host_config,
listen,
loglevel,
log_rotate_count,
log_rotate_size,
negotiation_timeout,
net_ticktime,
new_sql_schema,
node_start,
oauth_cache_life_time,
oauth_cache_missed,
oauth_cache_size,
oauth_db_type,
oauth_expire,
oauth_use_cache,
oom_killer,
oom_queue,
oom_watermark,
queue_dir,
redis_connect_timeout,
redis_db,
redis_password,
redis_pool_size,
redis_port,
redis_queue_type,
redis_server,
registration_timeout,
router_cache_life_time,
router_cache_missed,
router_cache_size,
router_db_type,
router_use_cache,
rpc_timeout,
s2s_max_retry_delay,
shaper,
sm_cache_life_time,
sm_cache_missed,
sm_cache_size,
trusted_proxies,
validate_stream,
version,
websocket_origin,
websocket_ping_interval,
websocket_timeout].
doc() ->
ejabberd_options_doc:doc().
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec validator() -> econf:validator().
validator() ->
Disallowed = ejabberd_config:globals(),
{Validators, Required} = ejabberd_config:validators(Disallowed),
econf:and_then(
fun econf:group_dups/1,
econf:options(
Validators,
[{disallowed, Required ++ Disallowed}, unique])).
-spec fqdn(global | binary()) -> [binary()].
fqdn(global) ->
{ok, Hostname} = inet:gethostname(),
case inet:gethostbyname(Hostname) of
{ok, #hostent{h_name = FQDN}} ->
case jid:nameprep(iolist_to_binary(FQDN)) of
error -> [];
Domain -> [Domain]
end;
{error, _} ->
[]
end;
fqdn(_) ->
ejabberd_config:get_option(fqdn).
-spec concat_binary(char()) -> fun(([binary()]) -> binary()).
concat_binary(C) ->
fun(Opts) -> str:join(Opts, <<C>>) end.