24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-07-14 23:44:18 +02:00

Use new configuration validator

This commit is contained in:
Evgeny Khramtsov 2019-06-14 12:33:26 +03:00
parent d48c067681
commit a02cff0e78
265 changed files with 12412 additions and 9918 deletions

View File

@ -119,6 +119,11 @@ update:
xref: all
$(REBAR) skip_deps=true xref
hooks: all
tools/hook_deps.sh ebin
options: all
tools/opt_types.sh ebin
translations:
tools/prepare-tr.sh
@ -335,8 +340,8 @@ dialyzer/erlang.plt:
@mkdir -p dialyzer
@dialyzer --build_plt --output_plt dialyzer/erlang.plt \
-o dialyzer/erlang.log --apps kernel stdlib sasl crypto \
public_key ssl mnesia inets odbc tools compiler erts \
runtime_tools asn1 observer xmerl et gs wx syntax_tools; \
public_key ssl mnesia inets odbc compiler erts \
os_mon asn1 syntax_tools; \
status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi
dialyzer/deps.plt:
@ -377,4 +382,4 @@ test:
.PHONY: src edoc dialyzer Makefile TAGS clean clean-rel distclean rel \
install uninstall uninstall-binary uninstall-all translations deps test \
quicktest erlang_plt deps_plt ejabberd_plt
quicktest erlang_plt deps_plt ejabberd_plt xref hooks options

View File

@ -12,22 +12,10 @@
### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY *******
### *******************************************************
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
### However, ejabberd treats different literals as different types:
###
### - unquoted or single-quoted strings. They are called "atoms".
### Example: dog, 'Jupiter', '3.14159', YELLOW
###
### - numeric literals. Example: 3, -45.0, .0
###
### - quoted or folded strings.
### Examples of quoted string: "Lizzard", "orange".
### Example of folded string:
### > Art thou not Romeo,
### and a Montague?
###
hosts:
- "localhost"
- localhost
loglevel: 4
log_rotate_size: 10485760
@ -36,8 +24,8 @@ log_rotate_count: 1
log_rate_limit: 100
certfiles:
- "/etc/letsencrypt/live/localhost/fullchain.pem"
- "/etc/letsencrypt/live/localhost/privkey.pem"
- /etc/letsencrypt/live/localhost/fullchain.pem
- /etc/letsencrypt/live/localhost/privkey.pem
listen:
-
@ -84,25 +72,25 @@ acl:
user_regexp: ""
loopback:
ip:
- "127.0.0.0/8"
- "::1/128"
- 127.0.0.0/8
- ::1/128
access_rules:
local:
- allow: local
allow: local
c2s:
- deny: blocked
- allow
deny: blocked
allow: all
announce:
- allow: admin
allow: admin
configure:
- allow: admin
allow: admin
muc_create:
- allow: local
allow: local
pubsub_createnode:
- allow: local
allow: local
trusted_network:
- allow: loopback
allow: loopback
api_permissions:
"console commands":
@ -112,26 +100,26 @@ api_permissions:
what: "*"
"admin access":
who:
- access:
- allow:
- acl: loopback
- acl: admin
- oauth:
- scope: "ejabberd:admin"
- access:
- allow:
- acl: loopback
- acl: admin
access:
allow:
acl: loopback
acl: admin
oauth:
scope: "ejabberd:admin"
access:
allow:
acl: loopback
acl: admin
what:
- "*"
- "!stop"
- "!start"
"public commands":
who:
- ip: "127.0.0.1/8"
ip: 127.0.0.1/8
what:
- "status"
- "connected_users_number"
- status
- connected_users_number
shaper:
normal: 1000
@ -140,11 +128,11 @@ shaper:
shaper_rules:
max_user_sessions: 10
max_user_offline_messages:
- 5000: admin
- 100
5000: admin
100: all
c2s_shaper:
- none: admin
- normal
none: admin
normal: all
s2s_shaper: fast
modules:
@ -163,7 +151,7 @@ modules:
mod_fail2ban: {}
mod_http_api: {}
mod_http_upload:
put_url: "https://@HOST@:5443/upload"
put_url: https://@HOST@:5443/upload
mod_last: {}
mod_mam:
## Mnesia is limited to 2GB, better to use an SQL backend
@ -196,11 +184,11 @@ modules:
mod_pubsub:
access_createnode: pubsub_createnode
plugins:
- "flat"
- "pep"
- flat
- pep
force_node_config:
## Avoid buggy clients to make their bookmarks public
"storage:bookmarks":
storage:bookmarks:
access_model: whitelist
mod_push: {}
mod_push_keepalive: {}

View File

@ -23,7 +23,7 @@
path = [] :: [binary()],
q = [] :: [{binary() | nokey, binary()}],
us = {<<>>, <<>>} :: {binary(), binary()},
auth :: {binary(), binary()} | {oauth, binary(), []} | undefined,
auth :: {binary(), binary()} | {oauth, binary(), []} | undefined | invalid,
lang = <<"">> :: binary(),
data = <<"">> :: binary(),
ip :: {inet:ip_address(), inet:port_number()},

View File

@ -2,7 +2,7 @@
-type local_hint() :: integer() | {apply, atom(), atom()}.
-record(route, {domain :: binary() | '_',
server_host :: binary() | '_',
-record(route, {domain :: binary(),
server_host :: binary(),
pid :: undefined | pid(),
local_hint :: local_hint() | undefined | '_'}).
local_hint :: local_hint() | undefined}).

View File

@ -30,7 +30,7 @@
-type info() :: [{conn, atom()} | {ip, ip()} | {node, atom()}
| {oor, boolean()} | {auth_module, atom()}
| {num_stanzas_in, non_neg_integer()}
| offline].
| {atom(), term()}].
-type prio() :: undefined | integer().
-endif.

View File

@ -17,19 +17,5 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-define(SQL_MARK, sql__mark_).
-define(SQL(SQL), ?SQL_MARK(SQL)).
-define(SQL_UPSERT_MARK, sql_upsert__mark_).
-define(SQL_UPSERT(Host, Table, Fields),
ejabberd_sql:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))).
-define(SQL_UPSERT_T(Table, Fields),
ejabberd_sql:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))).
-define(SQL_INSERT_MARK, sql_insert__mark_).
-define(SQL_INSERT(Table, Fields), ?SQL_INSERT_MARK(Table, Fields)).
-record(sql_query, {hash, format_query, format_res, args, loc}).
-record(sql_escape, {string, integer, boolean, in_array_string}).
-compile([{parse_transform, ejabberd_sql_pt}]).
-include("ejabberd_sql.hrl").

View File

@ -44,6 +44,7 @@
attributes = [] :: [{binary(), [binary()]}]}).
-type tlsopts() :: [{encrypt, tls | starttls | none} |
{tls_certfile, binary() | undefined} |
{tls_cacertfile, binary() | undefined} |
{tls_depth, non_neg_integer() | undefined} |
{tls_verify, hard | soft | false}].
@ -61,3 +62,18 @@
-type eldap_config() :: #eldap_config{}.
-type eldap_search() :: #eldap_search{}.
-type eldap_entry() :: #eldap_entry{}.
-define(eldap_config(M, H),
#eldap_config{
servers = M:ldap_servers(H),
backups = M:ldap_backups(H),
tls_options = [{encrypt, M:ldap_encrypt(H)},
{tls_verify, M:ldap_tls_verify(H)},
{tls_certfile, M:ldap_tls_certfile(H)},
{tls_cacertfile, M:ldap_tls_cacertfile(H)},
{tls_depth, M:ldap_tls_depth(H)}],
port = M:ldap_port(H),
dn = M:ldap_rootdn(H),
password = M:ldap_password(H),
base = M:ldap_base(H),
deref_aliases = M:ldap_deref_aliases(H)}).

View File

@ -22,19 +22,19 @@
-compile([{parse_transform, lager_transform}]).
-define(DEBUG(Format, Args),
lager:debug(Format, Args)).
lager:debug(Format, Args), ok).
-define(INFO_MSG(Format, Args),
lager:info(Format, Args)).
lager:info(Format, Args), ok).
-define(WARNING_MSG(Format, Args),
lager:warning(Format, Args)).
lager:warning(Format, Args), ok).
-define(ERROR_MSG(Format, Args),
lager:error(Format, Args)).
lager:error(Format, Args), ok).
-define(CRITICAL_MSG(Format, Args),
lager:critical(Format, Args)).
lager:critical(Format, Args), ok).
%% Use only when trying to troubleshoot test problem with ExUnit
-define(EXUNIT_LOG(Format, Args),

View File

@ -19,12 +19,12 @@
%%%----------------------------------------------------------------------
-record(archive_msg,
{us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
id = <<>> :: binary() | '_',
timestamp = erlang:timestamp() :: erlang:timestamp() | '_' | '$1',
peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
packet = #xmlel{} :: xmlel() | message() | '_',
{us = {<<"">>, <<"">>} :: {binary(), binary()},
id = <<>> :: binary(),
timestamp = erlang:timestamp() :: erlang:timestamp(),
peer = {<<"">>, <<"">>, <<"">>} :: ljid() | undefined,
bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid(),
packet = #xmlel{} :: xmlel() | message(),
nick = <<"">> :: binary(),
type = chat :: chat | groupchat}).

View File

@ -24,11 +24,13 @@
-record(lqueue,
{
queue :: p1_queue:queue(),
max = 0 :: integer()
queue = p1_queue:new() :: p1_queue:queue(),
max = 0 :: integer()
}).
-type lqueue() :: #lqueue{}.
-type lqueue_elem() :: {binary(), message(), boolean(),
erlang:timestamp(), non_neg_integer()}.
-record(config,
{
@ -63,7 +65,7 @@
captcha_whitelist = (?SETS):empty() :: gb_sets:set(),
mam = false :: boolean(),
pubsub = <<"">> :: binary(),
lang = ejabberd_config:get_mylang() :: binary()
lang = ejabberd_option:language() :: binary()
}).
-type config() :: #config{}.
@ -89,8 +91,8 @@
{
message_time = 0 :: integer(),
presence_time = 0 :: integer(),
message_shaper = none :: shaper:shaper(),
presence_shaper = none :: shaper:shaper(),
message_shaper = none :: ejabberd_shaper:shaper(),
presence_shaper = none :: ejabberd_shaper:shaper(),
message :: message() | undefined,
presence :: {binary(), presence()} | undefined
}).
@ -110,11 +112,11 @@
robots = #{} :: map(),
nicks = #{} :: map(),
affiliations = #{} :: map(),
history :: lqueue(),
history = #lqueue{} :: lqueue(),
subject = [] :: [text()],
subject_author = <<"">> :: binary(),
just_created = erlang:system_time(microsecond) :: true | integer(),
activity = treap:empty() :: treap:treap(),
room_shaper = none :: shaper:shaper(),
room_shaper = none :: ejabberd_shaper:shaper(),
room_queue :: p1_queue:queue() | undefined
}).

View File

@ -26,11 +26,12 @@
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.36"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.3.4"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.19"}}},
{yconf, ".*", {git, "https://github.com/processone/yconf", "master"}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.5"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.2"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.8.4"}}},
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.11"}}},
{eimp, ".*", {git, "https://github.com/processone/eimp", "master"}},
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.3"}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.28"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.29"}}}},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

529
src/econf.erl Normal file
View File

@ -0,0 +1,529 @@
%%%----------------------------------------------------------------------
%%% File : econf.erl
%%% Purpose : Validator for ejabberd configuration options
%%%
%%%
%%% 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(econf).
%% API
-export([parse/3, validate/2, fail/1, format_error/2, replace_macros/1]).
%% Simple types
-export([pos_int/0, pos_int/1, non_neg_int/0, non_neg_int/1]).
-export([int/0, int/2, number/1, octal/0]).
-export([binary/0, binary/1]).
-export([string/0, string/1]).
-export([enum/1, bool/0, atom/0, any/0]).
%% Complex types
-export([url/0, url/1]).
-export([file/0, file/1]).
-export([directory/0, directory/1]).
-export([ip/0, ipv4/0, ipv6/0, ip_mask/0, port/0]).
-export([re/0, glob/0]).
-export([path/0, binary_sep/1]).
-export([beam/0, beam/1]).
-export([timeout/1, timeout/2]).
%% Composite types
-export([list/1, list/2]).
-export([list_or_single/1, list_or_single/2]).
-export([map/2, map/3]).
-export([either/2, and_then/2, non_empty/1]).
-export([options/1, options/2]).
%% Custom types
-export([acl/0, shaper/0, url_or_file/0, lang/0]).
-export([pem/0, queue_type/0]).
-export([jid/0, user/0, domain/0, resource/0]).
-export([db_type/1, ldap_filter/0, well_known/2]).
-ifdef(SIP).
-export([sip_uri/0]).
-endif.
-type error_reason() :: term().
-type error_return() :: {error, error_reason(), yconf:ctx()}.
-type validator() :: yconf:validator().
-type validator(T) :: yconf:validator(T).
-type validators() :: yconf:validators().
-export_type([validator/0, validator/1, validators/0]).
-export_type([error_reason/0, error_return/0]).
%%%===================================================================
%%% API
%%%===================================================================
parse(File, Validators, Options) ->
try yconf:parse(File, Validators, Options)
catch _:{?MODULE, Reason, Ctx} ->
{error, Reason, Ctx}
end.
validate(Validator, Y) ->
try yconf:validate(Validator, Y)
catch _:{?MODULE, Reason, Ctx} ->
{error, Reason, Ctx}
end.
replace_macros(Y) ->
yconf:replace_macros(Y).
-spec fail(error_reason()) -> no_return().
fail(Reason) ->
yconf:fail(?MODULE, Reason).
format_error({bad_module, Mod}, Ctx)
when Ctx == [listen, module];
Ctx == [listen, request_handlers] ->
Mods = ejabberd_config:beams(all),
format("~s: unknown ~s: ~s. Did you mean ~s?",
[yconf:format_ctx(Ctx),
format_module_type(Ctx), Mod,
misc:best_match(Mod, Mods)]);
format_error({bad_module, Mod}, Ctx)
when Ctx == [modules] ->
Mods = lists:filter(
fun(M) ->
case atom_to_list(M) of
"mod_" ++ _ -> true;
_ -> false
end
end, ejabberd_config:beams(all)),
format("~s: unknown ~s: ~s. Did you mean ~s?",
[yconf:format_ctx(Ctx),
format_module_type(Ctx), Mod,
misc:best_match(Mod, Mods)]);
format_error({bad_export, {F, A}, Mod}, Ctx)
when Ctx == [listen, module];
Ctx == [listen, request_handlers];
Ctx == [modules] ->
Type = format_module_type(Ctx),
Slogan = yconf:format_ctx(Ctx),
case lists:member(Mod, ejabberd_config:beams(local)) of
true ->
format("~s: '~s' is not a ~s", [Slogan, Mod, Type]);
false ->
case lists:member(Mod, ejabberd_config:beams(external)) of
true ->
format("~s: third-party ~s '~s' doesn't export "
"function ~s/~B. If it's really a ~s, "
"consider to upgrade it",
[Slogan, Type, Mod, F, A, Type]);
false ->
format("~s: '~s' doesn't match any known ~s",
[Slogan, Mod, Type])
end
end;
format_error({unknown_option, [], _} = Why, Ctx) ->
format("~s. There are no available options",
[yconf:format_error(Why, Ctx)]);
format_error({unknown_option, Known, Opt} = Why, Ctx) ->
format("~s. Did you mean ~s? ~s",
[yconf:format_error(Why, Ctx),
misc:best_match(Opt, Known),
format_known("Available options", Known)]);
format_error({bad_enum, Known, Bad} = Why, Ctx) ->
format("~s. Did you mean ~s? ~s",
[yconf:format_error(Why, Ctx),
misc:best_match(Bad, Known),
format_known("Possible values", Known)]);
format_error({bad_yaml, _, _} = Why, _) ->
format_error(Why);
format_error(Reason, Ctx) ->
[H|T] = format_error(Reason),
yconf:format_ctx(Ctx) ++ ": " ++ [string:to_lower(H)|T].
format_error({bad_db_type, _, Atom}) ->
format("unsupported database: ~s", [Atom]);
format_error({bad_lang, Lang}) ->
format("Invalid language tag: ~s", [Lang]);
format_error({bad_pem, Why, Path}) ->
format("Failed to read PEM file '~s': ~s",
[Path, pkix:format_error(Why)]);
format_error({bad_cert, Why, Path}) ->
format_error({bad_pem, Why, Path});
format_error({bad_jid, Bad}) ->
format("Invalid XMPP address: ~s", [Bad]);
format_error({bad_user, Bad}) ->
format("Invalid user part: ~s", [Bad]);
format_error({bad_domain, Bad}) ->
format("Invalid domain: ~s", [Bad]);
format_error({bad_resource, Bad}) ->
format("Invalid resource part: ~s", [Bad]);
format_error({bad_ldap_filter, Bad}) ->
format("Invalid LDAP filter: ~s", [Bad]);
format_error({bad_sip_uri, Bad}) ->
format("Invalid SIP URI: ~s", [Bad]);
format_error({route_conflict, R}) ->
format("Failed to reuse route '~s' because it's "
"already registered on a virtual host",
[R]);
format_error({listener_dup, AddrPort}) ->
format("Overlapping listeners found at ~s",
[format_addr_port(AddrPort)]);
format_error({listener_conflict, AddrPort1, AddrPort2}) ->
format("Overlapping listeners found at ~s and ~s",
[format_addr_port(AddrPort1),
format_addr_port(AddrPort2)]);
format_error({invalid_syntax, Reason}) ->
format("~s", [Reason]);
format_error({missing_module_dep, Mod, DepMod}) ->
format("module ~s depends on module ~s, "
"which is not found in the config",
[Mod, DepMod]);
format_error(eimp_error) ->
format("ejabberd is built without image converter support", []);
format_error({mqtt_codec, Reason}) ->
mqtt_codec:format_error(Reason);
format_error(Reason) ->
yconf:format_error(Reason).
format_module_type([listen, module]) ->
"listening module";
format_module_type([listen, request_handlers]) ->
"HTTP request handler";
format_module_type([modules]) ->
"ejabberd module".
format_known(_, Known) when length(Known) > 20 ->
"";
format_known(Prefix, Known) ->
[Prefix, " are: ", format_join(Known)].
format_join([]) ->
"(empty)";
format_join([H|_] = L) when is_atom(H) ->
format_join([atom_to_binary(A, utf8) || A <- L]);
format_join(L) ->
str:join(lists:sort(L), <<", ">>).
%%%===================================================================
%%% Validators from yconf
%%%===================================================================
pos_int() ->
yconf:pos_int().
pos_int(Inf) ->
yconf:pos_int(Inf).
non_neg_int() ->
yconf:non_neg_int().
non_neg_int(Inf) ->
yconf:non_neg_int(Inf).
int() ->
yconf:int().
int(Min, Max) ->
yconf:int(Min, Max).
number(Min) ->
yconf:number(Min).
octal() ->
yconf:octal().
binary() ->
yconf:binary().
binary(Re) ->
yconf:binary(Re).
enum(L) ->
yconf:enum(L).
bool() ->
yconf:bool().
atom() ->
yconf:atom().
string() ->
yconf:string().
string(Re) ->
yconf:string(Re).
any() ->
yconf:any().
url() ->
yconf:url().
url(Schemes) ->
yconf:url(Schemes).
file() ->
yconf:file().
file(Type) ->
yconf:file(Type).
directory() ->
yconf:directory().
directory(Type) ->
yconf:directory(Type).
ip() ->
yconf:ip().
ipv4() ->
yconf:ipv4().
ipv6() ->
yconf:ipv6().
ip_mask() ->
yconf:ip_mask().
port() ->
yconf:port().
re() ->
yconf:re().
glob() ->
yconf:glob().
path() ->
yconf:path().
binary_sep(Sep) ->
yconf:binary_sep(Sep).
beam() ->
yconf:beam().
beam(Exports) ->
yconf:beam(Exports).
timeout(Units) ->
yconf:timeout(Units).
timeout(Units, Inf) ->
yconf:timeout(Units, Inf).
non_empty(F) ->
yconf:non_empty(F).
list(F) ->
yconf:list(F).
list(F, Opts) ->
yconf:list(F, Opts).
list_or_single(F) ->
yconf:list_or_single(F).
list_or_single(F, Opts) ->
yconf:list_or_single(F, Opts).
map(F1, F2) ->
yconf:map(F1, F2).
map(F1, F2, Opts) ->
yconf:map(F1, F2, Opts).
either(F1, F2) ->
yconf:either(F1, F2).
and_then(F1, F2) ->
yconf:and_then(F1, F2).
options(V) ->
yconf:options(V).
options(V, O) ->
yconf:options(V, O).
%%%===================================================================
%%% Custom validators
%%%===================================================================
acl() ->
either(
atom(),
acl:access_rules_validator()).
shaper() ->
either(
atom(),
ejabberd_shaper:shaper_rules_validator()).
-spec url_or_file() -> yconf:validator({file | url, binary()}).
url_or_file() ->
either(
and_then(url(), fun(URL) -> {url, URL} end),
and_then(file(), fun(File) -> {file, File} end)).
-spec lang() -> yconf:validator(binary()).
lang() ->
and_then(
binary(),
fun(Lang) ->
try xmpp_lang:check(Lang)
catch _:_ -> fail({bad_lang, Lang})
end
end).
-spec pem() -> yconf:validator(binary()).
pem() ->
and_then(
path(),
fun(Path) ->
case pkix:is_pem_file(Path) of
true -> Path;
{false, Reason} ->
fail({bad_pem, Reason, Path})
end
end).
-spec jid() -> yconf:validator(jid:jid()).
jid() ->
and_then(
binary(),
fun(Val) ->
try jid:decode(Val)
catch _:{bad_jid, _} = Reason -> fail(Reason)
end
end).
-spec user() -> yconf:validator(binary()).
user() ->
and_then(
binary(),
fun(Val) ->
case jid:nodeprep(Val) of
error -> fail({bad_user, Val});
U -> U
end
end).
-spec domain() -> yconf:validator(binary()).
domain() ->
and_then(
non_empty(binary()),
fun(Val) ->
try jid:tolower(jid:decode(Val)) of
{<<"">>, Domain, <<"">>} -> Domain;
_ -> fail({bad_domain, Val})
catch _:{bad_jid, _} ->
fail({bad_domain, Val})
end
end).
-spec resource() -> yconf:validator(binary()).
resource() ->
and_then(
binary(),
fun(Val) ->
case jid:resourceprep(Val) of
error -> fail({bad_resource, Val});
R -> R
end
end).
-spec db_type(module()) -> yconf:validator(atom()).
db_type(M) ->
and_then(
atom(),
fun(T) ->
case code:ensure_loaded(db_module(M, T)) of
{module, _} -> T;
{error, _} -> fail({bad_db_type, M, T})
end
end).
-spec queue_type() -> yconf:validator(ram | file).
queue_type() ->
enum([ram, file]).
-spec ldap_filter() -> yconf:validator(binary()).
ldap_filter() ->
and_then(
binary(),
fun(Val) ->
case eldap_filter:parse(Val) of
{ok, _} -> Val;
_ -> fail({bad_ldap_filter, Val})
end
end).
well_known(queue_type, _) ->
queue_type();
well_known(db_type, M) ->
db_type(M);
well_known(ram_db_type, M) ->
db_type(M);
well_known(cache_life_time, _) ->
pos_int(infinity);
well_known(cache_size, _) ->
pos_int(infinity);
well_known(use_cache, _) ->
bool();
well_known(cache_missed, _) ->
bool();
well_known(host, _) ->
host();
well_known(hosts, _) ->
list(host(), [unique]).
-ifdef(SIP).
sip_uri() ->
and_then(
binary(),
fun(Val) ->
case esip:decode_uri(Val) of
error -> fail({bad_sip_uri, Val});
URI -> URI
end
end).
-endif.
-spec host() -> yconf:validator(binary()).
host() ->
fun(Domain) ->
Host = ejabberd_config:get_myname(),
Hosts = ejabberd_config:get_option(hosts),
Domain1 = (binary())(Domain),
Domain2 = misc:expand_keyword(<<"@HOST@">>, Domain1, Host),
Domain3 = (domain())(Domain2),
case lists:member(Domain3, Hosts) of
true -> fail({route_conflict, Domain3});
false -> Domain3
end
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec db_module(module(), atom()) -> module().
db_module(M, Type) ->
try list_to_atom(atom_to_list(M) ++ "_" ++ atom_to_list(Type))
catch _:system_limit ->
fail({bad_length, 255})
end.
format_addr_port({IP, Port}) ->
IPStr = case tuple_size(IP) of
4 -> inet:ntoa(IP);
8 -> "[" ++ inet:ntoa(IP) ++ "]"
end,
IPStr ++ ":" ++ integer_to_list(Port).
-spec format(iolist(), list()) -> string().
format(Fmt, Args) ->
lists:flatten(io_lib:format(Fmt, Args)).

View File

@ -38,7 +38,7 @@
-protocol({xep, 270, '1.0'}).
-export([start/0, stop/0, halt/0, start_app/1, start_app/2,
get_pid_file/0, check_app/1, module_name/1, is_loaded/0]).
get_pid_file/0, check_apps/0, module_name/1, is_loaded/0]).
-include("logger.hrl").
@ -49,8 +49,8 @@ stop() ->
application:stop(ejabberd).
halt() ->
application:stop(lager),
application:stop(sasl),
_ = application:stop(lager),
_ = application:stop(sasl),
erlang:halt(1, [{flush, true}]).
%% @spec () -> false | string()
@ -71,21 +71,15 @@ start_app(App, Type) ->
StartFlag = not is_loaded(),
start_app(App, Type, StartFlag).
check_app(App) ->
StartFlag = not is_loaded(),
spawn(fun() -> check_app_modules(App, StartFlag) end),
ok.
is_loaded() ->
Apps = application:which_applications(),
lists:keymember(ejabberd, 1, Apps).
start_app(App, Type, StartFlag) when not is_list(App) ->
start_app(App, Type, StartFlag) when is_atom(App) ->
start_app([App], Type, StartFlag);
start_app([App|Apps], Type, StartFlag) ->
case application:start(App,Type) of
ok ->
spawn(fun() -> check_app_modules(App, StartFlag) end),
start_app(Apps, Type, StartFlag);
{error, {already_started, _}} ->
start_app(Apps, Type, StartFlag);
@ -93,23 +87,23 @@ start_app([App|Apps], Type, StartFlag) ->
case lists:member(DepApp, [App|Apps]) of
true ->
Reason = io_lib:format(
"failed to start application '~p': "
"circular dependency on '~p' detected",
"Failed to start Erlang application '~s': "
"circular dependency with '~s' detected",
[App, DepApp]),
exit_or_halt(Reason, StartFlag);
false ->
start_app([DepApp,App|Apps], Type, StartFlag)
end;
Err ->
Reason = io_lib:format("failed to start application '~p': ~p",
[App, Err]),
{error, Why} ->
Reason = io_lib:format(
"Failed to start Erlang application '~s': ~s. ~s",
[App, format_error(Why), hint()]),
exit_or_halt(Reason, StartFlag)
end;
start_app([], _Type, _StartFlag) ->
ok.
check_app_modules(App, StartFlag) ->
sleep(5000),
case application:get_key(App, modules) of
{ok, Mods} ->
lists:foreach(
@ -118,12 +112,12 @@ check_app_modules(App, StartFlag) ->
non_existing ->
File = get_module_file(App, Mod),
Reason = io_lib:format(
"couldn't find module ~s "
"needed for application '~p'",
[File, App]),
"Couldn't find file ~s needed "
"for Erlang application '~s'. ~s",
[File, App, hint()]),
exit_or_halt(Reason, StartFlag);
_ ->
sleep(10)
ok
end
end, Mods);
_ ->
@ -131,6 +125,23 @@ check_app_modules(App, StartFlag) ->
ok
end.
check_apps() ->
spawn(
fun() ->
Apps = [ejabberd |
[App || {App, _, _} <- application:which_applications(),
App /= ejabberd]],
?DEBUG("Checking consistency of applications: ~s",
[misc:join_atoms(Apps, <<", ">>)]),
misc:peach(
fun(App) ->
check_app_modules(App, true)
end, Apps),
?DEBUG("All applications are intact", []),
lists:foreach(fun erlang:garbage_collect/1, processes())
end).
-spec exit_or_halt(iodata(), boolean()) -> no_return().
exit_or_halt(Reason, StartFlag) ->
?CRITICAL_MSG(Reason, []),
if StartFlag ->
@ -140,9 +151,6 @@ exit_or_halt(Reason, StartFlag) ->
erlang:error(application_start_failed)
end.
sleep(N) ->
timer:sleep(p1_rand:uniform(N)).
get_module_file(App, Mod) ->
BaseName = atom_to_list(Mod),
case code:lib_dir(App, ebin) of
@ -177,3 +185,12 @@ erlang_name(Atom) when is_atom(Atom) ->
misc:atom_to_binary(Atom);
erlang_name(Bin) when is_binary(Bin) ->
Bin.
format_error({Reason, File}) when is_list(Reason), is_list(File) ->
Reason ++ ": " ++ File;
format_error(Term) ->
io_lib:format("~p", [Term]).
hint() ->
"This usually means that ejabberd or Erlang "
"was compiled/installed incorrectly.".

View File

@ -29,17 +29,13 @@
-include("logger.hrl").
-behaviour(gen_server).
-behaviour(ejabberd_config).
%% API
-export([start_link/0,
parse_api_permissions/1,
can_access/2,
invalidate/0,
opt_type/1,
show_current_definitions/0,
register_permission_addon/2,
unregister_permission_addon/1]).
validator/0,
show_current_definitions/0]).
%% gen_server callbacks
-export([init/1,
@ -51,16 +47,29 @@
-define(SERVER, ?MODULE).
-record(state, {
definitions = none,
fragments_generators = []
}).
-record(state,
{definitions = none :: none | [definition()]}).
-type state() :: #state{}.
-type rule() :: {access, acl:access()} |
{acl, all | none | acl:acl_rule()}.
-type what() :: all | none | [atom() | {tag, atom()}].
-type who() :: rule() | {oauth, {[binary()], [rule()]}}.
-type from() :: atom().
-type permission() :: {binary(), {[from()], [who()], {what(), what()}}}.
-type definition() :: {binary(), {[from()], [who()], [atom()] | all}}.
-type caller_info() :: #{caller_module => module(),
caller_host => global | binary(),
tag => binary() | none,
extra_permissions => [definition()],
atom() => term()}.
-export_type([permission/0]).
%%%===================================================================
%%% API
%%%===================================================================
-spec can_access(atom(), map()) -> allow | deny.
-spec can_access(atom(), caller_info()) -> allow | deny.
can_access(Cmd, CallerInfo) ->
gen_server:call(?MODULE, {can_access, Cmd, CallerInfo}).
@ -68,65 +77,24 @@ can_access(Cmd, CallerInfo) ->
invalidate() ->
gen_server:cast(?MODULE, invalidate).
-spec register_permission_addon(atom(), fun()) -> ok.
register_permission_addon(Name, Fun) ->
gen_server:call(?MODULE, {register_config_fragment_generator, Name, Fun}).
-spec unregister_permission_addon(atom()) -> ok.
unregister_permission_addon(Name) ->
gen_server:call(?MODULE, {unregister_config_fragment_generator, Name}).
-spec show_current_definitions() -> any().
-spec show_current_definitions() -> [definition()].
show_current_definitions() ->
gen_server:call(?MODULE, show_current_definitions).
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore.
-spec init([]) -> {ok, state()}.
init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, invalidate, 90),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}.
-spec handle_call({can_access, atom(), caller_info()} |
show_current_definitions | term(),
term(), state()) -> {reply, term(), state()}.
handle_call({can_access, Cmd, CallerInfo}, _From, State) ->
CallerModule = maps:get(caller_module, CallerInfo, none),
Host = maps:get(caller_host, CallerInfo, global),
@ -134,123 +102,61 @@ handle_call({can_access, Cmd, CallerInfo}, _From, State) ->
{State2, Defs0} = get_definitions(State),
Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0,
Res = lists:foldl(
fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of
true ->
?DEBUG("Command '~p' execution allowed by rule '~s' (CallerInfo=~p)", [Cmd, Name, CallerInfo]),
allow;
_ ->
none
end;
(_, Val) ->
Val
end, none, Defs),
fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of
true ->
?DEBUG("Command '~p' execution allowed by rule "
"'~s' (CallerInfo=~p)", [Cmd, Name, CallerInfo]),
allow;
_ ->
none
end;
(_, Val) ->
Val
end, none, Defs),
Res2 = case Res of
allow -> allow;
_ ->
?DEBUG("Command '~p' execution denied (CallerInfo=~p)", [Cmd, CallerInfo]),
?DEBUG("Command '~p' execution denied "
"(CallerInfo=~p)", [Cmd, CallerInfo]),
deny
end,
{reply, Res2, State2};
handle_call(show_current_definitions, _From, State) ->
{State2, Defs} = get_definitions(State),
{reply, Defs, State2};
handle_call({register_config_fragment_generator, Name, Fun}, _From, #state{fragments_generators = Gens} = State) ->
NGens = lists:keystore(Name, 1, Gens, {Name, Fun}),
{reply, ok, State#state{fragments_generators = NGens}};
handle_call({unregister_config_fragment_generator, Name}, _From, #state{fragments_generators = Gens} = State) ->
NGens = lists:keydelete(Name, 1, Gens),
{reply, ok, State#state{fragments_generators = NGens}};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}.
-spec handle_cast(invalidate | term(), state()) -> {noreply, state()}.
handle_cast(invalidate, State) ->
{noreply, State#state{definitions = none}};
handle_cast(_Request, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}.
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(