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(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term().
terminate(_Reason, _State) ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, invalidate, 90).
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec get_definitions(#state{}) -> {#state{}, any()}.
-spec get_definitions(state()) -> {state(), [definition()]}.
get_definitions(#state{definitions = Defs} = State) when Defs /= none ->
{State, Defs};
get_definitions(#state{definitions = none, fragments_generators = Gens} = State) ->
DefaultOptions = [{<<"admin access">>,
{[],
[{acl,{acl,admin}},
{oauth,[<<"ejabberd:admin">>],[{acl,{acl,admin}}]}],
{all, [start, stop]}}}],
ApiPerms = ejabberd_config:get_option(api_permissions, DefaultOptions),
get_definitions(#state{definitions = none} = State) ->
ApiPerms = ejabberd_option:api_permissions(),
AllCommands = ejabberd_commands:get_commands_definition(),
Frags = lists:foldl(
fun({_Name, Generator}, Acc) ->
Acc ++ Generator()
end, [], Gens),
NDefs0 = lists:map(
fun({Name, {From, Who, {Add, Del}}}) ->
Cmds = filter_commands_with_permissions(AllCommands, Add, Del),
{Name, {From, Who, Cmds}}
end, ApiPerms ++ Frags),
end, ApiPerms),
NDefs = case lists:keyfind(<<"console commands">>, 1, NDefs0) of
false ->
[{<<"console commands">>,
@ -262,6 +168,8 @@ get_definitions(#state{definitions = none, fragments_generators = Gens} = State)
end,
{State#state{definitions = NDefs}, NDefs}.
-spec matches_definition(definition(), atom(), module(),
atom(), global | binary(), caller_info()) -> boolean().
matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInfo) ->
case What == all orelse lists:member(Cmd, What) of
true ->
@ -271,25 +179,29 @@ matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInf
true ->
Scope = maps:get(oauth_scope, CallerInfo, none),
lists:any(
fun({access, Access}) when Scope == none ->
acl:access_matches(Access, CallerInfo, Host) == allow;
({acl, Acl}) when Scope == none ->
acl:acl_rule_matches(Acl, CallerInfo, Host);
({oauth, Scopes, List}) when Scope /= none ->
case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of
true ->
lists:any(
fun({access, Access}) ->
acl:access_matches(Access, CallerInfo, Host) == allow;
({acl, Acl}) ->
acl:acl_rule_matches(Acl, CallerInfo, Host)
end, List);
_ ->
false
end;
(_) ->
false
end, Who);
fun({access, Access}) when Scope == none ->
acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Name} = Acl) when Scope == none, is_atom(Name) ->
acl:match_acl(Host, Acl, CallerInfo);
({acl, Acl}) when Scope == none ->
acl:match_acl(Host, Acl, CallerInfo);
({oauth, {Scopes, List}}) when Scope /= none ->
case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of
true ->
lists:any(
fun({access, Access}) ->
acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Name} = Acl) when is_atom(Name) ->
acl:match_acl(Host, Acl, CallerInfo);
({acl, Acl}) ->
acl:match_acl(Host, Acl, CallerInfo)
end, List);
_ ->
false
end;
(_) ->
false
end, Who);
_ ->
false
end;
@ -297,12 +209,15 @@ matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInf
false
end.
-spec filter_commands_with_permissions([#ejabberd_commands{}], what(), what()) -> [atom()].
filter_commands_with_permissions(AllCommands, Add, Del) ->
CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []),
CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []),
lists:map(fun(#ejabberd_commands{name = N}) -> N end,
CommandsAdd -- CommandsDel).
-spec filter_commands_with_patterns([#ejabberd_commands{}], what(),
[#ejabberd_commands{}]) -> [#ejabberd_commands{}].
filter_commands_with_patterns([], _Patterns, Acc) ->
Acc;
filter_commands_with_patterns([C | CRest], Patterns, Acc) ->
@ -313,6 +228,7 @@ filter_commands_with_patterns([C | CRest], Patterns, Acc) ->
filter_commands_with_patterns(CRest, Patterns, Acc)
end.
-spec command_matches_patterns(#ejabberd_commands{}, what()) -> boolean().
command_matches_patterns(_, all) ->
true;
command_matches_patterns(_, none) ->
@ -332,125 +248,26 @@ command_matches_patterns(C, [_ | Tail]) ->
command_matches_patterns(C, Tail).
%%%===================================================================
%%% Options parsing code
%%% Validators
%%%===================================================================
parse_api_permissions(Data) when is_list(Data) ->
[parse_api_permission(Name, Args) || {Name, Args} <- Data].
parse_api_permission(Name, Args0) ->
Args = lists:flatten(Args0),
{From, Who, What} = case key_split(Args, [{from, []}, {who, none}, {what, []}]) of
{error, Msg} ->
report_error(<<"~s inside api_permission '~s' section">>, [Msg, Name]);
Val -> Val
end,
{Name, {parse_from(Name, From), parse_who(Name, Who, oauth), parse_what(Name, What)}}.
parse_from(_Name, Module) when is_atom(Module) ->
[Module];
parse_from(Name, Modules) when is_list(Modules) ->
lists:map(
fun(Module) when is_atom(Module) ->
Module;
([{tag, Tag}]) when is_binary(Tag) ->
{tag, Tag};
(Val) ->
report_error(<<"Invalid value '~p' used inside 'from' section for api_permission '~s'">>,
[Val, Name])
end, Modules);
parse_from(Name, Val) ->
report_error(<<"Invalid value '~p' used inside 'from' section for api_permission '~s'">>,
[Val, Name]).
parse_who(Name, Atom, ParseOauth) when is_atom(Atom) ->
parse_who(Name, [Atom], ParseOauth);
parse_who(Name, Defs, ParseOauth) when is_list(Defs) ->
lists:map(
fun([Val]) ->
[NVal] = parse_who(Name, [Val], ParseOauth),
NVal;
({access, Val}) ->
try acl:access_rules_validator(Val) of
Rule ->
{access, Rule}
catch
throw:{invalid_syntax, Msg} ->
report_error(<<"Invalid access rule: '~s' used inside 'who' section for api_permission '~s'">>,
[Msg, Name]);
error:_ ->
report_error(<<"Invalid access rule '~p' used inside 'who' section for api_permission '~s'">>,
[Val, Name])
end;
({oauth, OauthList}) when is_list(OauthList) ->
case ParseOauth of
oauth ->
Nested = parse_who(Name, lists:flatten(OauthList), scope),
{Scopes, Rest} = lists:partition(
fun({scope, _}) -> true;
(_) -> false
end, Nested),
case Scopes of
[] ->
report_error(<<"Oauth rule must contain at least one scope rule in 'who' section for api_permission '~s'">>,
[Name]);
_ ->
{oauth, lists:foldl(fun({scope, S}, A) -> S ++ A end, [], Scopes), Rest}
end;
scope ->
report_error(<<"Oauth rule can't be embedded inside other oauth rule in 'who' section for api_permission '~s'">>,
[Name])
end;
({scope, ScopeList}) ->
case ParseOauth of
oauth ->
report_error(<<"Scope can be included only inside oauth rule in 'who' section for api_permission '~s'">>,
[Name]);
scope ->
ScopeList2 = case ScopeList of
V when is_binary(V) -> [V];
V2 when is_list(V2) -> V2;
V3 ->
report_error(<<"Invalid value for scope '~p' in 'who' section for api_permission '~s'">>,
[V3, Name])
end,
{scope, ScopeList2}
end;
(Atom) when is_atom(Atom) ->
{acl, {acl, Atom}};
(Other) ->
try acl:normalize_spec(Other) of
Rule2 ->
{acl, Rule2}
catch
_:_ ->
report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>,
[Other, Name])
end
end, Defs);
parse_who(Name, Val, _ParseOauth) ->
report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>,
[Val, Name]).
parse_what(Name, Binary) when is_binary(Binary) ->
parse_what(Name, [Binary]);
parse_what(Name, Defs) when is_list(Defs) ->
{A, D} = lists:foldl(
fun(Def, {Add, Del}) ->
case parse_single_what(Def) of
{error, Err} ->
report_error(<<"~s used in value '~p' in 'what' section for api_permission '~s'">>,
[Err, Def, Name]);
all ->
{case Add of none -> none; _ -> all end, Del};
{neg, all} ->
{none, all};
{neg, Value} ->
{Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end};
Value ->
{case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del}
end
end, {[], []}, Defs),
-spec parse_what([binary()]) -> {what(), what()}.
parse_what(Defs) ->
{A, D} =
lists:foldl(
fun(Def, {Add, Del}) ->
case parse_single_what(Def) of
{error, Err} ->
econf:fail({invalid_syntax, [Err, ": ", Def]});
all ->
{case Add of none -> none; _ -> all end, Del};
{neg, all} ->
{none, all};
{neg, Value} ->
{Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end};
Value ->
{case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del}
end
end, {[], []}, Defs),
case {A, D} of
{[], _} ->
{none, all};
@ -458,11 +275,9 @@ parse_what(Name, Defs) when is_list(Defs) ->
{A2, none};
V ->
V
end;
parse_what(Name, Val) ->
report_error(<<"Invalid value '~p' used inside 'what' section for api_permission '~s'">>,
[Val, Name]).
end.
-spec parse_single_what(binary()) -> atom() | {neg, atom()} | {tag, atom()} | {error, string()}.
parse_single_what(<<"*">>) ->
all;
parse_single_what(<<"!*">>) ->
@ -470,7 +285,7 @@ parse_single_what(<<"!*">>) ->
parse_single_what(<<"!", Rest/binary>>) ->
case parse_single_what(Rest) of
{neg, _} ->
{error, <<"Double negation">>};
{error, "double negation"};
{error, _} = Err ->
Err;
V ->
@ -485,71 +300,78 @@ parse_single_what(<<"[tag:", Rest/binary>>) ->
V when is_atom(V) ->
{tag, V};
_ ->
{error, <<"Invalid tag">>}
{error, "invalid tag"}
end;
_ ->
{error, <<"Invalid tag">>}
{error, "invalid tag"}
end;
parse_single_what(Binary) when is_binary(Binary) ->
case is_valid_command_name(Binary) of
true ->
binary_to_atom(Binary, latin1);
_ ->
{error, <<"Invalid value">>}
end;
parse_single_what(Atom) when is_atom(Atom) ->
parse_single_what(atom_to_binary(Atom, latin1));
parse_single_what(_) ->
{error, <<"Invalid value">>}.
is_valid_command_name(<<>>) ->
false;
is_valid_command_name(Val) ->
is_valid_command_name2(Val).
is_valid_command_name2(<<>>) ->
true;
is_valid_command_name2(<<K:8, Rest/binary>>) when (K >= $a andalso K =< $z)
orelse (K >= $0 andalso K =< $9)
orelse K == $_ orelse K == $- ->
is_valid_command_name2(Rest);
is_valid_command_name2(_) ->
false.
key_split(Args, Fields) ->
{_, Order1, Results1, Required1} = lists:foldl(
fun({Field, Default}, {Idx, Order, Results, Required}) ->
{Idx + 1, maps:put(Field, Idx, Order), [Default | Results], Required};
(Field, {Idx, Order, Results, Required}) ->
{Idx + 1, maps:put(Field, Idx, Order), [none | Results], maps:put(Field, 1, Required)}
end, {1, #{}, [], #{}}, Fields),
key_split(Args, list_to_tuple(Results1), Order1, Required1, #{}).
key_split([], _Results, _Order, Required, _Duplicates) when map_size(Required) > 0 ->
parse_error(<<"Missing fields '~s">>, [str:join(maps:keys(Required), <<", ">>)]);
key_split([], Results, _Order, _Required, _Duplicates) ->
Results;
key_split([{Arg, Value} | Rest], Results, Order, Required, Duplicates) ->
case maps:find(Arg, Order) of
{ok, Idx} ->
case maps:is_key(Arg, Duplicates) of
false ->
Results2 = setelement(Idx, Results, Value),
key_split(Rest, Results2, Order, maps:remove(Arg, Required), maps:put(Arg, 1, Duplicates));
true ->
parse_error(<<"Duplicate field '~s'">>, [Arg])
end;
_ ->
parse_error(<<"Unknown field '~s'">>, [Arg])
parse_single_what(B) ->
case re:run(B, "^[a-z0-9_\\-]*$") of
nomatch -> {error, "invalid command"};
_ -> binary_to_atom(B, latin1)
end.
report_error(Format, Args) ->
throw({invalid_syntax, (str:format(Format, Args))}).
validator(Map, Opts) ->
econf:and_then(
fun(L) when is_list(L) ->
lists:map(
fun({K, V}) -> {(econf:atom())(K), V};
(A) -> {acl, (econf:atom())(A)}
end, lists:flatten(L));
(A) ->
[{acl, (econf:atom())(A)}]
end,
econf:and_then(
econf:options(maps:merge(acl:validators(), Map), Opts),
fun(Rules) ->
lists:flatmap(
fun({Type, Rs}) when is_list(Rs) ->
case maps:is_key(Type, acl:validators()) of
true -> [{acl, {Type, R}} || R <- Rs];
false -> [{Type, Rs}]
end;
(Other) ->
[Other]
end, Rules)
end)).
parse_error(Format, Args) ->
{error, (str:format(Format, Args))}.
validator(from) ->
fun(L) when is_list(L) ->
lists:map(
fun({K, V}) -> {(econf:enum([tag]))(K), (econf:binary())(V)};
(A) -> (econf:enum([ejabberd_xmlrpc, mod_http_api, ejabberd_ctl]))(A)
end, lists:flatten(L));
(A) ->
[(econf:enum([ejabberd_xmlrpc, mod_http_api, ejabberd_ctl]))(A)]
end;
validator(what) ->
econf:and_then(
econf:list_or_single(econf:non_empty(econf:binary())),
fun parse_what/1);
validator(who) ->
validator(#{access => econf:acl(), oauth => validator(oauth)}, []);
validator(oauth) ->
econf:and_then(
validator(#{access => econf:acl(),
scope => econf:non_empty(
econf:list_or_single(econf:binary()))},
[{required, [scope]}]),
fun(Os) ->
{[Scopes], Rest} = proplists:split(Os, [scope]),
{lists:flatten([S || {_, S} <- Scopes]), Rest}
end).
opt_type(api_permissions) ->
fun parse_api_permissions/1;
opt_type(_) ->
[api_permissions].
validator() ->
econf:map(
econf:binary(),
econf:and_then(
econf:options(
#{from => validator(from),
what => validator(what),
who => validator(who)}),
fun(Os) ->
{proplists:get_value(from, Os, []),
proplists:get_value(who, Os, none),
proplists:get_value(what, Os, [])}
end),
[unique]).

View File

@ -1,6 +1,5 @@
-module (ejabberd_acme).
-behaviour(gen_server).
-behaviour(ejabberd_config).
%% ejabberdctl commands
-export([get_commands_spec/0,
@ -18,7 +17,7 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([start_link/0, opt_type/1, register_certfiles/0]).
-export([start_link/0, register_certfiles/0]).
-include("logger.hrl").
-include("xmpp.hrl").
@ -188,7 +187,7 @@ get_certificates1(CAUrl, DomainString, PrivateKey) ->
Hosts = [list_to_bitstring(D) || D <- Domains],
get_certificates2(CAUrl, PrivateKey, Hosts).
-spec get_certificates2(url(), jose_jwk:key(), [bitstring()]) -> string().
-spec get_certificates2(url(), jose_jwk:key(), [binary()]) -> string().
get_certificates2(CAUrl, PrivateKey, Hosts) ->
%% Get a certificate for each host
PemCertKeys = [get_certificate(CAUrl, Host, PrivateKey) || Host <- Hosts],
@ -199,8 +198,8 @@ get_certificates2(CAUrl, PrivateKey, Hosts) ->
%% Format the result to send back to ejabberdctl
format_get_certificates_result(SavedCerts).
-spec format_get_certificates_result([{'ok', bitstring(), _} |
{'error', bitstring(), _}]) ->
-spec format_get_certificates_result([{'ok', binary(), _} |
{'error', binary(), _}]) ->
string().
format_get_certificates_result(Certs) ->
Cond = lists:all(fun(Cert) ->
@ -217,21 +216,21 @@ format_get_certificates_result(Certs) ->
lists:flatten(Result)
end.
-spec format_get_certificate({'ok', bitstring(), _} |
{'error', bitstring(), _}) ->
-spec format_get_certificate({'ok', binary(), _} |
{'error', binary(), _}) ->
string().
format_get_certificate({ok, Domain, saved}) ->
io_lib:format(" Certificate for domain: \"~s\" acquired and saved", [Domain]);
format_get_certificate({ok, Domain, not_found}) ->
format_get_certificate({error, Domain, not_found}) ->
io_lib:format(" Certificate for domain: \"~s\" not found, so it was not renewed", [Domain]);
format_get_certificate({ok, Domain, no_expire}) ->
io_lib:format(" Certificate for domain: \"~s\" is not close to expiring", [Domain]);
format_get_certificate({error, Domain, Reason}) ->
io_lib:format(" Error for domain: \"~s\", with reason: \'~s\'", [Domain, Reason]).
-spec get_certificate(url(), bitstring(), jose_jwk:key()) ->
{'ok', bitstring(), pem()} |
{'error', bitstring(), _}.
-spec get_certificate(url(), binary(), jose_jwk:key()) ->
{'ok', binary(), pem()} |
{'error', binary(), _}.
get_certificate(CAUrl, DomainName, PrivateKey) ->
try
AllSubDomains = find_all_sub_domains(DomainName),
@ -266,7 +265,7 @@ create_save_new_account(CAUrl) ->
%% TODO:
%% Find a way to ask the user if he accepts the TOS
-spec create_new_account(url(), bitstring(), jose_jwk:key()) -> {'ok', string()} |
-spec create_new_account(url(), binary(), jose_jwk:key()) -> {'ok', string()} |
no_return().
create_new_account(CAUrl, Contact, PrivateKey) ->
try
@ -287,7 +286,7 @@ create_new_account(CAUrl, Contact, PrivateKey) ->
throw({error,create_new_account})
end.
-spec create_new_authorization(url(), bitstring(), jose_jwk:key()) ->
-spec create_new_authorization(url(), binary(), jose_jwk:key()) ->
{'ok', proplist()} | no_return().
create_new_authorization(CAUrl, DomainName, PrivateKey) ->
acme_challenge:register_hooks(DomainName),
@ -320,12 +319,12 @@ create_new_authorization(CAUrl, DomainName, PrivateKey) ->
acme_challenge:unregister_hooks(DomainName)
end.
-spec create_new_certificate(url(), {bitstring(), [bitstring()]}, jose_jwk:key()) ->
{ok, bitstring(), pem()}.
-spec create_new_certificate(url(), {binary(), [binary()]}, jose_jwk:key()) ->
{ok, binary(), pem()}.
create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) ->
try
{ok, Dirs, Nonce0} = ejabberd_acme_comm:directory(CAUrl),
CSRSubject = [{commonName, bitstring_to_list(DomainName)}],
CSRSubject = [{?'id-at-commonName', bitstring_to_list(DomainName)}],
SANs = [{dNSName, SAN} || SAN <- AllSubDomains],
{CSR, CSRKey} = make_csr(CSRSubject, SANs),
{NotBefore, NotAfter} = not_before_not_after(),
@ -404,9 +403,9 @@ renew_certificates0(CAUrl) ->
%% Format the result to send back to ejabberdctl
format_get_certificates_result(SavedCerts).
-spec renew_certificate(url(), {bitstring(), data_cert()}, jose_jwk:key()) ->
{'ok', bitstring(), _} |
{'error', bitstring(), _}.
-spec renew_certificate(url(), {binary(), data_cert()}, jose_jwk:key()) ->
{'ok', binary(), _} |
{'error', binary(), _}.
renew_certificate(CAUrl, {DomainName, _} = Cert, PrivateKey) ->
case cert_to_expire(Cert) of
true ->
@ -416,7 +415,7 @@ renew_certificate(CAUrl, {DomainName, _} = Cert, PrivateKey) ->
end.
-spec cert_to_expire({bitstring(), data_cert()}) -> boolean().
-spec cert_to_expire({binary(), data_cert()}) -> boolean().
cert_to_expire({_DomainName, #data_cert{pem = Pem}}) ->
Certificate = pem_to_certificate(Pem),
Validity = get_utc_validity(Certificate),
@ -494,7 +493,7 @@ format_certificate(DataCert, Verbose) ->
fail_format_certificate(DomainName)
end.
-spec format_certificate_plain(bitstring(), [string()], {expired | ok, string()}, string())
-spec format_certificate_plain(binary(), [string()], {expired | ok, string()}, string())
-> string().
format_certificate_plain(DomainName, SANs, NotAfter, Path) ->
Result = lists:flatten(io_lib:format(
@ -507,7 +506,7 @@ format_certificate_plain(DomainName, SANs, NotAfter, Path) ->
format_validity(NotAfter), Path])),
Result.
-spec format_certificate_verbose(bitstring(), [string()], {expired | ok, string()}, bitstring())
-spec format_certificate_verbose(binary(), [string()], {expired | ok, string()}, binary())
-> string().
format_certificate_verbose(DomainName, SANs, NotAfter, PemCert) ->
Result = lists:flatten(io_lib:format(
@ -526,7 +525,7 @@ format_validity({expired, NotAfter}) ->
format_validity({ok, NotAfter}) ->
io_lib:format("Valid until: ~s UTC", [NotAfter]).
-spec fail_format_certificate(bitstring()) -> string().
-spec fail_format_certificate(binary()) -> string().
fail_format_certificate(DomainName) ->
Result = lists:flatten(io_lib:format(
" Domain: ~s~n"
@ -542,7 +541,7 @@ get_commonName(#'Certificate'{tbsCertificate = TbsCertificate}) ->
%% TODO: Not the best way to find the commonName
ShallowSubjectList = [Attribute || [Attribute] <- SubjectList],
{_, _, CommonName} = lists:keyfind(attribute_oid(commonName), 2, ShallowSubjectList),
{_, _, CommonName} = lists:keyfind(?'id-at-commonName', 2, ShallowSubjectList),
%% TODO: Remove the length-encoding from the commonName before returning it
CommonName.
@ -574,7 +573,7 @@ get_subjectAltNames(#'Certificate'{tbsCertificate = TbsCertificate}) ->
} = TbsCertificate,
EncodedSANs = [Val || #'Extension'{extnID = Oid, extnValue = Val} <- Exts,
Oid =:= attribute_oid(subjectAltName)],
Oid == ?'id-ce-subjectAltName'],
lists:flatmap(
fun(EncSAN) ->
@ -624,7 +623,7 @@ revoke_certificate0(CAUrl, DomainOrFile) ->
ParsedCert = parse_revoke_cert_argument(DomainOrFile),
revoke_certificate1(CAUrl, ParsedCert).
-spec revoke_certificate1(url(), {domain, bitstring()} | {file, file:filename()}) ->
-spec revoke_certificate1(url(), {domain, binary()} | {file, file:filename()}) ->
{ok, deleted}.
revoke_certificate1(CAUrl, {domain, Domain}) ->
case domain_certificate_exists(Domain) of
@ -657,13 +656,13 @@ revoke_certificate2(CAUrl, PemEncodedCert) ->
{ok, [], _Nonce1} = ejabberd_acme_comm:revoke_cert(Dirs, CertPrivateKey, Req, Nonce),
ok.
-spec parse_revoke_cert_argument(string()) -> {domain, bitstring()} | {file, file:filename()}.
-spec parse_revoke_cert_argument(string()) -> {domain, binary()} | {file, file:filename()}.
parse_revoke_cert_argument([$f, $i, $l, $e, $:|File]) ->
{file, File};
parse_revoke_cert_argument([$d, $o, $m, $a, $i, $n, $: | Domain]) ->
{domain, list_to_bitstring(Domain)}.
-spec prepare_certificate_revoke(pem()) -> {bitstring(), jose_jwk:key()}.
-spec prepare_certificate_revoke(pem()) -> {binary(), jose_jwk:key()}.
prepare_certificate_revoke(PemEncodedCert) ->
PemList = public_key:pem_decode(PemEncodedCert),
PemCertEnc = lists:keyfind('Certificate', 1, PemList),
@ -674,7 +673,7 @@ prepare_certificate_revoke(PemEncodedCert) ->
{ok, Key} = find_private_key_in_pem(PemEncodedCert),
{Base64Cert, Key}.
-spec domain_certificate_exists(bitstring()) -> {bitstring(), data_cert()} | false.
-spec domain_certificate_exists(binary()) -> {binary(), data_cert()} | false.
domain_certificate_exists(Domain) ->
Certs = read_certificates_persistent(),
lists:keyfind(Domain, 1, Certs).
@ -688,7 +687,7 @@ domain_certificate_exists(Domain) ->
%% For now we accept only generating a key of
%% specific type for signing the csr
-spec make_csr(proplist(), [{dNSName, bitstring()}])
-spec make_csr(proplist(), [{dNSName, binary()}])
-> {binary(), jose_jwk:key()}.
make_csr(Attributes, SANs) ->
Key = generate_key(),
@ -698,7 +697,7 @@ make_csr(Attributes, SANs) ->
SubPKInfoAlgo = subject_pk_info_algo(KeyPub),
{ok, RawBinPubKey} = raw_binary_public_key(KeyPub),
SubPKInfo = subject_pk_info(SubPKInfoAlgo, RawBinPubKey),
{ok, Subject} = attributes_from_list(Attributes),
Subject = attributes_from_list(Attributes),
ExtensionRequest = extension_request(SANs),
CRI = certificate_request_info(SubPKInfo, Subject, ExtensionRequest),
{ok, EncodedCRI} = der_encode(
@ -737,7 +736,7 @@ subject_pk_info(Algo, RawBinPubKey) ->
extension(SANs) ->
#'Extension'{
extnID = attribute_oid(subjectAltName),
extnID = ?'id-ce-subjectAltName',
critical = false,
extnValue = public_key:der_encode('SubjectAltName', SANs)}.
@ -791,45 +790,12 @@ der_encode(Type, Term) ->
{error, der_encode}
end.
%%
%% Attributes Parser
%%
attributes_from_list(Attrs) ->
ParsedAttrs = [attribute_parser_fun(Attr) || Attr <- Attrs],
case lists:any(fun is_error/1, ParsedAttrs) of
true ->
{error, bad_attributes};
false ->
{ok, {rdnSequence, [[PAttr] || PAttr <- ParsedAttrs]}}
end.
attribute_parser_fun({AttrName, AttrVal}) ->
try
#'AttributeTypeAndValue'{
type = attribute_oid(AttrName),
%% TODO: Check if every attribute should be encoded as
%% common name. Actually it doesn't matter in
%% practice. Only in theory in order to have cleaner code.
value = public_key:der_encode('X520CommonName', {printableString, AttrVal})
%% value = length_bitstring(list_to_bitstring(AttrVal))
}
catch
_:_ ->
?ERROR_MSG("Bad attribute: ~p~n", [{AttrName, AttrVal}]),
{error, bad_attributes}
end.
-spec attribute_oid(atom()) -> tuple() | no_return().
attribute_oid(commonName) -> ?'id-at-commonName';
attribute_oid(countryName) -> ?'id-at-countryName';
attribute_oid(stateOrProvinceName) -> ?'id-at-stateOrProvinceName';
attribute_oid(localityName) -> ?'id-at-localityName';
attribute_oid(organizationName) -> ?'id-at-organizationName';
attribute_oid(subjectAltName) -> ?'id-ce-subjectAltName';
attribute_oid(_) -> error(bad_attributes).
{rdnSequence,
[[#'AttributeTypeAndValue'{
type = AttrName,
value = public_key:der_encode('X520CommonName', {printableString, AttrVal})
}] || {AttrName, AttrVal} <- Attrs]}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
@ -939,7 +905,7 @@ private_key_types() ->
'DSAPrivateKey',
'ECPrivateKey'].
-spec find_all_sub_domains(bitstring()) -> [bitstring()].
-spec find_all_sub_domains(binary()) -> [binary()].
find_all_sub_domains(DomainName) ->
AllRoutes = ejabberd_router:get_all_routes(),
DomainLen = size(DomainName),
@ -1094,8 +1060,8 @@ remove_certificate_persistent(DataCert) ->
NewData = data_remove_certificate(Data, DataCert),
ok = write_persistent(NewData).
-spec save_certificate({ok, bitstring(), binary()} | {error, _, _}) ->
{ok, bitstring(), saved} | {error, bitstring(), _}.
-spec save_certificate({ok, binary(), binary()} | {error, _, _}) ->
{ok, binary(), saved} | {error, binary(), _}.
save_certificate({error, _, _} = Error) ->
Error;
save_certificate({ok, DomainName, Cert}) ->
@ -1123,8 +1089,8 @@ save_certificate({ok, DomainName, Cert}) ->
{error, DomainName, saving}
end.
-spec save_renewed_certificate({ok, bitstring(), _} | {error, _, _}) ->
{ok, bitstring(), _} | {error, bitstring(), _}.
-spec save_renewed_certificate({ok, binary(), _} | {error, _, _}) ->
{ok, binary(), _} | {error, binary(), _}.
save_renewed_certificate({error, _, _} = Error) ->
Error;
save_renewed_certificate({ok, _, no_expire} = Cert) ->
@ -1141,7 +1107,7 @@ register_certfiles() ->
ejabberd_pkix:add_certfile(Path)
end, Paths).
-spec write_cert(file:filename(), binary(), bitstring()) -> {ok, bitstring(), saved}.
-spec write_cert(file:filename(), binary(), binary()) -> ok.
write_cert(CertificateFile, Cert, DomainName) ->
case file:write_file(CertificateFile, Cert) of
ok ->
@ -1150,59 +1116,34 @@ write_cert(CertificateFile, Cert, DomainName) ->
{error, Why} ->
?WARNING_MSG("Failed to change mode of file ~s: ~s",
[CertificateFile, file:format_error(Why)])
end,
{ok, DomainName, saved};
end;
{error, Reason} ->
?ERROR_MSG("Error: ~p saving certificate at file: ~p",
[Reason, CertificateFile]),
throw({error, DomainName, saving})
end.
-spec get_config_acme() -> acme_config().
get_config_acme() ->
case ejabberd_config:get_option(acme, undefined) of
undefined ->
?WARNING_MSG("No acme configuration has been specified", []),
%% throw({error, configuration});
[];
Acme ->
Acme
end.
-spec get_config_contact() -> bitstring().
-spec get_config_contact() -> binary().
get_config_contact() ->
Acme = get_config_acme(),
case lists:keyfind(contact, 1, Acme) of
{contact, Contact} ->
Contact;
false ->
Acme = ejabberd_option:acme(),
try maps:get(contact, Acme)
catch _:{badkey, _} ->
?WARNING_MSG("No contact has been specified in configuration", []),
?DEFAULT_CONFIG_CONTACT
%% throw({error, configuration_contact})
end.
-spec get_config_ca_url() -> url().
get_config_ca_url() ->
Acme = get_config_acme(),
case lists:keyfind(ca_url, 1, Acme) of
{ca_url, CAUrl} ->
CAUrl;
false ->
Acme = ejabberd_option:acme(),
try maps:get(ca_url, Acme)
catch _:{badkey, _} ->
?ERROR_MSG("No CA url has been specified in configuration", []),
?DEFAULT_CONFIG_CA_URL
%% throw({error, configuration_ca_url})
end.
-spec get_config_hosts() -> [bitstring()].
-spec get_config_hosts() -> [binary()].
get_config_hosts() ->
case ejabberd_config:get_option(hosts, undefined) of
undefined ->
?ERROR_MSG("No hosts have been specified in configuration", []),
throw({error, configuration_hosts});
Hosts ->
Hosts
end.
ejabberd_option:hosts().
-spec acme_certs_dir() -> file:filename().
acme_certs_dir() ->
@ -1210,23 +1151,3 @@ acme_certs_dir() ->
generate_key() ->
jose_jwk:generate_key({ec, secp256r1}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Option Parsing Code
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(acme) ->
fun(L) ->
lists:map(
fun({ca_url, URL}) ->
{ca_url, misc:try_url(URL)};
({contact, Contact}) ->
[<<_, _/binary>>, <<_, _/binary>>] =
binary:split(Contact, <<":">>),
{contact, Contact}
end, L)
end;
opt_type(_) ->
[acme].

View File

@ -35,6 +35,8 @@
stop_kindly/2, send_service_message_all_mucs/2,
registered_vhosts/0,
reload_config/0,
dump_config/1,
convert_to_yaml/2,
%% Cluster
join_cluster/1, leave_cluster/1, list_cluster/0,
%% Erlang
@ -152,7 +154,7 @@ get_commands_spec() ->
result_desc = "The type of logger module used",
result_example = lager,
args = [{loglevel, integer}],
result = {logger, atom}},
result = {res, rescode}},
#ejabberd_commands{name = update_list, tags = [server],
desc = "List modified modules that can be updated",
@ -285,11 +287,18 @@ get_commands_spec() ->
#ejabberd_commands{name = convert_to_yaml, tags = [config],
desc = "Convert the input file from Erlang to YAML format",
module = ejabberd_config, function = convert_to_yaml,
module = ?MODULE, function = convert_to_yaml,
args_desc = ["Full path to the original configuration file", "And full path to final file"],
args_example = ["/etc/ejabberd/ejabberd.cfg", "/etc/ejabberd/ejabberd.yml"],
args = [{in, string}, {out, string}],
result = {res, rescode}},
#ejabberd_commands{name = dump_config, tags = [config],
desc = "Dump configuration in YAML format as seen by ejabberd",
module = ?MODULE, function = dump_config,
args_desc = ["Full path to output file"],
args_example = ["/tmp/ejabberd.yml"],
args = [{out, string}],
result = {res, rescode}},
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
desc = "Delete expired offline messages from database",
@ -407,9 +416,7 @@ rotate_log() ->
ejabberd_logger:rotate_log().
set_loglevel(LogLevel) ->
{module, Module} = ejabberd_logger:set(LogLevel),
Module.
ejabberd_logger:set(LogLevel).
%%%
%%% Stop Kindly
@ -454,11 +461,13 @@ send_service_message_all_mucs(Subject, AnnouncementText) ->
Message = str:format("~s~n~s", [Subject, AnnouncementText]),
lists:foreach(
fun(ServerHost) ->
MUCHost = gen_mod:get_module_opt_host(
ServerHost, mod_muc, <<"conference.@HOST@">>),
mod_muc:broadcast_service_message(ServerHost, MUCHost, Message)
MUCHosts = gen_mod:get_module_opt_hosts(ServerHost, mod_muc),
lists:foreach(
fun(MUCHost) ->
mod_muc:broadcast_service_message(ServerHost, MUCHost, Message)
end, MUCHosts)
end,
ejabberd_config:get_myhosts()).
ejabberd_option:hosts()).
%%%
%%% ejabberd_update
@ -512,10 +521,31 @@ registered_users(Host) ->
lists:map(fun({U, _S}) -> U end, SUsers).
registered_vhosts() ->
ejabberd_config:get_myhosts().
ejabberd_option:hosts().
reload_config() ->
ejabberd_config:reload_file().
case ejabberd_config:reload() of
ok -> {ok, ""};
Err ->
Reason = ejabberd_config:format_error(Err),
{invalid_config, Reason}
end.
dump_config(Path) ->
case ejabberd_config:dump(Path) of
ok -> {ok, ""};
Err ->
Reason = ejabberd_config:format_error(Err),
{invalid_file, Reason}
end.
convert_to_yaml(In, Out) ->
case ejabberd_config:convert_to_yaml(In, Out) of
ok -> {ok, ""};
Err ->
Reason = ejabberd_config:format_error(Err),
{invalid_config, Reason}
end.
%%%
%%% Cluster management
@ -562,13 +592,13 @@ delete_expired_messages() ->
lists:foreach(
fun(Host) ->
{atomic, ok} = mod_offline:remove_expired_messages(Host)
end, ejabberd_config:get_myhosts()).
end, ejabberd_option:hosts()).
delete_old_messages(Days) ->
lists:foreach(
fun(Host) ->
{atomic, _} = mod_offline:remove_old_messages(Days, Host)
end, ejabberd_config:get_myhosts()).
end, ejabberd_option:hosts()).
%%%
%%% Mnesia management
@ -602,10 +632,6 @@ restore_mnesia(Path) ->
case ejabberd_admin:restore(Path) of
{atomic, _} ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't restore backup from ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_restore, String};
{aborted,{no_exists,Table}} ->
String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
[filename:absname(Path), node(), Table]),

View File

@ -32,42 +32,48 @@
-export([start/2, prep_stop/1, stop/1]).
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
%%%
%%% Application API
%%%
start(normal, _Args) ->
{T1, _} = statistics(wall_clock),
ejabberd_logger:start(),
write_pid_file(),
start_included_apps(),
start_elixir_application(),
ejabberd:check_app(ejabberd),
setup_if_elixir_conf_used(),
case ejabberd_config:start() of
ok ->
ejabberd_mnesia:start(),
file_queue_init(),
maybe_add_nameservers(),
case ejabberd_sup:start_link() of
{ok, SupPid} ->
ejabberd_system_monitor:start(),
register_elixir_config_hooks(),
ejabberd_cluster:wait_for_sync(infinity),
ejabberd_hooks:run(ejabberd_started, []),
{T2, _} = statistics(wall_clock),
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
[ejabberd_config:get_version(),
node(), (T2-T1)/1000]),
lists:foreach(fun erlang:garbage_collect/1, processes()),
{ok, SupPid};
Err ->
?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]),
ejabberd:halt()
end;
{error, Reason} ->
?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Reason]),
try
{T1, _} = statistics(wall_clock),
ejabberd_logger:start(),
write_pid_file(),
start_included_apps(),
start_elixir_application(),
setup_if_elixir_conf_used(),
case ejabberd_config:load() of
ok ->
ejabberd_mnesia:start(),
file_queue_init(),
maybe_add_nameservers(),
case ejabberd_sup:start_link() of
{ok, SupPid} ->
ejabberd_system_monitor:start(),
register_elixir_config_hooks(),
ejabberd_cluster:wait_for_sync(infinity),
ejabberd_hooks:run(ejabberd_started, []),
ejabberd:check_apps(),
{T2, _} = statistics(wall_clock),
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
[ejabberd_option:version(),
node(), (T2-T1)/1000]),
{ok, SupPid};
Err ->
?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]),
ejabberd:halt()
end;
Err ->
?CRITICAL_MSG("Failed to start ejabberd application: ~s",
[ejabberd_config:format_error(Err)]),
ejabberd:halt()
end
catch throw:{?MODULE, Error} ->
?DEBUG("Failed to start ejabberd application: ~p", [Error]),
ejabberd:halt()
end;
start(_, _) ->
@ -92,18 +98,15 @@ start_included_apps() ->
prep_stop(State) ->
ejabberd_hooks:run(ejabberd_stopping, []),
ejabberd_listener:stop_listeners(),
ejabberd_sm:stop(),
_ = ejabberd_sm:stop(),
gen_mod:stop_modules(),
State.
%% All the processes were killed when this function is called
stop(_State) ->
?INFO_MSG("ejabberd ~s is stopped in the node ~p",
[ejabberd_config:get_version(), node()]),
delete_pid_file(),
%%ejabberd_debug:stop(),
ok.
[ejabberd_option:version(), node()]),
delete_pid_file().
%%%
%%% Internal functions
@ -134,13 +137,13 @@ write_pid_file() ->
end.
write_pid_file(Pid, PidFilename) ->
case file:open(PidFilename, [write]) of
{ok, Fd} ->
io:format(Fd, "~s~n", [Pid]),
file:close(Fd);
{error, Reason} ->
?ERROR_MSG("Cannot write PID file ~s~nReason: ~p", [PidFilename, Reason]),
throw({cannot_write_pid_file, PidFilename, Reason})
case file:write_file(PidFilename, io_lib:format("~s~n", [Pid])) of
ok ->
ok;
{error, Reason} = Err ->
?CRITICAL_MSG("Cannot write PID file ~s: ~s",
[PidFilename, file:format_error(Reason)]),
throw({?MODULE, Err})
end.
delete_pid_file() ->
@ -152,34 +155,42 @@ delete_pid_file() ->
end.
file_queue_init() ->
QueueDir = case ejabberd_config:queue_dir() of
QueueDir = case ejabberd_option:queue_dir() of
undefined ->
MnesiaDir = mnesia:system_info(directory),
filename:join(MnesiaDir, "queue");
Path ->
Path
end,
p1_queue:start(QueueDir).
case p1_queue:start(QueueDir) of
ok -> ok;
Err -> throw({?MODULE, Err})
end.
-ifdef(ELIXIR_ENABLED).
is_using_elixir_config() ->
Config = ejabberd_config:path(),
'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config).
setup_if_elixir_conf_used() ->
case ejabberd_config:is_using_elixir_config() of
case is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config.Store':start_link();
false -> ok
end.
register_elixir_config_hooks() ->
case ejabberd_config:is_using_elixir_config() of
case is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config':start_hooks();
false -> ok
end.
start_elixir_application() ->
case ejabberd_config:is_elixir_enabled() of
true ->
case application:ensure_started(elixir) of
ok -> ok;
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
end;
_ ->
ok
case application:ensure_started(elixir) of
ok -> ok;
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
end.
-else.
setup_if_elixir_conf_used() -> ok.
register_elixir_config_hooks() -> ok.
start_elixir_application() -> ok.
-endif.

View File

@ -25,7 +25,6 @@
-module(ejabberd_auth).
-behaviour(gen_server).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
@ -47,7 +46,7 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([auth_modules/1, opt_type/1]).
-export([auth_modules/1]).
-include("scram.hrl").
-include("logger.hrl").
@ -107,7 +106,7 @@ init([]) ->
fun(Host, Acc) ->
Modules = auth_modules(Host),
maps:put(Host, Modules, Acc)
end, #{}, ejabberd_config:get_myhosts()),
end, #{}, ejabberd_option:hosts()),
lists:foreach(
fun({Host, Modules}) ->
start(Host, Modules)
@ -141,7 +140,7 @@ handle_cast(config_reloaded, #state{host_modules = HostModules} = State) ->
stop(Host, OldModules -- NewModules),
reload(Host, misc:intersection(OldModules, NewModules)),
maps:put(Host, NewModules, Acc)
end, HostModules, ejabberd_config:get_myhosts()),
end, HostModules, ejabberd_option:hosts()),
init_cache(NewHostModules),
{noreply, State#state{host_modules = NewHostModules}};
handle_cast(Msg, State) ->
@ -530,7 +529,7 @@ backend_type(Mod) ->
-spec password_format(binary() | global) -> plain | scram.
password_format(LServer) ->
ejabberd_config:get_option({auth_password_format, LServer}, plain).
ejabberd_option:auth_password_format(LServer).
%%%----------------------------------------------------------------------
%%% Backend calls
@ -767,15 +766,9 @@ init_cache(HostModules) ->
-spec cache_opts() -> [proplists:property()].
cache_opts() ->
MaxSize = ejabberd_config:get_option(
auth_cache_size,
ejabberd_config:cache_size(global)),
CacheMissed = ejabberd_config:get_option(
auth_cache_missed,
ejabberd_config:cache_missed(global)),
LifeTime = case ejabberd_config:get_option(
auth_cache_life_time,
ejabberd_config:cache_life_time(global)) of
MaxSize = ejabberd_option:auth_cache_size(),
CacheMissed = ejabberd_option:auth_cache_missed(),
LifeTime = case ejabberd_option:auth_cache_life_time() of
infinity -> infinity;
I -> timer:seconds(I)
end,
@ -803,9 +796,7 @@ use_cache(Mod, LServer) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(LServer);
false ->
ejabberd_config:get_option(
{auth_use_cache, LServer},
ejabberd_config:use_cache(LServer))
ejabberd_option:auth_use_cache(LServer)
end.
-spec cache_nodes(module(), binary()) -> [node()].
@ -827,13 +818,12 @@ auth_modules() ->
lists:flatmap(
fun(Host) ->
[{Host, Mod} || Mod <- auth_modules(Host)]
end, ejabberd_config:get_myhosts()).
end, ejabberd_option:hosts()).
-spec auth_modules(binary()) -> [module()].
auth_modules(Server) ->
LServer = jid:nameprep(Server),
Default = ejabberd_config:default_db(LServer, ?MODULE),
Methods = ejabberd_config:get_option({auth_method, LServer}, [Default]),
Methods = ejabberd_option:auth_method(LServer),
[ejabberd:module_name([<<"ejabberd">>, <<"auth">>,
misc:atom_to_binary(M)])
|| M <- Methods].
@ -911,31 +901,3 @@ import(Server, {sql, _}, riak, <<"users">>, Fields) ->
ejabberd_auth_riak:import(Server, Fields);
import(_LServer, {sql, _}, sql, <<"users">>, _) ->
ok.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(auth_method) ->
fun (V) when is_list(V) ->
lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V);
(V) -> [ejabberd_config:v_db(?MODULE, V)]
end;
opt_type(auth_password_format) ->
fun (plain) -> plain;
(scram) -> scram
end;
opt_type(auth_use_cache) ->
fun(B) when is_boolean(B) -> B end;
opt_type(auth_cache_missed) ->
fun(B) when is_boolean(B) -> B end;
opt_type(auth_cache_life_time) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
opt_type(auth_cache_size) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
opt_type(_) ->
[auth_method, auth_password_format, auth_use_cache,
auth_cache_missed, auth_cache_life_time, auth_cache_size].

View File

@ -25,7 +25,6 @@
-module(ejabberd_auth_anonymous).
-behaviour(ejabberd_config).
-behaviour(ejabberd_auth).
-author('mickael.remond@process-one.net').
@ -43,7 +42,7 @@
-export([login/2, check_password/4, user_exists/2,
get_users/2, count_users/2, store_type/1,
plain_password_required/1, opt_type/1]).
plain_password_required/1]).
-include("logger.hrl").
-include("jid.hrl").
@ -98,12 +97,12 @@ is_login_anonymous_enabled(Host) ->
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
%% defaults to login_anon
anonymous_protocol(Host) ->
ejabberd_config:get_option({anonymous_protocol, Host}, sasl_anon).
ejabberd_option:anonymous_protocol(Host).
%% Return true if multiple connections have been allowed in the config file
%% defaults to false
allow_multiple_connections(Host) ->
ejabberd_config:get_option({allow_multiple_connections, Host}, false).
ejabberd_option:allow_multiple_connections(Host).
anonymous_user_exist(User, Server) ->
lists:any(
@ -188,14 +187,3 @@ plain_password_required(_) ->
store_type(_) ->
external.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(allow_multiple_connections) ->
fun (V) when is_boolean(V) -> V end;
opt_type(anonymous_protocol) ->
fun (sasl_anon) -> sasl_anon;
(login_anon) -> login_anon;
(both) -> both
end;
opt_type(_) ->
[allow_multiple_connections, anonymous_protocol].

View File

@ -25,15 +25,13 @@
-module(ejabberd_auth_external).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
-export([start/1, stop/1, reload/1, set_password/3, check_password/4,
try_register/3, user_exists/2, remove_user/2,
store_type/1, plain_password_required/1, opt_type/1]).
store_type/1, plain_password_required/1]).
-include("logger.hrl").
@ -91,7 +89,7 @@ check_password_extauth(User, _AuthzId, Server, Password) ->
case extauth:check_password(User, Server, Password) of
Res when is_boolean(Res) -> Res;
{error, Reason} ->
failure(User, Server, check_password, Reason),
_ = failure(User, Server, check_password, Reason),
false
end;
true ->
@ -103,26 +101,3 @@ failure(User, Server, Fun, Reason) ->
?ERROR_MSG("External authentication program failed when calling "
"'~s' for ~s@~s: ~p", [Fun, User, Server, Reason]),
{error, db_failure}.
opt_type(extauth_cache) ->
?WARNING_MSG("option 'extauth_cache' is deprecated and has no effect, "
"use authentication or global cache configuration "
"options: auth_use_cache, auth_cache_life_time, "
"use_cache, cache_life_time, and so on", []),
fun (false) -> false;
(I) when is_integer(I), I >= 0 -> I
end;
opt_type(extauth_instances) ->
?WARNING_MSG("option 'extauth_instances' is deprecated and has no effect, "
"use 'extauth_pool_size'", []),
fun (V) when is_integer(V), V > 0 -> V end;
opt_type(extauth_program) ->
fun (V) -> binary_to_list(iolist_to_binary(V)) end;
opt_type(extauth_pool_name) ->
fun (V) -> iolist_to_binary(V) end;
opt_type(extauth_pool_size) ->
fun(I) when is_integer(I), I>0 -> I end;
opt_type(_) ->
[extauth_program, extauth_pool_size, extauth_pool_name,
%% Deprecated:
extauth_cache, extauth_instances].

View File

@ -25,8 +25,6 @@
-module(ejabberd_auth_ldap).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(gen_server).
@ -39,8 +37,7 @@
-export([start/1, stop/1, start_link/1, set_password/3,
check_password/4, user_exists/2,
get_users/2, count_users/2,
store_type/1, plain_password_required/1,
opt_type/1]).
store_type/1, plain_password_required/1]).
-include("logger.hrl").
@ -60,7 +57,6 @@
uids = [] :: [{binary()} | {binary(), binary()}],
ufilter = <<"">> :: binary(),
sfilter = <<"">> :: binary(),
lfilter :: {any(), any()} | undefined,
deref_aliases = never :: never | searching | finding | always,
dn_filter :: binary() | undefined,
dn_filter_attrs = [] :: [binary()]}).
@ -85,8 +81,10 @@ start(Host) ->
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
supervisor:terminate_child(ejabberd_backend_sup, Proc),
supervisor:delete_child(ejabberd_backend_sup, Proc).
case supervisor:terminate_child(ejabberd_backend_sup, Proc) of
ok -> supervisor:delete_child(ejabberd_backend_sup, Proc);
Err -> Err
end.
start_link(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
@ -246,19 +244,12 @@ find_user_dn(User, State) ->
[#eldap_entry{attributes = Attrs,
object_name = DN}
| _]} ->
dn_filter(DN, Attrs, State);
is_valid_dn(DN, Attrs, State);
_ -> false
end;
_ -> false
end.
%% apply the dn filter and the local filter:
dn_filter(DN, Attrs, State) ->
case check_local_filter(Attrs, State) of
false -> false;
true -> is_valid_dn(DN, Attrs, State)
end.
%% Check that the DN is valid, based on the dn filter
is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN;
is_valid_dn(DN, Attrs, State) ->
@ -294,30 +285,6 @@ is_valid_dn(DN, Attrs, State) ->
_ -> false
end.
%% The local filter is used to check an attribute in ejabberd
%% and not in LDAP to limit the load on the LDAP directory.
%% A local rule can be either:
%% {equal, {"accountStatus",["active"]}}
%% {notequal, {"accountStatus",["disabled"]}}
%% {ldap_local_filter, {notequal, {"accountStatus",["disabled"]}}}
check_local_filter(_Attrs,
#state{lfilter = undefined}) ->
true;
check_local_filter(Attrs,
#state{lfilter = LocalFilter}) ->
{Operation, FilterMatch} = LocalFilter,
local_filter(Operation, Attrs, FilterMatch).
local_filter(equal, Attrs, FilterMatch) ->
{Attr, Value} = FilterMatch,
case lists:keysearch(Attr, 1, Attrs) of
false -> false;
{value, {Attr, Value}} -> true;
_ -> false
end;
local_filter(notequal, Attrs, FilterMatch) ->
not local_filter(equal, Attrs, FilterMatch).
result_attrs(#state{uids = UIDs,
dn_filter_attrs = DNFilterAttrs}) ->
lists:foldl(fun ({UID}, Acc) -> [UID | Acc];
@ -329,25 +296,21 @@ result_attrs(#state{uids = UIDs,
%%% Auxiliary functions
%%%----------------------------------------------------------------------
parse_options(Host) ->
Cfg = eldap_utils:get_config(Host, []),
Cfg = ?eldap_config(ejabberd_option, Host),
Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
Bind_Eldap_ID = misc:atom_to_binary(
gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
UIDsTemp = ejabberd_config:get_option(
{ldap_uids, Host}, [{<<"uid">>, <<"%u">>}]),
UIDsTemp = ejabberd_option:ldap_uids(Host),
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
SubFilter = eldap_utils:generate_subfilter(UIDs),
UserFilter = case ejabberd_config:get_option({ldap_filter, Host}, <<"">>) of
UserFilter = case ejabberd_option:ldap_filter(Host) of
<<"">> ->
SubFilter;
F ->
<<"(&", SubFilter/binary, F/binary, ")">>
end,
SearchFilter = eldap_filter:do_sub(UserFilter,
[{<<"%u">>, <<"*">>}]),
{DNFilter, DNFilterAttrs} =
ejabberd_config:get_option({ldap_dn_filter, Host}, {undefined, []}),
LocalFilter = ejabberd_config:get_option({ldap_local_filter, Host}),
SearchFilter = eldap_filter:do_sub(UserFilter, [{<<"%u">>, <<"*">>}]),
{DNFilter, DNFilterAttrs} = ejabberd_option:ldap_dn_filter(Host),
#state{host = Host, eldap_id = Eldap_ID,
bind_eldap_id = Bind_Eldap_ID,
servers = Cfg#eldap_config.servers,
@ -359,19 +322,5 @@ parse_options(Host) ->
base = Cfg#eldap_config.base,
deref_aliases = Cfg#eldap_config.deref_aliases,
uids = UIDs, ufilter = UserFilter,
sfilter = SearchFilter, lfilter = LocalFilter,
sfilter = SearchFilter,
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(ldap_dn_filter) ->
fun ([{DNF, DNFA}]) ->
NewDNFA = case DNFA of
undefined -> [];
_ -> [iolist_to_binary(A) || A <- DNFA]
end,
NewDNF = eldap_utils:check_filter(DNF),
{NewDNF, NewDNFA}
end;
opt_type(ldap_local_filter) -> fun (V) -> V end;
opt_type(_) ->
[ldap_dn_filter, ldap_local_filter].

View File

@ -25,8 +25,6 @@
-module(ejabberd_auth_mnesia).
-compile([{parse_transform, ejabberd_sql_pt}]).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
@ -77,9 +75,7 @@ update_reg_users_counter_table(Server) ->
use_cache(Host) ->
case mnesia:table_info(passwd, storage_type) of
disc_only_copies ->
ejabberd_config:get_option(
{auth_use_cache, Host},
ejabberd_config:use_cache(Host));
ejabberd_option:auth_use_cache(Host);
_ ->
false
end.
@ -207,7 +203,7 @@ remove_user(User, Server) ->
need_transform(#reg_users_counter{}) ->
false;
need_transform(#passwd{us = {U, S}, password = Pass}) ->
need_transform({passwd, {U, S}, Pass}) ->
if is_binary(Pass) ->
case store_type(S) of
scram ->
@ -234,7 +230,7 @@ need_transform(#passwd{us = {U, S}, password = Pass}) ->
true
end.
transform(#passwd{us = {U, S}, password = Pass} = R)
transform({passwd, {U, S}, Pass})
when is_list(U) orelse is_list(S) orelse is_list(Pass) ->
NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
NewPass = case Pass of
@ -248,7 +244,7 @@ transform(#passwd{us = {U, S}, password = Pass} = R)
_ ->
iolist_to_binary(Pass)
end,
transform(R#passwd{us = NewUS, password = NewPass});
transform(#passwd{us = NewUS, password = NewPass});
transform(#passwd{us = {U, S}, password = Password} = P)
when is_binary(Password) ->
case store_type(S) of

View File

@ -24,15 +24,12 @@
%%%-------------------------------------------------------------------
-module(ejabberd_auth_pam).
-behaviour(ejabberd_config).
-author('xram@jabber.ru').
-behaviour(ejabberd_auth).
-export([start/1, stop/1, check_password/4,
user_exists/2, store_type/1, plain_password_required/1,
opt_type/1]).
user_exists/2, store_type/1, plain_password_required/1]).
start(_Host) ->
ejabberd:start_app(epam).
@ -77,15 +74,7 @@ store_type(_) -> external.
%% Internal functions
%%====================================================================
get_pam_service(Host) ->
ejabberd_config:get_option({pam_service, Host}, <<"ejabberd">>).
ejabberd_option:pam_service(Host).
get_pam_userinfotype(Host) ->
ejabberd_config:get_option({pam_userinfotype, Host}, username).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(pam_service) -> fun iolist_to_binary/1;
opt_type(pam_userinfotype) ->
fun (username) -> username;
(jid) -> jid
end;
opt_type(_) -> [pam_service, pam_userinfotype].
ejabberd_option:pam_userinfotype(Host).

View File

@ -25,8 +25,6 @@
-module(ejabberd_auth_riak).
-compile([{parse_transform, ejabberd_sql_pt}]).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).

View File

@ -25,17 +25,15 @@
-module(ejabberd_auth_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
-behaviour(ejabberd_config).
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, count_users/2, get_password/2,
remove_user/2, store_type/1, plain_password_required/1,
convert_to_scram/1, opt_type/1, export/1, which_users_exists/2]).
convert_to_scram/1, export/1, which_users_exists/2]).
-include("scram.hrl").
-include("logger.hrl").
@ -221,8 +219,7 @@ users_number(LServer) ->
LServer,
fun(pgsql, _) ->
case
ejabberd_config:get_option(
{pgsql_users_number_estimate, LServer}, false) of
ejabberd_option:pgsql_users_number_estimate(LServer) of
true ->
ejabberd_sql:sql_query_t(
?SQL("select @(reltuples :: bigint)d from pg_class"
@ -349,8 +346,3 @@ export(_Server) ->
(_Host, _R) ->
[]
end}].
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(pgsql_users_number_estimate) ->
fun (V) when is_boolean(V) -> V end;
opt_type(_) -> [pgsql_users_number_estimate].

View File

@ -91,8 +91,8 @@
xmpp_ver = <<"">> :: binary(),
inactivity_timer :: reference() | undefined,
wait_timer :: reference() | undefined,
wait_timeout = ?DEFAULT_WAIT :: timeout(),
inactivity_timeout :: timeout(),
wait_timeout = ?DEFAULT_WAIT :: pos_integer(),
inactivity_timeout :: pos_integer(),
prev_rid = 0 :: non_neg_integer(),
prev_key = <<"">> :: binary(),
prev_poll :: erlang:timestamp() | undefined,
@ -274,8 +274,7 @@ init([#body{attrs = Attrs}, IP, SID]) ->
Socket = make_socket(self(), IP),
XMPPVer = get_attr('xmpp:version', Attrs),
XMPPDomain = get_attr(to, Attrs),
{InBuf, Opts} = case gen_mod:get_module_opt(
XMPPDomain, mod_bosh, prebind) of
{InBuf, Opts} = case mod_bosh_opt:prebind(XMPPDomain) of
true ->
JID = make_random_jid(XMPPDomain),
{buf_new(XMPPDomain), [{jid, JID} | Opts2]};
@ -287,9 +286,8 @@ init([#body{attrs = Attrs}, IP, SID]) ->
case ejabberd_c2s:start(?MODULE, Socket, [{receiver, self()}|Opts]) of
{ok, C2SPid} ->
ejabberd_c2s:accept(C2SPid),
Inactivity = gen_mod:get_module_opt(XMPPDomain,
mod_bosh, max_inactivity),
MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat),
Inactivity = mod_bosh_opt:max_inactivity(XMPPDomain),
MaxConcat = mod_bosh_opt:max_concat(XMPPDomain),
ShapedReceivers = buf_new(XMPPDomain, ?MAX_SHAPED_REQUESTS_QUEUE_LEN),
State = #state{host = XMPPDomain, sid = SID, ip = IP,
xmpp_ver = XMPPVer, el_ibuf = InBuf,
@ -298,8 +296,12 @@ init([#body{attrs = Attrs}, IP, SID]) ->
shaped_receivers = ShapedReceivers,
shaper_state = ShaperState},
NewState = restart_inactivity_timer(State),
mod_bosh:open_session(SID, self()),
{ok, wait_for_session, NewState};
case mod_bosh:open_session(SID, self()) of
ok ->
{ok, wait_for_session, NewState};
{error, Reason} ->
{stop, Reason}
end;
{error, Reason} ->
{stop, Reason};
ignore ->
@ -328,8 +330,7 @@ wait_for_session(#body{attrs = Attrs} = Req, From,
Wait == 0, Hold == 0 -> erlang:timestamp();
true -> undefined
end,
MaxPause = gen_mod:get_module_opt(State#state.host,
mod_bosh, max_pause),
MaxPause = mod_bosh_opt:max_pause(State#state.host),
Resp = #body{attrs =
[{sid, State#state.sid}, {wait, Wait},
{ver, ?BOSH_VERSION}, {polling, ?DEFAULT_POLLING},
@ -887,7 +888,9 @@ decode_body(Data, Size, Type) ->
end.
decode(Data, xml) ->
fxml_stream:parse_element(Data).
fxml_stream:parse_element(Data);
decode(Data, json) ->
Data.
attrs_to_body_attrs(Attrs) ->
lists:foldl(fun (_, {error, Reason}) -> {error, Reason};
@ -991,8 +994,7 @@ buf_new(Host) ->
buf_new(Host, unlimited).
buf_new(Host, Limit) ->
QueueType = gen_mod:get_module_opt(
Host, mod_bosh, queue_type),
QueueType = mod_bosh_opt:queue_type(Host),
p1_queue:new(QueueType, Limit).
buf_in(Xs, Buf) ->

View File

@ -21,15 +21,13 @@
%%%-------------------------------------------------------------------
-module(ejabberd_c2s).
-behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
-behaviour(ejabberd_listener).
-dialyzer([{no_fail_call, [stop/1, process_closed/2]},
{no_return, process_closed/2}]).
-protocol({rfc, 6121}).
%% ejabberd_listener callbacks
-export([start/3, start_link/3, accept/1, listen_opt_type/1, listen_options/0]).
%% ejabberd_config callbacks
-export([opt_type/1, transform_listen_option/2]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@ -109,8 +107,7 @@ resend_presence(Pid, To) ->
close(Ref) ->
xmpp_stream_in:close(Ref).
-spec close(pid(), atom()) -> ok;
(state(), atom()) -> state().
-spec close(pid(), atom()) -> ok.
close(Ref, Reason) ->
xmpp_stream_in:close(Ref, Reason).
@ -319,37 +316,34 @@ tls_options(#{lserver := LServer, tls_options := DefaultOpts,
TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of
{true, CertFile} when CertFile /= undefined -> DefaultOpts;
{_, _} ->
case get_certfile(LServer) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
case ejabberd_pkix:get_certfile(LServer) of
error -> DefaultOpts;
{ok, CertFile} ->
lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
end
end,
TLSOpts2 = case ejabberd_config:get_option(
{c2s_ciphers, LServer}) of
TLSOpts2 = case ejabberd_option:c2s_ciphers(LServer) of
undefined -> TLSOpts1;
Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
{ciphers, Ciphers})
end,
TLSOpts3 = case ejabberd_config:get_option(
{c2s_protocol_options, LServer}) of
TLSOpts3 = case ejabberd_option:c2s_protocol_options(LServer) of
undefined -> TLSOpts2;
ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
{protocol_options, ProtoOpts})
end,
TLSOpts4 = case ejabberd_config:get_option(
{c2s_dhfile, LServer}) of
TLSOpts4 = case ejabberd_option:c2s_dhfile(LServer) of
undefined -> TLSOpts3;
DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
{dhfile, DHFile})
end,
TLSOpts5 = case ejabberd_config:get_option(
{c2s_cafile, LServer}) of
TLSOpts5 = case ejabberd_option:c2s_cafile(LServer) of
undefined -> TLSOpts4;
CAFile -> lists:keystore(cafile, 1, TLSOpts4,
{cafile, CAFile})
end,
case ejabberd_config:get_option({c2s_tls_compression, LServer}) of
case ejabberd_option:c2s_tls_compression(LServer) of
undefined -> TLSOpts5;
false -> [compression_none | TLSOpts5];
true -> lists:delete(compression_none, TLSOpts5)
@ -376,7 +370,7 @@ authenticated_stream_features(#{lserver := LServer}) ->
sasl_mechanisms(Mechs, #{lserver := LServer} = State) ->
Type = ejabberd_auth:store_type(LServer),
Mechs1 = ejabberd_config:get_option({disable_sasl_mechanisms, LServer}, []),
Mechs1 = ejabberd_option:disable_sasl_mechanisms(LServer),
%% I re-created it from cyrsasl ets magic, but I think it's wrong
%% TODO: need to check before 18.09 release
lists:filter(
@ -423,9 +417,8 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
{error, xmpp:err_conflict(), State};
{accept_resource, Resource} ->
JID = jid:make(U, S, Resource),
case acl:access_matches(Access,
#{usr => jid:split(JID), ip => IP},
LServer) of
case acl:match_rule(LServer, Access,
#{usr => jid:split(JID), ip => IP}) of
allow ->
State1 = open_session(State#{resource => Resource,
sid => ejabberd_sm:make_sid()}),
@ -538,14 +531,14 @@ init([State, Opts]) ->
TLSRequired = proplists:get_bool(starttls_required, Opts),
TLSVerify = proplists:get_bool(tls_verify, Opts),
Zlib = proplists:get_bool(zlib, Opts),
Timeout = ejabberd_config:negotiation_timeout(),
Timeout = ejabberd_option:negotiation_timeout(),
State1 = State#{tls_options => TLSOpts2,
tls_required => TLSRequired,
tls_enabled => TLSEnabled,
tls_verify => TLSVerify,
pres_a => ?SETS:new(),
zlib => Zlib,
lang => ejabberd_config:get_mylang(),
lang => ejabberd_option:language(),
server => ejabberd_config:get_myname(),
lserver => ejabberd_config:get_myname(),
access => Access,
@ -668,36 +661,39 @@ route_probe_reply(_, _) ->
-spec process_presence_out(state(), presence()) -> state().
process_presence_out(#{lserver := LServer, jid := JID,
lang := Lang, pres_a := PresA} = State,
lang := Lang, pres_a := PresA} = State0,
#presence{from = From, to = To, type = Type} = Pres) ->
if Type == subscribe; Type == subscribed;
Type == unsubscribe; Type == unsubscribed ->
Access = gen_mod:get_module_opt(LServer, mod_roster, access),
MyBareJID = jid:remove_resource(JID),
case acl:match_rule(LServer, Access, MyBareJID) of
deny ->
AccessErrTxt = <<"Access denied by service policy">>,
AccessErr = xmpp:err_forbidden(AccessErrTxt, Lang),
send_error(State, Pres, AccessErr);
allow ->
ejabberd_hooks:run(roster_out_subscription, LServer, [Pres])
end;
true -> ok
end,
case privacy_check_packet(State, Pres, out) of
State1 =
if Type == subscribe; Type == subscribed;
Type == unsubscribe; Type == unsubscribed ->
Access = mod_roster_opt:access(LServer),
MyBareJID = jid:remove_resource(JID),
case acl:match_rule(LServer, Access, MyBareJID) of
deny ->
AccessErrTxt = <<"Access denied by service policy">>,
AccessErr = xmpp:err_forbidden(AccessErrTxt, Lang),
send_error(State0, Pres, AccessErr);
allow ->
ejabberd_hooks:run(roster_out_subscription, LServer, [Pres]),
State0
end;
true ->
State0
end,
case privacy_check_packet(State1, Pres, out) of
deny ->
PrivErrTxt = <<"Your active privacy list has denied "
"the routing of this stanza.">>,
PrivErr = xmpp:err_not_acceptable(PrivErrTxt, Lang),
send_error(State, Pres, PrivErr);
send_error(State1, Pres, PrivErr);
allow when Type == subscribe; Type == subscribed;
Type == unsubscribe; Type == unsubscribed ->
BareFrom = jid:remove_resource(From),
ejabberd_router:route(xmpp:set_from_to(Pres, BareFrom, To)),
State;
State1;
allow when Type == error; Type == probe ->
ejabberd_router:route(Pres),
State;
State1;
allow ->
ejabberd_router:route(Pres),
LTo = jid:tolower(To),
@ -710,12 +706,12 @@ process_presence_out(#{lserver := LServer, jid := JID,
available -> ?SETS:add_element(LTo, PresA);
unavailable -> ?SETS:del_element(LTo, PresA)
end,
State#{pres_a => A};
State1#{pres_a => A};
true ->
State
State1
end;
true ->
State
State1
end
end.
@ -724,7 +720,7 @@ process_self_presence(#{lserver := LServer, sid := SID,
user := U, server := S, resource := R} = State,
#presence{type = unavailable} = Pres) ->
Status = xmpp:get_text(Pres#presence.status),
ejabberd_sm:unset_presence(SID, U, S, R, Status),
_ = ejabberd_sm:unset_presence(SID, U, S, R, Status),
{Pres1, State1} = ejabberd_hooks:run_fold(
c2s_self_presence, LServer, {Pres, State}, []),
State2 = broadcast_presence_unavailable(State1, Pres1),
@ -732,7 +728,7 @@ process_self_presence(#{lserver := LServer, sid := SID,
process_self_presence(#{lserver := LServer} = State,
#presence{type = available} = Pres) ->
PreviousPres = maps:get(pres_last, State, undefined),
update_priority(State, Pres),
_ = update_priority(State, Pres),
{Pres1, State1} = ejabberd_hooks:run_fold(
c2s_self_presence, LServer, {Pres, State}, []),
State2 = State1#{pres_last => Pres1,
@ -867,8 +863,7 @@ get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
resource_conflict_action(U, S, R) ->
OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
true ->
ejabberd_config:get_option(
{resource_conflict, S}, acceptnew);
ejabberd_option:resource_conflict(S);
false ->
acceptnew
end,
@ -934,9 +929,8 @@ fix_from_to(Pkt, _State) ->
change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
user := U, server := S, resource := R} = State) ->
JID = jid:make(U, S, R),
Shaper = acl:access_matches(ShaperName,
#{usr => jid:split(JID), ip => IP},
LServer),
Shaper = ejabberd_shaper:match(LServer, ShaperName,
#{usr => jid:split(JID), ip => IP}),
xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)).
-spec format_reason(state(), term()) -> binary().
@ -951,84 +945,24 @@ format_reason(_, {shutdown, _}) ->
format_reason(_, _) ->
<<"internal server error">>.
-spec get_certfile(binary()) -> file:filename_all() | undefined.
get_certfile(LServer) ->
case ejabberd_pkix:get_certfile(LServer) of
{ok, CertFile} ->
CertFile;
error ->
ejabberd_config:get_option(
{domain_certfile, LServer},
ejabberd_config:get_option({c2s_certfile, LServer}))
end.
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(c2s_ciphers) -> fun iolist_to_binary/1;
opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
opt_type(c2s_cafile) -> fun misc:try_read_file/1;
opt_type(c2s_protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(c2s_tls_compression) ->
fun (true) -> true;
(false) -> false
end;
opt_type(resource_conflict) ->
fun (setresource) -> setresource;
(closeold) -> closeold;
(closenew) -> closenew;
(acceptnew) -> acceptnew
end;
opt_type(disable_sasl_mechanisms) ->
fun (V) when is_list(V) ->
lists:map(fun (M) -> str:to_upper(M) end, V);
(V) -> [str:to_upper(V)]
end;
opt_type(_) ->
[c2s_ciphers, c2s_cafile, c2s_dhfile,
c2s_protocol_options, c2s_tls_compression, resource_conflict,
disable_sasl_mechanisms].
listen_opt_type(certfile = Opt) ->
fun(S) ->
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
"'certfiles' global option instead", [Opt, ?MODULE]),
{ok, File} = ejabberd_pkix:add_certfile(S),
File
end;
listen_opt_type(starttls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(starttls_required) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(starttls) ->
econf:bool();
listen_opt_type(starttls_required) ->
econf:bool();
listen_opt_type(tls_verify) ->
econf:bool();
listen_opt_type(zlib) ->
fun(true) ->
ejabberd:start_app(ezlib),
true;
(false) ->
false
end;
listen_opt_type(stream_management) ->
fun(B) when is_boolean(B) ->
?ERROR_MSG("Listening option 'stream_management' is ignored: "
"use mod_stream_mgmt module", []),
B
end;
listen_opt_type(O) ->
MgmtOpts = mod_stream_mgmt:mod_options(ejabberd_config:get_myname()),
case lists:keymember(O, 1, MgmtOpts) of
true ->
fun(V) ->
?ERROR_MSG("Listening option '~s' is ignored: use '~s' "
"option from mod_stream_mgmt module", [O, O]),
(mod_stream_mgmt:mod_opt_type(O))(V)
end
end.
econf:and_then(
econf:bool(),
fun(false) -> false;
(true) ->
ejabberd:start_app(ezlib),
true
end).
listen_options() ->
[{access, all},
{shaper, none},
{certfile, undefined},
{ciphers, undefined},
{dhfile, undefined},
{cafile, undefined},
@ -1040,5 +974,4 @@ listen_options() ->
{tls_verify, false},
{zlib, false},
{max_stanza_size, infinity},
{max_fsm_queue, 5000}|
mod_stream_mgmt:mod_options(ejabberd_config:get_myname())].
{max_fsm_queue, 5000}].

View File

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

View File

@ -25,8 +25,6 @@
-module(ejabberd_captcha).
-behaviour(ejabberd_config).
-protocol({xep, 158, '1.0'}).
-behaviour(gen_server).
@ -41,7 +39,7 @@
-export([create_captcha/6, build_captcha_html/2,
check_captcha/2, process_reply/1, process/2,
is_feature_available/0, create_captcha_x/5,
opt_type/1, host_up/1, host_down/1,
host_up/1, host_down/1,
config_reloaded/0, process_iq/1]).
-include("xmpp.hrl").
@ -52,6 +50,7 @@
-define(LIMIT_PERIOD, 60*1000*1000).
-type image_error() :: efbig | enodata | limit | malformed_image | timeout.
-type priority() :: neg_integer().
-record(state, {limits = treap:empty() :: treap:treap(),
enabled = false :: boolean()}).
@ -66,11 +65,11 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
-spec captcha_text(undefined | binary()) -> binary().
-spec captcha_text(binary()) -> binary().
captcha_text(Lang) ->
translate:translate(Lang, <<"Enter the text you see">>).
-spec mk_ocr_field(binary() | undefined, binary(), binary()) -> xdata_field().
-spec mk_ocr_field(binary(), binary(), binary()) -> xdata_field().
mk_ocr_field(Lang, CID, Type) ->
URI = #media_uri{type = Type, uri = <<"cid:", CID/binary>>},
#xdata_field{var = <<"ocr">>,
@ -79,13 +78,13 @@ mk_ocr_field(Lang, CID, Type) ->
required = true,
sub_els = [#media{uri = [URI]}]}.
-spec mk_field(_, binary(), binary()) -> xdata_field().
mk_field(Type, Var, Value) ->
#xdata_field{type = Type, var = Var, values = [Value]}.
-spec create_captcha(binary(), jid(), jid(),
binary(), any(), any()) -> {error, image_error()} |
{ok, binary(), [text()], [xmlel()]}.
{ok, binary(), [text()], [xmpp_element()]}.
create_captcha(SID, From, To, Lang, Limiter, Args) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
@ -116,8 +115,7 @@ create_captcha(SID, From, To, Lang, Limiter, Args) ->
end.
-spec create_captcha_x(binary(), jid(), binary(), any(), xdata()) ->
{ok, xdata()} | {error, image_error()}.
{ok, [xmpp_element()]} | {error, image_error()}.
create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
@ -151,7 +149,7 @@ create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) ->
-spec build_captcha_html(binary(), binary()) -> captcha_not_found |
{xmlel(),
{xmlel(), xmlel(),
{xmlel(), cdata(),
xmlel(), xmlel()}}.
build_captcha_html(Id, Lang) ->
@ -161,7 +159,7 @@ build_captcha_html(Id, Lang) ->
attrs =
[{<<"src">>, get_url(<<Id/binary, "/image">>)}],
children = []},
TextEl = {xmlcdata, captcha_text(Lang)},
Text = {xmlcdata, captcha_text(Lang)},
IdEl = #xmlel{name = <<"input">>,
attrs =
[{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>},
@ -181,7 +179,7 @@ build_captcha_html(Id, Lang) ->
[ImgEl,
#xmlel{name = <<"br">>, attrs = [],
children = []},
TextEl,
Text,
#xmlel{name = <<"br">>, attrs = [],
children = []},
IdEl, KeyEl,
@ -193,7 +191,7 @@ build_captcha_html(Id, Lang) ->
{<<"name">>, <<"enter">>},
{<<"value">>, <<"OK">>}],
children = []}]},
{FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
{FormEl, {ImgEl, Text, IdEl, KeyEl}};
_ -> captcha_not_found
end.
@ -216,6 +214,7 @@ process_reply(#xcaptcha{xdata = #xdata{} = X}) ->
process_reply(_) ->
{error, malformed}.
-spec process_iq(iq()) -> iq().
process_iq(#iq{type = set, lang = Lang, sub_els = [#xcaptcha{} = El]} = IQ) ->
case process_reply(El) of
ok ->
@ -238,7 +237,7 @@ process(_Handlers,
#request{method = 'GET', lang = Lang,
path = [_, Id]}) ->
case build_captcha_html(Id, Lang) of
{FormEl, _} when is_tuple(FormEl) ->
{FormEl, _} ->
Form = #xmlel{name = <<"div">>,
attrs = [{<<"align">>, <<"center">>}],
children = [FormEl]},
@ -292,8 +291,8 @@ config_reloaded() ->
gen_server:call(?MODULE, config_reloaded, timer:minutes(1)).
init([]) ->
mnesia:delete_table(captcha),
ets:new(captcha, [named_table, public, {keypos, #captcha.id}]),
_ = mnesia:delete_table(captcha),
_ = ets:new(captcha, [named_table, public, {keypos, #captcha.id}]),
case check_captcha_setup() of
true ->
register_handlers(),
@ -364,27 +363,36 @@ terminate(_Reason, #state{enabled = Enabled}) ->
register_handlers() ->
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:add(host_down, ?MODULE, host_down, 50),
lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()).
lists:foreach(fun host_up/1, ejabberd_option:hosts()).
unregister_handlers() ->
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 50),
lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()).
lists:foreach(fun host_down/1, ejabberd_option:hosts()).
code_change(_OldVsn, State, _Extra) -> {ok, State}.
create_image() -> create_image(undefined).
-spec create_image() -> {ok, binary(), binary(), binary()} |
{error, image_error()}.
create_image() ->
create_image(undefined).
-spec create_image(term()) -> {ok, binary(), binary(), binary()} |
{error, image_error()}.
create_image(Limiter) ->
Key = str:substr(p1_rand:get_string(), 1, 6),
create_image(Limiter, Key).
-spec create_image(term(), binary()) -> {ok, binary(), binary(), binary()} |
{error, image_error()}.
create_image(Limiter, Key) ->
case is_limited(Limiter) of
true -> {error, limit};
false -> do_create_image(Key)
true -> {error, limit};
false -> do_create_image(Key)
end.
-spec do_create_image(binary()) -> {ok, binary(), binary(), binary()} |
{error, image_error()}.
do_create_image(Key) ->
FileName = get_prog_name(),
Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])),
@ -416,7 +424,7 @@ do_create_image(Key) ->
end.
get_prog_name() ->
case ejabberd_config:get_option(captcha_cmd) of
case ejabberd_option:captcha_cmd() of
undefined ->
?DEBUG("The option captcha_cmd is not configured, "
"but some module wants to use the CAPTCHA "
@ -427,8 +435,9 @@ get_prog_name() ->
FileName
end.
-spec get_url(binary()) -> binary().
get_url(Str) ->
CaptchaHost = ejabberd_config:get_option(captcha_host, <<"">>),
CaptchaHost = ejabberd_option:captcha_host(),
case str:tokens(CaptchaHost, <<":">>) of
[Host] ->
<<"http://", Host/binary, "/captcha/", Str/binary>>;
@ -453,7 +462,7 @@ get_transfer_protocol(PortString) ->
get_captcha_transfer_protocol(PortListeners).
get_port_listeners(PortNumber) ->
AllListeners = ejabberd_config:get_option(listen, []),
AllListeners = ejabberd_option:listen(),
lists:filter(
fun({{Port, _IP, _Transport}, _Module, _Opts}) ->
Port == PortNumber
@ -465,21 +474,26 @@ get_captcha_transfer_protocol([]) ->
"'captcha' option. Change the port number "
"or specify http:// in that option.">>);
get_captcha_transfer_protocol([{_, ejabberd_http, Opts} | Listeners]) ->
case proplists:get_bool(captcha, Opts) of
true ->
case proplists:get_bool(tls, Opts) of
Handlers = maps:get(request_handlers, Opts, []),
case lists:any(
fun({_, ?MODULE}) -> true;
({_, _}) -> false
end, Handlers) of
true ->
case maps:get(tls, Opts) of
true -> https;
false -> http
end;
false -> get_captcha_transfer_protocol(Listeners)
false ->
get_captcha_transfer_protocol(Listeners)
end;
get_captcha_transfer_protocol([_ | Listeners]) ->
get_captcha_transfer_protocol(Listeners).
is_limited(undefined) -> false;
is_limited(Limiter) ->
case ejabberd_config:get_option(captcha_limit) of
undefined -> false;
case ejabberd_option:captcha_limit() of
infinity -> false;
Int ->
case catch gen_server:call(?MODULE,
{is_limited, Limiter, Int}, 5000)
@ -494,12 +508,14 @@ is_limited(Limiter) ->
-define(MAX_FILE_SIZE, 64 * 1024).
-spec cmd(string()) -> {ok, binary()} | {error, image_error()}.
cmd(Cmd) ->
Port = open_port({spawn, Cmd}, [stream, eof, binary]),
TRef = erlang:start_timer(?CMD_TIMEOUT, self(),
timeout),
recv_data(Port, TRef, <<>>).
-spec recv_data(port(), reference(), binary()) -> {ok, binary()} | {error, image_error()}.
recv_data(Port, TRef, Buf) ->
receive
{Port, {data, Bytes}} ->
@ -516,6 +532,8 @@ recv_data(Port, TRef, Buf) ->
return(Port, TRef, {error, timeout})
end.
-spec return(port(), reference(), {ok, binary()} | {error, image_error()}) ->
{ok, binary()} | {error, image_error()}.
return(Port, TRef, Result) ->
misc:cancel_timer(TRef),
catch port_close(Port),
@ -543,10 +561,11 @@ check_captcha_setup() ->
false
end.
-spec lookup_captcha(binary()) -> {ok, #captcha{}} | {error, enoent}.
lookup_captcha(Id) ->
case ets:lookup(captcha, Id) of
[C] -> {ok, C};
_ -> {error, enoent}
[] -> {error, enoent}
end.
-spec check_captcha(binary(), binary()) -> captcha_not_found |
@ -554,8 +573,8 @@ lookup_captcha(Id) ->
captcha_non_valid.
check_captcha(Id, ProvidedKey) ->
case ets:lookup(captcha, Id) of
[#captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}] ->
case lookup_captcha(Id) of
{ok, #captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}} ->
ets:delete(captcha, Id),
misc:cancel_timer(Tref),
if ValidKey == ProvidedKey ->
@ -565,10 +584,11 @@ check_captcha(Id, ProvidedKey) ->
callback(captcha_failed, Pid, Args),
captcha_non_valid
end;
_ ->
{error, _} ->
captcha_not_found
end.
-spec clean_treap(treap:treap(), priority()) -> treap:treap().
clean_treap(Treap, CleanPriority) ->
case treap:is_empty(Treap) of
true -> Treap;
@ -588,16 +608,6 @@ callback(Result, Pid, Args) when is_pid(Pid) ->
callback(_, _, _) ->
ok.
-spec now_priority() -> priority().
now_priority() ->
-erlang:system_time(microsecond).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(captcha_cmd) ->
fun (FileName) ->
F = iolist_to_binary(FileName), if F /= <<"">> -> F end
end;
opt_type(captcha_host) -> fun iolist_to_binary/1;
opt_type(captcha_limit) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) ->
[captcha_cmd, captcha_host, captcha_limit].

View File

@ -21,7 +21,6 @@
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_cluster).
-behaviour(ejabberd_config).
-behaviour(gen_server).
%% API
@ -33,7 +32,6 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([opt_type/1]).
-include("logger.hrl").
@ -154,9 +152,9 @@ subscribe(Proc) ->
%%% gen_server API
%%%===================================================================
init([]) ->
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
Nodes = ejabberd_config:get_option(cluster_nodes, []),
net_kernel:set_net_ticktime(Ticktime),
Ticktime = ejabberd_option:net_ticktime(),
Nodes = ejabberd_option:cluster_nodes(),
_ = net_kernel:set_net_ticktime(Ticktime),
lists:foreach(fun(Node) ->
net_kernel:connect_node(Node)
end, Nodes),
@ -195,19 +193,8 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions
%%%===================================================================
get_mod() ->
Backend = ejabberd_config:get_option(cluster_backend, mnesia),
Backend = ejabberd_option:cluster_backend(),
list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)).
rpc_timeout() ->
timer:seconds(ejabberd_config:get_option(rpc_timeout, 5)).
opt_type(net_ticktime) ->
fun (P) when is_integer(P), P > 0 -> P end;
opt_type(cluster_nodes) ->
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
opt_type(rpc_timeout) ->
fun (T) when is_integer(T), T > 0 -> T end;
opt_type(cluster_backend) ->
fun (T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(_) ->
[rpc_timeout, cluster_backend, cluster_nodes, net_ticktime].
ejabberd_option:rpc_timeout().

View File

@ -211,7 +211,6 @@
-author('badlop@process-one.net').
-behaviour(gen_server).
-behaviour(ejabberd_config).
-define(DEFAULT_VERSION, 1000000).
@ -225,11 +224,8 @@
get_command_definition/2,
get_tags_commands/0,
get_tags_commands/1,
get_exposed_commands/0,
register_commands/1,
unregister_commands/1,
expose_commands/1,
opt_type/1,
get_commands_spec/0,
get_commands_definition/0,
get_commands_definition/1,
@ -245,6 +241,8 @@
-define(POLICY_ACCESS, '$policy').
-type auth() :: {binary(), binary(), binary() | {oauth, binary()}, boolean()} | map().
-record(state, {}).
get_commands_spec() ->
@ -292,7 +290,6 @@ init([]) ->
{attributes, record_info(fields, ejabberd_commands)},
{type, bag}]),
register_commands(get_commands_spec()),
ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
@ -338,29 +335,7 @@ unregister_commands(Commands) ->
mnesia:dirty_delete_object(Command)
end,
Commands),
ejabberd_access_permissions:invalidate(),
ok.
%% @doc Expose command through ejabberd ReST API.
%% Pass a list of command names or policy to expose.
-spec expose_commands([ejabberd_commands()|atom()|open|user|admin|restricted]) -> ok | {error, atom()}.
expose_commands(Commands) ->
Names = lists:map(fun(#ejabberd_commands{name = Name}) ->
Name;
(Name) when is_atom(Name) ->
Name
end,
Commands),
case ejabberd_config:add_option(commands, [{add_commands, Names}]) of
ok ->
ok;
{aborted, Reason} ->
{error, Reason};
{atomic, Result} ->
Result
end.
ejabberd_access_permissions:invalidate().
-spec list_commands() -> [{atom(), [aterm()], string()}].
@ -378,20 +353,6 @@ list_commands(Version) ->
args = Args,
desc = Desc} <- Commands].
-spec list_commands_policy(integer()) ->
[{atom(), [aterm()], string(), atom()}].
%% @doc Get a list of all the available commands, arguments,
%% description, and policy in a given API version.
list_commands_policy(Version) ->
Commands = get_commands_definition(Version),
[{Name, Args, Desc, Policy} ||
#ejabberd_commands{name = Name,
args = Args,
desc = Desc,
policy = Policy} <- Commands].
-spec get_command_format(atom()) -> {[aterm()], rterm()}.
%% @doc Get the format of arguments and result of a command.
@ -402,12 +363,7 @@ get_command_format(Name, Version) when is_integer(Version) ->
get_command_format(Name, Auth) ->
get_command_format(Name, Auth, ?DEFAULT_VERSION).
-spec get_command_format(atom(),
{binary(), binary(), binary(), boolean()} |
noauth | admin,
integer()) ->
{[aterm()], rterm()}.
-spec get_command_format(atom(), noauth | admin | auth(), integer()) -> {[aterm()], rterm()}.
get_command_format(Name, Auth, Version) ->
Admin = is_admin(Name, Auth, #{}),
#ejabberd_commands{args = Args,
@ -422,12 +378,6 @@ get_command_format(Name, Auth, Version) ->
{Args, Result}
end.
%% The oauth scopes for a command are the command name itself,
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
[erlang:atom_to_binary(Name,utf8)] ++ [<<"ejabberd:user">> || Policy == user] ++ [<<"ejabberd:admin">> || Policy == admin].
-spec get_command_definition(atom()) -> ejabberd_commands().
%% @doc Get the definition record of a command.
@ -533,95 +483,12 @@ get_tags_commands(Version) ->
%% -----------------------------
%% Access verification
%% -----------------------------
-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
(ejabberd_commands(),
{binary(), binary(), binary(), boolean()}) ->
{ok, binary(), binary()}.
check_auth(_Command, noauth) ->
no_auth_provided;
check_auth(Command, {User, Server, {oauth, Token}, _}) ->
ScopeList = cmd_scope(Command),
case ejabberd_oauth:check_token(User, Server, ScopeList, Token) of
true ->
{ok, User, Server};
_ ->
throw({error, invalid_account_data})
end;
check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
%% Check the account exists and password is valid
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true -> {ok, User, Server};
_ -> throw({error, invalid_account_data})
end.
get_exposed_commands() ->
get_exposed_commands(?DEFAULT_VERSION).
get_exposed_commands(Version) ->
Opts0 = ejabberd_config:get_option(commands, []),
Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
CommandsList = list_commands_policy(Version),
OpenCmds = [N || {N, _, _, open} <- CommandsList],
RestrictedCmds = [N || {N, _, _, restricted} <- CommandsList],
AdminCmds = [N || {N, _, _, admin} <- CommandsList],
UserCmds = [N || {N, _, _, user} <- CommandsList],
Cmds =
lists:foldl(
fun([{add_commands, L}], Acc) ->
Cmds = expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds),
lists:usort(Cmds ++ Acc);
([{remove_commands, L}], Acc) ->
Cmds = expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds),
Acc -- Cmds;
(_, Acc) -> Acc
end, [], Opts),
Cmds.
%% This is used to allow mixing command policy (like open, user, admin, restricted), with command entry
expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_atom(L) ->
expand_commands([L], OpenCmds, UserCmds, AdminCmds, RestrictedCmds);
expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_list(L) ->
lists:foldl(fun(open, Acc) -> OpenCmds ++ Acc;
(user, Acc) -> UserCmds ++ Acc;
(admin, Acc) -> AdminCmds ++ Acc;
(restricted, Acc) -> RestrictedCmds ++ Acc;
(Command, Acc) when is_atom(Command) ->
[Command|Acc]
end, [], L).
-spec is_admin(atom(), admin | noauth | auth(), map()) -> boolean().
is_admin(_Name, admin, _Extra) ->
true;
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
false;
is_admin(_Name, Map, _extra) when is_map(Map) ->
true;
is_admin(Name, Auth, Extra) ->
{ACLInfo, Server} = case Auth of
{U, S, _, _} ->
{Extra#{usr=>jid:split(jid:make(U, S))}, S};
_ ->
{Extra, global}
end,
AdminAccess = ejabberd_config:get_option(commands_admin_access, none),
case acl:access_matches(AdminAccess, ACLInfo, Server) of
allow ->
case catch check_auth(get_command_definition(Name), Auth) of
{ok, _, _} -> true;
no_auth_provided -> true;
_ -> false
end;
deny -> false
end.
permission_addon() ->
[{<<"'commands' option compatibility shim">>,
{[],
[{access, ejabberd_config:get_option(commands_admin_access, none)}],
{get_exposed_commands(), []}}}].
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
opt_type(commands) ->
fun(V) when is_list(V) -> V end;
opt_type(_) -> [commands, commands_admin_access].
is_admin(_Name, _Auth, _Extra) ->
false.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,517 @@
%%%----------------------------------------------------------------------
%%% 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_config_transformer).
%% API
-export([map_reduce/1]).
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
map_reduce(Y) ->
F = fun(Y1) ->
Y2 = (validator())(Y1),
Y3 = transform(Y2),
if Y2 /= Y3 ->
?DEBUG("Transformed configuration:~s~n",
[misc:format_val({yaml, Y3})]);
true ->
ok
end,
Y3
end,
econf:validate(F, Y).
%%%===================================================================
%%% Transformer
%%%===================================================================
transform(Y) ->
{Y1, Acc1} = transform(global, Y, #{}),
{Y2, Acc2} = update(Y1, Acc1),
filter(global, Y2, Acc2).
transform(Host, Y, Acc) ->
filtermapfoldr(
fun({Opt, HostOpts}, Acc1) when (Opt == host_config orelse
Opt == append_host_config)
andalso Host == global ->
case filtermapfoldr(
fun({Host1, Opts}, Acc2) ->
case transform(Host1, Opts, Acc2) of
{[], Acc3} ->
{false, Acc3};
{Opts1, Acc3} ->
{{true, {Host1, Opts1}}, Acc3}
end
end, Acc1, HostOpts) of
{[], Acc4} ->
{false, Acc4};
{HostOpts1, Acc4} ->
{{true, {Opt, HostOpts1}}, Acc4}
end;
({Opt, Val}, Acc1) ->
transform(Host, Opt, Val, Acc1)
end, Acc, Y).
transform(Host, modules, ModOpts, Acc) ->
{ModOpts1, Acc2} =
lists:mapfoldr(
fun({Mod, Opts}, Acc1) ->
Opts1 = transform_module_options(Opts),
transform_module(Host, Mod, Opts1, Acc1)
end, Acc, ModOpts),
{{true, {modules, ModOpts1}}, Acc2};
transform(global, listen, Listeners, Acc) ->
{Listeners1, Acc2} =
lists:mapfoldr(
fun(Opts, Acc1) ->
transform_listener(Opts, Acc1)
end, Acc, Listeners),
{{true, {listen, Listeners1}}, Acc2};
transform(_Host, Opt, CertFile, Acc) when (Opt == domain_certfile) orelse
(Opt == c2s_certfile) orelse
(Opt == s2s_certfile) ->
?WARNING_MSG("Option '~s' is deprecated and was automatically "
"appended to 'certfiles' option. ~s",
[Opt, adjust_hint()]),
CertFiles = maps:get(certfiles, Acc, []),
Acc1 = maps:put(certfiles, CertFiles ++ [CertFile], Acc),
{false, Acc1};
transform(_Host, certfiles, CertFiles1, Acc) ->
CertFiles2 = maps:get(certfiles, Acc, []),
Acc1 = maps:put(certfiles, CertFiles1 ++ CertFiles2, Acc),
{true, Acc1};
transform(Host, s2s_use_starttls, required_trusted, Acc) ->
?WARNING_MSG("The value 'required_trusted' of option "
"'s2s_use_starttls' is deprecated and was "
"automatically replaced with value 'required'. "
"The module 'mod_s2s_dialback' has also "
"been automatically removed from the configuration. ~s",
[adjust_hint()]),
Hosts = maps:get(remove_s2s_dialback, Acc, []),
Acc1 = maps:put(remove_s2s_dialback, [Host|Hosts], Acc),
{{true, {s2s_use_starttls, required}}, Acc1};
transform(_Host, _Opt, _Val, Acc) ->
{true, Acc}.
update(Y, Acc) ->
set_certfiles(Y, Acc).
filter(Host, Y, Acc) ->
lists:filtermap(
fun({Opt, HostOpts}) when (Opt == host_config orelse
Opt == append_host_config)
andalso Host == global ->
HostOpts1 = lists:map(
fun({Host1, Opts1}) ->
{Host1, filter(Host1, Opts1, Acc)}
end, HostOpts),
{true, {Opt, HostOpts1}};
({Opt, Val}) ->
filter(Host, Opt, Val, Acc)
end, Y).
filter(_Host, ca_path, _, _) ->
warn_removed_option(ca_path, ca_file),
false;
filter(_Host, iqdisc, _, _) ->
warn_removed_option(iqdisc),
false;
filter(_Host, access, _, _) ->
warn_removed_option(access, access_rules),
false;
filter(_Host, commands, _, _) ->
warn_removed_option(commands, api_permissions),
false;
filter(_Host, ejabberdctl_access_commands, _, _) ->
warn_removed_option(ejabberdctl_access_commands, api_permissions),
false;
filter(_Host, commands_admin_access, _, _) ->
warn_removed_option(commands_admin_access, api_permissions),
false;
filter(_Host, ldap_group_cache_size, _, _) ->
warn_removed_option(ldap_group_cache_size, cache_size),
false;
filter(_Host, ldap_user_cache_size, _, _) ->
warn_removed_option(ldap_user_cache_size, cache_size),
false;
filter(_Host, ldap_group_cache_validity, _, _) ->
warn_removed_option(ldap_group_cache_validity, cache_life_time),
false;
filter(_Host, ldap_user_cache_validity, _, _) ->
warn_removed_option(ldap_user_cache_validity, cache_life_time),
false;
filter(_Host, ldap_local_filter, _, _) ->
warn_removed_option(ldap_local_filter),
false;
filter(_Host, deref_aliases, Val, _) ->
warn_replaced_option(deref_aliases, ldap_deref_aliases),
{true, {ldap_deref_aliases, Val}};
filter(_Host, default_db, internal, _) ->
{true, {default_db, mnesia}};
filter(_Host, default_db, odbc, _) ->
{true, {default_db, sql}};
filter(_Host, auth_method, Ms, _) ->
Ms1 = lists:map(
fun(internal) -> mnesia;
(odbc) -> sql;
(M) -> M
end, Ms),
{true, {auth_method, Ms1}};
filter(_Host, default_ram_db, internal, _) ->
{true, {default_ram_db, mnesia}};
filter(_Host, default_ram_db, odbc, _) ->
{true, {default_ram_db, sql}};
filter(_Host, extauth_cache, _, _) ->
?WARNING_MSG("Option 'extauth_cache' is deprecated "
"and has no effect, use authentication "
"or global cache configuration options: "
"auth_use_cache, auth_cache_life_time, "
"use_cache, cache_life_time, and so on", []),
false;
filter(_Host, extauth_instances, Val, _) ->
warn_replaced_option(extauth_instances, extauth_pool_size),
{true, {extauth_pool_size, Val}};
filter(_Host, Opt, Val, _) when Opt == outgoing_s2s_timeout;
Opt == s2s_dns_timeout ->
warn_huge_timeout(Opt, Val),
true;
filter(Host, modules, ModOpts, #{remove_s2s_dialback := Hosts}) ->
ModOpts1 = case lists:member(Host, Hosts) of
true ->
lists:filter(
fun({mod_s2s_dialback, _}) -> false;
(_) -> true
end, ModOpts);
false ->
ModOpts
end,
{true, {modules, ModOpts1}};
filter(_, _, _, _) ->
true.
%%%===================================================================
%%% Listener transformers
%%%===================================================================
transform_listener(Opts, Acc) ->
Opts1 = transform_request_handlers(Opts),
Opts2 = remove_inet_options(Opts1),
collect_listener_certfiles(Opts2, Acc).
transform_request_handlers(Opts) ->
case lists:keyfind(module, 1, Opts) of
{_, ejabberd_http} ->
replace_request_handlers(Opts);
_ ->
Opts
end.
replace_request_handlers(Opts) ->
Handlers = proplists:get_value(request_handlers, Opts, []),
Handlers1 =
lists:foldl(
fun({captcha, true}, Acc) ->
Handler = {<<"/captcha">>, ejabberd_captcha},
warn_replaced_handler(captcha, Handler),
[Handler|Acc];
({register, true}, Acc) ->
Handler = {<<"/register">>, mod_register_web},
warn_replaced_handler(register, Handler),
[Handler|Acc];
({web_admin, true}, Acc) ->
Handler = {<<"/admin">>, ejabberd_web_admin},
warn_replaced_handler(web_admin, Handler),
[Handler|Acc];
({http_bind, true}, Acc) ->
Handler = {<<"/bosh">>, mod_bosh},
warn_replaced_handler(http_bind, Handler),
[Handler|Acc];
({xmlrpc, true}, Acc) ->
Handler = {<<"/">>, ejabberd_xmlrpc},
warn_replaced_handler(xmlrpc, Handler),
Acc ++ [Handler];
(_, Acc) ->
Acc
end, Handlers, Opts),
Handlers2 = lists:map(
fun({Path, mod_http_bind}) ->
warn_replaced_module(mod_http_bind, mod_bosh),
{Path, mod_bosh};
(PathMod) ->
PathMod
end, Handlers1),
lists:filtermap(
fun({captcha, _}) -> false;
({register, _}) -> false;
({web_admin, _}) -> false;
({http_bind, _}) -> false;
({xmlrpc, _}) -> false;
({http_poll, _}) ->
?WARNING_MSG("Listening option 'http_poll' is "
"ignored: HTTP Polling support was "
"removed in ejabberd 15.04. ~s",
[adjust_hint()]),
false;
({request_handlers, _}) ->
{true, {request_handlers, Handlers2}};
(_) -> true
end, Opts).
remove_inet_options(Opts) ->
lists:filter(
fun({Opt, _}) when Opt == inet; Opt == inet6 ->
warn_removed_option(Opt, ip),
false;
(_) ->
true
end, Opts).
collect_listener_certfiles(Opts, Acc) ->
Mod = proplists:get_value(module, Opts),
if Mod == ejabberd_http;
Mod == ejabberd_c2s;
Mod == ejabberd_s2s_in ->
case lists:keyfind(certfile, 1, Opts) of
{_, CertFile} ->
?WARNING_MSG("Listening option 'certfile' of module ~s "
"is deprecated and was automatically "
"appended to global 'certfiles' option. ~s",
[Mod, adjust_hint()]),
CertFiles = maps:get(certfiles, Acc, []),
{proplists:delete(certfile, Opts),
maps:put(certfiles, [CertFile|CertFiles], Acc)};
false ->
{Opts, Acc}
end;
true ->
{Opts, Acc}
end.
%%%===================================================================
%%% Module transformers
%%% NOTE: transform_module_options/1 is called before transform_module/4
%%%===================================================================
transform_module_options(Opts) ->
lists:filtermap(
fun({Opt, internal}) when Opt == db_type;
Opt == ram_db_type ->
{true, {Opt, mnesia}};
({Opt, odbc}) when Opt == db_type;
Opt == ram_db_type ->
{true, {Opt, sql}};
({deref_aliases, Val}) ->
warn_replaced_option(deref_aliases, ldap_deref_aliases),
{true, {ldap_deref_aliases, Val}};
({ldap_group_cache_size, _}) ->
warn_removed_option(ldap_group_cache_size, cache_size),
false;
({ldap_user_cache_size, _}) ->
warn_removed_option(ldap_user_cache_size, cache_size),
false;
({ldap_group_cache_validity, _}) ->
warn_removed_option(ldap_group_cache_validity, cache_life_time),
false;
({ldap_user_cache_validity, _}) ->
warn_removed_option(ldap_user_cache_validity, cache_life_time),
false;
({iqdisc, _}) ->
warn_removed_option(iqdisc),
false;
(_) ->
true
end, Opts).
transform_module(_Host, mod_http_bind, Opts, Acc) ->
warn_replaced_module(mod_http_bind, mod_bosh),
{{mod_bosh, Opts}, Acc};
transform_module(_Host, mod_vcard_xupdate_odbc, Opts, Acc) ->
warn_replaced_module(mod_vcard_xupdate_odbc, mod_vcard_xupdate),
{{mod_vcard_xupdate, Opts}, Acc};
transform_module(_Host, mod_vcard_ldap, Opts, Acc) ->
warn_replaced_module(mod_vcard_ldap, mod_vcard, ldap),
{{mod_vcard, [{db_type, ldap}|Opts]}, Acc};
transform_module(_Host, M, Opts, Acc) when (M == mod_announce_odbc orelse
M == mod_blocking_odbc orelse
M == mod_caps_odbc orelse
M == mod_last_odbc orelse
M == mod_muc_odbc orelse
M == mod_offline_odbc orelse
M == mod_privacy_odbc orelse
M == mod_private_odbc orelse
M == mod_pubsub_odbc orelse
M == mod_roster_odbc orelse
M == mod_shared_roster_odbc orelse
M == mod_vcard_odbc) ->
M1 = strip_odbc_suffix(M),
warn_replaced_module(M, M1, sql),
{{M1, [{db_type, sql}|Opts]}, Acc};
transform_module(_Host, mod_blocking, Opts, Acc) ->
Opts1 = lists:filter(
fun({db_type, _}) ->
warn_removed_module_option(db_type, mod_blocking),
false;
(_) ->
true
end, Opts),
{{mod_blocking, Opts1}, Acc};
transform_module(_Host, mod_carboncopy, Opts, Acc) ->
Opts1 = lists:filter(
fun({Opt, _}) when Opt == ram_db_type;
Opt == use_cache;
Opt == cache_size;
Opt == cache_missed;
Opt == cache_life_time ->
warn_removed_module_option(Opt, mod_carboncopy),
false;
(_) ->
true
end, Opts),
{{mod_carboncopy, Opts1}, Acc};
transform_module(_Host, mod_http_api, Opts, Acc) ->
Opts1 = lists:filter(
fun({admin_ip_access, _}) ->
warn_removed_option(admin_ip_access, api_permissions),
false;
(_) ->
true
end, Opts),
{{mod_http_api, Opts1}, Acc};
transform_module(_Host, Mod, Opts, Acc) ->
{{Mod, Opts}, Acc}.
strip_odbc_suffix(M) ->
[_|T] = lists:reverse(string:tokens(atom_to_list(M), "_")),
list_to_atom(string:join(lists:reverse(T), "_")).
%%%===================================================================
%%% Aux
%%%===================================================================
filtermapfoldr(Fun, Init, List) ->
lists:foldr(
fun(X, {Ret, Acc}) ->
case Fun(X, Acc) of
{true, Acc1} -> {[X|Ret], Acc1};
{{true, X1}, Acc1} -> {[X1|Ret], Acc1};
{false, Acc1} -> {Ret, Acc1}
end
end, {[], Init}, List).
set_certfiles(Y, #{certfiles := CertFiles} = Acc) ->
{lists:keystore(certfiles, 1, Y, {certfiles, CertFiles}), Acc};
set_certfiles(Y, Acc) ->
{Y, Acc}.
%%%===================================================================
%%% Warnings
%%%===================================================================
warn_replaced_module(From, To) ->
?WARNING_MSG("Module ~s is deprecated and was automatically "
"replaced by ~s. ~s",
[From, To, adjust_hint()]).
warn_replaced_module(From, To, Type) ->
?WARNING_MSG("Module ~s is deprecated and was automatically "
"replaced by ~s with db_type: ~s. ~s",
[From, To, Type, adjust_hint()]).
warn_replaced_handler(Opt, {Path, Module}) ->
?WARNING_MSG("Listening option '~s' is deprecated "
"and was automatically replaced by "
"HTTP request handler: \"~s\" -> ~s. ~s",
[Opt, Path, Module, adjust_hint()]).
warn_replaced_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~s' is deprecated and was automatically "
"replaced by '~s'. ~s",
[OldOpt, NewOpt, adjust_hint()]).
warn_removed_option(Opt) ->
?WARNING_MSG("Option '~s' is deprecated and has no effect anymore. "
"Please remove it from the configuration.", [Opt]).
warn_removed_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~s' is deprecated and has no effect anymore. "
"Use option '~s' instead.", [OldOpt, NewOpt]).
warn_removed_module_option(Opt, Mod) ->
?WARNING_MSG("Option '~s' of module ~s is deprecated "
"and has no effect anymore. ~s",
[Opt, Mod, adjust_hint()]).
warn_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
?WARNING_MSG("Value '~B' of option '~s' is too big, "
"are you sure you have set seconds?",
[T, Opt]);
warn_huge_timeout(_, _) ->
ok.
adjust_hint() ->
"Please adjust your configuration accordingly. "
"Hint: use `ejabberdctl dump-config` command to view current "
"configuration as it is seen by ejabberd.".
%%%===================================================================
%%% Very raw validator: just to make sure we get properly typed terms
%%% Expand it if you need to transform more options, but don't
%%% abuse complex types: simple and composite types are preferred
%%%===================================================================
validator() ->
Validators =
#{s2s_use_starttls => econf:atom(),
certfiles => econf:list(econf:any()),
c2s_certfile => econf:binary(),
s2s_certfile => econf:binary(),
domain_certfile => econf:binary(),
default_db => econf:atom(),
default_ram_db => econf:atom(),
auth_method => econf:list_or_single(econf:atom()),
listen =>
econf:list(
econf:options(
#{captcha => econf:bool(),
register => econf:bool(),
web_admin => econf:bool(),
http_bind => econf:bool(),
http_poll => econf:bool(),
xmlrpc => econf:bool(),
module => econf:atom(),
certfile => econf:binary(),
request_handlers =>
econf:map(econf:binary(), econf:atom()),
'_' => econf:any()},
[])),
modules =>
econf:options(
#{'_' =>
econf:options(
#{db_type => econf:atom(),
'_' => econf:any()},
[])},
[]),
'_' => econf:any()},
econf:options(
Validators#{host_config =>
econf:map(econf:binary(),
econf:options(Validators, [])),
append_host_config =>
econf:map(econf:binary(),
econf:options(Validators, []))},
[]).

View File

@ -45,13 +45,11 @@
-module(ejabberd_ctl).
-behaviour(ejabberd_config).
-behaviour(gen_server).
-author('alexey@process-one.net').
-export([start/0, start_link/0, process/1, process2/2,
register_commands/3, unregister_commands/3,
opt_type/1]).
register_commands/3, unregister_commands/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
@ -177,7 +175,7 @@ process(["status"], _Version) ->
"or other files in that directory.~n", [EjabberdLogPath]),
?STATUS_ERROR;
true ->
print("ejabberd ~s is running in that node~n", [ejabberd_config:get_version()]),
print("ejabberd ~s is running in that node~n", [ejabberd_option:version()]),
?STATUS_SUCCESS
end;
@ -248,8 +246,7 @@ process(["--version", Arg | Args], _) ->
process(Args, Version);
process(Args, Version) ->
AccessCommands = get_accesscommands(),
{String, Code} = process2(Args, AccessCommands, Version),
{String, Code} = process2(Args, [], Version),
case String of
[] -> ok;
_ ->
@ -291,9 +288,6 @@ process2(Args, AccessCommands, Auth, Version) ->
{"Erroneous result: " ++ io_lib:format("~p", [Other]), ?STATUS_ERROR}
end.
get_accesscommands() ->
ejabberd_config:get_option(ejabberdctl_access_commands, []).
%%-----------------------------
%% Command calling
%%-----------------------------
@ -322,8 +316,8 @@ try_run_ctp(Args, Auth, AccessCommands, Version) ->
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
try_call_command(Args, Auth, AccessCommands, Version) ->
try call_command(Args, Auth, AccessCommands, Version) of
{error, wrong_command_arguments} ->
{"Error: wrong arguments", ?STATUS_ERROR};
{Reason, wrong_command_arguments} ->
{Reason, ?STATUS_ERROR};
Res ->
Res
catch
@ -346,32 +340,28 @@ call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
Command = list_to_atom(binary_to_list(CmdStringU)),
case ejabberd_commands:get_command_format(Command, Auth, Version) of
{error, command_unknown} ->
throw({error, unknown_command});
{ArgsFormat, ResultFormat} ->
case (catch format_args(Args, ArgsFormat)) of
ArgsFormatted when is_list(ArgsFormatted) ->
CI = case Auth of
{U, S, _, _} -> #{usr => {U, S, <<"">>}, caller_host => S};
_ -> #{}
end,
CI2 = CI#{caller_module => ?MODULE},
Result = ejabberd_commands:execute_command2(Command,
ArgsFormatted,
CI2,
Version),
format_result(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
{NumCompa, TextCompa} =
case {length(A1), length(A2)} of
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
{L1, L2} when L1 > L2 -> {L1-L2, "more argument"}
end,
{io_lib:format("Error: the command ~p requires ~p ~s.",
[CmdString, NumCompa, TextCompa]),
wrong_command_arguments}
end
{ArgsFormat, ResultFormat} = ejabberd_commands:get_command_format(Command, Auth, Version),
case (catch format_args(Args, ArgsFormat)) of
ArgsFormatted when is_list(ArgsFormatted) ->
CI = case Auth of
{U, S, _, _} -> #{usr => {U, S, <<"">>}, caller_host => S};
_ -> #{}
end,
CI2 = CI#{caller_module => ?MODULE},
Result = ejabberd_commands:execute_command2(Command,
ArgsFormatted,
CI2,
Version),
format_result(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
{NumCompa, TextCompa} =
case {length(A1), length(A2)} of
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
{L1, L2} when L1 > L2 -> {L1-L2, "more argument"}
end,
{io_lib:format("Error: the command ~p requires ~p ~s.",
[CmdString, NumCompa, TextCompa]),
wrong_command_arguments}
end.
@ -735,11 +725,12 @@ print_usage_help(MaxC, ShCode) ->
"Those commands can be identified because the description starts with: *"],
ArgsDef = [],
C = #ejabberd_commands{
desc = "Show help of ejabberd commands",
longdesc = lists:flatten(LongDesc),
args = ArgsDef,
result = {help, string}},
print_usage_command("help", C, MaxC, ShCode).
name = help,
desc = "Show help of ejabberd commands",
longdesc = lists:flatten(LongDesc),
args = ArgsDef,
result = {help, string}},
print_usage_command2("help", C, MaxC, ShCode).
%%-----------------------------
@ -792,12 +783,8 @@ filter_commands_regexp(All, Glob) ->
%% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean()) -> ok
print_usage_command(Cmd, MaxC, ShCode, Version) ->
Name = list_to_atom(Cmd),
case ejabberd_commands:get_command_definition(Name, Version) of
command_not_found ->
io:format("Error: command ~p not known.~n", [Cmd]);
C ->
print_usage_command2(Cmd, C, MaxC, ShCode)
end.
C = ejabberd_commands:get_command_definition(Name, Version),
print_usage_command2(Cmd, C, MaxC, ShCode).
print_usage_command2(Cmd, C, MaxC, ShCode) ->
#ejabberd_commands{
@ -881,9 +868,3 @@ print(Format, Args) ->
%% Struct(Integer res) create_account(Struct(String user, String server, String password))
%%format_usage_xmlrpc(ArgsDef, ResultDef) ->
%% ["aaaa bbb ccc"].
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(ejabberdctl_access_commands) ->
fun (V) when is_list(V) -> V end;
opt_type(_) -> [ejabberdctl_access_commands].

46
src/ejabberd_db_sup.erl Normal file
View File

@ -0,0 +1,46 @@
%%%-------------------------------------------------------------------
%%% Created : 13 June 2019 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% 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_db_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
%%%===================================================================
%%% API functions
%%%===================================================================
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================
init([]) ->
{ok, {{one_for_one, 10, 1}, []}}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -151,11 +151,13 @@ run(Hook, Args) ->
-spec run(atom(), binary() | global, list()) -> ok.
run(Hook, Host, Args) ->
case ets:lookup(hooks, {Hook, Host}) of
try ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
run1(Ls, Hook, Args);
[] ->
ok
catch _:badarg ->
ok
end.
-spec run_fold(atom(), any(), list()) -> any().
@ -171,11 +173,13 @@ run_fold(Hook, Val, Args) ->
-spec run_fold(atom(), binary() | global, any(), list()) -> any().
run_fold(Hook, Host, Val, Args) ->
case ets:lookup(hooks, {Hook, Host}) of
try ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
run_fold1(Ls, Hook, Val, Args);
[] ->
Val
catch _:badarg ->
Val
end.
%%%----------------------------------------------------------------------
@ -190,7 +194,7 @@ run_fold(Hook, Host, Val, Args) ->
%% {stop, Reason}
%%----------------------------------------------------------------------
init([]) ->
ets:new(hooks, [named_table, {read_concurrency, true}]),
_ = ets:new(hooks, [named_table, {read_concurrency, true}]),
{ok, #state{}}.
%%----------------------------------------------------------------------
@ -381,13 +385,14 @@ safe_apply(Hook, Module, Function, Args) ->
apply(Module, Function, Args)
end
catch ?EX_RULE(E, R, St) when E /= exit; R /= normal ->
Stack = ?EX_STACK(St),
?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n" ++
string:join(
["** Reason = ~p"|
["** ~s"|
["** Arg " ++ integer_to_list(I) ++ " = ~p"
|| I <- lists:seq(1, length(Args))]],
"~n"),
[Hook, Module, Function, length(Args),
{E, R, ?EX_STACK(St)}|Args]),
misc:format_exception(2, E, R, Stack)|Args]),
'EXIT'
end.

View File

@ -25,17 +25,15 @@
-module(ejabberd_http).
-behaviour(ejabberd_listener).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
%% External exports
-export([start/3, start_link/3,
accept/1, receive_headers/1, recv_file/2,
transform_listen_option/2, listen_opt_type/1,
listen_options/0]).
listen_opt_type/1, listen_options/0]).
-export([init/3, opt_type/1]).
-export([init/3]).
-include("logger.hrl").
-include("xmpp.hrl").
@ -112,9 +110,10 @@ init(SockMod, Socket, Opts) ->
false -> [compression_none | TLSOpts1];
true -> TLSOpts1
end,
TLSOpts3 = case get_certfile(Opts) of
undefined -> TLSOpts2;
CertFile -> [{certfile, CertFile}|TLSOpts2]
TLSOpts3 = case ejabberd_pkix:get_certfile(
ejabberd_config:get_myname()) of
error -> TLSOpts2;
{ok, CertFile} -> [{certfile, CertFile}|TLSOpts2]
end,
TLSOpts = [verify_none | TLSOpts3],
{SockMod1, Socket1} = if TLSEnabled ->
@ -124,30 +123,8 @@ init(SockMod, Socket, Opts) ->
{fast_tls, TLSSocket};
true -> {SockMod, Socket}
end,
Captcha = case proplists:get_bool(captcha, Opts) of
true -> [{[<<"captcha">>], ejabberd_captcha}];
false -> []
end,
Register = case proplists:get_bool(register, Opts) of
true -> [{[<<"register">>], mod_register_web}];
false -> []
end,
Admin = case proplists:get_bool(web_admin, Opts) of
true -> [{[<<"admin">>], ejabberd_web_admin}];
false -> []
end,
Bind = case proplists:get_bool(http_bind, Opts) of
true -> [{[<<"http-bind">>], mod_bosh}];
false -> []
end,
XMLRPC = case proplists:get_bool(xmlrpc, Opts) of
true -> [{[], ejabberd_xmlrpc}];
false -> []
end,
SockPeer = proplists:get_value(sock_peer_name, Opts, none),
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC,
RequestHandlers = proplists:get_value(request_handlers, Opts, []),
?DEBUG("S: ~p~n", [RequestHandlers]),
DefaultHost = proplists:get_value(default_host, Opts),
@ -557,7 +534,7 @@ analyze_ip_xff(IP, [], _Host) -> IP;
analyze_ip_xff({IPLast, Port}, XFF, Host) ->
[ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++
[misc:ip_to_list(IPLast)],
TrustedProxies = ejabberd_config:get_option({trusted_proxies, Host}, []),
TrustedProxies = ejabberd_option:trusted_proxies(Host),
IPClient = case is_ipchain_trusted(ProxiesIPs,
TrustedProxies)
of
@ -581,7 +558,7 @@ is_ipchain_trusted(UserIPs, Masks) ->
{ok, IP2} ->
lists:any(
fun({Mask, MaskLen}) ->
acl:ip_matches_mask(IP2, Mask, MaskLen)
misc:match_ip_mask(IP2, Mask, MaskLen)
end, Masks);
_ ->
false
@ -803,7 +780,7 @@ rest_dir(N, Path, <<_H, T/binary>>) -> rest_dir(N, Path, T).
expand_custom_headers(Headers) ->
lists:map(fun({K, V}) ->
{K, misc:expand_keyword(<<"@VERSION@">>, V,
ejabberd_config:get_version())}
ejabberd_option:version())}
end, Headers).
code_to_phrase(100) -> <<"Continue">>;
@ -851,7 +828,7 @@ code_to_phrase(503) -> <<"Service Unavailable">>;
code_to_phrase(504) -> <<"Gateway Timeout">>;
code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | invalid.
parse_auth(<<"Basic ", Auth64/binary>>) ->
try base64:decode(Auth64) of
Auth ->
@ -927,150 +904,32 @@ normalize_path([_Parent, <<"..">>|Path], Norm) ->
normalize_path([Part | Path], Norm) ->
normalize_path(Path, [Part|Norm]).
-spec get_certfile([proplists:property()]) -> binary() | undefined.
get_certfile(Opts) ->
case lists:keyfind(certfile, 1, Opts) of
{_, CertFile} ->
CertFile;
false ->
case ejabberd_pkix:get_certfile(ejabberd_config:get_myname()) of
{ok, CertFile} ->
CertFile;
error ->
ejabberd_config:get_option({domain_certfile, ejabberd_config:get_myname()})
end
end.
transform_listen_option(captcha, Opts) ->
[{captcha, true}|Opts];
transform_listen_option(register, Opts) ->
[{register, true}|Opts];
transform_listen_option(web_admin, Opts) ->
[{web_admin, true}|Opts];
transform_listen_option(http_bind, Opts) ->
[{http_bind, true}|Opts];
transform_listen_option(http_poll, Opts) ->
Opts;
transform_listen_option({request_handlers, Hs}, Opts) ->
Hs1 = lists:map(
fun({PList, Mod}) when is_list(PList) ->
Path = iolist_to_binary([[$/, P] || P <- PList]),
{Path, Mod};
(Opt) ->
Opt
end, Hs),
[{request_handlers, Hs1} | Opts];
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
prepare_request_module(mod_http_bind) ->
mod_bosh;
prepare_request_module(Mod) when is_atom(Mod) ->
case code:ensure_loaded(Mod) of
{module, Mod} ->
Mod;
Err ->
?ERROR_MSG(
"Failed to load request handler ~s, "
"did you mean ~s? Hint: "
"make sure there is no typo and file ~s.beam "
"exists inside either ~s or ~s directory",
[Mod,
misc:best_match(Mod, ejabberd_config:get_modules()),
Mod,
filename:dirname(code:which(?MODULE)),
ext_mod:modules_dir()]),
erlang:error(Err)
end.
emit_option_replacement(Option, Path, Handler) ->
?WARNING_MSG(
"Listening option '~s' is deprecated, enable it via request handlers, e.g.:~n"
"listen:~n"
" ...~n"
" -~n"
" module: ~s~n"
" request_handlers:~n"
" ...~n"
" \"~s\": ~s~n",
[Option, ?MODULE, Path, Handler]).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(trusted_proxies) ->
fun (all) -> all;
(TPs) -> lists:filtermap(
fun(TP) ->
case acl:parse_ip_netmask(iolist_to_binary(TP)) of
{ok, Ip, Mask} -> {true, {Ip, Mask}};
_ -> false
end
end, TPs)
end;
opt_type(_) -> [trusted_proxies].
listen_opt_type(certfile = Opt) ->
fun(S) ->
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
"'certfiles' global option instead", [Opt, ?MODULE]),
{ok, File} = ejabberd_pkix:add_certfile(S),
File
end;
listen_opt_type(captcha) ->
fun(B) when is_boolean(B) ->
emit_option_replacement(captcha, "/captcha", ejabberd_captcha),
B
end;
listen_opt_type(register) ->
fun(B) when is_boolean(B) ->
emit_option_replacement(register, "/register", mod_register_web),
B
end;
listen_opt_type(web_admin) ->
fun(B) when is_boolean(B) ->
emit_option_replacement(web_admin, "/admin", ejabberd_web_admin),
B
end;
listen_opt_type(http_bind) ->
fun(B) when is_boolean(B) ->
emit_option_replacement(http_bind, "/bosh", mod_bosh),
B
end;
listen_opt_type(xmlrpc) ->
fun(B) when is_boolean(B) ->
emit_option_replacement(xmlrpc, "/", ejabberd_xmlrpc),
B
end;
listen_opt_type(tag) ->
fun(B) when is_binary(B) -> B end;
econf:binary();
listen_opt_type(request_handlers) ->
fun(Hs) ->
Hs1 = lists:map(fun
({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
({Path, Mod}) -> {Path, Mod}
end, Hs),
Hs2 = [{str:tokens(
iolist_to_binary(Path), <<"/">>),
Mod} || {Path, Mod} <- Hs1],
[{Path, prepare_request_module(Mod)} || {Path, Mod} <- Hs2]
end;
econf:and_then(
econf:map(
econf:binary(),
econf:beam([[{socket_handoff, 3}, {process, 2}]])),
fun(L) ->
[{str:tokens(Path, <<"/">>), Mod} || {Path, Mod} <- L]
end);
listen_opt_type(default_host) ->
fun iolist_to_binary/1;
econf:domain();
listen_opt_type(custom_headers) ->
fun expand_custom_headers/1.
econf:and_then(
econf:map(
econf:binary(),
econf:binary()),
fun expand_custom_headers/1).
listen_options() ->
[{certfile, undefined},
{ciphers, undefined},
[{ciphers, undefined},
{dhfile, undefined},
{cafile, undefined},
{protocol_options, undefined},
{tls, false},
{tls_compression, false},
{captcha, false},
{register, false},
{web_admin, false},
{http_bind, false},
{xmlrpc, false},
{request_handlers, []},
{tag, <<>>},
{default_host, undefined},

View File

@ -40,15 +40,12 @@
-include("ejabberd_http.hrl").
-define(PING_INTERVAL, 60).
-define(WEBSOCKET_TIMEOUT, 300).
-record(state,
{socket :: ws_socket(),
ping_interval = ?PING_INTERVAL :: non_neg_integer(),
ping_interval :: non_neg_integer(),
ping_timer = make_ref() :: reference(),
pong_expected = false :: boolean(),
timeout = ?WEBSOCKET_TIMEOUT :: non_neg_integer(),
timeout :: non_neg_integer(),
timer = make_ref() :: reference(),
input = [] :: list(),
active = false :: boolean(),
@ -133,12 +130,8 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
(_) -> false
end, HOpts),
Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts,
PingInterval = ejabberd_config:get_option(
{websocket_ping_interval, ejabberd_config:get_myname()},
?PING_INTERVAL) * 1000,
WSTimeout = ejabberd_config:get_option(
{websocket_timeout, ejabberd_config:get_myname()},
?WEBSOCKET_TIMEOUT) * 1000,
PingInterval = ejabberd_option:websocket_ping_interval(),
WSTimeout = ejabberd_option:websocket_timeout(),
Socket = {http_ws, self(), IP},
?DEBUG("Client connected through websocket ~p",
[Socket]),
@ -201,15 +194,15 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
case Packet2 of
{xmlstreamstart, Name, Attrs3} ->
B = fxml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
WsPid ! {text, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>};
route_text(WsPid, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>);
{xmlstreamend, Name} ->
WsPid ! {text, <<"</", Name/binary, ">">>};
route_text(WsPid, <<"</", Name/binary, ">">>);
{xmlstreamelement, El} ->
WsPid ! {text, fxml:element_to_binary(El)};
route_text(WsPid, fxml:element_to_binary(El));
{xmlstreamraw, Bin} ->
WsPid ! {text, Bin};
route_text(WsPid, Bin);
{xmlstreamcdata, Bin2} ->
WsPid ! {text, Bin2};
route_text(WsPid, Bin2);
skip ->
ok
end,
@ -224,7 +217,7 @@ handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}, rfc_compilant
when StateName /= stream_end_sent ->
Close = #xmlel{name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]},
WsPid ! {text, fxml:element_to_binary(Close)},
route_text(WsPid, fxml:element_to_binary(Close)),
{stop, normal, StateData};
handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}.
@ -366,3 +359,8 @@ parsed_items(List) ->
after 0 ->
lists:reverse(List)
end.
-spec route_text(pid(), binary()) -> ok.
route_text(Pid, Data) ->
Pid ! {text, Data},
ok.

View File

@ -1,7 +1,7 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_iq.erl
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Purpose :
%%% Purpose :
%%% Created : 10 Nov 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
@ -70,7 +70,7 @@ dispatch(_) ->
%%% gen_server callbacks
%%%===================================================================
init([]) ->
ets:new(?MODULE, [named_table, ordered_set, public]),
_ = ets:new(?MODULE, [named_table, ordered_set, public]),
{ok, #state{}}.
handle_call(Request, From, State) ->
@ -166,7 +166,7 @@ decode_id(_) ->
-spec calc_checksum(binary()) -> binary().
calc_checksum(Data) ->
Key = ejabberd_config:get_option(shared_key),
Key = ejabberd_config:get_shared_key(),
base64:encode(crypto:hash(sha, <<Data/binary, Key/binary>>)).
-spec callback(atom() | pid(), #iq{} | timeout, term()) -> any().

View File

@ -25,36 +25,40 @@
-module(ejabberd_listener).
-behaviour(supervisor).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-author('ekhramtsov@process-one.net').
-export([start_link/0, init/1, start/3, init/3,
start_listeners/0, start_listener/3, stop_listeners/0,
stop_listener/2, add_listener/3, delete_listener/2,
transform_options/1, validate_cfg/1, opt_type/1,
config_reloaded/0, get_certfiles/0]).
%% Legacy API
-export([parse_listener_portip/2]).
add_listener/3, delete_listener/2,
config_reloaded/0]).
-export([listen_options/0, listen_opt_type/1, validator/0]).
-export([tls_listeners/0]).
-include("logger.hrl").
-type transport() :: tcp | udp.
-type endpoint() :: {inet:port_number(), inet:ip_address(), transport()}.
-type listen_opts() :: [proplists:property()].
-type listener() :: {endpoint(), module(), listen_opts()}.
-type list_opts() :: [{atom(), term()}].
-type opts() :: #{atom() => term()}.
-type listener() :: {endpoint(), module(), opts()}.
-type sockmod() :: gen_tcp.
-type socket() :: inet:socket().
-type state() :: term().
-callback start(sockmod(), socket(), listen_opts()) ->
-export_type([listener/0]).
-callback start(sockmod(), socket(), state()) ->
{ok, pid()} | {error, any()} | ignore.
-callback start_link(sockmod(), socket(), listen_opts()) ->
-callback start_link(sockmod(), socket(), state()) ->
{ok, pid()} | {error, any()} | ignore.
-callback accept(pid()) -> any().
-callback listen_opt_type(atom()) -> fun((term()) -> term()).
-callback listen_options() -> listen_opts().
-callback listen_opt_type(atom()) -> econf:validator().
-callback listen_options() -> [{atom(), term()} | atom()].
-callback tcp_init(socket(), list_opts()) -> state().
-callback udp_init(socket(), list_opts()) -> state().
-optional_callbacks([listen_opt_type/1]).
-optional_callbacks([listen_opt_type/1, tcp_init/2, udp_init/2]).
-define(TCP_SEND_TIMEOUT, 15000).
@ -62,9 +66,9 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init(_) ->
ets:new(?MODULE, [named_table, public]),
_ = ets:new(?MODULE, [named_table, public]),
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
Listeners = ejabberd_config:get_option(listen, []),
Listeners = ejabberd_option:listen(),
{ok, {{one_for_one, 10, 1}, listeners_childspec(Listeners)}}.
-spec listeners_childspec([listener()]) -> [supervisor:child_spec()].
@ -79,22 +83,22 @@ listeners_childspec(Listeners) ->
-spec start_listeners() -> ok.
start_listeners() ->
Listeners = ejabberd_config:get_option(listen, []),
Listeners = ejabberd_option:listen(),
lists:foreach(
fun(Spec) ->
supervisor:start_child(?MODULE, Spec)
end, listeners_childspec(Listeners)).
-spec start(endpoint(), module(), listen_opts()) -> term().
-spec start(endpoint(), module(), opts()) -> term().
start(EndPoint, Module, Opts) ->
proc_lib:start_link(?MODULE, init, [EndPoint, Module, Opts]).
-spec init(endpoint(), module(), listen_opts()) -> ok.
init(EndPoint, Module, AllOpts) ->
{ModuleOpts, SockOpts} = split_opts(AllOpts),
-spec init(endpoint(), module(), opts()) -> ok.
init({_, _, Transport} = EndPoint, Module, AllOpts) ->
{ModuleOpts, SockOpts} = split_opts(Transport, AllOpts),
init(EndPoint, Module, ModuleOpts, SockOpts).
-spec init(endpoint(), module(), listen_opts(), [gen_tcp:option()]) -> ok.
-spec init(endpoint(), module(), opts(), [gen_tcp:option()]) -> ok.
init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
case gen_udp:open(Port, [binary,
{active, false},
@ -104,22 +108,21 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
case inet:sockname(Socket) of
{ok, {Addr, Port1}} ->
proc_lib:init_ack({ok, self()}),
application:ensure_started(ejabberd),
?INFO_MSG("Start accepting UDP connections at ~s for ~p",
[format_endpoint({Port1, Addr, udp}), Module]),
case erlang:function_exported(Module, udp_init, 2) of
false ->
udp_recv(Socket, Module, Opts);
true ->
case catch Module:udp_init(Socket, Opts) of
{'EXIT', _} = Err ->
?ERROR_MSG("failed to process callback function "
"~p:~s(~p, ~p): ~p",
[Module, udp_init, Socket, Opts, Err]),
udp_recv(Socket, Module, Opts);
NewOpts ->
udp_recv(Socket, Module, NewOpts)
end
case application:ensure_started(ejabberd) of
ok ->
?INFO_MSG("Start accepting ~s connections at ~s for ~p",
[format_transport(udp, Opts),
format_endpoint({Port1, Addr, udp}), Module]),
Opts1 = opts_to_list(Module, Opts),
case erlang:function_exported(Module, udp_init, 2) of
false ->
udp_recv(Socket, Module, Opts1);
true ->
State = Module:udp_init(Socket, Opts1),
udp_recv(Socket, Module, State)
end;
{error, _} ->
ok
end;
{error, Reason} = Err ->
report_socket_error(Reason, EndPoint, Module),
@ -135,27 +138,28 @@ init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) ->
case inet:sockname(ListenSocket) of
{ok, {Addr, Port1}} ->
proc_lib:init_ack({ok, self()}),
application:ensure_started(ejabberd),
Sup = start_module_sup(Module, Opts),
?INFO_MSG("Start accepting TCP connections at ~s for ~p",
[format_endpoint({Port1, Addr, tcp}), Module]),
case erlang:function_exported(Module, tcp_init, 2) of
false ->
accept(ListenSocket, Module, Opts, Sup);
true ->
case catch Module:tcp_init(ListenSocket, Opts) of
{'EXIT', _} = Err ->
?ERROR_MSG("failed to process callback function "
"~p:~s(~p, ~p): ~p",
[Module, tcp_init, ListenSocket, Opts, Err]),
accept(ListenSocket, Module, Opts, Sup);
NewOpts ->
accept(ListenSocket, Module, NewOpts, Sup)
end
case application:ensure_started(ejabberd) of
ok ->
Sup = start_module_sup(Module, Opts),
Interval = maps:get(accept_interval, Opts),
Proxy = maps:get(use_proxy_protocol, Opts),
?INFO_MSG("Start accepting ~s connections at ~s for ~p",
[format_transport(tcp, Opts),
format_endpoint({Port1, Addr, tcp}), Module]),
Opts1 = opts_to_list(Module, Opts),
case erlang:function_exported(Module, tcp_init, 2) of
false ->
accept(ListenSocket, Module, Opts1, Sup, Interval, Proxy);
true ->
State = Module:tcp_init(ListenSocket, Opts1),
accept(ListenSocket, Module, State, Sup, Interval, Proxy)
end;
{error, _} ->
ok
end;
{error, Reason} = Err ->
report_socket_error(Reason, EndPoint, Module),
Err
proc_lib:init_ack(Err)
end;
{error, Reason} = Err ->
report_socket_error(Reason, EndPoint, Module),
@ -181,113 +185,115 @@ listen_tcp(Port, SockOpts) ->
Err
end.
-spec split_opts(listen_opts()) -> {listen_opts(), [gen_tcp:option()]}.
split_opts(Opts) ->
lists:foldl(
fun(Opt, {ModOpts, SockOpts} = Acc) ->
case Opt of
{ip, _} -> {ModOpts, [Opt|SockOpts]};
{backlog, _} -> {ModOpts, [Opt|SockOpts]};
{inet, true} -> {ModOpts, [inet|SockOpts]};
{inet6, true} -> {ModOpts, [int6|SockOpts]};
{inet, false} -> Acc;
{inet6, false} -> Acc;
_ -> {[Opt|ModOpts], SockOpts}
-spec split_opts(transport(), opts()) -> {opts(), [gen_tcp:option()]}.
split_opts(Transport, Opts) ->
maps:fold(
fun(Opt, Val, {ModOpts, SockOpts}) ->
case OptVal = {Opt, Val} of
{ip, _} ->
{ModOpts, [OptVal|SockOpts]};
{backlog, _} when Transport == tcp ->
{ModOpts, [OptVal|SockOpts]};
{backlog, _} ->
{ModOpts, SockOpts};
_ ->
{ModOpts#{Opt => Val}, SockOpts}
end
end, {[], []}, Opts).
end, {#{}, []}, Opts).
-spec accept(inet:socket(), module(), listen_opts(), atom()) -> no_return().
accept(ListenSocket, Module, Opts, Sup) ->
Interval = proplists:get_value(accept_interval, Opts, 0),
-spec accept(inet:socket(), module(), state(), atom(),
non_neg_integer(), boolean()) -> no_return().
accept(ListenSocket, Module, State, Sup, Interval, Proxy) ->
Arity = case erlang:function_exported(Module, start, 3) of
true -> 3;
false -> 2
end,
accept(ListenSocket, Module, Opts, Sup, Interval, Arity).
accept(ListenSocket, Module, State, Sup, Interval, Proxy, Arity).
-spec accept(inet:socket(), module(), listen_opts(), atom(),
non_neg_integer(), 2|3) -> no_return().
accept(ListenSocket, Module, Opts, Sup, Interval, Arity) ->
NewInterval = check_rate_limit(Interval),
-spec accept(inet:socket(), module(), state(), atom(),
non_neg_integer(), boolean(), 2|3) -> no_return().
accept(ListenSocket, Module, State, Sup, Interval, Proxy, Arity) ->
NewInterval = apply_rate_limit(Interval),
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
case proplists:get_value(use_proxy_protocol, Opts, false) of
true ->
case proxy_protocol:decode(gen_tcp, Socket, 10000) of
{error, Err} ->
?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s",
[ListenSocket, inet:format_error(Err)]),
gen_tcp:close(Socket);
{{Addr, Port}, {PAddr, PPort}} = SP ->
Opts2 = [{sock_peer_name, SP} | Opts],
Receiver = case start_connection(Module, Arity, Socket, Opts2, Sup) of
{ok, RecvPid} ->
RecvPid;
_ ->
gen_tcp:close(Socket),
none
end,
?INFO_MSG("(~p) Accepted proxied connection ~s:~p -> ~s:~p",
[Receiver,
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
PPort, inet_parse:ntoa(Addr), Port])
end;
_ ->
case {inet:sockname(Socket), inet:peername(Socket)} of
{{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
Receiver = case start_connection(Module, Arity, Socket, Opts, Sup) of
{ok, RecvPid} ->
RecvPid;
_ ->
gen_tcp:close(Socket),
none
end,
?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p",
[Receiver,
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
PPort, inet_parse:ntoa(Addr), Port]);
_ ->
gen_tcp:close(Socket)
end
{ok, Socket} when Proxy ->
case proxy_protocol:decode(gen_tcp, Socket, 10000) of
{error, Err} ->
?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s",
[ListenSocket, format_error(Err)]),
gen_tcp:close(Socket);
{{Addr, Port}, {PAddr, PPort}} = SP ->
%% THIS IS WRONG
State2 = [{sock_peer_name, SP} | State],
Receiver = case start_connection(Module, Arity, Socket, State2, Sup) of
{ok, RecvPid} ->
RecvPid;
_ ->
gen_tcp:close(Socket),
none
end,
?INFO_MSG("(~p) Accepted proxied connection ~s -> ~s",
[Receiver,
ejabberd_config:may_hide_data(
format_endpoint({PPort, PAddr, tcp})),
format_endpoint({Port, Addr, tcp})])
end,
accept(ListenSocket, Module, Opts, Sup, NewInterval, Arity);
accept(ListenSocket, Module, State, Sup, NewInterval, Proxy, Arity);
{ok, Socket} ->
case {inet:sockname(Socket), inet:peername(Socket)} of
{{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
Receiver = case start_connection(Module, Arity, Socket, State, Sup) of
{ok, RecvPid} ->
RecvPid;
_ ->
gen_tcp:close(Socket),
none
end,
?INFO_MSG("(~p) Accepted connection ~s -> ~s",
[Receiver,
ejabberd_config:may_hide_data(
format_endpoint({PPort, PAddr, tcp})),
format_endpoint({Port, Addr, tcp})]);
_ ->
gen_tcp:close(Socket)
end,
accept(ListenSocket, Module, State, Sup, NewInterval, Proxy, Arity);
{error, Reason} ->
?ERROR_MSG("(~w) Failed TCP accept: ~s",
[ListenSocket, inet:format_error(Reason)]),
accept(ListenSocket, Module, Opts, Sup, NewInterval, Arity)
[ListenSocket, format_error(Reason)]),
accept(ListenSocket, Module, State, Sup, NewInterval, Proxy, Arity)
end.
-spec udp_recv(inet:socket(), module(), listen_opts()) -> no_return().
udp_recv(Socket, Module, Opts) ->
-spec udp_recv(inet:socket(), module(), state()) -> no_return().
udp_recv(Socket, Module, State) ->
case gen_udp:recv(Socket, 0) of
{ok, {Addr, Port, Packet}} ->
case catch Module:udp_recv(Socket, Addr, Port, Packet, Opts) of
case catch Module:udp_recv(Socket, Addr, Port, Packet, State) of
{'EXIT', Reason} ->
?ERROR_MSG("failed to process UDP packet:~n"
?ERROR_MSG("Failed to process UDP packet:~n"
"** Source: {~p, ~p}~n"
"** Reason: ~p~n** Packet: ~p",
[Addr, Port, Reason, Packet]),
udp_recv(Socket, Module, Opts);
NewOpts ->
udp_recv(Socket, Module, NewOpts)
udp_recv(Socket, Module, State);
NewState ->
udp_recv(Socket, Module, NewState)
end;
{error, Reason} ->
?ERROR_MSG("unexpected UDP error: ~s", [format_error(Reason)]),
?ERROR_MSG("Unexpected UDP error: ~s", [format_error(Reason)]),
throw({error, Reason})
end.
-spec start_connection(module(), 2|3, inet:socket(), listen_opts(), atom()) ->
-spec start_connection(module(), 2|3, inet:socket(), state(), atom()) ->
{ok, pid()} | {error, any()} | ignore.
start_connection(Module, Arity, Socket, Opts, Sup) ->
start_connection(Module, Arity, Socket, State, Sup) ->
Res = case Sup of
undefined when Arity == 3 ->
Module:start(gen_tcp, Socket, Opts);
Module:start(gen_tcp, Socket, State);
undefined ->
Module:start({gen_tcp, Socket}, Opts);
Module:start({gen_tcp, Socket}, State);
_ when Arity == 3 ->
supervisor:start_child(Sup, [gen_tcp, Socket, Opts]);
supervisor:start_child(Sup, [gen_tcp, Socket, State]);
_ ->
supervisor:start_child(Sup, [{gen_tcp, Socket}, Opts])
supervisor:start_child(Sup, [{gen_tcp, Socket}, State])
end,
case Res of
{ok, Pid} ->
@ -303,7 +309,7 @@ start_connection(Module, Arity, Socket, Opts, Sup) ->
Err
end.
-spec start_listener(endpoint(), module(), listen_opts()) ->
-spec start_listener(endpoint(), module(), opts()) ->
{ok, pid()} | {error, any()}.
start_listener(EndPoint, Module, Opts) ->
%% It is only required to start the supervisor in some cases.
@ -323,9 +329,9 @@ start_listener(EndPoint, Module, Opts) ->
{error, Error}
end.
-spec start_module_sup(module(), [proplists:property()]) -> atom().
-spec start_module_sup(module(), opts()) -> atom().
start_module_sup(Module, Opts) ->
case proplists:get_value(supervisor, Opts, true) of
case maps:get(supervisor, Opts) of
true ->
Proc = list_to_atom(atom_to_list(Module) ++ "_sup"),
ChildSpec = {Proc, {ejabberd_tmp_sup, start_link, [Proc, Module]},
@ -333,13 +339,15 @@ start_module_sup(Module, Opts) ->
infinity,
supervisor,
[ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec),
Proc;
case supervisor:start_child(ejabberd_sup, ChildSpec) of
{ok, _} -> Proc;
_ -> undefined
end;
false ->
undefined
end.
-spec start_listener_sup(endpoint(), module(), listen_opts()) ->
-spec start_listener_sup(endpoint(), module(), opts()) ->
{ok, pid()} | {error, any()}.
start_listener_sup(EndPoint, Module, Opts) ->
ChildSpec = {EndPoint,
@ -352,19 +360,19 @@ start_listener_sup(EndPoint, Module, Opts) ->
-spec stop_listeners() -> ok.
stop_listeners() ->
Ports = ejabberd_config:get_option(listen, []),
Ports = ejabberd_option:listen(),
lists:foreach(
fun({PortIpNetp, Module, _Opts}) ->
delete_listener(PortIpNetp, Module)
end,
Ports).
-spec stop_listener(endpoint(), module()) -> ok | {error, any()}.
stop_listener({_, _, Transport} = EndPoint, Module) ->
-spec stop_listener(endpoint(), module(), opts()) -> ok | {error, any()}.
stop_listener({_, _, Transport} = EndPoint, Module, Opts) ->
case supervisor:terminate_child(?MODULE, EndPoint) of
ok ->
?INFO_MSG("Stop accepting ~s connections at ~s for ~p",
[case Transport of udp -> "UDP"; tcp -> "TCP" end,
[format_transport(Transport, Opts),
format_endpoint(EndPoint), Module]),
ets:delete(?MODULE, EndPoint),
supervisor:delete_child(?MODULE, EndPoint);
@ -372,9 +380,10 @@ stop_listener({_, _, Transport} = EndPoint, Module) ->
Err
end.
-spec add_listener(endpoint(), module(), listen_opts()) -> ok | {error, any()}.
-spec add_listener(endpoint(), module(), opts()) -> ok | {error, any()}.
add_listener(EndPoint, Module, Opts) ->
case start_listener(EndPoint, Module, Opts) of
Opts1 = apply_defaults(Module, Opts),
case start_listener(EndPoint, Module, Opts1) of
{ok, _Pid} ->
ok;
{error, {already_started, _Pid}} ->
@ -385,17 +394,30 @@ add_listener(EndPoint, Module, Opts) ->
-spec delete_listener(endpoint(), module()) -> ok | {error, any()}.
delete_listener(EndPoint, Module) ->
stop_listener(EndPoint, Module).
try ets:lookup_element(?MODULE, EndPoint, 3) of
Opts -> stop_listener(EndPoint, Module, Opts)
catch _:badarg ->
ok
end.
-spec tls_listeners() -> [module()].
tls_listeners() ->
lists:usort(
lists:filtermap(
fun({_, Module, #{tls := true}}) -> {true, Module};
({_, Module, #{starttls := true}}) -> {true, Module};
(_) -> false
end, ets:tab2list(?MODULE))).
-spec config_reloaded() -> ok.
config_reloaded() ->
New = ejabberd_config:get_option(listen, []),
New = ejabberd_option:listen(),
Old = ets:tab2list(?MODULE),
lists:foreach(
fun({EndPoint, Module, _Opts}) ->
fun({EndPoint, Module, Opts}) ->
case lists:keyfind(EndPoint, 1, New) of
false ->
stop_listener(EndPoint, Module);
stop_listener(EndPoint, Module, Opts);
_ ->
ok
end
@ -405,8 +427,8 @@ config_reloaded() ->
case lists:keyfind(EndPoint, 1, Old) of
{_, Module, Opts} ->
ok;
{_, OldModule, _} ->
stop_listener(EndPoint, OldModule),
{_, OldModule, OldOpts} ->
_ = stop_listener(EndPoint, OldModule, OldOpts),
ets:insert(?MODULE, {EndPoint, Module, Opts}),
start_listener(EndPoint, Module, Opts);
false ->
@ -415,22 +437,12 @@ config_reloaded() ->
end
end, New).
-spec get_certfiles() -> [binary()].
get_certfiles() ->
lists:filtermap(
fun({_, _, Opts}) ->
case proplists:get_value(certfile, Opts) of
undefined -> false;
Cert -> {true, Cert}
end
end, ets:tab2list(?MODULE)).
-spec report_socket_error(inet:posix(), endpoint(), module()) -> ok.
report_socket_error(Reason, EndPoint, Module) ->
?ERROR_MSG("Failed to open socket at ~s for ~s: ~s",
[format_endpoint(EndPoint), Module, format_error(Reason)]).
-spec format_error(inet:posix()) -> string().
-spec format_error(inet:posix() | atom()) -> string().
format_error(Reason) ->
case inet:format_error(Reason) of
"unknown POSIX error" ->
@ -447,8 +459,17 @@ format_endpoint({Port, IP, _Transport}) ->
end,
IPStr ++ ":" ++ integer_to_list(Port).
-spec check_rate_limit(non_neg_integer()) -> non_neg_integer().
check_rate_limit(Interval) ->
-spec format_transport(transport(), opts()) -> string().
format_transport(Transport, Opts) ->
case maps:get(tls, Opts, false) of
true when Transport == tcp -> "TLS";
true when Transport == udp -> "DTLS";
false when Transport == tcp -> "TCP";
false when Transport == udp -> "UDP"
end.
-spec apply_rate_limit(non_neg_integer()) -> non_neg_integer().
apply_rate_limit(Interval) ->
NewInterval = receive
{rate_limit, AcceptInterval} ->
AcceptInterval
@ -473,318 +494,171 @@ check_rate_limit(Interval) ->
end,
NewInterval.
transform_option({{Port, IP, Transport}, Mod, Opts}) ->
IPStr = if is_tuple(IP) ->
list_to_binary(inet_parse:ntoa(IP));
true ->
IP
end,
Opts1 = lists:map(
fun({ip, IPT}) when is_tuple(IPT) ->
{ip, list_to_binary(inet_parse:ntoa(IP))};
(ssl) -> {tls, true};
(A) when is_atom(A) -> {A, true};
(Opt) -> Opt
end, Opts),
Opts2 = lists:foldl(
fun(Opt, Acc) ->
try
Mod:transform_listen_option(Opt, Acc)
catch error:undef ->
[Opt|Acc]
end
end, [], Opts1),
TransportOpt = if Transport == tcp -> [];
true -> [{transport, Transport}]
end,
IPOpt = if IPStr == <<"0.0.0.0">> -> [];
true -> [{ip, IPStr}]
end,
IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2];
transform_option({{Port, Transport}, Mod, Opts})
when Transport == tcp orelse Transport == udp ->
transform_option({{Port, all_zero_ip(Opts), Transport}, Mod, Opts});
transform_option({{Port, IP}, Mod, Opts}) ->
transform_option({{Port, IP, tcp}, Mod, Opts});
transform_option({Port, Mod, Opts}) ->
transform_option({{Port, all_zero_ip(Opts), tcp}, Mod, Opts});
transform_option(Opt) ->
Opt.
-spec validator() -> econf:validator().
validator() ->
econf:and_then(
econf:list(
econf:and_then(
econf:options(
#{module => listen_opt_type(module),
transport => listen_opt_type(transport),
'_' => econf:any()},
[{required, [module]}]),
fun(Opts) ->
M = proplists:get_value(module, Opts),
T = proplists:get_value(transport, Opts, tcp),
(validator(M, T))(Opts)
end)),
fun prepare_opts/1).
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
-spec validator(module(), transport()) -> econf:validator().
validator(M, T) ->
Options = listen_options() ++ M:listen_options(),
Required = lists:usort([Opt || Opt <- Options, is_atom(Opt)]),
Disallowed = if T == udp ->
[backlog, use_proxy_protocol, accept_interval];
true ->
[]
end,
Validator = maps:from_list(
lists:map(
fun(Opt) ->
try {Opt, M:listen_opt_type(Opt)}
catch _:_ when M /= ?MODULE ->
{Opt, listen_opt_type(Opt)}
end
end, proplists:get_keys(Options))),
econf:options(
Validator,
[{required, Required}, {disallowed, Disallowed},
{return, map}, unique]).
transform_options({listen, LOpts}, Opts) ->
[{listen, lists:map(fun transform_option/1, LOpts)} | Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
-spec validate_cfg(list()) -> [listener()].
validate_cfg(Listeners) ->
Listeners1 = lists:map(fun validate_opts/1, Listeners),
Listeners2 = lists:keysort(1, Listeners1),
check_overlapping_listeners(Listeners2).
-spec validate_module(module()) -> ok.
validate_module(Mod) ->
case code:ensure_loaded(Mod) of
{module, Mod} ->
lists:foreach(
fun({Fun, Arities}) ->
case lists:any(
fun(Arity) ->
erlang:function_exported(Mod, Fun, Arity)
end, Arities) of
true -> ok;
false ->
?ERROR_MSG("Failed to load listening module ~s, "
"because it doesn't export ~s/~B callback. "
"The module is either not a listening module "
"or it is a third-party module which "
"requires update",
[Mod, Fun, hd(Arities)]),
erlang:error(badarg)
end
end, [{start, [3,2]}, {start_link, [3,2]},
{accept, [1]}, {listen_options, [0]}]);
_ ->
?ERROR_MSG("Failed to load unknown listening module ~s: "
"make sure there is no typo and ~s.beam "
"exists inside either ~s or ~s directory",
[Mod, Mod,
filename:dirname(code:which(?MODULE)),
ext_mod:modules_dir()]),
erlang:error(badarg)
end.
-spec validate_opts(listen_opts()) -> listener().
validate_opts(Opts) ->
case lists:keyfind(module, 1, Opts) of
{_, Mod} ->
validate_module(Mod),
Opts1 = validate_opts(Mod, Opts),
{Opts2, Opts3} = lists:partition(
fun({port, _}) -> true;
({transport, _}) -> true;
({module, _}) -> true;
(_) -> false
end, Opts1),
Port = proplists:get_value(port, Opts2),
Transport = proplists:get_value(transport, Opts2, tcp),
IP = proplists:get_value(ip, Opts3, all_zero_ip(Opts3)),
{{Port, IP, Transport}, Mod, Opts3};
false ->
?ERROR_MSG("Missing required listening option: module", []),
erlang:error(badarg)
end.
-spec validate_opts(module(), listen_opts()) -> listen_opts().
validate_opts(Mod, Opts) ->
Defaults = listen_options() ++ Mod:listen_options(),
{Opts1, Defaults1} =
lists:mapfoldl(
fun({Opt, Val} = OptVal, Defs) ->
case proplists:is_defined(Opt, Defaults) of
true ->
NewOptVal = case lists:member(OptVal, Defaults) of
true -> [];
false -> [validate_module_opt(Mod, Opt, Val)]
end,
{NewOptVal, proplists:delete(Opt, Defs)};
false ->
?ERROR_MSG("Unknown listening option '~s' of "
"module ~s; available options are: ~s",
[Opt, Mod,
misc:join_atoms(
proplists:get_keys(Defaults),
<<", ">>)]),
erlang:error(badarg)
end
end, Defaults, Opts),
case lists:filter(fun is_atom/1, Defaults1) of
[] ->
lists:flatten(Opts1);
MissingRequiredOpts ->
?ERROR_MSG("Missing required listening option(s): ~s",
[misc:join_atoms(MissingRequiredOpts, <<", ">>)]),
erlang:error(badarg)
end.
-spec validate_module_opt(module(), atom(), any()) -> {atom(), any()}.
validate_module_opt(Module, Opt, Val) ->
VFun = try Module:listen_opt_type(Opt)
catch _:_ -> listen_opt_type(Opt)
end,
try {Opt, VFun(Val)}
catch _:R when R /= undef ->
?ERROR_MSG("Invalid value of listening option ~s: ~s",
[Opt, misc:format_val({yaml, Val})]),
erlang:error(badarg)
end.
-spec all_zero_ip(listen_opts()) -> inet:ip_address().
all_zero_ip(Opts) ->
case proplists:get_bool(inet6, Opts) of
true -> {0,0,0,0,0,0,0,0};
false -> {0,0,0,0}
end.
-spec prepare_opts([opts()]) -> [listener()].
prepare_opts(Listeners) ->
check_overlapping_listeners(
lists:map(
fun(Opts1) ->
{Opts2, Opts3} = partition(
fun({port, _}) -> true;
({transport, _}) -> true;
({module, _}) -> true;
(_) -> false
end, Opts1),
Mod = maps:get(module, Opts2),
Port = maps:get(port, Opts2),
Transport = maps:get(transport, Opts2, tcp),
IP = maps:get(ip, Opts3, {0,0,0,0}),
Opts4 = apply_defaults(Mod, Opts3),
{{Port, IP, Transport}, Mod, Opts4}
end, Listeners)).
-spec check_overlapping_listeners([listener()]) -> [listener()].
check_overlapping_listeners(Listeners) ->
lists:foldl(
fun({{Port, IP, Transport} = Key, _, _}, Acc) ->
case lists:member(Key, Acc) of
true ->
?ERROR_MSG("Overlapping listeners found at ~s",
[format_endpoint(Key)]),
erlang:error(badarg);
false ->
ZeroIP = case size(IP) of
8 -> {0,0,0,0,0,0,0,0};
4 -> {0,0,0,0}
end,
Key1 = {Port, ZeroIP, Transport},
case lists:member(Key1, Acc) of
true ->
?ERROR_MSG(
"Overlapping listeners found at ~s and ~s",
[format_endpoint(Key), format_endpoint(Key1)]),
erlang:error(badarg);
false ->
[Key|Acc]
end
end
end, [], Listeners),
_ = lists:foldl(
fun({{Port, IP, Transport} = Key, _, _}, Acc) ->
case lists:member(Key, Acc) of
true ->
econf:fail({listener_dup, {IP, Port}});
false ->
ZeroIP = case size(IP) of
8 -> {0,0,0,0,0,0,0,0};
4 -> {0,0,0,0}
end,
Key1 = {Port, ZeroIP, Transport},
case lists:member(Key1, Acc) of
true ->
econf:fail({listener_conflict,
{IP, Port}, {ZeroIP, Port}});
false ->
[Key|Acc]
end
end
end, [], Listeners),
Listeners.
-spec apply_defaults(module(), opts()) -> opts().
apply_defaults(Mod, Opts) ->
lists:foldl(
fun({Opt, Default}, M) ->
case maps:is_key(Opt, M) of
true -> M;
false -> M#{Opt => Default}
end;
(_, M) ->
M
end, Opts, Mod:listen_options() ++ listen_options()).
%% Convert options to list with removing defaults
-spec opts_to_list(module(), opts()) -> list_opts().
opts_to_list(Mod, Opts) ->
Defaults = Mod:listen_options() ++ listen_options(),
maps:fold(
fun(Opt, Val, Acc) ->
case proplists:get_value(Opt, Defaults) of
Val -> Acc;
_ -> [{Opt, Val}|Acc]
end
end, [], Opts).
-spec partition(fun(({atom(), term()}) -> boolean()), opts()) -> {opts(), opts()}.
partition(Fun, Opts) ->
maps:fold(
fun(Opt, Val, {True, False}) ->
case Fun({Opt, Val}) of
true -> {True#{Opt => Val}, False};
false -> {True, False#{Opt => Val}}
end
end, {#{}, #{}}, Opts).
-spec listen_opt_type(atom()) -> econf:validator().
listen_opt_type(port) ->
fun(I) when is_integer(I), I>0, I<65536 -> I end;
econf:int(0, 65535);
listen_opt_type(module) ->
fun(A) when is_atom(A) -> A end;
econf:beam([[{start, 3}, {start, 2}],
[{start_link, 3}, {start_link, 2}],
{accept, 1}, {listen_options, 0}]);
listen_opt_type(ip) ->
fun(S) ->
{ok, Addr} = inet_parse:address(binary_to_list(S)),
Addr
end;
econf:ip();
listen_opt_type(transport) ->
fun(tcp) -> tcp;
(udp) -> udp
end;
econf:enum([tcp, udp]);
listen_opt_type(accept_interval) ->
fun(I) when is_integer(I), I>=0 -> I end;
econf:non_neg_int();
listen_opt_type(backlog) ->
fun(I) when is_integer(I), I>=0 -> I end;
listen_opt_type(inet) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(inet6) ->
fun(B) when is_boolean(B) -> B end;
econf:non_neg_int();
listen_opt_type(supervisor) ->
fun(B) when is_boolean(B) -> B end;
econf:bool();
listen_opt_type(ciphers) ->
econf:binary();
listen_opt_type(dhfile) ->
econf:file();
listen_opt_type(cafile) ->
econf:pem();
listen_opt_type(certfile) ->
fun(S) ->
{ok, File} = ejabberd_pkix:add_certfile(S),
File
end;
listen_opt_type(ciphers) -> fun iolist_to_binary/1;
listen_opt_type(dhfile) -> fun misc:try_read_file/1;
listen_opt_type(cafile) -> fun ejabberd_pkix:try_certfile/1;
econf:pem();
listen_opt_type(protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end;
econf:and_then(
econf:list(econf:binary()),
fun(Options) -> str:join(Options, <<"|">>) end);
listen_opt_type(tls_compression) ->
fun(B) when is_boolean(B) -> B end;
econf:bool();
listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end;
econf:bool();
listen_opt_type(max_stanza_size) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
econf:pos_int(infinity);
listen_opt_type(max_fsm_queue) ->
fun(I) when is_integer(I), I>0 -> I end;
econf:pos_int();
listen_opt_type(shaper) ->
fun acl:shaper_rules_validator/1;
econf:shaper();
listen_opt_type(access) ->
fun acl:access_rules_validator/1;
econf:acl();
listen_opt_type(use_proxy_protocol) ->
fun(B) when is_boolean(B) -> B end.
econf:bool().
listen_options() ->
[module, port,
{transport, tcp},
{ip, <<"0.0.0.0">>},
{inet, true},
{inet6, false},
{ip, {0,0,0,0}},
{accept_interval, 0},
{backlog, 5},
{use_proxy_protocol, false},
{supervisor, true}].
opt_type(listen) -> fun validate_cfg/1;
opt_type(_) -> [listen].
%%%----------------------------------------------------------------------
%%% Some legacy code used by ejabberd_web_admin only
%%%----------------------------------------------------------------------
parse_listener_portip(PortIP, Opts) ->
{IPOpt, Opts2} = strip_ip_option(Opts),
{IPVOpt, OptsClean} = case proplists:get_bool(inet6, Opts2) of
true -> {inet6, proplists:delete(inet6, Opts2)};
false -> {inet, Opts2}
end,
{Port, IPT, Proto} =
case add_proto(PortIP, Opts) of
{P, Prot} ->
T = get_ip_tuple(IPOpt, IPVOpt),
{P, T, Prot};
{P, T, Prot} when is_integer(P) and is_tuple(T) ->
{P, T, Prot};
{P, S, Prot} when is_integer(P) and is_binary(S) ->
{ok, T} = inet_parse:address(binary_to_list(S)),
{P, T, Prot}
end,
IPV = case tuple_size(IPT) of
4 -> inet;
8 -> inet6
end,
{Port, IPT, IPV, Proto, OptsClean}.
add_proto(Port, Opts) when is_integer(Port) ->
{Port, get_proto(Opts)};
add_proto({Port, Proto}, _Opts) when is_atom(Proto) ->
{Port, normalize_proto(Proto)};
add_proto({Port, Addr}, Opts) ->
{Port, Addr, get_proto(Opts)};
add_proto({Port, Addr, Proto}, _Opts) ->
{Port, Addr, normalize_proto(Proto)}.
strip_ip_option(Opts) ->
{IPL, OptsNoIP} = lists:partition(
fun({ip, _}) -> true;
(_) -> false
end,
Opts),
case IPL of
%% Only the first ip option is considered
[{ip, T1} | _] ->
{T1, OptsNoIP};
[] ->
{no_ip_option, OptsNoIP}
end.
get_ip_tuple(no_ip_option, inet) ->
{0, 0, 0, 0};
get_ip_tuple(no_ip_option, inet6) ->
{0, 0, 0, 0, 0, 0, 0, 0};
get_ip_tuple(IPOpt, _IPVOpt) ->
IPOpt.
get_proto(Opts) ->
case proplists:get_value(proto, Opts) of
undefined ->
tcp;
Proto ->
normalize_proto(Proto)
end.
normalize_proto(udp) -> udp;
normalize_proto(_) -> tcp.

View File

@ -106,7 +106,7 @@ get_features(Host) ->
init([]) ->
process_flag(trap_exit, true),
lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()),
lists:foreach(fun host_up/1, ejabberd_option:hosts()),
ejabberd_hooks:add(host_up, ?MODULE, host_up, 10),
ejabberd_hooks:add(host_down, ?MODULE, host_down, 100),
gen_iq_handler:start(?MODULE),
@ -126,7 +126,7 @@ handle_info(Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()),
lists:foreach(fun host_down/1, ejabberd_option:hosts()),
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 10),
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 100),
ok.

View File

@ -24,21 +24,21 @@
%%%-------------------------------------------------------------------
-module(ejabberd_logger).
-behaviour(ejabberd_config).
%% API
-export([start/0, restart/0, reopen_log/0, rotate_log/0, get/0, set/1,
get_log_path/0, opt_type/1]).
get_log_path/0]).
-type loglevel() :: 0 | 1 | 2 | 3 | 4 | 5.
-type lager_level() :: none | emergency | alert | critical |
error | warning | notice | info | debug.
-spec start() -> ok.
-spec get_log_path() -> string().
-spec reopen_log() -> ok.
-spec rotate_log() -> ok.
-spec get() -> {loglevel(), atom(), string()}.
-spec set(loglevel() | {loglevel(), list()}) -> {module, module()}.
-spec set(loglevel()) -> ok.
%%%===================================================================
%%% API
@ -64,17 +64,6 @@ get_log_path() ->
end
end.
opt_type(log_rotate_date) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(log_rotate_size) ->
fun(I) when is_integer(I), I >= 0 -> I end;
opt_type(log_rotate_count) ->
fun(I) when is_integer(I), I >= 0 -> I end;
opt_type(log_rate_limit) ->
fun(I) when is_integer(I), I >= 0 -> I end;
opt_type(_) ->
[log_rotate_date, log_rotate_size, log_rotate_count, log_rate_limit].
get_integer_env(Name, Default) ->
case application:get_env(ejabberd, Name) of
{ok, I} when is_integer(I), I>=0 ->
@ -130,7 +119,7 @@ do_start_for_logger(Level) ->
ejabberd:start_app(lager),
ok.
%% Start lager
-spec do_start(atom()) -> ok.
do_start(Level) ->
application:load(sasl),
application:set_env(sasl, sasl_error_logger, false),
@ -162,11 +151,10 @@ do_start(Level) ->
ejabberd:start_app(lager),
lists:foreach(fun(Handler) ->
lager:set_loghwm(Handler, LogRateLimit)
end, gen_event:which_handlers(lager_event)),
ok.
end, gen_event:which_handlers(lager_event)).
restart() ->
Level = ejabberd_config:get_option(loglevel, 4),
Level = ejabberd_option:loglevel(),
application:stop(lager),
start(Level).
@ -199,7 +187,6 @@ get() ->
debug -> {5, debug, "Debug"}
end.
%% @spec (loglevel() | {loglevel(), list()}) -> {module, module()}
set(LogLevel) when is_integer(LogLevel) ->
LagerLogLevel = get_lager_loglevel(LogLevel),
case get_lager_loglevel() of
@ -216,16 +203,12 @@ set(LogLevel) when is_integer(LogLevel) ->
lager:set_loglevel(H, LagerLogLevel);
(_) ->
ok
end, gen_event:which_handlers(lager_event))
end, get_lager_handlers())
end,
case LogLevel of
5 -> xmpp:set_config([{debug, true}]);
_ -> xmpp:set_config([{debug, false}])
end,
{module, lager};
set({_LogLevel, _}) ->
error_logger:error_msg("custom loglevels are not supported for 'lager'"),
{module, lager}.
end.
get_lager_loglevel() ->
Handlers = get_lager_handlers(),
@ -238,6 +221,7 @@ get_lager_loglevel() ->
end,
none, Handlers).
-spec get_lager_loglevel(loglevel()) -> lager_level().
get_lager_loglevel(LogLevel) ->
case LogLevel of
0 -> none;
@ -245,8 +229,7 @@ get_lager_loglevel(LogLevel) ->
2 -> error;
3 -> warning;
4 -> info;
5 -> debug;
E -> erlang:error({wrong_loglevel, E})
5 -> debug
end.
get_lager_handlers() ->
@ -257,6 +240,7 @@ get_lager_handlers() ->
Result
end.
-spec get_lager_version() -> string().
get_lager_version() ->
Apps = application:loaded_applications(),
case lists:keyfind(lager, 1, Apps) of

View File

@ -27,7 +27,6 @@
-module(ejabberd_oauth).
-behaviour(gen_server).
-behaviour(ejabberd_config).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
@ -38,7 +37,6 @@
verify_redirection_uri/3,
authenticate_user/2,
authenticate_client/2,
verify_resowner_scope/3,
associate_access_code/3,
associate_access_token/3,
associate_refresh_token/3,
@ -47,8 +45,7 @@
check_token/2,
scope_in_scope_list/2,
process/2,
config_reloaded/0,
opt_type/1]).
config_reloaded/0]).
-export([get_commands_spec/0,
oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1]).
@ -73,8 +70,6 @@
%% * Using the command line and oauth_issue_token command, the token is generated in behalf of ejabberd' sysadmin
%% (as it has access to ejabberd command line).
-define(EXPIRE, 4294967).
get_commands_spec() ->
[
#ejabberd_commands{name = oauth_issue_token, tags = [oauth],
@ -189,9 +184,7 @@ authenticate_user({User, Server}, Ctx) ->
case jid:make(User, Server) of
#jid{} = JID ->
Access =
ejabberd_config:get_option(
{oauth_access, JID#jid.lserver},
none),
ejabberd_option:oauth_access(JID#jid.lserver),
case acl:match_rule(JID#jid.lserver, Access, JID) of
allow ->
case Ctx of
@ -214,21 +207,6 @@ authenticate_user({User, Server}, Ctx) ->
authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
Cmds = ejabberd_commands:get_exposed_commands(),
Cmds1 = ['ejabberd:user', 'ejabberd:admin', sasl_auth | Cmds],
RegisteredScope = [atom_to_binary(C, utf8) || C <- Cmds1],
case oauth2_priv_set:is_subset(oauth2_priv_set:new(Scope),
oauth2_priv_set:new(RegisteredScope)) of
true ->
{ok, {Ctx, Scope}};
false ->
{error, badscope}
end;
verify_resowner_scope(_, _, _) ->
{error, badscope}.
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are
%% made available.
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
@ -286,6 +264,8 @@ scope_in_scope_list(Scope, ScopeList) ->
oauth2_priv_set:is_member(Scope2, TokenScopeSet) end,
ScopeList).
-spec check_token(binary()) -> {ok, {binary(), binary()}, [binary()]} |
{false, expired | not_found}.
check_token(Token) ->
case lookup(Token) of
{ok, #oauth_token{us = US,
@ -380,29 +360,20 @@ init_cache(DBMod) ->
use_cache(DBMod) ->
case erlang:function_exported(DBMod, use_cache, 0) of
true -> DBMod:use_cache();
false ->
ejabberd_config:get_option(
oauth_use_cache,
ejabberd_config:use_cache(global))
false -> ejabberd_option:oauth_use_cache()
end.
cache_opts() ->
MaxSize = ejabberd_config:get_option(
oauth_cache_size,
ejabberd_config:cache_size(global)),
CacheMissed = ejabberd_config:get_option(
oauth_cache_missed,
ejabberd_config:cache_missed(global)),
LifeTime = case ejabberd_config:get_option(
oauth_cache_life_time,
ejabberd_config:cache_life_time(global)) of
MaxSize = ejabberd_option:oauth_cache_size(),
CacheMissed = ejabberd_option:oauth_cache_missed(),
LifeTime = case ejabberd_option:oauth_cache_life_time() of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {life_time, LifeTime}, {cache_missed, CacheMissed}].
expire() ->
ejabberd_config:get_option(oauth_expire, ?EXPIRE).
ejabberd_option:oauth_expire().
-define(DIV(Class, Els),
?XAE(<<"div">>, [{<<"class">>, Class}], Els)).
@ -596,9 +567,7 @@ process(_Handlers, _Request) ->
-spec get_db_backend() -> module().
get_db_backend() ->
DBType = ejabberd_config:get_option(
oauth_db_type,
ejabberd_config:default_db(?MODULE)),
DBType = ejabberd_option:oauth_db_type(),
list_to_atom("ejabberd_oauth_" ++ atom_to_list(DBType)).
@ -645,21 +614,3 @@ logo() ->
{error, _} ->
<<>>
end.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(oauth_expire) ->
fun(I) when is_integer(I), I >= 0 -> I end;
opt_type(oauth_access) ->
fun acl:access_rules_validator/1;
opt_type(oauth_db_type) ->
fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(O) when O == oauth_cache_life_time; O == oauth_cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
opt_type(O) when O == oauth_use_cache; O == oauth_cache_missed ->
fun (B) when is_boolean(B) -> B end;
opt_type(_) ->
[oauth_expire, oauth_access, oauth_db_type,
oauth_cache_life_time, oauth_cache_size, oauth_use_cache,
oauth_cache_missed].

View File

@ -45,9 +45,7 @@ init() ->
use_cache() ->
case mnesia:table_info(oauth_token, storage_type) of
disc_only_copies ->
ejabberd_config:get_option(
oauth_use_cache,
ejabberd_config:use_cache(global));
ejabberd_option:oauth_use_cache();
_ ->
false
end.
@ -73,4 +71,3 @@ clean(TS) ->
lists:foreach(fun mnesia:delete_object/1, Ts)
end,
mnesia:async_dirty(F).

View File

@ -26,13 +26,11 @@
-module(ejabberd_oauth_rest).
-behaviour(ejabberd_oauth).
-behaviour(ejabberd_config).
-export([init/0,
store/1,
lookup/1,
clean/1,
opt_type/1]).
clean/1]).
-include("ejabberd_oauth.hrl").
-include("logger.hrl").
@ -88,11 +86,5 @@ clean(_TS) ->
ok.
path(Path) ->
Base = ejabberd_config:get_option(ext_api_path_oauth, <<"/oauth">>),
Base = ejabberd_option:ext_api_path_oauth(),
<<Base/binary, "/", Path/binary>>.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(ext_api_path_oauth) ->
fun (X) -> iolist_to_binary(X) end;
opt_type(_) -> [ext_api_path_oauth].

View File

@ -26,7 +26,6 @@
-module(ejabberd_oauth_sql).
-behaviour(ejabberd_oauth).
-compile([{parse_transform, ejabberd_sql_pt}]).
-export([init/0,
store/1,

655
src/ejabberd_old_config.erl Normal file
View File

@ -0,0 +1,655 @@
%%%----------------------------------------------------------------------
%%% Purpose: Transform old-style Erlang config to YAML config
%%%
%%% 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_old_config).
%% API
-export([read_file/1]).
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
read_file(File) ->
case consult(File) of
{ok, Terms1} ->
?INFO_MSG("Converting from old configuration format", []),
Terms2 = strings_to_binary(Terms1),
Terms3 = transform(Terms2),
Terms4 = transform_certfiles(Terms3),
Terms5 = transform_host_config(Terms4),
{ok, collect_options(Terms5)};
{error, Reason} ->
{error, {old_config, File, Reason}}
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================
collect_options(Opts) ->
{D, InvalidOpts} =
lists:foldl(
fun({K, V}, {D, Os}) when is_list(V) ->
{orddict:append_list(K, V, D), Os};
({K, V}, {D, Os}) ->
{orddict:store(K, V, D), Os};
(Opt, {D, Os}) ->
{D, [Opt|Os]}
end, {orddict:new(), []}, Opts),
InvalidOpts ++ orddict:to_list(D).
transform(Opts) ->
Opts1 = transform_register(Opts),
Opts2 = transform_s2s(Opts1),
Opts3 = transform_listeners(Opts2),
Opts4 = transform_sql(Opts3),
Opts5 = transform_riak(Opts4),
Opts6 = transform_shaper(Opts5),
Opts7 = transform_s2s_out(Opts6),
Opts8 = transform_acl(Opts7),
Opts9 = transform_modules(Opts8),
Opts10 = transform_globals(Opts9),
collect_options(Opts10).
%%%===================================================================
%%% mod_register
%%%===================================================================
transform_register(Opts) ->
try
{value, {modules, ModOpts}, Opts1} = lists:keytake(modules, 1, Opts),
{value, {?MODULE, RegOpts}, ModOpts1} = lists:keytake(?MODULE, 1, ModOpts),
{value, {ip_access, L}, RegOpts1} = lists:keytake(ip_access, 1, RegOpts),
true = is_list(L),
?WARNING_MSG("Old 'ip_access' format detected. "
"The old format is still supported "
"but it is better to fix your config: "
"use access rules instead.", []),
ACLs = lists:flatmap(
fun({Action, S}) ->
ACLName = misc:binary_to_atom(
iolist_to_binary(
["ip_", S])),
[{Action, ACLName},
{acl, ACLName, {ip, S}}]
end, L),
Access = {access, mod_register_networks,
[{Action, ACLName} || {Action, ACLName} <- ACLs]},
[ACL || {acl, _, _} = ACL <- ACLs] ++
[Access,
{modules,
[{mod_register,
[{ip_access, mod_register_networks}|RegOpts1]}
| ModOpts1]}|Opts1]
catch error:{badmatch, false} ->
Opts
end.
%%%===================================================================
%%% ejabberd_s2s
%%%===================================================================
transform_s2s(Opts) ->
lists:foldl(fun transform_s2s/2, [], Opts).
transform_s2s({{s2s_host, Host}, Action}, Opts) ->
?WARNING_MSG("Option 's2s_host' is deprecated.", []),
ACLName = misc:binary_to_atom(
iolist_to_binary(["s2s_access_", Host])),
[{acl, ACLName, {server, Host}},
{access, s2s, [{Action, ACLName}]},
{s2s_access, s2s} |
Opts];
transform_s2s({s2s_default_policy, Action}, Opts) ->
?WARNING_MSG("Option 's2s_default_policy' is deprecated. "
"The option is still supported but it is better to "
"fix your config: "
"use 's2s_access' with an access rule.", []),
[{access, s2s, [{Action, all}]},
{s2s_access, s2s} |
Opts];
transform_s2s(Opt, Opts) ->
[Opt|Opts].
%%%===================================================================
%%% ejabberd_s2s_out
%%%===================================================================
transform_s2s_out(Opts) ->
lists:foldl(fun transform_s2s_out/2, [], Opts).
transform_s2s_out({outgoing_s2s_options, Families, Timeout}, Opts) ->
?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 'outgoing_s2s_timeout' and "
"'outgoing_s2s_families' instead.", []),
[{outgoing_s2s_families, Families},
{outgoing_s2s_timeout, Timeout}
| Opts];
transform_s2s_out({s2s_dns_options, S2SDNSOpts}, AllOpts) ->
?WARNING_MSG("Option 's2s_dns_options' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 's2s_dns_timeout' and "
"'s2s_dns_retries' instead", []),
lists:foldr(
fun({timeout, T}, AccOpts) ->
[{s2s_dns_timeout, T}|AccOpts];
({retries, R}, AccOpts) ->
[{s2s_dns_retries, R}|AccOpts];
(_, AccOpts) ->
AccOpts
end, AllOpts, S2SDNSOpts);
transform_s2s_out(Opt, Opts) ->
[Opt|Opts].
%%%===================================================================
%%% ejabberd_listener
%%%===================================================================
transform_listeners(Opts) ->
lists:foldl(fun transform_listeners/2, [], Opts).
transform_listeners({listen, LOpts}, Opts) ->
[{listen, lists:map(fun transform_listener/1, LOpts)} | Opts];
transform_listeners(Opt, Opts) ->
[Opt|Opts].
transform_listener({{Port, IP, Transport}, Mod, Opts}) ->
IPStr = if is_tuple(IP) ->
list_to_binary(inet_parse:ntoa(IP));
true ->
IP
end,
Opts1 = lists:map(
fun({ip, IPT}) when is_tuple(IPT) ->
{ip, list_to_binary(inet_parse:ntoa(IP))};
(ssl) -> {tls, true};
(A) when is_atom(A) -> {A, true};
(Opt) -> Opt
end, Opts),
Opts2 = lists:foldl(
fun(Opt, Acc) ->
transform_listen_option(Mod, Opt, Acc)
end, [], Opts1),
TransportOpt = if Transport == tcp -> [];
true -> [{transport, Transport}]
end,
IPOpt = if IPStr == <<"0.0.0.0">> -> [];
true -> [{ip, IPStr}]
end,
IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2];
transform_listener({{Port, Transport}, Mod, Opts})
when Transport == tcp orelse Transport == udp ->
transform_listener({{Port, all_zero_ip(Opts), Transport}, Mod, Opts});
transform_listener({{Port, IP}, Mod, Opts}) ->
transform_listener({{Port, IP, tcp}, Mod, Opts});
transform_listener({Port, Mod, Opts}) ->
transform_listener({{Port, all_zero_ip(Opts), tcp}, Mod, Opts});
transform_listener(Opt) ->
Opt.
transform_listen_option(ejabberd_http, captcha, Opts) ->
[{captcha, true}|Opts];
transform_listen_option(ejabberd_http, register, Opts) ->
[{register, true}|Opts];
transform_listen_option(ejabberd_http, web_admin, Opts) ->
[{web_admin, true}|Opts];
transform_listen_option(ejabberd_http, http_bind, Opts) ->
[{http_bind, true}|Opts];
transform_listen_option(ejabberd_http, http_poll, Opts) ->
[{http_poll, true}|Opts];
transform_listen_option(ejabberd_http, {request_handlers, Hs}, Opts) ->
Hs1 = lists:map(
fun({PList, Mod}) when is_list(PList) ->
Path = iolist_to_binary([[$/, P] || P <- PList]),
{Path, Mod};
(Opt) ->
Opt
end, Hs),
[{request_handlers, Hs1} | Opts];
transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) ->
case lists:keyfind(hosts, 1, Opts) of
{_, PrevHostOpts} ->
NewHostOpts =
lists:foldl(
fun(H, Acc) ->
dict:append_list(H, O, Acc)
end, dict:from_list(PrevHostOpts), Hosts),
[{hosts, dict:to_list(NewHostOpts)}|
lists:keydelete(hosts, 1, Opts)];
_ ->
[{hosts, [{H, O} || H <- Hosts]}|Opts]
end;
transform_listen_option(ejabberd_service, {host, Host, Os}, Opts) ->
transform_listen_option(ejabberd_service, {hosts, [Host], Os}, Opts);
transform_listen_option(ejabberd_xmlrpc, {access_commands, ACOpts}, Opts) ->
NewACOpts = lists:map(
fun({AName, ACmds, AOpts}) ->
{AName, [{commands, ACmds}, {options, AOpts}]};
(Opt) ->
Opt
end, ACOpts),
[{access_commands, NewACOpts}|Opts];
transform_listen_option(_, Opt, Opts) ->
[Opt|Opts].
-spec all_zero_ip([proplists:property()]) -> inet:ip_address().
all_zero_ip(Opts) ->
case proplists:get_bool(inet6, Opts) of
true -> {0,0,0,0,0,0,0,0};
false -> {0,0,0,0}
end.
%%%===================================================================
%%% ejabberd_shaper
%%%===================================================================
transform_shaper(Opts) ->
lists:foldl(fun transform_shaper/2, [], Opts).
transform_shaper({shaper, Name, {maxrate, N}}, Opts) ->
[{shaper, [{Name, N}]} | Opts];
transform_shaper({shaper, Name, none}, Opts) ->
[{shaper, [{Name, none}]} | Opts];
transform_shaper({shaper, List}, Opts) when is_list(List) ->
R = lists:map(
fun({Name, Args}) when is_list(Args) ->
MaxRate = proplists:get_value(rate, Args, 1000),
BurstSize = proplists:get_value(burst_size, Args, MaxRate),
{Name, MaxRate, BurstSize};
({Name, Val}) ->
{Name, Val, Val}
end, List),
[{shaper, R} | Opts];
transform_shaper(Opt, Opts) ->
[Opt | Opts].
%%%===================================================================
%%% acl
%%%===================================================================
transform_acl(Opts) ->
Opts1 = lists:foldl(fun transform_acl/2, [], Opts),
{ACLOpts, Opts2} = lists:mapfoldl(
fun({acl, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts1),
{AccessOpts, Opts3} = lists:mapfoldl(
fun({access, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts2),
{NewAccessOpts, Opts4} = lists:mapfoldl(
fun({access_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts3),
{ShaperOpts, Opts5} = lists:mapfoldl(
fun({shaper_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts4),
ACLOpts1 = collect_options(lists:flatten(ACLOpts)),
AccessOpts1 = case collect_options(lists:flatten(AccessOpts)) of
[] -> [];
L1 -> [{access, L1}]
end,
ACLOpts2 = case lists:map(
fun({ACLName, Os}) ->
{ACLName, collect_options(Os)}
end, ACLOpts1) of
[] -> [];
L2 -> [{acl, L2}]
end,
NewAccessOpts1 = case lists:map(
fun({NAName, Os}) ->
{NAName, transform_access_rules_config(Os)}
end, lists:flatten(NewAccessOpts)) of
[] -> [];
L3 -> [{access_rules, L3}]
end,
ShaperOpts1 = case lists:map(
fun({SName, Ss}) ->
{SName, transform_access_rules_config(Ss)}
end, lists:flatten(ShaperOpts)) of
[] -> [];
L4 -> [{shaper_rules, L4}]
end,
ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5.
transform_acl({acl, Name, Type}, Opts) ->
T = case Type of
all -> all;
none -> none;
{user, U} -> {user, [b(U)]};
{user, U, S} -> {user, [[{b(U), b(S)}]]};
{shared_group, G} -> {shared_group, [b(G)]};
{shared_group, G, H} -> {shared_group, [[{b(G), b(H)}]]};
{user_regexp, UR} -> {user_regexp, [b(UR)]};
{user_regexp, UR, S} -> {user_regexp, [[{b(UR), b(S)}]]};
{node_regexp, UR, SR} -> {node_regexp, [[{b(UR), b(SR)}]]};
{user_glob, UR} -> {user_glob, [b(UR)]};
{user_glob, UR, S} -> {user_glob, [[{b(UR), b(S)}]]};
{node_glob, UR, SR} -> {node_glob, [[{b(UR), b(SR)}]]};
{server, S} -> {server, [b(S)]};
{resource, R} -> {resource, [b(R)]};
{server_regexp, SR} -> {server_regexp, [b(SR)]};
{server_glob, S} -> {server_glob, [b(S)]};
{ip, S} -> {ip, [b(S)]};
{resource_glob, R} -> {resource_glob, [b(R)]};
{resource_regexp, R} -> {resource_regexp, [b(R)]}
end,
[{acl, [{Name, [T]}]}|Opts];
transform_acl({access, Name, Rules}, Opts) ->
NewRules = [{ACL, Action} || {Action, ACL} <- Rules],
[{access, [{Name, NewRules}]}|Opts];
transform_acl(Opt, Opts) ->
[Opt|Opts].
transform_access_rules_config(Config) when is_list(Config) ->
lists:map(fun transform_access_rules_config2/1, lists:flatten(Config));
transform_access_rules_config(Config) ->
transform_access_rules_config([Config]).
transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) ->
{Type, [all]};
transform_access_rules_config2({Type, ACL}) when is_atom(ACL) ->
{Type, [{acl, ACL}]};
transform_access_rules_config2({Res, Rules}) when is_list(Rules) ->
T = lists:map(fun({Type, Args}) when is_list(Args) ->
{Type, hd(lists:flatten(Args))};
(V) ->
V
end, lists:flatten(Rules)),
{Res, T};
transform_access_rules_config2({Res, Rule}) ->
{Res, [Rule]}.
%%%===================================================================
%%% SQL
%%%===================================================================
-define(PGSQL_PORT, 5432).
-define(MYSQL_PORT, 3306).
transform_sql(Opts) ->
lists:foldl(fun transform_sql/2, [], Opts).
transform_sql({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
[{sql_type, Type},
{sql_server, Server},
{sql_port, Port},
{sql_database, DB},
{sql_username, User},
{sql_password, Pass}|Opts];
transform_sql({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) ->
transform_sql({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts);
transform_sql({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) ->
transform_sql({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts);
transform_sql({odbc_server, {sqlite, DB}}, Opts) ->
[{sql_type, sqlite},
{sql_database, DB}|Opts];
transform_sql({odbc_pool_size, N}, Opts) ->
[{sql_pool_size, N}|Opts];
transform_sql(Opt, Opts) ->
[Opt|Opts].
%%%===================================================================
%%% Riak
%%%===================================================================
transform_riak(Opts) ->
lists:foldl(fun transform_riak/2, [], Opts).
transform_riak({riak_server, {S, P}}, Opts) ->
[{riak_server, S}, {riak_port, P}|Opts];
transform_riak(Opt, Opts) ->
[Opt|Opts].
%%%===================================================================
%%% modules
%%%===================================================================
transform_modules(Opts) ->
lists:foldl(fun transform_modules/2, [], Opts).
transform_modules({modules, ModOpts}, Opts) ->
[{modules, lists:map(
fun({Mod, Opts1}) ->
{Mod, transform_module(Mod, Opts1)};
(Other) ->
Other
end, ModOpts)}|Opts];
transform_modules(Opt, Opts) ->
[Opt|Opts].
transform_module(mod_disco, Opts) ->
lists:map(
fun({server_info, Infos}) ->
NewInfos = lists:map(
fun({Modules, Name, URLs}) ->
[[{modules, Modules},
{name, Name},
{urls, URLs}]];
(Opt) ->
Opt
end, Infos),
{server_info, NewInfos};
(Opt) ->
Opt
end, Opts);
transform_module(mod_muc_log, Opts) ->
lists:map(
fun({top_link, {S1, S2}}) ->
{top_link, [{S1, S2}]};
(Opt) ->
Opt
end, Opts);
transform_module(mod_proxy65, Opts) ->
lists:map(
fun({ip, IP}) when is_tuple(IP) ->
{ip, misc:ip_to_list(IP)};
({hostname, IP}) when is_tuple(IP) ->
{hostname, misc:ip_to_list(IP)};
(Opt) ->
Opt
end, Opts);
transform_module(mod_register, Opts) ->
lists:flatmap(
fun({welcome_message, {Subj, Body}}) ->
[{welcome_message, [{subject, Subj}, {body, Body}]}];
(Opt) ->
[Opt]
end, Opts);
transform_module(_Mod, Opts) ->
Opts.
%%%===================================================================
%%% Host config
%%%===================================================================
transform_host_config(Opts) ->
Opts1 = lists:foldl(fun transform_host_config/2, [], Opts),
{HOpts, Opts2} = lists:mapfoldl(
fun({host_config, O}, Os) ->
{[O], Os};
(O, Os) ->
{[], [O|Os]}
end, [], Opts1),
{AHOpts, Opts3} = lists:mapfoldl(
fun({append_host_config, O}, Os) ->
{[O], Os};
(O, Os) ->
{[], [O|Os]}
end, [], Opts2),
HOpts1 = case collect_options(lists:flatten(HOpts)) of
[] ->
[];
HOs ->
[{host_config,
[{H, transform(O)} || {H, O} <- HOs]}]
end,
AHOpts1 = case collect_options(lists:flatten(AHOpts)) of
[] ->
[];
AHOs ->
[{append_host_config,
[{H, transform(O)} || {H, O} <- AHOs]}]
end,
HOpts1 ++ AHOpts1 ++ Opts3.
transform_host_config({host_config, Host, HOpts}, Opts) ->
{AddOpts, HOpts1} =
lists:mapfoldl(
fun({{add, Opt}, Val}, Os) ->
{[{Opt, Val}], Os};
(O, Os) ->
{[], [O|Os]}
end, [], HOpts),
[{append_host_config, [{Host, lists:flatten(AddOpts)}]},
{host_config, [{Host, HOpts1}]}|Opts];
transform_host_config(Opt, Opts) ->
[Opt|Opts].
%%%===================================================================
%%% Top-level options
%%%===================================================================
transform_globals(Opts) ->
lists:foldl(fun transform_globals/2, [], Opts).
transform_globals(Opt, Opts) when Opt == override_global;
Opt == override_local;
Opt == override_acls ->
?WARNING_MSG("Option '~s' has no effect anymore", [Opt]),
Opts;
transform_globals({node_start, _}, Opts) ->
?WARNING_MSG("Option 'node_start' has no effect anymore", []),
Opts;
transform_globals({iqdisc, {queues, N}}, Opts) ->
[{iqdisc, N}|Opts];
transform_globals({define_macro, Macro, Val}, Opts) ->
[{define_macro, [{Macro, Val}]}|Opts];
transform_globals(Opt, Opts) ->
[Opt|Opts].
%%%===================================================================
%%% Certfiles
%%%===================================================================
transform_certfiles(Opts) ->
lists:foldl(fun transform_certfiles/2, [], Opts).
transform_certfiles({domain_certfile, Domain, CertFile}, Opts) ->
[{host_config, [{Domain, [{domain_certfile, CertFile}]}]}|Opts];
transform_certfiles(Opt, Opts) ->
[Opt|Opts].
%%%===================================================================
%%% Consult file
%%%===================================================================
consult(File) ->
case file:consult(File) of
{ok, Terms} ->
include_config_files(Terms);
Err ->
Err
end.
include_config_files(Terms) ->
include_config_files(Terms, []).
include_config_files([], Res) ->
{ok, Res};
include_config_files([{include_config_file, Filename} | Terms], Res) ->
include_config_files([{include_config_file, Filename, []} | Terms], Res);
include_config_files([{include_config_file, Filename, Options} | Terms], Res) ->
case consult(Filename) of
{ok, Included_terms} ->
Disallow = proplists:get_value(disallow, Options, []),
Included_terms2 = delete_disallowed(Disallow, Included_terms),
Allow_only = proplists:get_value(allow_only, Options, all),
Included_terms3 = keep_only_allowed(Allow_only, Included_terms2),
include_config_files(Terms, Res ++ Included_terms3);
Err ->
Err
end;
include_config_files([Term | Terms], Res) ->
include_config_files(Terms, Res ++ [Term]).
delete_disallowed(Disallowed, Terms) ->
lists:foldl(
fun(Dis, Ldis) ->
delete_disallowed2(Dis, Ldis)
end,
Terms,
Disallowed).
delete_disallowed2(Disallowed, [H|T]) ->
case element(1, H) of
Disallowed ->
delete_disallowed2(Disallowed, T);
_ ->
[H|delete_disallowed2(Disallowed, T)]
end;
delete_disallowed2(_, []) ->
[].
keep_only_allowed(all, Terms) ->
Terms;
keep_only_allowed(Allowed, Terms) ->
{As, _NAs} = lists:partition(
fun(Term) ->
lists:member(element(1, Term), Allowed)
end, Terms),
As.
%%%===================================================================
%%% Aux functions
%%%===================================================================
strings_to_binary([]) ->
[];
strings_to_binary(L) when is_list(L) ->
case is_string(L) of
true ->
list_to_binary(L);
false ->
strings_to_binary1(L)
end;
strings_to_binary({A, B, C, D}) when
is_integer(A), is_integer(B), is_integer(C), is_integer(D) ->
{A, B, C ,D};
strings_to_binary(T) when is_tuple(T) ->
list_to_tuple(strings_to_binary1(tuple_to_list(T)));
strings_to_binary(X) ->
X.
strings_to_binary1([El|L]) ->
[strings_to_binary(El)|strings_to_binary1(L)];
strings_to_binary1([]) ->
[];
strings_to_binary1(T) ->
T.
is_string([C|T]) when (C >= 0) and (C =< 255) ->
is_string(T);
is_string([]) ->
true;
is_string(_) ->
false.
b(S) ->
iolist_to_binary(S).

1058
src/ejabberd_option.erl Normal file

File diff suppressed because it is too large Load Diff

757
src/ejabberd_options.erl Normal file
View File

@ -0,0 +1,757 @@
%%%----------------------------------------------------------------------
%%% 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).
-behaviour(ejabberd_config).
-export([opt_type/1, options/0, globals/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:binary("^[a-zA-Z]+:[^:]+$")},
[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) ->
econf:map(
econf:and_then(
econf:domain(),
econf:enum(ejabberd_option:hosts())),
validator());
opt_type(auth_cache_life_time) ->
econf:pos_int(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) ->
econf:binary();
opt_type(c2s_dhfile) ->
econf:file();
opt_type(c2s_protocol_options) ->
econf:and_then(
econf:list(econf:binary(), [unique]),
fun concat_tls_protocol_options/1);
opt_type(c2s_tls_compression) ->
econf:bool();
opt_type(ca_file) ->
econf:pem();
opt_type(cache_life_time) ->
econf:pos_int(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(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, riak, sql]);
opt_type(default_ram_db) ->
econf:enum([mnesia, riak, 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:map(
econf:and_then(
econf:domain(),
econf:enum(ejabberd_option:hosts())),
validator());
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_rate_limit) ->
econf:non_neg_int();
opt_type(log_rotate_count) ->
econf:non_neg_int();
opt_type(log_rotate_date) ->
econf:string("^(\\$((D(([0-9])|(1[0-9])|(2[0-3])))|"
"(((W[0-6])|(M(([1-2][0-9])|(3[0-1])|([1-9]))))"
"(D(([0-9])|(1[0-9])|(2[0-3])))?)))?$");
opt_type(log_rotate_size) ->
econf:non_neg_int();
opt_type(loglevel) ->
econf:int(0, 5);
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:pos_int();
opt_type(new_sql_schema) ->
econf:bool();
opt_type(oauth_access) ->
econf:acl();
opt_type(oauth_cache_life_time) ->
econf:pos_int(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:non_neg_int();
opt_type(oauth_use_cache) ->
econf:bool();
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:pos_int(infinity);
opt_type(resource_conflict) ->
econf:enum([setresource, closeold, closenew, acceptnew]);
opt_type(riak_cacertfile) ->
econf:and_then(econf:pem(), econf:string());
opt_type(riak_password) ->
econf:string();
opt_type(riak_pool_size) ->
econf:pos_int();
opt_type(riak_port) ->
econf:port();
opt_type(riak_server) ->
econf:string();
opt_type(riak_start_interval) ->
econf:timeout(second);
opt_type(riak_username) ->
econf:string();
opt_type(route_subdomains) ->
econf:enum([s2s, local]);
opt_type(router_cache_life_time) ->
econf:pos_int(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) ->
econf:binary();
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:pos_int();
opt_type(s2s_protocol_options) ->
econf:and_then(
econf:list(econf:binary(), [unique]),
fun concat_tls_protocol_options/1);
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:pos_int(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(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).
%% 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()} |
{websocket_origin, [binary()]} |
{disable_sasl_mechanisms, [binary()]} |
{s2s_zlib, boolean()} |
{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()]} |
{append_host_config, [{binary(), any()}]} |
{host_config, [{binary(), any()}]} |
{define_macro, any()} |
{include_config_file, any()} |
{atom(), any()}].
options() ->
[%% Top-priority options
hosts,
{loglevel, 4},
{cache_life_time, 3600},
{cache_missed, true},
{cache_size, 1000},
{use_cache, true},
{default_db, mnesia},
{default_ram_db, mnesia},
{queue_type, ram},
%% 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},
{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_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_rate_limit, undefined},
{log_rotate_count, undefined},
{log_rotate_date, undefined},
{log_rotate_size, undefined},
{max_fsm_queue, undefined},
{modules, []},
{negotiation_timeout, timer:seconds(30)},
{net_ticktime, 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},
{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, 600},
{resource_conflict, acceptnew},
{riak_cacertfile, nil},
{riak_password, nil},
{riak_pool_size, 10},
{riak_port, 8087},
{riak_server, "127.0.0.1"},
{riak_start_interval, timer:seconds(30)},
{riak_username, nil},
{route_subdomains, local},
{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, 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, undefined},
{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">>},
{trusted_proxies, []},
{validate_stream, false},
{version, fun version/1},
{websocket_origin, []},
{websocket_ping_interval, timer:seconds(60)},
{websocket_timeout, timer:minutes(5)}].
-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,
certfiles,
cluster_backend,
cluster_nodes,
domain_balancing,
ext_api_path_oauth,
fqdn,
hosts,
host_config,
listen,
loglevel,
log_rate_limit,
log_rotate_count,
log_rotate_date,
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,
riak_cacertfile,
riak_password,
riak_pool_size,
riak_port,
riak_server,
riak_start_interval,
riak_username,
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,
version,
websocket_origin,
websocket_ping_interval,
websocket_timeout].
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec validator() -> econf:validator().
validator() ->
Disallowed = ejabberd_config:globals(),
{Validators, Required} = ejabberd_config:validators(Disallowed),
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 version(global | binary()) -> binary().
version(global) ->
case application:get_env(ejabberd, custom_vsn) of
{ok, Vsn0} when is_list(Vsn0) ->
list_to_binary(Vsn0);
{ok, Vsn1} when is_binary(Vsn1) ->
Vsn1;
_ ->
case application:get_key(ejabberd, vsn) of
undefined -> <<"">>;
{ok, Vsn} -> list_to_binary(Vsn)
end
end;
version(_) ->
ejabberd_config:get_option(version).
-spec concat_tls_protocol_options([binary()]) -> binary().
concat_tls_protocol_options(Opts) ->
str:join(Opts, <<"|">>).

View File

@ -92,7 +92,7 @@ import_file(FileName, State) ->
-spec export_server(binary()) -> any().
export_server(Dir) ->
export_hosts(ejabberd_config:get_myhosts(), Dir).
export_hosts(ejabberd_option:hosts(), Dir).
-spec export_host(binary(), binary()) -> any().
export_host(Dir, Host) ->

View File

@ -22,11 +22,10 @@
%%%-------------------------------------------------------------------
-module(ejabberd_pkix).
-behaviour(gen_server).
-behaviour(ejabberd_config).
%% API
-export([start_link/0, opt_type/1]).
-export([certs_dir/0, ca_file/0]).
-export([start_link/0]).
-export([certs_dir/0]).
-export([add_certfile/1, try_certfile/1, get_certfile/0, get_certfile/1]).
%% Hooks
-export([ejabberd_started/0, config_reloaded/0]).
@ -99,11 +98,7 @@ get_certfile() ->
Ret -> {ok, select_certfile(Ret)}
end.
-spec ca_file() -> filename() | undefined.
ca_file() ->
ejabberd_config:get_option(ca_file).
-spec certs_dir() -> file:dirname_all().
-spec certs_dir() -> file:filename_all().
certs_dir() ->
MnesiaDir = mnesia:system_info(directory),
filename:join(MnesiaDir, "certs").
@ -116,24 +111,6 @@ ejabberd_started() ->
config_reloaded() ->
gen_server:call(?MODULE, config_reloaded, ?CALL_TIMEOUT).
opt_type(ca_path) ->
fun(_) ->
?WARNING_MSG("Option 'ca_path' has no effect anymore, "
"use 'ca_file' instead", []),
undefined
end;
opt_type(ca_file) ->
fun try_certfile/1;
opt_type(certfiles) ->
fun(Paths) -> [iolist_to_binary(Path) || Path <- Paths] end;
opt_type(O) when O == c2s_certfile; O == s2s_certfile; O == domain_certfile ->
fun(Path) ->
?WARNING_MSG("Option '~s' is deprecated, use 'certfiles' instead", [O]),
prep_path(Path)
end;
opt_type(_) ->
[ca_path, ca_file, certfiles, c2s_certfile, s2s_certfile, domain_certfile].
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
@ -177,7 +154,7 @@ handle_call(config_reloaded, _From, State) ->
Old = State#state.files,
New = get_certfiles_from_config_options(),
del_files(sets:subtract(Old, New)),
add_files(New),
_ = add_files(New),
case commit() of
{ok, _} ->
check_domain_certfiles(),
@ -258,10 +235,9 @@ del_files(Files) ->
-spec commit() -> {ok, [{filename(), pkix:error_reason()}]} | error.
commit() ->
Opts = case ca_file() of
undefined -> [];
CAFile -> [{cafile, CAFile}]
end,
CAFile = ejabberd_option:ca_file(),
?DEBUG("Using CA root certificates from: ~s", [CAFile]),
Opts = [{cafile, CAFile}],
case pkix:commit(certs_dir(), Opts) of
{ok, Errors, Warnings, CAError} ->
log_errors(Errors),
@ -277,35 +253,36 @@ commit() ->
-spec check_domain_certfiles() -> ok.
check_domain_certfiles() ->
Hosts = ejabberd_config:get_myhosts(),
Hosts = ejabberd_option:hosts(),
Routes = ejabberd_router:get_all_routes(),
check_domain_certfiles(Hosts ++ Routes).
-spec check_domain_certfiles([binary()]) -> ok.
check_domain_certfiles(Hosts) ->
lists:foreach(
fun(Host) ->
case get_certfile_no_default(Host) of
error ->
?WARNING_MSG("No certificate found matching '~s': strictly "
"configured clients or servers will reject "
"connections with this host; obtain "
"a certificate for this (sub)domain from any "
"trusted CA such as Let's Encrypt "
"(www.letsencrypt.org)",
[Host]);
_ ->
ok
end
end, Hosts).
case ejabberd_listener:tls_listeners() of
[] -> ok;
_ ->
lists:foreach(
fun(Host) ->
case get_certfile_no_default(Host) of
error ->
?WARNING_MSG(
"No certificate found matching '~s': strictly "
"configured clients or servers will reject "
"connections with this host; obtain "
"a certificate for this (sub)domain from any "
"trusted CA such as Let's Encrypt "
"(www.letsencrypt.org)",
[Host]);
_ ->
ok
end
end, Hosts)
end.
-spec deprecated_options() -> [atom()].
deprecated_options() ->
[c2s_certfile, s2s_certfile, domain_certfile].
-spec global_certfiles() -> sets:set(filename()).
global_certfiles() ->
case ejabberd_config:get_option(certfiles) of
-spec get_certfiles_from_config_options() -> sets:set(filename()).
get_certfiles_from_config_options() ->
case ejabberd_option:certfiles() of
undefined ->
sets:new();
Paths ->
@ -316,25 +293,6 @@ global_certfiles() ->
end, sets:new(), Paths)
end.
-spec local_certfiles() -> sets:set(filename()).
local_certfiles() ->
Opts = [{Opt, Host} || Opt <- deprecated_options(),
Host <- ejabberd_config:get_myhosts()],
lists:foldl(
fun(OptHost, Acc) ->
case ejabberd_config:get_option(OptHost) of
undefined -> Acc;
Path -> sets:add_element(Path, Acc)
end
end, sets:new(), Opts).
-spec get_certfiles_from_config_options() -> sets:set(filename()).
get_certfiles_from_config_options() ->
Global = global_certfiles(),
Local = local_certfiles(),
Listen = sets:from_list(ejabberd_listener:get_certfiles()),
sets:union([Global, Local, Listen]).
-spec prep_path(file:filename_all()) -> filename().
prep_path(Path0) ->
case filename:pathtype(Path0) of

View File

@ -26,11 +26,10 @@
-module(ejabberd_rdbms).
-behaviour(supervisor).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-export([start_link/0, init/1, opt_type/1,
-export([start_link/0, init/1,
config_reloaded/0, start_host/1, stop_host/1]).
-include("logger.hrl").
@ -55,7 +54,7 @@ get_specs() ->
{ok, Spec} -> [Spec];
undefined -> []
end
end, ejabberd_config:get_myhosts()).
end, ejabberd_option:hosts()).
-spec get_spec(binary()) -> {ok, supervisor:child_spec()} | undefined.
get_spec(Host) ->
@ -71,7 +70,7 @@ get_spec(Host) ->
-spec config_reloaded() -> ok.
config_reloaded() ->
lists:foreach(fun reload_host/1, ejabberd_config:get_myhosts()).
lists:foreach(fun reload_host/1, ejabberd_option:hosts()).
-spec start_host(binary()) -> ok.
start_host(Host) ->
@ -89,12 +88,13 @@ start_host(Host) ->
ok
end.
-spec stop_host(binary()) -> ok.
-spec stop_host(binary()) -> ok | {error, atom()}.
stop_host(Host) ->
SupName = gen_mod:get_module_proc(Host, ejabberd_sql_sup),
supervisor:terminate_child(?MODULE, SupName),
supervisor:delete_child(?MODULE, SupName),
ok.
case supervisor:terminate_child(?MODULE, SupName) of
ok -> supervisor:delete_child(?MODULE, SupName);
Err -> Err
end.
-spec reload_host(binary()) -> ok.
reload_host(Host) ->
@ -106,7 +106,7 @@ reload_host(Host) ->
%% Returns {true, App} if we have configured sql for the given host
needs_sql(Host) ->
LHost = jid:nameprep(Host),
case ejabberd_config:get_option({sql_type, LHost}, undefined) of
case ejabberd_option:sql_type(LHost) of
mysql -> {true, p1_mysql};
pgsql -> {true, p1_pgsql};
sqlite -> {true, sqlite3};
@ -114,13 +114,3 @@ needs_sql(Host) ->
odbc -> {true, odbc};
undefined -> false
end.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end;
opt_type(_) -> [sql_type].

View File

@ -59,8 +59,8 @@
-type error_reason() :: binary() | timeout | disconnected | overloaded.
-type redis_error() :: {error, error_reason()}.
-type redis_reply() :: binary() | [binary()].
-type redis_command() :: [binary()].
-type redis_reply() :: undefined | binary() | [binary()].
-type redis_command() :: [iodata() | integer()].
-type redis_pipeline() :: [redis_command()].
-type redis_info() :: server | clients | memory | persistence |
stats | replication | cpu | commandstats |
@ -89,19 +89,18 @@ get_connection(I) ->
q(Command) ->
call(get_rnd_id(), {q, Command}, ?MAX_RETRIES).
-spec qp(redis_pipeline()) -> {ok, [redis_reply()]} | redis_error().
-spec qp(redis_pipeline()) -> [{ok, redis_reply()} | redis_error()] | redis_error().
qp(Pipeline) ->
call(get_rnd_id(), {qp, Pipeline}, ?MAX_RETRIES).
-spec multi(fun(() -> any())) -> {ok, [redis_reply()]} | redis_error().
-spec multi(fun(() -> any())) -> {ok, redis_reply()} | redis_error().
multi(F) ->
case erlang:get(?TR_STACK) of
undefined ->
erlang:put(?TR_STACK, []),
try F() of
_ ->
Stack = erlang:get(?TR_STACK),
erlang:erase(?TR_STACK),
Stack = erlang:erase(?TR_STACK),
Command = [["MULTI"]|lists:reverse([["EXEC"]|Stack])],
case qp(Command) of
{error, _} = Err -> Err;
@ -298,7 +297,7 @@ hkeys(Key) ->
-spec subscribe([binary()]) -> ok | redis_error().
subscribe(Channels) ->
try ?GEN_SERVER:call(get_proc(1), {subscribe, self(), Channels}, ?CALL_TIMEOUT)
try gen_server_call(get_proc(1), {subscribe, self(), Channels})
catch exit:{Why, {?GEN_SERVER, call, _}} ->
Reason = case Why of
timeout -> timeout;
@ -329,7 +328,7 @@ script_load(Data) ->
erlang:error(transaction_unsupported)
end.
-spec evalsha(binary(), [iodata()], [iodata()]) -> {ok, binary()} | redis_error().
-spec evalsha(binary(), [iodata()], [iodata() | integer()]) -> {ok, binary()} | redis_error().
evalsha(SHA, Keys, Args) ->
case erlang:get(?TR_STACK) of
undefined ->
@ -458,13 +457,11 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
-spec connect(state()) -> {ok, pid()} | {error, any()}.
connect(#state{num = Num}) ->
Server = ejabberd_config:get_option(redis_server, "localhost"),
Port = ejabberd_config:get_option(redis_port, 6379),
DB = ejabberd_config:get_option(redis_db, 0),
Pass = ejabberd_config:get_option(redis_password, ""),
ConnTimeout = timer:seconds(
ejabberd_config:get_option(
redis_connect_timeout, 1)),
Server = ejabberd_option:redis_server(),
Port = ejabberd_option:redis_port(),
DB = ejabberd_option:redis_db(),
Pass = ejabberd_option:redis_password(),
ConnTimeout = ejabberd_option:redis_connect_timeout(),
try case do_connect(Num, Server, Port, Pass, DB, ConnTimeout) of
{ok, Client} ->
?DEBUG("Connection #~p established to Redis at ~s:~p",
@ -498,7 +495,7 @@ do_connect(_, Server, Port, Pass, DB, ConnTimeout) ->
-spec call(pos_integer(), {q, redis_command()}, integer()) ->
{ok, redis_reply()} | redis_error();
(pos_integer(), {qp, redis_pipeline()}, integer()) ->
{ok, [redis_reply()]} | redis_error().
[{ok, redis_reply()} | redis_error()] | redis_error().
call(I, {F, Cmd}, Retries) ->
?DEBUG("redis query: ~p", [Cmd]),
Conn = get_connection(I),
@ -513,7 +510,7 @@ call(I, {F, Cmd}, Retries) ->
end,
case Res of
{error, disconnected} when Retries > 0 ->
try ?GEN_SERVER:call(get_proc(I), connect, ?CALL_TIMEOUT) of
try gen_server_call(get_proc(I), connect) of
ok -> call(I, {F, Cmd}, Retries-1);
{error, _} = Err -> Err
catch exit:{Why, {?GEN_SERVER, call, _}} ->
@ -531,6 +528,14 @@ call(I, {F, Cmd}, Retries) ->
Res
end.
gen_server_call(Proc, Msg) ->
case ejabberd_redis_sup:start() of
ok ->
?GEN_SERVER:call(Proc, Msg, ?CALL_TIMEOUT);
{error, _} ->
{error, disconnected}
end.
-spec log_error(redis_command() | redis_pipeline(), atom() | binary()) -> ok.
log_error(Cmd, Reason) ->
?ERROR_MSG("Redis request has failed:~n"
@ -542,8 +547,8 @@ log_error(Cmd, Reason) ->
get_rnd_id() ->
p1_rand:round_robin(ejabberd_redis_sup:get_pool_size() - 1) + 2.
-spec get_result([{error, atom() | binary()} | {ok, iodata()}]) ->
{ok, [redis_reply()]} | {error, binary()}.
-spec get_result([{ok, redis_reply()} | redis_error()]) ->
{ok, redis_reply()} | redis_error().
get_result([{error, _} = Err|_]) ->
Err;
get_result([{ok, _} = OK]) ->
@ -584,9 +589,7 @@ fsm_limit_opts() ->
ejabberd_config:fsm_limit_opts([]).
get_queue_type() ->
ejabberd_config:get_option(
redis_queue_type,
ejabberd_config:default_queue_type(global)).
ejabberd_option:redis_queue_type().
-spec flush_queue(p1_queue:queue()) -> p1_queue:queue().
flush_queue(Q) ->

View File

@ -23,41 +23,41 @@
-module(ejabberd_redis_sup).
-behaviour(supervisor).
-behaviour(ejabberd_config).
%% API
-export([start_link/0, get_pool_size/0,
host_up/1, config_reloaded/0, opt_type/1]).
-export([start/0, start_link/0]).
-export([get_pool_size/0, config_reloaded/0]).
%% Supervisor callbacks
-export([init/1]).
-include("logger.hrl").
-define(DEFAULT_POOL_SIZE, 10).
%%%===================================================================
%%% API functions
%%%===================================================================
start() ->
case is_started() of
true -> ok;
false ->
ejabberd:start_app(eredis),
Spec = {?MODULE, {?MODULE, start_link, []},
permanent, infinity, supervisor, [?MODULE]},
case supervisor:start_child(ejabberd_db_sup, Spec) of
{ok, _} -> ok;
{error, {already_started, _}} -> ok;
{error, Why} = Err ->
?ERROR_MSG("Failed to start ~s: ~p", [?MODULE, Why]),
Err
end
end.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
host_up(Host) ->
case is_redis_configured(Host) of
true ->
ejabberd:start_app(eredis),
lists:foreach(
fun(Spec) ->
supervisor:start_child(?MODULE, Spec)
end, get_specs());
false ->
ok
end.
config_reloaded() ->
case is_redis_configured() of
case is_started() of
true ->
ejabberd:start_app(eredis),
lists:foreach(
fun(Spec) ->
supervisor:start_child(?MODULE, Spec)
@ -65,17 +65,15 @@ config_reloaded() ->
PoolSize = get_pool_size(),
lists:foreach(
fun({Id, _, _, _}) when Id > PoolSize ->
supervisor:terminate_child(?MODULE, Id),
supervisor:delete_child(?MODULE, Id);
case supervisor:terminate_child(?MODULE, Id) of
ok -> supervisor:delete_child(?MODULE, Id);
_ -> ok
end;
(_) ->
ok
end, supervisor:which_children(?MODULE));
false ->
lists:foreach(
fun({Id, _, _, _}) ->
supervisor:terminate_child(?MODULE, Id),
supervisor:delete_child(?MODULE, Id)
end, supervisor:which_children(?MODULE))
ok
end.
%%%===================================================================
@ -83,36 +81,11 @@ config_reloaded() ->
%%%===================================================================
init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 20),
ejabberd_hooks:add(host_up, ?MODULE, host_up, 20),
Specs = case is_redis_configured() of
true ->
ejabberd:start_app(eredis),
get_specs();
false ->
[]
end,
{ok, {{one_for_one, 500, 1}, Specs}}.
{ok, {{one_for_one, 500, 1}, get_specs()}}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
is_redis_configured() ->
lists:any(fun is_redis_configured/1, ejabberd_config:get_myhosts()).
is_redis_configured(Host) ->
ServerConfigured = ejabberd_config:has_option({redis_server, Host}),
PortConfigured = ejabberd_config:has_option({redis_port, Host}),
DBConfigured = ejabberd_config:has_option({redis_db, Host}),
PassConfigured = ejabberd_config:has_option({redis_password, Host}),
PoolSize = ejabberd_config:has_option({redis_pool_size, Host}),
ConnTimeoutConfigured = ejabberd_config:has_option(
{redis_connect_timeout, Host}),
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == redis,
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == redis,
ServerConfigured or PortConfigured or DBConfigured or PassConfigured or
PoolSize or ConnTimeoutConfigured or
SMConfigured or RouterConfigured.
get_specs() ->
lists:map(
fun(I) ->
@ -121,24 +94,7 @@ get_specs() ->
end, lists:seq(1, get_pool_size())).
get_pool_size() ->
ejabberd_config:get_option(redis_pool_size, ?DEFAULT_POOL_SIZE) + 1.
ejabberd_option:redis_pool_size() + 1.
iolist_to_list(IOList) ->
binary_to_list(iolist_to_binary(IOList)).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(redis_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_db) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(redis_password) -> fun iolist_to_list/1;
opt_type(redis_port) ->
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
opt_type(redis_server) -> fun iolist_to_list/1;
opt_type(redis_pool_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(_) ->
[redis_connect_timeout, redis_db, redis_password,
redis_port, redis_pool_size, redis_server, redis_queue_type].
is_started() ->
whereis(?MODULE) /= undefined.

View File

@ -125,13 +125,12 @@ sh_to_awk_3(<<"]", Sh/binary>>, false) ->
sh_to_awk_3(<<C:8, Sh/binary>>, UpArrow) ->
[C|sh_to_awk_3(Sh, UpArrow)];
sh_to_awk_3(<<>>, true) ->
[$^|sh_to_awk_1([])];
[$^|sh_to_awk_1(<<>>)];
sh_to_awk_3(<<>>, false) ->
sh_to_awk_1([]).
sh_to_awk_1(<<>>).
%% -type sh_special_char(char()) -> bool().
%% Test if a character is a special character.
-spec sh_special_char(char()) -> boolean().
sh_special_char($|) -> true;
sh_special_char($*) -> true;
sh_special_char($+) -> true;
@ -146,4 +145,3 @@ sh_special_char($[) -> true;
sh_special_char($]) -> true;
sh_special_char($") -> true;
sh_special_char(_C) -> false.

View File

@ -181,7 +181,7 @@ get(Table, RecSchema, Key) ->
-spec get_by_index(atom(), record_schema(), binary(), any()) ->
{ok, [any()]} | {error, any()}.
%% @doc Reads records by `Index' and value `Key' from `Table'
%% @doc Reads records by `Index' and value `Key' from `Table'
get_by_index(Table, RecSchema, Index, Key) ->
{NewIndex, NewKey} = encode_index_key(Index, Key),
case get_by_index_raw(Table, NewIndex, NewKey) of
@ -520,8 +520,13 @@ make_invalid_object(Val) ->
(str:format("Invalid object: ~p", [Val])).
get_random_pid() ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
get_riak_pid(PoolPid).
case ejabberd_riak_sup:start() of
ok ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
get_riak_pid(PoolPid);
{error, _} = Err ->
Err
end.
get_riak_pid(PoolPid) ->
case catch gen_server:call(PoolPid, get_pid) of

View File

@ -26,88 +26,64 @@
-module(ejabberd_riak_sup).
-behaviour(supervisor).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-export([start_link/0, init/1, get_pids/0,
transform_options/1, get_random_pid/0,
host_up/1, config_reloaded/0, opt_type/1]).
-export([start/0, start_link/0, init/1, get_pids/0,
get_random_pid/0, config_reloaded/0]).
-include("logger.hrl").
-define(DEFAULT_POOL_SIZE, 10).
-define(DEFAULT_RIAK_START_INTERVAL, 30). % 30 seconds
-define(DEFAULT_RIAK_HOST, "127.0.0.1").
-define(DEFAULT_RIAK_PORT, 8087).
% time to wait for the supervisor to start its child before returning
% a timeout error to the request
-define(CONNECT_TIMEOUT, 500). % milliseconds
host_up(Host) ->
case is_riak_configured(Host) of
true ->
ejabberd:start_app(riakc),
lists:foreach(
fun(Spec) ->
supervisor:start_child(?MODULE, Spec)
end, get_specs());
start() ->
case is_started() of
true -> ok;
false ->
ok
ejabberd:start_app(riakc),
Spec = {?MODULE, {?MODULE, start_link, []},
permanent, infinity, supervisor, [?MODULE]},
case supervisor:start_child(ejabberd_db_sup, Spec) of
{ok, _} -> ok;
{error, {already_started, _}} -> ok;
{error, Why} = Err ->
?ERROR_MSG("Failed to start ~s: ~p",
[?MODULE, Why]),
Err
end
end.
config_reloaded() ->
case is_riak_configured() of
case is_started() of
true ->
ejabberd:start_app(riakc),
lists:foreach(
fun(Spec) ->
supervisor:start_child(?MODULE, Spec)
end, get_specs());
false ->
end, get_specs()),
PoolSize = get_pool_size(),
lists:foreach(
fun({Id, _, _, _}) ->
supervisor:terminate_child(?MODULE, Id),
supervisor:delete_child(?MODULE, Id)
end, supervisor:which_children(?MODULE))
fun({Id, _, _, _}) when Id > PoolSize ->
case supervisor:terminate_child(?MODULE, Id) of
ok -> supervisor:delete_child(?MODULE, Id);
_ -> ok
end;
(_) ->
ok
end, supervisor:which_children(?MODULE));
false ->
ok
end.
is_riak_configured() ->
lists:any(fun is_riak_configured/1, ejabberd_config:get_myhosts()).
is_riak_configured(Host) ->
ServerConfigured = ejabberd_config:has_option({riak_server, Host}),
PortConfigured = ejabberd_config:has_option({riak_port, Host}),
StartIntervalConfigured = ejabberd_config:has_option({riak_start_interval, Host}),
PoolConfigured = ejabberd_config:has_option({riak_pool_size, Host}),
CacertConfigured = ejabberd_config:has_option({riak_cacertfile, Host}),
UserConfigured = ejabberd_config:has_option({riak_username, Host}),
PassConfigured = ejabberd_config:has_option({riak_password, Host}),
AuthConfigured = lists:member(
ejabberd_auth_riak,
ejabberd_auth:auth_modules(Host)),
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == riak,
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == riak,
ServerConfigured or PortConfigured or StartIntervalConfigured
or PoolConfigured or CacertConfigured
or UserConfigured or PassConfigured
or SMConfigured or RouterConfigured
or AuthConfigured.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 20),
ejabberd_hooks:add(host_up, ?MODULE, host_up, 20),
Specs = case is_riak_configured() of
true ->
ejabberd:start_app(riakc),
get_specs();
false ->
[]
end,
{ok, {{one_for_one, 500, 1}, Specs}}.
{ok, {{one_for_one, 500, 1}, get_specs()}}.
is_started() ->
whereis(?MODULE) /= undefined.
-spec get_specs() -> [supervisor:child_spec()].
get_specs() ->
@ -133,30 +109,30 @@ get_specs() ->
fun(I) ->
{ejabberd_riak:get_proc(I),
{ejabberd_riak, start_link,
[I, Server, Port, StartInterval*1000, Options]},
[I, Server, Port, StartInterval, Options]},
transient, 2000, worker, [?MODULE]}
end, lists:seq(1, PoolSize)).
get_start_interval() ->
ejabberd_config:get_option(riak_start_interval, ?DEFAULT_RIAK_START_INTERVAL).
ejabberd_option:riak_start_interval().
get_pool_size() ->
ejabberd_config:get_option(riak_pool_size, ?DEFAULT_POOL_SIZE).
ejabberd_option:riak_pool_size().
get_riak_server() ->
ejabberd_config:get_option(riak_server, ?DEFAULT_RIAK_HOST).
ejabberd_option:riak_server().
get_riak_cacertfile() ->
ejabberd_config:get_option(riak_cacertfile, nil).
ejabberd_option:riak_cacertfile().
get_riak_username() ->
ejabberd_config:get_option(riak_username, nil).
ejabberd_option:riak_username().
get_riak_password() ->
ejabberd_config:get_option(riak_password, nil).
ejabberd_option:riak_password().
get_riak_port() ->
ejabberd_config:get_option(riak_port, ?DEFAULT_RIAK_PORT).
ejabberd_option:riak_port().
get_pids() ->
[ejabberd_riak:get_proc(I) || I <- lists:seq(1, get_pool_size())].
@ -164,30 +140,3 @@ get_pids() ->
get_random_pid() ->
I = p1_rand:round_robin(get_pool_size()) + 1,
ejabberd_riak:get_proc(I).
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({riak_server, {S, P}}, Opts) ->
[{riak_server, S}, {riak_port, P}|Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(riak_pool_size) ->
fun (N) when is_integer(N), N >= 1 -> N end;
opt_type(riak_port) ->
fun(P) when is_integer(P), P > 0, P < 65536 -> P end;
opt_type(riak_server) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(riak_start_interval) ->
fun (N) when is_integer(N), N >= 1 -> N end;
opt_type(riak_cacertfile) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(riak_username) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(riak_password) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(_) ->
[riak_pool_size, riak_port, riak_server,
riak_start_interval, riak_cacertfile, riak_username, riak_password].

View File

@ -25,8 +25,6 @@
-module(ejabberd_router).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-ifndef(GEN_SERVER).
@ -59,7 +57,7 @@
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3, opt_type/1]).
handle_info/2, terminate/2, code_change/3]).
%% Deprecated functions
-export([route/3, route_error/4]).
@ -388,10 +386,9 @@ do_route(_Pkt, _Route) ->
-spec balancing_route(jid(), jid(), stanza(), [#route{}]) -> any().
balancing_route(From, To, Packet, Rs) ->
LDstDomain = To#jid.lserver,
Value = get_domain_balancing(From, To, LDstDomain),
case get_component_number(LDstDomain) of
case get_domain_balancing(From, To, To#jid.lserver) of
undefined ->
Value = erlang:system_time(),
case [R || R <- Rs, node(R#route.pid) == node()] of
[] ->
R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
@ -400,7 +397,7 @@ balancing_route(From, To, Packet, Rs) ->
R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
do_route(Packet, R)
end;
_ ->
Value ->
SRs = lists:ukeysort(#route.local_hint, Rs),
R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
do_route(Packet, R)
@ -408,24 +405,30 @@ balancing_route(From, To, Packet, Rs) ->
-spec get_component_number(binary()) -> pos_integer() | undefined.
get_component_number(LDomain) ->
ejabberd_config:get_option({domain_balancing_component_number, LDomain}).
M = ejabberd_option:domain_balancing(),
case maps:get(LDomain, M, undefined) of
undefined -> undefined;
Opts -> maps:get(component_number, Opts)
end.
-spec get_domain_balancing(jid(), jid(), binary()) -> any().
-spec get_domain_balancing(jid(), jid(), binary()) -> integer() | ljid() | undefined.
get_domain_balancing(From, To, LDomain) ->
case ejabberd_config:get_option({domain_balancing, LDomain}) of
undefined -> erlang:system_time();
random -> erlang:system_time();
source -> jid:tolower(From);
destination -> jid:tolower(To);
bare_source -> jid:remove_resource(jid:tolower(From));
bare_destination -> jid:remove_resource(jid:tolower(To))
M = ejabberd_option:domain_balancing(),
case maps:get(LDomain, M, undefined) of
undefined -> undefined;
Opts ->
case maps:get(type, Opts, random) of
random -> erlang:system_time();
source -> jid:tolower(From);
destination -> jid:tolower(To);
bare_source -> jid:remove_resource(jid:tolower(From));
bare_destination -> jid:remove_resource(jid:tolower(To))
end
end.
-spec get_backend() -> module().
get_backend() ->
DBType = ejabberd_config:get_option(
router_db_type,
ejabberd_config:default_ram_db(?MODULE)),
DBType = ejabberd_option:router_db_type(),
list_to_atom("ejabberd_router_" ++ atom_to_list(DBType)).
-spec cache_nodes(module()) -> [node()].
@ -439,10 +442,7 @@ cache_nodes(Mod) ->
use_cache(Mod) ->
case erlang:function_exported(Mod, use_cache, 0) of
true -> Mod:use_cache();
false ->
ejabberd_config:get_option(
router_use_cache,
ejabberd_config:use_cache(global))
false -> ejabberd_option:router_use_cache()
end.
-spec delete_cache(module(), binary()) -> ok.
@ -466,15 +466,9 @@ init_cache(Mod) ->
-spec cache_opts() -> [proplists:property()].
cache_opts() ->
MaxSize = ejabberd_config:get_option(
router_cache_size,
ejabberd_config:cache_size(global)),
CacheMissed = ejabberd_config:get_option(
router_cache_missed,
ejabberd_config:cache_missed(global)),
LifeTime = case ejabberd_config:get_option(
router_cache_life_time,
ejabberd_config:cache_life_time(global)) of
MaxSize = ejabberd_option:router_cache_size(),
CacheMissed = ejabberd_option:router_cache_missed(),
LifeTime = case ejabberd_option:router_cache_life_time() of
infinity -> infinity;
I -> timer:seconds(I)
end,
@ -498,26 +492,3 @@ clean_cache(Node) ->
-spec clean_cache() -> ok.
clean_cache() ->
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(domain_balancing) ->
fun (random) -> random;
(source) -> source;
(destination) -> destination;
(bare_source) -> bare_source;
(bare_destination) -> bare_destination
end;
opt_type(domain_balancing_component_number) ->
fun (N) when is_integer(N), N > 1 -> N end;
opt_type(router_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(O) when O == router_use_cache; O == router_cache_missed ->
fun(B) when is_boolean(B) -> B end;
opt_type(O) when O == router_cache_size; O == router_cache_life_time ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
opt_type(_) ->
[domain_balancing, domain_balancing_component_number,
router_db_type, router_use_cache, router_cache_size,
router_cache_missed, router_cache_life_time].

View File

@ -100,8 +100,12 @@ register_route(Domain, ServerHost, _LocalHint, N, Pid) ->
unregister_route(Domain, undefined, Pid) ->
F = fun () ->
case mnesia:match_object(
#route{domain = Domain, pid = Pid, _ = '_'}) of
case mnesia:select(
route,
ets:fun2ms(
fun(#route{domain = D, pid = P} = R)
when D == Domain, P == Pid -> R
end)) of
[R] -> mnesia:delete_object(R);
_ -> ok
end
@ -109,8 +113,12 @@ unregister_route(Domain, undefined, Pid) ->
transaction(F);
unregister_route(Domain, _, Pid) ->
F = fun () ->
case mnesia:match_object(
#route{domain = Domain, pid = Pid, _ = '_'}) of
case mnesia:select(
route,
ets:fun2ms(
fun(#route{domain = D, pid = P} = R)
when D == Domain, P == Pid -> R
end)) of
[R] ->
I = R#route.local_hint,
ServerHost = R#route.server_host,
@ -147,8 +155,10 @@ init([]) ->
mnesia:subscribe({table, route, simple}),
lists:foreach(
fun (Pid) -> erlang:monitor(process, Pid) end,
mnesia:dirty_select(route,
[{#route{pid = '$1', _ = '_'}, [], ['$1']}])),
mnesia:dirty_select(
route,
ets:fun2ms(
fun(#route{pid = Pid}) -> Pid end))),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
@ -166,8 +176,12 @@ handle_info({mnesia_table_event, _}, State) ->
{noreply, State};
handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
F = fun () ->
Es = mnesia:select(route,
[{#route{pid = Pid, _ = '_'}, [], ['$_']}]),
Es = mnesia:select(
route,
ets:fun2ms(
fun(#route{pid = P} = E)
when P == Pid -> E
end)),
lists:foreach(
fun(E) ->
if is_integer(E#route.local_hint) ->

View File

@ -67,7 +67,7 @@ get_all_routes() ->
%%% Internal functions
%%%===================================================================
route_schema() ->
{record_info(fields, route), #route{}}.
{record_info(fields, route), #route{domain = <<>>, server_host = <<>>}}.
clean_table() ->
?DEBUG("Cleaning Riak 'route' table...", []),

View File

@ -23,7 +23,6 @@
-module(ejabberd_router_sql).
-behaviour(ejabberd_router).
-compile([{parse_transform, ejabberd_sql_pt}]).
%% API
-export([init/0, register_route/5, unregister_route/3, find_routes/1,

View File

@ -27,8 +27,6 @@
-protocol({xep, 220, '1.1'}).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(gen_server).
@ -44,15 +42,14 @@
list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1,
get_commands_spec/0, zlib_enabled/1, get_idle_timeout/1,
tls_required/1, tls_verify/1, tls_enabled/1, tls_options/2,
tls_required/1, tls_enabled/1, tls_options/2,
host_up/1, host_down/1, queue_type/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-export([get_info_s2s_connections/1,
transform_options/1, opt_type/1]).
-export([get_info_s2s_connections/1]).
-include("logger.hrl").
-include("xmpp.hrl").
@ -131,19 +128,21 @@ is_temporarly_blocked(Host) ->
end
end.
-spec remove_connection({binary(), binary()},
pid()) -> {atomic, ok} | ok | {aborted, any()}.
-spec remove_connection({binary(), binary()}, pid()) -> ok.
remove_connection(FromTo, Pid) ->
case catch mnesia:dirty_match_object(s2s,
#s2s{fromto = FromTo, pid = Pid})
of
[#s2s{pid = Pid}] ->
F = fun () ->
mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid})
end,
mnesia:transaction(F);
_ -> ok
case mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo, pid = Pid}) of
[#s2s{pid = Pid}] ->
F = fun() ->
mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid})
end,
case mnesia:transaction(F) of
{atomic, _} -> ok;
{aborted, Reason} ->
?ERROR_MSG("Failed to unregister s2s connection: "
"Mnesia failure: ~p", [Reason])
end;
_ ->
ok
end.
-spec have_connection({binary(), binary()}) -> boolean().
@ -195,36 +194,32 @@ dirty_get_connections() ->
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
tls_options(LServer, DefaultOpts) ->
TLSOpts1 = case get_certfile(LServer) of
undefined -> DefaultOpts;
CertFile ->
TLSOpts1 = case ejabberd_pkix:get_certfile(LServer) of
error -> DefaultOpts;
{ok, CertFile} ->
lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
end,
TLSOpts2 = case ejabberd_config:get_option(
{s2s_ciphers, LServer}) of
TLSOpts2 = case ejabberd_option:s2s_ciphers(LServer) of
undefined -> TLSOpts1;
Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
{ciphers, Ciphers})
end,
TLSOpts3 = case ejabberd_config:get_option(
{s2s_protocol_options, LServer}) of
TLSOpts3 = case ejabberd_option:s2s_protocol_options(LServer) of
undefined -> TLSOpts2;
ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
{protocol_options, ProtoOpts})
end,
TLSOpts4 = case ejabberd_config:get_option(
{s2s_dhfile, LServer}) of
TLSOpts4 = case ejabberd_option:s2s_dhfile(LServer) of
undefined -> TLSOpts3;
DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
{dhfile, DHFile})
end,
TLSOpts5 = case get_cafile(LServer) of
undefined -> TLSOpts4;
CAFile -> lists:keystore(cafile, 1, TLSOpts4,
{cafile, CAFile})
TLSOpts5 = case lists:keymember(cafile, 1, TLSOpts4) of
true -> TLSOpts4;
false -> [{cafile, get_cafile(LServer)}|TLSOpts4]
end,
case ejabberd_config:get_option({s2s_tls_compression, LServer}) of
case ejabberd_option:s2s_tls_compression(LServer) of
undefined -> TLSOpts5;
false -> [compression_none | TLSOpts5];
true -> lists:delete(compression_none, TLSOpts5)
@ -233,12 +228,7 @@ tls_options(LServer, DefaultOpts) ->
-spec tls_required(binary()) -> boolean().
tls_required(LServer) ->
TLS = use_starttls(LServer),
TLS == required orelse TLS == required_trusted.
-spec tls_verify(binary()) -> boolean().
tls_verify(LServer) ->
TLS = use_starttls(LServer),
TLS == required_trusted.
TLS == required.
-spec tls_enabled(binary()) -> boolean().
tls_enabled(LServer) ->
@ -247,38 +237,25 @@ tls_enabled(LServer) ->
-spec zlib_enabled(binary()) -> boolean().
zlib_enabled(LServer) ->
ejabberd_config:get_option({s2s_zlib, LServer}, false).
ejabberd_option:s2s_zlib(LServer).
-spec use_starttls(binary()) -> boolean() | optional | required | required_trusted.
-spec use_starttls(binary()) -> boolean() | optional | required.
use_starttls(LServer) ->
ejabberd_config:get_option({s2s_use_starttls, LServer}, false).
ejabberd_option:s2s_use_starttls(LServer).
-spec get_idle_timeout(binary()) -> non_neg_integer() | infinity.
get_idle_timeout(LServer) ->
ejabberd_config:get_option({s2s_timeout, LServer}, timer:minutes(10)).
ejabberd_option:s2s_timeout(LServer).
-spec queue_type(binary()) -> ram | file.
queue_type(LServer) ->
ejabberd_config:get_option(
{s2s_queue_type, LServer},
ejabberd_config:default_queue_type(LServer)).
-spec get_certfile(binary()) -> file:filename_all() | undefined.
get_certfile(LServer) ->
case ejabberd_pkix:get_certfile(LServer) of
{ok, CertFile} ->
CertFile;
error ->
ejabberd_config:get_option(
{domain_certfile, LServer},
ejabberd_config:get_option({s2s_certfile, LServer}))
end.
ejabberd_option:s2s_queue_type(LServer).
-spec get_cafile(binary()) -> file:filename_all() | undefined.
get_cafile(LServer) ->
case ejabberd_config:get_option({s2s_cafile, LServer}) of
case ejabberd_option:s2s_cafile(LServer) of
undefined ->
ejabberd_pkix:ca_file();
ejabberd_option:ca_file();
File ->
File
end.
@ -286,22 +263,26 @@ get_cafile(LServer) ->
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
update_tables(),
ejabberd_mnesia:create(?MODULE, s2s,
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, s2s)}]),
mnesia:subscribe(system),
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_mnesia:create(?MODULE, temporarily_blocked,
[{ram_copies, [node()]},
{attributes, record_info(fields, temporarily_blocked)}]),
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:add(host_down, ?MODULE, host_down, 60),
lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()),
{ok, #state{}}.
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, s2s)}]),
case mnesia:subscribe(system) of
{ok, _} ->
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_mnesia:create(
?MODULE, temporarily_blocked,
[{ram_copies, [node()]},
{attributes, record_info(fields, temporarily_blocked)}]),
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:add(host_down, ?MODULE, host_down, 60),
lists:foreach(fun host_up/1, ejabberd_option:hosts()),
{ok, #state{}};
{error, Reason} ->
{stop, Reason}
end.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@ -319,7 +300,7 @@ handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) ->
ejabberd_commands:unregister_commands(get_commands_spec()),
lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()),
lists:foreach(fun host_down/1, ejabberd_option:hosts()),
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 60),
ok.
@ -508,18 +489,18 @@ new_connection(MyServer, Server, From, FromTo,
[]
end.
-spec max_s2s_connections_number({binary(), binary()}) -> integer().
-spec max_s2s_connections_number({binary(), binary()}) -> pos_integer().
max_s2s_connections_number({From, To}) ->
case acl:match_rule(From, max_s2s_connections, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
case ejabberd_shaper:match(From, max_s2s_connections, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
end.
-spec max_s2s_connections_number_per_node({binary(), binary()}) -> integer().
-spec max_s2s_connections_number_per_node({binary(), binary()}) -> pos_integer().
max_s2s_connections_number_per_node({From, To}) ->
case acl:match_rule(From, max_s2s_connections_per_node, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
case ejabberd_shaper:match(From, max_s2s_connections_per_node, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
end.
-spec needed_connections_number([#s2s{}], integer(), integer()) -> integer().
@ -537,11 +518,11 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
-spec is_service(jid(), jid()) -> boolean().
is_service(From, To) ->
LFromDomain = From#jid.lserver,
case ejabberd_config:get_option({route_subdomains, LFromDomain}, local) of
case ejabberd_option:route_subdomains(LFromDomain) of
s2s -> % bypass RFC 3920 10.3
false;
local ->
Hosts = ejabberd_config:get_myhosts(),
Hosts = ejabberd_option:hosts(),
P = fun (ParentDomain) ->
lists:member(ParentDomain, Hosts)
end,
@ -602,32 +583,15 @@ stop_s2s_connections() ->
fun({_Id, Pid, _Type, _Module}) ->
supervisor:terminate_child(ejabberd_s2s_out_sup, Pid)
end, supervisor:which_children(ejabberd_s2s_out_sup)),
mnesia:clear_table(s2s),
_ = mnesia:clear_table(s2s),
ok.
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
update_tables() ->
case catch mnesia:table_info(s2s, type) of
bag -> ok;
{'EXIT', _} -> ok;
_ -> mnesia:delete_table(s2s)
end,
case catch mnesia:table_info(s2s, attributes) of
[fromto, node, key] ->
mnesia:transform_table(s2s, ignore, [fromto, pid]),
mnesia:clear_table(s2s);
[fromto, pid, key] ->
mnesia:transform_table(s2s, ignore, [fromto, pid]),
mnesia:clear_table(s2s);
[fromto, pid] -> ok;
{'EXIT', _} -> ok
end,
case lists:member(local_s2s, mnesia:system_info(tables)) of
true -> mnesia:delete_table(local_s2s);
false -> ok
end.
_ = mnesia:delete_table(local_s2s),
ok.
%% Check if host is in blacklist or white list
allow_host(MyServer, S2SHost) ->
@ -635,7 +599,7 @@ allow_host(MyServer, S2SHost) ->
not is_temporarly_blocked(S2SHost).
allow_host1(MyHost, S2SHost) ->
Rule = ejabberd_config:get_option({s2s_access, MyHost}, all),
Rule = ejabberd_option:s2s_access(MyHost),
JID = jid:make(S2SHost),
case acl:match_rule(MyHost, Rule, JID) of
deny -> false;
@ -648,30 +612,6 @@ allow_host1(MyHost, S2SHost) ->
end
end.
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({{s2s_host, Host}, Action}, Opts) ->
?WARNING_MSG("Option 's2s_host' is deprecated. "
"The option is still supported but it is better to "
"fix your config: use access rules instead.", []),
ACLName = misc:binary_to_atom(
iolist_to_binary(["s2s_access_", Host])),
[{acl, ACLName, {server, Host}},
{access, s2s, [{Action, ACLName}]},
{s2s_access, s2s} |
Opts];
transform_options({s2s_default_policy, Action}, Opts) ->
?WARNING_MSG("Option 's2s_default_policy' is deprecated. "
"The option is still supported but it is better to "
"fix your config: "
"use 's2s_access' with an access rule.", []),
[{access, s2s, [{Action, all}]},
{s2s_access, s2s} |
Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
%% Get information about S2S connections of the specified type.
%% @spec (Type) -> [Info]
%% where Type = in | out
@ -704,51 +644,3 @@ get_s2s_state(S2sPid) ->
{badrpc, _} -> [{status, error}]
end,
[{s2s_pid, S2sPid} | Infos].
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(route_subdomains) ->
fun (s2s) -> s2s;
(local) -> local
end;
opt_type(s2s_access) ->
fun acl:access_rules_validator/1;
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun misc:try_read_file/1;
opt_type(s2s_cafile) -> fun misc:try_read_file/1;
opt_type(s2s_protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(s2s_tls_compression) ->
fun (true) -> true;
(false) -> false
end;
opt_type(s2s_use_starttls) ->
fun (true) -> true;
(false) -> false;
(optional) -> optional;
(required) -> required;
(required_trusted) ->
?WARNING_MSG("The value 'required_trusted' of option "
"'s2s_use_starttls' is deprected and will be "
"unsupported in future releases. Instead, "
"set it to 'required' and make sure "
"mod_s2s_dialback is *NOT* loaded", []),
required_trusted
end;
opt_type(s2s_zlib) ->
fun(true) ->
ejabberd:start_app(ezlib),
true;
(false) ->
false
end;
opt_type(s2s_timeout) ->
fun(I) when is_integer(I), I >= 0 -> timer:seconds(I);
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(s2s_queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(_) ->
[route_subdomains, s2s_access, s2s_zlib,
s2s_ciphers, s2s_dhfile, s2s_cafile, s2s_protocol_options,
s2s_tls_compression, s2s_use_starttls, s2s_timeout, s2s_queue_type].

View File

@ -22,9 +22,11 @@
-module(ejabberd_s2s_in).
-behaviour(xmpp_stream_in).
-behaviour(ejabberd_listener).
-dialyzer([{no_fail_call, [stop/1, process_closed/2]},
{no_return, process_closed/2}]).
%% ejabberd_listener callbacks
-export([start/3, start_link/3, accept/1, listen_opt_type/1, listen_options/0]).
-export([start/3, start_link/3, accept/1, listen_options/0]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@ -252,11 +254,11 @@ init([State, Opts]) ->
false -> [compression_none | TLSOpts1];
true -> TLSOpts1
end,
Timeout = ejabberd_config:negotiation_timeout(),
Timeout = ejabberd_option:negotiation_timeout(),
State1 = State#{tls_options => TLSOpts2,
auth_domains => sets:new(),
xmlns => ?NS_SERVER,
lang => ejabberd_config:get_mylang(),
lang => ejabberd_option:language(),
server => ejabberd_config:get_myname(),
lserver => ejabberd_config:get_myname(),
server_host => ejabberd_config:get_myname(),
@ -337,20 +339,11 @@ set_idle_timeout(State) ->
-spec change_shaper(state(), binary()) -> state().
change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
RServer) ->
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
Shaper = ejabberd_shaper:match(ServerHost, ShaperName, jid:make(RServer)),
xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)).
listen_opt_type(certfile = Opt) ->
fun(S) ->
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
"'certfiles' global option instead", [Opt, ?MODULE]),
{ok, File} = ejabberd_pkix:add_certfile(S),
File
end.
listen_options() ->
[{shaper, none},
{certfile, undefined},
{ciphers, undefined},
{dhfile, undefined},
{cafile, undefined},

View File

@ -21,10 +21,9 @@
%%%-------------------------------------------------------------------
-module(ejabberd_s2s_out).
-behaviour(xmpp_stream_out).
-behaviour(ejabberd_config).
-dialyzer([{no_fail_call, [stop/1, process_closed/2, handle_timeout/1]},
{no_return, [process_closed/2, handle_timeout/1]}]).
%% ejabberd_config callbacks
-export([opt_type/1, transform_options/1]).
%% xmpp_stream_out callbacks
-export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
connect_timeout/1, address_families/1, default_port/1,
@ -77,8 +76,7 @@ connect(Ref) ->
close(Ref) ->
xmpp_stream_out:close(Ref).
-spec close(pid(), atom()) -> ok;
(state(), atom()) -> state().
-spec close(pid(), atom()) -> ok.
close(Ref, Reason) ->
xmpp_stream_out:close(Ref, Reason).
@ -184,30 +182,26 @@ tls_options(#{server := LServer}) ->
tls_required(#{server := LServer}) ->
ejabberd_s2s:tls_required(LServer).
tls_verify(#{server := LServer}) ->
ejabberd_s2s:tls_verify(LServer).
tls_verify(#{server := LServer} = State) ->
ejabberd_hooks:run_fold(s2s_out_tls_verify, LServer, true, [State]).
tls_enabled(#{server := LServer}) ->
ejabberd_s2s:tls_enabled(LServer).
connect_timeout(#{server := LServer}) ->
ejabberd_config:get_option(
{outgoing_s2s_timeout, LServer},
timer:seconds(10)).
ejabberd_option:outgoing_s2s_timeout(LServer).
default_port(#{server := LServer}) ->
ejabberd_config:get_option({outgoing_s2s_port, LServer}, 5269).
ejabberd_option:outgoing_s2s_port(LServer).
address_families(#{server := LServer}) ->
ejabberd_config:get_option(
{outgoing_s2s_families, LServer},
[inet, inet6]).
ejabberd_option:outgoing_s2s_families(LServer).
dns_retries(#{server := LServer}) ->
ejabberd_config:get_option({s2s_dns_retries, LServer}, 2).
ejabberd_option:s2s_dns_retries(LServer).
dns_timeout(#{server := LServer}) ->
ejabberd_config:get_option({s2s_dns_timeout, LServer}, timer:seconds(10)).
ejabberd_option:s2s_dns_timeout(LServer).
handle_auth_success(Mech, #{socket := Socket, ip := IP,
remote_server := RServer,
@ -269,11 +263,11 @@ init([#{server := LServer, remote_server := RServer} = State, Opts]) ->
{_, N} -> N;
false -> unlimited
end,
Timeout = ejabberd_config:negotiation_timeout(),
Timeout = ejabberd_option:negotiation_timeout(),
State1 = State#{on_route => queue,
queue => p1_queue:new(QueueType, QueueLimit),
xmlns => ?NS_SERVER,
lang => ejabberd_config:get_mylang(),
lang => ejabberd_option:language(),
server_host => ServerHost,
shaper => none},
State2 = xmpp_stream_out:set_timeout(State1, Timeout),
@ -314,8 +308,8 @@ terminate(Reason, #{server := LServer,
normal -> State;
_ -> State#{stop_reason => internal_failure}
end,
bounce_queue(State1),
bounce_message_queue(State1).
State2 = bounce_queue(State1),
bounce_message_queue(State2).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@ -374,7 +368,7 @@ mk_bounce_error(_Lang, _State) ->
-spec get_delay() -> non_neg_integer().
get_delay() ->
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
MaxDelay = ejabberd_option:s2s_max_retry_delay(),
p1_rand:uniform(MaxDelay).
-spec set_idle_timeout(state()) -> state().
@ -400,76 +394,3 @@ format_error(queue_full) ->
<<"Stream queue is overloaded">>;
format_error(Reason) ->
xmpp_stream_out:format_error(Reason).
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({outgoing_s2s_options, Families, Timeout}, Opts) ->
?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 'outgoing_s2s_timeout' and "
"'outgoing_s2s_families' instead.", []),
maybe_report_huge_timeout(outgoing_s2s_timeout, Timeout),
[{outgoing_s2s_families, Families},
{outgoing_s2s_timeout, Timeout}
| Opts];
transform_options({s2s_dns_options, S2SDNSOpts}, AllOpts) ->
?WARNING_MSG("Option 's2s_dns_options' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 's2s_dns_timeout' and "
"'s2s_dns_retries' instead", []),
lists:foldr(
fun({timeout, T}, AccOpts) ->
maybe_report_huge_timeout(s2s_dns_timeout, T),
[{s2s_dns_timeout, T}|AccOpts];
({retries, R}, AccOpts) ->
[{s2s_dns_retries, R}|AccOpts];
(_, AccOpts) ->
AccOpts
end, AllOpts, S2SDNSOpts);
transform_options({Opt, T}, Opts)
when Opt == outgoing_s2s_timeout; Opt == s2s_dns_timeout ->
maybe_report_huge_timeout(Opt, T),
[{Opt, T}|Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
maybe_report_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
?WARNING_MSG("value '~p' of option '~p' is too big, "
"are you sure you have set seconds?",
[T, Opt]);
maybe_report_huge_timeout(_, _) ->
ok.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(outgoing_s2s_families) ->
fun(Families) ->
lists:map(
fun(ipv4) -> inet;
(ipv6) -> inet6
end, Families)
end;
opt_type(outgoing_s2s_port) ->
fun (I) when is_integer(I), I > 0, I < 65536 -> I end;
opt_type(outgoing_s2s_timeout) ->
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
timer:seconds(TimeOut);
(unlimited) ->
infinity;
(infinity) ->
infinity
end;
opt_type(s2s_dns_retries) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(s2s_dns_timeout) ->
fun(I) when is_integer(I), I>=0 -> timer:seconds(I);
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(s2s_max_retry_delay) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) ->
[outgoing_s2s_families, outgoing_s2s_port, outgoing_s2s_timeout,
s2s_dns_retries, s2s_dns_timeout, s2s_max_retry_delay].

View File

@ -27,7 +27,7 @@
%% ejabberd_listener callbacks
-export([start/3, start_link/3, accept/1]).
-export([listen_opt_type/1, listen_options/0, transform_listen_option/2]).
-export([listen_opt_type/1, listen_options/0]).
%% xmpp_stream_in callbacks
-export([init/1, handle_info/2, terminate/2, code_change/3]).
-export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4,
@ -65,8 +65,7 @@ send(Stream, Pkt) ->
close(Ref) ->
xmpp_stream_in:close(Ref).
-spec close(pid(), atom()) -> ok;
(state(), atom()) -> state().
-spec close(pid(), atom()) -> ok.
close(Ref, Reason) ->
xmpp_stream_in:close(Ref, Reason).
@ -100,12 +99,12 @@ init([State, Opts]) ->
true -> TLSOpts1
end,
GlobalRoutes = proplists:get_value(global_routes, Opts, true),
Timeout = ejabberd_config:negotiation_timeout(),
Timeout = ejabberd_option:negotiation_timeout(),
State1 = xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)),
State2 = xmpp_stream_in:set_timeout(State1, Timeout),
State3 = State2#{access => Access,
xmlns => ?NS_COMPONENT,
lang => ejabberd_config:get_mylang(),
lang => ejabberd_option:language(),
server => ejabberd_config:get_myname(),
host_opts => dict:from_list(HostOpts1),
stream_version => undefined,
@ -263,45 +262,30 @@ check_from(From, #{host_opts := HostOpts}) ->
random_password() ->
str:sha(p1_rand:bytes(20)).
transform_listen_option({hosts, Hosts, O}, Opts) ->
case lists:keyfind(hosts, 1, Opts) of
{_, PrevHostOpts} ->
NewHostOpts =
lists:foldl(
fun(H, Acc) ->
dict:append_list(H, O, Acc)
end, dict:from_list(PrevHostOpts), Hosts),
[{hosts, dict:to_list(NewHostOpts)}|
lists:keydelete(hosts, 1, Opts)];
_ ->
[{hosts, [{H, O} || H <- Hosts]}|Opts]
end;
transform_listen_option({host, Host, Os}, Opts) ->
transform_listen_option({hosts, [Host], Os}, Opts);
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
listen_opt_type(shaper_rule) ->
fun(V) ->
?WARNING_MSG("Listening option 'shaper_rule' of module ~s "
"is renamed to 'shaper'", [?MODULE]),
acl:shaper_rules_validator(V)
end;
listen_opt_type(check_from) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(password) -> fun iolist_to_binary/1;
econf:and_then(
econf:shaper(),
fun(S) ->
?WARNING_MSG("Listening option 'shaper_rule' of module ~s "
"is renamed to 'shaper'. Please adjust your "
"configuration", [?MODULE]),
S
end);
listen_opt_type(check_from) ->
econf:bool();
listen_opt_type(password) ->
econf:binary();
listen_opt_type(hosts) ->
fun(HostOpts) ->
lists:map(
fun({Host, Opts}) ->
Password = case proplists:get_value(password, Opts) of
undefined -> undefined;
P -> iolist_to_binary(P)
end,
{iolist_to_binary(Host), Password}
end, HostOpts)
end;
econf:and_then(
econf:map(
econf:domain(),
econf:options(
#{password => econf:binary()})),
fun({Host, Opts}) ->
{Host, proplists:get_value(password, Opts)}
end);
listen_opt_type(global_routes) ->
fun(B) when is_boolean(B) -> B end.
econf:bool().
listen_options() ->
[{access, all},

View File

@ -1,10 +1,4 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_shaper.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Functions to control connections traffic
%%% Created : 9 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
@ -22,131 +16,225 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_shaper).
-behaviour(gen_server).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-export([start_link/0, new/1, update/2,
get_max_rate/1, transform_options/1, load_from_config/0,
opt_type/1]).
-export([start_link/0, new/1, update/2, match/3, get_max_rate/1]).
-export([reload_from_config/0]).
-export([validator/1, shaper_rules_validator/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("logger.hrl").
-record(shaper, {name :: {atom(), global},
maxrate :: integer(),
burst_size :: integer()}).
-record(state, {}).
-type state() :: #{hosts := [binary()]}.
-type shaper() :: none | p1_shaper:state().
-export_type([shaper/0]).
-type shaper_rate() :: {pos_integer(), pos_integer()} | pos_integer() | infinity.
-type shaper_rule() :: {atom() | pos_integer(), [acl:access_rule()]}.
-type shaper_rate_rule() :: {shaper_rate(), [acl:access_rule()]}.
-export_type([shaper/0, shaper_rule/0, shaper_rate/0]).
%%%===================================================================
%%% API
%%%===================================================================
-spec start_link() -> {ok, pid()} | {error, any()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec match(global | binary(), atom() | [shaper_rule()],
jid:jid() | jid:ljid() | inet:ip_address() | acl:match()) -> none | shaper_rate().
match(_, none, _) -> none;
match(_, infinity, _) -> infinity;
match(Host, Shaper, Match) when is_map(Match) ->
Rules = if is_atom(Shaper) -> read_shaper_rules(Shaper, Host);
true -> Shaper
end,
Rate = acl:match_rules(Host, Rules, Match, none),
read_shaper(Rate);
match(Host, Shaper, IP) when tuple_size(IP) == 4; tuple_size(IP) == 8 ->
match(Host, Shaper, #{ip => IP});
match(Host, Shaper, JID) ->
match(Host, Shaper, #{usr => jid:tolower(JID)}).
-spec get_max_rate(none | shaper_rate()) -> none | pos_integer().
get_max_rate({Rate, _}) -> Rate;
get_max_rate(Rate) when is_integer(Rate), Rate > 0 -> Rate;
get_max_rate(_) -> none.
-spec new(none | shaper_rate()) -> shaper().
new({Rate, Burst}) -> p1_shaper:new(Rate, Burst);
new(Rate) when is_integer(Rate), Rate > 0 -> p1_shaper:new(Rate);
new(_) -> none.
-spec update(shaper(), non_neg_integer()) -> {shaper(), non_neg_integer()}.
update(none, _Size) -> {none, 0};
update(Shaper1, Size) ->
Shaper2 = p1_shaper:update(Shaper1, Size),
?DEBUG("Shaper update:~n~s =>~n~s",
[p1_shaper:pp(Shaper1), p1_shaper:pp(Shaper2)]),
Shaper2.
-spec validator(shaper | shaper_rules) -> econf:validator().
validator(shaper) ->
econf:options(
#{'_' => shaper_validator()},
[{disallowed, reserved()}, {return, map}, unique]);
validator(shaper_rules) ->
econf:options(
#{'_' => shaper_rules_validator()},
[{disallowed, reserved()}, unique]).
-spec shaper_rules_validator() -> econf:validator().
shaper_rules_validator() ->
fun(L) when is_list(L) ->
lists:map(
fun({K, V}) ->
{(shaper_name())(K), (acl:access_validator())(V)};
(N) ->
{(shaper_name())(N), [{acl, all}]}
end, lists:flatten(L));
(N) ->
[{(shaper_name())(N), [{acl, all}]}]
end.
-spec reload_from_config() -> ok.
reload_from_config() ->
gen_server:call(?MODULE, reload_from_config, timer:minutes(1)).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
ejabberd_mnesia:create(?MODULE, shaper,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, shaper)}]),
ejabberd_hooks:add(config_reloaded, ?MODULE, load_from_config, 20),
load_from_config(),
{ok, #state{}}.
create_tabs(),
Hosts = ejabberd_option:hosts(),
load_from_config([], Hosts),
ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20),
{ok, #{hosts => Hosts}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
-spec handle_call(term(), term(), state()) -> {reply, ok, state()} | {noreply, state()}.
handle_call(reload_from_config, _, #{hosts := OldHosts} = State) ->
NewHosts = ejabberd_option:hosts(),
load_from_config(OldHosts, NewHosts),
{reply, ok, State#{hosts => NewHosts}};
handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
handle_info(_Info, State) ->
-spec handle_cast(term(), state()) -> {noreply, state()}.
handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}.
-spec handle_info(term(), state()) -> {noreply, state()}.
handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
-spec terminate(any(), state()) -> ok.
terminate(_Reason, _State) ->
ok.
ejabberd_hooks:delete(config_reloaded, ?MODULE, reload_from_config, 20).
-spec code_change(term(), state(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-spec load_from_config() -> ok | {error, any()}.
load_from_config() ->
Shapers = ejabberd_config:get_option(shaper, []),
case mnesia:transaction(
fun() ->
lists:foreach(
fun({Name, MaxRate, BurstSize}) ->
mnesia:write(
#shaper{name = {Name, global},
maxrate = MaxRate,
burst_size = BurstSize})
end,
Shapers)
end) of
{atomic, ok} ->
ok;
Err ->
{error, Err}
%%%===================================================================
%%% Internal functions
%%%===================================================================
%%%===================================================================
%%% Table management
%%%===================================================================
-spec load_from_config([binary()], [binary()]) -> ok.
load_from_config(OldHosts, NewHosts) ->
?DEBUG("Loading shaper rules from config", []),
Shapers = ejabberd_option:shaper(),
ets:insert(shaper, maps:to_list(Shapers)),
ets:insert(
shaper_rules,
lists:flatmap(
fun(Host) ->
lists:flatmap(
fun({Name, List}) ->
case resolve_shapers(Name, List, Shapers) of
[] -> [];
List1 ->
[{{Name, Host}, List1}]
end
end, ejabberd_option:shaper_rules(Host))
end, [global|NewHosts])),
lists:foreach(
fun(Host) ->
ets:match_delete(shaper_rules, {{'_', Host}, '_'})
end, OldHosts -- NewHosts),
?DEBUG("Shaper rules loaded successfully", []).
-spec create_tabs() -> ok.
create_tabs() ->
_ = mnesia:delete_table(shaper),
_ = ets:new(shaper, [named_table, {read_concurrency, true}]),
_ = ets:new(shaper_rules, [named_table, {read_concurrency, true}]),
ok.
-spec read_shaper_rules(atom(), global | binary()) -> [shaper_rate_rule()].
read_shaper_rules(Name, Host) ->
case ets:lookup(shaper_rules, {Name, Host}) of
[{_, Rule}] -> Rule;
[] -> []
end.
-spec get_max_rate(atom()) -> none | non_neg_integer().
get_max_rate(none) ->
none;
get_max_rate(Name) ->
case ets:lookup(shaper, {Name, global}) of
[#shaper{maxrate = R}] ->
R;
[] ->
none
end.
-spec read_shaper(atom() | shaper_rate()) -> none | shaper_rate().
read_shaper(Name) when is_atom(Name), Name /= none, Name /= infinity ->
case ets:lookup(shaper, Name) of
[{_, Rate}] -> Rate;
[] -> none
end;
read_shaper(Rate) ->
Rate.
-spec new(atom()) -> shaper().
new(none) ->
none;
new(Name) ->
case ets:lookup(shaper, {Name, global}) of
[#shaper{maxrate = R, burst_size = B}] ->
p1_shaper:new(R, B);
[] ->
none
end.
%%%===================================================================
%%% Validators
%%%===================================================================
shaper_name() ->
econf:either(
econf:and_then(
econf:atom(),
fun(infinite) -> infinity;
(unlimited) -> infinity;
(A) -> A
end),
econf:pos_int()).
-spec update(shaper(), integer()) -> {shaper(), integer()}.
update(none, _Size) -> {none, 0};
update(Shaper, Size) ->
Result = p1_shaper:update(Shaper, Size),
?DEBUG("Shaper update:~n~s =>~n~s",
[p1_shaper:pp(Shaper), p1_shaper:pp(Result)]),
Result.
shaper_validator() ->
econf:either(
econf:and_then(
econf:options(
#{rate => econf:pos_int(),
burst_size => econf:pos_int()},
[unique, {required, [rate]}, {return, map}]),
fun(#{rate := Rate} = Map) ->
{Rate, maps:get(burst_size, Map, Rate)}
end),
econf:pos_int(infinity)).
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
%%%===================================================================
%%% Aux
%%%===================================================================
reserved() ->
[none, infinite, unlimited, infinity].
transform_options({shaper, Name, {maxrate, N}}, Opts) ->
[{shaper, [{Name, N}]} | Opts];
transform_options({shaper, Name, none}, Opts) ->
[{shaper, [{Name, none}]} | Opts];
transform_options({shaper, List}, Opts) when is_list(List) ->
R = lists:map(
fun({Name, Args}) when is_list(Args) ->
MaxRate = proplists:get_value(rate, Args, 1000),
BurstSize = proplists:get_value(burst_size, Args, MaxRate),
{Name, MaxRate, BurstSize};
({Name, Val}) ->
{Name, Val, Val}
end, List),
[{shaper, R} | Opts];
transform_options(Opt, Opts) ->
[Opt | Opts].
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(shaper) -> fun(V) -> V end;
opt_type(_) -> [shaper].
-spec resolve_shapers(atom(), [shaper_rule()], #{atom() => shaper_rate()}) -> [shaper_rate_rule()].
resolve_shapers(ShaperRule, Rules, Shapers) ->
lists:filtermap(
fun({Name, Rule}) when is_atom(Name), Name /= none, Name /= infinity ->
try {true, {maps:get(Name, Shapers), Rule}}
catch _:{badkey, _} ->
?WARNING_MSG(
"Shaper rule '~s' refers to unknown shaper: ~s",
[ShaperRule, Name]),
false
end;
(_) ->
true
end, Rules).

View File

@ -45,8 +45,8 @@ start_link(_, _, _) ->
-else.
%% API
-export([tcp_init/2, udp_init/2, udp_recv/5, start/3,
start_link/3, accept/1, listen_options/0]).
start_link/3, accept/1]).
-export([listen_opt_type/1, listen_options/0]).
%%%===================================================================
%%% API
@ -80,15 +80,13 @@ set_certfile(Opts) ->
{ok, CertFile} ->
[{certfile, CertFile}|Opts];
error ->
case ejabberd_config:get_option({domain_certfile, ejabberd_config:get_myname()}) of
undefined ->
Opts;
CertFile ->
[{certfile, CertFile}|Opts]
end
Opts
end
end.
listen_opt_type(certfile) ->
econf:pem().
listen_options() ->
[{tls, false},
{certfile, undefined}].

View File

@ -25,8 +25,6 @@
-module(ejabberd_sm).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-ifndef(GEN_SERVER).
@ -83,7 +81,7 @@
]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3, opt_type/1]).
handle_info/2, terminate/2, code_change/3]).
-include("logger.hrl").
@ -117,11 +115,12 @@
start_link() ->
?GEN_SERVER:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec stop() -> ok.
-spec stop() -> ok | {error, atom()}.
stop() ->
supervisor:terminate_child(ejabberd_sup, ?MODULE),
supervisor:delete_child(ejabberd_sup, ?MODULE),
ok.
case supervisor:terminate_child(ejabberd_sup, ?MODULE) of
ok -> supervisor:delete_child(ejabberd_sup, ?MODULE);
Err -> Err
end.
-spec route(jid(), term()) -> ok.
%% @doc route arbitrary term to c2s process(es)
@ -477,7 +476,7 @@ init([]) ->
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:add(host_down, ?MODULE, host_down, 60),
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()),
lists:foreach(fun host_up/1, ejabberd_option:hosts()),
ejabberd_commands:register_commands(get_commands_spec()),
{ok, #state{}};
{error, Why} ->
@ -497,7 +496,7 @@ handle_info(Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()),
lists:foreach(fun host_down/1, ejabberd_option:hosts()),
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 60),
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50),
@ -860,12 +859,11 @@ check_max_sessions(LUser, LServer) ->
%% Defaults to infinity
-spec get_max_user_sessions(binary(), binary()) -> infinity | non_neg_integer().
get_max_user_sessions(LUser, Host) ->
case acl:match_rule(Host, max_user_sessions,
jid:make(LUser, Host))
of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_SESSIONS
case ejabberd_shaper:match(Host, max_user_sessions,
jid:make(LUser, Host)) of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_SESSIONS
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@ -882,15 +880,13 @@ force_update_presence({LUser, LServer}) ->
-spec get_sm_backend(binary()) -> module().
get_sm_backend(Host) ->
DBType = ejabberd_config:get_option(
{sm_db_type, Host},
ejabberd_config:default_ram_db(Host, ?MODULE)),
DBType = ejabberd_option:sm_db_type(Host),
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
-spec get_sm_backends() -> [module()].
get_sm_backends() ->
lists:usort([get_sm_backend(Host) || Host <- ejabberd_config:get_myhosts()]).
lists:usort([get_sm_backend(Host) || Host <- ejabberd_option:hosts()]).
-spec get_vh_by_backend(module()) -> [binary()].
@ -898,7 +894,7 @@ get_vh_by_backend(Mod) ->
lists:filter(
fun(Host) ->
get_sm_backend(Host) == Mod
end, ejabberd_config:get_myhosts()).
end, ejabberd_option:hosts()).
%%--------------------------------------------------------------------
%%% Cache stuff
@ -914,15 +910,9 @@ init_cache() ->
-spec cache_opts() -> [proplists:property()].
cache_opts() ->
MaxSize = ejabberd_config:get_option(
sm_cache_size,
ejabberd_config:cache_size(global)),
CacheMissed = ejabberd_config:get_option(
sm_cache_missed,
ejabberd_config:cache_missed(global)),
LifeTime = case ejabberd_config:get_option(
sm_cache_life_time,
ejabberd_config:cache_life_time(global)) of
MaxSize = ejabberd_option:sm_cache_size(),
CacheMissed = ejabberd_option:sm_cache_missed(),
LifeTime = case ejabberd_option:sm_cache_life_time() of
infinity -> infinity;
I -> timer:seconds(I)
end,
@ -949,10 +939,7 @@ clean_cache() ->
use_cache(Mod, LServer) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(LServer);
false ->
ejabberd_config:get_option(
{sm_use_cache, LServer},
ejabberd_config:use_cache(LServer))
false -> ejabberd_option:sm_use_cache(LServer)
end.
-spec use_cache() -> boolean().
@ -961,7 +948,7 @@ use_cache() ->
fun(Host) ->
Mod = get_sm_backend(Host),
use_cache(Mod, Host)
end, ejabberd_config:get_myhosts()).
end, ejabberd_option:hosts()).
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, LServer) ->
@ -1041,16 +1028,3 @@ kick_user(User, Server, Resource) ->
make_sid() ->
{misc:unique_timestamp(), self()}.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(O) when O == sm_use_cache; O == sm_cache_missed ->
fun(B) when is_boolean(B) -> B end;
opt_type(O) when O == sm_cache_size; O == sm_cache_life_time ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
opt_type(_) ->
[sm_db_type, sm_use_cache, sm_cache_size, sm_cache_missed,
sm_cache_life_time].

View File

@ -24,7 +24,6 @@
-module(ejabberd_sm_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_sm).

View File

@ -25,8 +25,6 @@
-module(ejabberd_sql).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(p1_fsm).
@ -65,8 +63,7 @@
code_change/4]).
-export([connecting/2, connecting/3,
session_established/2, session_established/3,
opt_type/1]).
session_established/2, session_established/3]).
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
@ -86,24 +83,12 @@
-define(TOP_LEVEL_TXN, 0).
-define(PGSQL_PORT, 5432).
-define(MYSQL_PORT, 3306).
-define(MSSQL_PORT, 1433).
-define(MAX_TRANSACTION_RESTARTS, 10).
-define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]).
-define(PREPARE_KEY, ejabberd_sql_prepare).
-ifdef(NEW_SQL_SCHEMA).
-define(USE_NEW_SCHEMA_DEFAULT, true).
-else.
-define(USE_NEW_SCHEMA_DEFAULT, false).
-endif.
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
@ -128,13 +113,15 @@ start_link(Host, StartInterval) ->
[Host, StartInterval],
fsm_limit_opts() ++ (?FSMOPTS)).
-type sql_query() :: [sql_query() | binary()] | #sql_query{} |
fun(() -> any()) | fun((atom(), _) -> any()).
-type sql_query_simple() :: [sql_query() | binary()] | #sql_query{} |
fun(() -> any()) | fun((atom(), _) -> any()).
-type sql_query() :: sql_query_simple() |
[{atom() | {atom(), any()}, sql_query_simple()}].
-type sql_query_result() :: {updated, non_neg_integer()} |
{error, binary()} |
{selected, [binary()],
[[binary()]]} |
{selected, [any()]}.
{selected, [binary()], [[binary()]]} |
{selected, [any()]} |
ok.
-spec sql_query(binary(), sql_query()) -> sql_query_result().
@ -300,39 +287,41 @@ sqlite_db(Host) ->
-spec sqlite_file(binary()) -> string().
sqlite_file(Host) ->
case ejabberd_config:get_option({sql_database, Host}) of
case ejabberd_option:sql_database(Host) of
undefined ->
{ok, Cwd} = file:get_cwd(),
filename:join([Cwd, "sqlite", atom_to_list(node()),
binary_to_list(Host), "ejabberd.db"]);
Path = ["sqlite", atom_to_list(node()),
binary_to_list(Host), "ejabberd.db"],
case file:get_cwd() of
{ok, Cwd} ->
filename:join([Cwd|Path]);
{error, Reason} ->
?ERROR_MSG("Failed to get current directory: ~s",
[file:format_error(Reason)]),
filename:join(Path)
end;
File ->
binary_to_list(File)
end.
use_new_schema() ->
ejabberd_config:get_option(new_sql_schema, ?USE_NEW_SCHEMA_DEFAULT).
ejabberd_option:new_sql_schema().
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
init([Host, StartInterval]) ->
process_flag(trap_exit, true),
case ejabberd_config:get_option({sql_keepalive_interval, Host}) of
case ejabberd_option:sql_keepalive_interval(Host) of
undefined ->
ok;
KeepaliveInterval ->
timer:apply_interval(KeepaliveInterval * 1000, ?MODULE,
timer:apply_interval(KeepaliveInterval, ?MODULE,
keep_alive, [Host, self()])
end,
[DBType | _] = db_opts(Host),
p1_fsm:send_event(self(), connect),
ejabberd_sql_sup:add_pid(Host, self()),
QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of
undefined ->
ejabberd_config:default_queue_type(Host);
Type ->
Type
end,
QueueType = ejabberd_option:sql_queue_type(Host),
{ok, connecting,
#state{db_type = DBType, host = Host,
pending_requests = p1_queue:new(QueueType, max_fsm_queue()),
@ -995,11 +984,13 @@ log(Level, Format, Args) ->
end.
db_opts(Host) ->
Type = ejabberd_config:get_option({sql_type, Host}, odbc),
Server = ejabberd_config:get_option({sql_server, Host}, <<"localhost">>),
Timeout = timer:seconds(
ejabberd_config:get_option({sql_connect_timeout, Host}, 5)),
Transport = case ejabberd_config:get_option({sql_ssl, Host}, false) of
Type = case ejabberd_option:sql_type(Host) of
undefined -> odbc;
T -> T
end,
Server = ejabberd_option:sql_server(Host),
Timeout = ejabberd_option:sql_connect_timeout(Host),
Transport = case ejabberd_option:sql_ssl(Host) of
false -> tcp;
true -> ssl
end,
@ -1010,19 +1001,13 @@ db_opts(Host) ->
sqlite ->
[sqlite, Host];
_ ->
Port = ejabberd_config:get_option(
{sql_port, Host},
case Type of
mssql -> ?MSSQL_PORT;
mysql -> ?MYSQL_PORT;
pgsql -> ?PGSQL_PORT
end),
DB = ejabberd_config:get_option({sql_database, Host},
<<"ejabberd">>),
User = ejabberd_config:get_option({sql_username, Host},
<<"ejabberd">>),
Pass = ejabberd_config:get_option({sql_password, Host},
<<"">>),
Port = ejabberd_option:sql_port(Host),
DB = case ejabberd_option:sql_database(Host) of
undefined -> <<"ejabberd">>;
D -> D
end,
User = ejabberd_option:sql_username(Host),
Pass = ejabberd_option:sql_password(Host),
SSLOpts = get_ssl_opts(Transport, Host),
case Type of
mssql ->
@ -1041,15 +1026,15 @@ warn_if_ssl_unsupported(ssl, Type) ->
?WARNING_MSG("SSL connection is not supported for ~s", [Type]).
get_ssl_opts(ssl, Host) ->
Opts1 = case ejabberd_config:get_option({sql_ssl_certfile, Host}) of
Opts1 = case ejabberd_option:sql_ssl_certfile(Host) of
undefined -> [];
CertFile -> [{certfile, CertFile}]
end,
Opts2 = case ejabberd_config:get_option({sql_ssl_cafile, Host}) of
Opts2 = case ejabberd_option:sql_ssl_cafile(Host) of
undefined -> Opts1;
CAFile -> [{cacertfile, CAFile}|Opts1]
end,
case ejabberd_config:get_option({sql_ssl_verify, Host}, false) of
case ejabberd_option:sql_ssl_verify(Host) of
true ->
case lists:keymember(cacertfile, 1, Opts2) of
true ->
@ -1068,9 +1053,12 @@ get_ssl_opts(tcp, _) ->
[].
init_mssql(Host) ->
Server = ejabberd_config:get_option({sql_server, Host}, <<"localhost">>),
Port = ejabberd_config:get_option({sql_port, Host}, ?MSSQL_PORT),
DB = ejabberd_config:get_option({sql_database, Host}, <<"ejabberd">>),
Server = ejabberd_option:sql_server(Host),
Port = ejabberd_option:sql_port(Host),
DB = case ejabberd_option:sql_database(Host) of
undefined -> <<"ejabberd">>;
D -> D
end,
FreeTDS = io_lib:fwrite("[~s]~n"
"\thost = ~s~n"
"\tport = ~p~n"
@ -1142,8 +1130,7 @@ fsm_limit_opts() ->
ejabberd_config:fsm_limit_opts([]).
query_timeout(LServer) ->
timer:seconds(
ejabberd_config:get_option({sql_query_timeout, LServer}, 60)).
ejabberd_option:sql_query_timeout(LServer).
%% ***IMPORTANT*** This error format requires extended_errors turned on.
extended_error({"08S01", _, Reason}) ->
@ -1186,31 +1173,3 @@ check_error({error, Why}, Query) ->
{error, Err};
check_error(Result, _Query) ->
Result.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(sql_database) -> fun iolist_to_binary/1;
opt_type(sql_keepalive_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_password) -> fun iolist_to_binary/1;
opt_type(sql_port) ->
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
opt_type(sql_server) -> fun iolist_to_binary/1;
opt_type(sql_username) -> fun iolist_to_binary/1;
opt_type(sql_ssl) -> fun(B) when is_boolean(B) -> B end;
opt_type(sql_ssl_verify) -> fun(B) when is_boolean(B) -> B end;
opt_type(sql_ssl_certfile) -> fun ejabberd_pkix:try_certfile/1;
opt_type(sql_ssl_cafile) -> fun ejabberd_pkix:try_certfile/1;
opt_type(sql_query_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(new_sql_schema) -> fun(B) when is_boolean(B) -> B end;
opt_type(_) ->
[sql_database, sql_keepalive_interval,
sql_password, sql_port, sql_server,
sql_username, sql_ssl, sql_ssl_verify, sql_ssl_certfile,
sql_ssl_cafile, sql_queue_type, sql_query_timeout,
sql_connect_timeout,
new_sql_schema].

View File

@ -28,9 +28,7 @@
%% API
-export([parse_transform/2, format_error/1]).
%-export([parse/2]).
-include("ejabberd_sql_pt.hrl").
-include("ejabberd_sql.hrl").
-record(state, {loc,
'query' = [],
@ -66,10 +64,8 @@
%% Description:
%%--------------------------------------------------------------------
parse_transform(AST, _Options) ->
%io:format("PT: ~p~nOpts: ~p~n", [AST, Options]),
put(warnings, []),
NewAST = top_transform(AST),
%io:format("NewPT: ~p~n", [NewAST]),
NewAST ++ get(warnings).
@ -141,7 +137,6 @@ transform(Form) ->
case erl_syntax:attribute_arguments(Form) of
[M | _] ->
Module = erl_syntax:atom_value(M),
%io:format("module ~p~n", [Module]),
put(?MOD, Module),
Form;
_ ->
@ -158,11 +153,7 @@ top_transform(Forms) when is_list(Forms) ->
lists:map(
fun(Form) ->
try
Form2 = erl_syntax_lib:map(
fun(Node) ->
%io:format("asd ~p~n", [Node]),
transform(Node)
end, Form),
Form2 = erl_syntax_lib:map(fun transform/1, Form),
Form3 = erl_syntax:revert(Form2),
Form3
catch
@ -517,7 +508,6 @@ parse_upsert(Fields) ->
"a constant string"})
end
end, {[], 0}, Fields),
%io:format("upsert ~p~n", [{Fields, Fs}]),
Fs.
%% key | {Update}

View File

@ -25,23 +25,14 @@
-module(ejabberd_sql_sup).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-export([start_link/1, init/1, add_pid/2, remove_pid/2,
get_pids/1, get_random_pid/1, transform_options/1,
reload/1, opt_type/1]).
get_pids/1, get_random_pid/1, reload/1]).
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-define(PGSQL_PORT, 5432).
-define(MYSQL_PORT, 3306).
-define(DEFAULT_POOL_SIZE, 10).
-define(DEFAULT_SQL_START_INTERVAL, 30).
-define(CONNECT_TIMEOUT, 500).
-record(sql_pool, {host :: binary(),
pid :: pid()}).
@ -57,7 +48,7 @@ start_link(Host) ->
?MODULE, [Host]).
init([Host]) ->
Type = ejabberd_config:get_option({sql_type, Host}, odbc),
Type = ejabberd_option:sql_type(Host),
PoolSize = get_pool_size(Type, Host),
case Type of
sqlite ->
@ -71,7 +62,7 @@ init([Host]) ->
[child_spec(I, Host) || I <- lists:seq(1, PoolSize)]}}.
reload(Host) ->
Type = ejabberd_config:get_option({sql_type, Host}, odbc),
Type = ejabberd_option:sql_type(Host),
NewPoolSize = get_pool_size(Type, Host),
OldPoolSize = ets:select_count(
sql_pool,
@ -125,12 +116,7 @@ remove_pid(Host, Pid) ->
-spec get_pool_size(atom(), binary()) -> pos_integer().
get_pool_size(SQLType, Host) ->
PoolSize = ejabberd_config:get_option(
{sql_pool_size, Host},
case SQLType of
sqlite -> 1;
_ -> ?DEFAULT_POOL_SIZE
end),
PoolSize = ejabberd_option:sql_pool_size(Host),
if PoolSize > 1 andalso SQLType == sqlite ->
?WARNING_MSG("it's not recommended to set sql_pool_size > 1 for "
"sqlite, because it may cause race conditions", []);
@ -140,31 +126,10 @@ get_pool_size(SQLType, Host) ->
PoolSize.
child_spec(I, Host) ->
StartInterval = ejabberd_config:get_option(
{sql_start_interval, Host},
?DEFAULT_SQL_START_INTERVAL),
{I, {ejabberd_sql, start_link, [Host, timer:seconds(StartInterval)]},
StartInterval = ejabberd_option:sql_start_interval(Host),
{I, {ejabberd_sql, start_link, [Host, StartInterval]},
transient, 2000, worker, [?MODULE]}.
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
[{sql_type, Type},
{sql_server, Server},
{sql_port, Port},
{sql_database, DB},
{sql_username, User},
{sql_password, Pass}|Opts];
transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) ->
transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts);
transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) ->
transform_options({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts);
transform_options({odbc_server, {sqlite, DB}}, Opts) ->
transform_options({odbc_server, {sqlite, DB}}, Opts);
transform_options(Opt, Opts) ->
[Opt|Opts].
check_sqlite_db(Host) ->
DB = ejabberd_sql:sqlite_db(Host),
File = ejabberd_sql:sqlite_file(Host),
@ -234,11 +199,3 @@ read_lines(Fd, File, Acc) ->
?ERROR_MSG("Failed read from lite.sql, reason: ~p", [Err]),
[]
end.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(sql_pool_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_start_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) ->
[sql_pool_size, sql_start_interval].

View File

@ -57,6 +57,7 @@ tcp_init(Socket, Opts) ->
ejabberd:start_app(stun),
stun:tcp_init(Socket, prepare_turn_opts(Opts)).
-dialyzer({nowarn_function, udp_init/2}).
udp_init(Socket, Opts) ->
ejabberd:start_app(stun),
stun:udp_init(Socket, prepare_turn_opts(Opts)).
@ -83,11 +84,11 @@ prepare_turn_opts(Opts) ->
prepare_turn_opts(Opts, _UseTurn = false) ->
set_certfile(Opts);
prepare_turn_opts(Opts, _UseTurn = true) ->
NumberOfMyHosts = length(ejabberd_config:get_myhosts()),
NumberOfMyHosts = length(ejabberd_option:hosts()),
case proplists:get_value(turn_ip, Opts) of
undefined ->
?WARNING_MSG("option 'turn_ip' is undefined, "
"more likely the TURN relay won't be working "
?WARNING_MSG("Option 'turn_ip' is undefined, "
"most likely the TURN relay won't be working "
"properly", []);
_ ->
ok
@ -98,11 +99,11 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
Realm = case proplists:get_value(auth_realm, Opts) of
undefined when AuthType == user ->
if NumberOfMyHosts > 1 ->
?WARNING_MSG("you have several virtual "
?WARNING_MSG("You have several virtual "
"hosts configured, but option "
"'auth_realm' is undefined and "
"'auth_type' is set to 'user', "
"more likely the TURN relay won't "
"most likely the TURN relay won't "
"be working properly. Using ~s as "
"a fallback", [ejabberd_config:get_myname()]);
true ->
@ -127,44 +128,32 @@ set_certfile(Opts) ->
{ok, CertFile} ->
[{certfile, CertFile}|Opts];
error ->
case ejabberd_config:get_option({domain_certfile, Realm}) of
undefined ->
Opts;
CertFile ->
[{certfile, CertFile}|Opts]
end
Opts
end
end.
listen_opt_type(use_turn) ->
fun(B) when is_boolean(B) -> B end;
econf:bool();
listen_opt_type(ip) ->
econf:ipv4();
listen_opt_type(turn_ip) ->
fun(S) ->
{ok, Addr} = inet_parse:ipv4_address(binary_to_list(S)),
Addr
end;
econf:ipv4();
listen_opt_type(auth_type) ->
fun(anonymous) -> anonymous;
(user) -> user
end;
econf:enum([anonymous, user]);
listen_opt_type(auth_realm) ->
fun iolist_to_binary/1;
econf:binary();
listen_opt_type(turn_min_port) ->
fun(P) when is_integer(P), P > 1024, P < 65536 -> P end;
econf:int(1025, 65535);
listen_opt_type(turn_max_port) ->
fun(P) when is_integer(P), P > 1024, P < 65536 -> P end;
econf:int(1025, 65535);
listen_opt_type(turn_max_allocations) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
econf:pos_int(infinity);
listen_opt_type(turn_max_permissions) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
econf:pos_int(infinity);
listen_opt_type(server_name) ->
fun iolist_to_binary/1.
econf:binary();
listen_opt_type(certfile) ->
econf:pem().
listen_options() ->
[{shaper, none},

View File

@ -30,7 +30,7 @@
-export([start_link/0, init/1]).
-define(SHUTDOWN_TIMEOUT, timer:seconds(30)).
-define(SHUTDOWN_TIMEOUT, timer:minutes(1)).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
@ -53,10 +53,9 @@ init([]) ->
simple_supervisor(ejabberd_service),
worker(acl),
worker(ejabberd_shaper),
supervisor(ejabberd_db_sup),
supervisor(ejabberd_backend_sup),
supervisor(ejabberd_rdbms),
supervisor(ejabberd_riak_sup),
supervisor(ejabberd_redis_sup),
worker(ejabberd_iq),
worker(ejabberd_router),
worker(ejabberd_router_multicast),

View File

@ -25,13 +25,12 @@
-module(ejabberd_system_monitor).
-behaviour(gen_event).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-author('ekhramtsov@process-one.net').
%% API
-export([start/0, opt_type/1, config_reloaded/0]).
-export([start/0, config_reloaded/0]).
%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2,
@ -43,8 +42,8 @@
-define(CHECK_INTERVAL, timer:seconds(30)).
-record(state, {tref :: reference(),
mref :: reference()}).
-record(state, {tref :: undefined | reference(),
mref :: undefined | reference()}).
-record(proc_stat, {qlen :: non_neg_integer(),
memory :: non_neg_integer(),
initial_call :: mfa(),
@ -134,7 +133,7 @@ handle_overload(State) ->
handle_overload(_State, Procs) ->
AppPids = get_app_pids(),
{TotalMsgs, ProcsNum, Apps, Stats} = overloaded_procs(AppPids, Procs),
MaxMsgs = ejabberd_config:get_option(oom_queue, 10000),
MaxMsgs = ejabberd_option:oom_queue(),
if TotalMsgs >= MaxMsgs ->
SortedStats = lists:reverse(lists:keysort(#proc_stat.qlen, Stats)),
error_logger:warning_msg(
@ -224,14 +223,14 @@ restart_timer(State) ->
TRef = erlang:start_timer(?CHECK_INTERVAL, self(), handle_overload),
State#state{tref = TRef}.
-spec format_apps(dict:dict()) -> io:data().
-spec format_apps(dict:dict()) -> iodata().
format_apps(Apps) ->
AppList = lists:reverse(lists:keysort(2, dict:to_list(Apps))),
string:join(
[io_lib:format("~p (~b msgs)", [App, Msgs]) || {App, Msgs} <- AppList],
", ").
-spec format_top_procs([proc_stat()]) -> io:data().
-spec format_top_procs([proc_stat()]) -> iodata().
format_top_procs(Stats) ->
Stats1 = lists:sublist(Stats, 5),
string:join(
@ -241,7 +240,7 @@ format_top_procs(Stats) ->
end,Stats1),
io_lib:nl()).
-spec format_proc(proc_stat()) -> io:data().
-spec format_proc(proc_stat()) -> iodata().
format_proc(#proc_stat{qlen = Len, memory = Mem, initial_call = InitCall,
current_function = CurrFun, ancestors = Ancs,
application = App}) ->
@ -250,7 +249,7 @@ format_proc(#proc_stat{qlen = Len, memory = Mem, initial_call = InitCall,
"current_function = ~s, ancestors = ~w, application = ~w",
[Len, Mem, format_mfa(InitCall), format_mfa(CurrFun), Ancs, App]).
-spec format_mfa(mfa()) -> io:data().
-spec format_mfa(mfa()) -> iodata().
format_mfa({M, F, A}) when is_atom(M), is_atom(F), is_integer(A) ->
io_lib:format("~s:~s/~b", [M, F, A]);
format_mfa(WTF) ->
@ -258,7 +257,7 @@ format_mfa(WTF) ->
-spec kill([proc_stat()], non_neg_integer()) -> ok.
kill(Stats, Threshold) ->
case ejabberd_config:get_option(oom_killer, true) of
case ejabberd_option:oom_killer() of
true ->
do_kill(Stats, Threshold);
false ->
@ -308,7 +307,7 @@ kill_proc(Pid) ->
-spec set_oom_watermark() -> ok.
set_oom_watermark() ->
WaterMark = ejabberd_config:get_option(oom_watermark, 80),
WaterMark = ejabberd_option:oom_watermark(),
memsup:set_sysmem_high_watermark(WaterMark/100).
-spec maybe_restart_app(atom()) -> any().
@ -316,11 +315,3 @@ maybe_restart_app(lager) ->
ejabberd_logger:restart();
maybe_restart_app(_) ->
ok.
opt_type(oom_killer) ->
fun(B) when is_boolean(B) -> B end;
opt_type(oom_watermark) ->
fun(I) when is_integer(I), I>0, I<100 -> I end;
opt_type(oom_queue) ->
fun(I) when is_integer(I), I>0 -> I end;
opt_type(_) -> [oom_killer, oom_watermark, oom_queue].

File diff suppressed because it is too large Load Diff

View File

@ -37,12 +37,11 @@
%%%----------------------------------------------------------------------
-module(ejabberd_websocket).
-behaviour(ejabberd_config).
-protocol({rfc, 6455}).
-author('ecestari@process-one.net').
-export([socket_handoff/5, opt_type/1]).
-export([socket_handoff/5]).
-include("logger.hrl").
@ -429,27 +428,4 @@ websocket_close(Socket, WsHandleLoopPid, SocketMode, _CloseCode) ->
SocketMode:close(Socket).
get_origin() ->
ejabberd_config:get_option(websocket_origin, []).
opt_type(websocket_ping_interval) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(websocket_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(websocket_origin) ->
fun Verify(V) when is_binary(V) ->
Verify([V]);
Verify([]) ->
[];
Verify([<<"null">> | R]) ->
[<<"null">> | Verify(R)];
Verify([null | R]) ->
[<<"null">> | Verify(R)];
Verify([V | R]) when is_binary(V) ->
URIs = [_|_] = lists:filtermap(
fun(<<>>) -> false;
(URI) -> {true, misc:try_url(URI)}
end, re:split(V, "\\s+")),
[str:join(URIs, <<" ">>) | Verify(R)]
end;
opt_type(_) ->
[websocket_ping_interval, websocket_timeout, websocket_origin].
ejabberd_option:websocket_origin().

View File

@ -36,7 +36,7 @@
-author('badlop@process-one.net').
-export([start/3, start_link/3, handler/2, process/2, accept/1,
transform_listen_option/2, listen_opt_type/1, listen_options/0]).
listen_opt_type/1, listen_options/0]).
-include("logger.hrl").
-include("ejabberd_http.hrl").
@ -233,7 +233,8 @@ process(_, _) ->
%% -----------------------------
%% Access verification
%% -----------------------------
-spec extract_auth([{user | server | token | password, binary()}]) ->
map() | {error, not_found | expired | invalid_auth}.
extract_auth(AuthList) ->
?DEBUG("AUTHLIST ~p", [AuthList]),
try get_attrs([user, server, token], AuthList) of
@ -306,10 +307,6 @@ handler(#state{get_auth = true, auth = noauth, ip = IP} = State,
build_fault_response(-118,
"Invalid oauth token",
[]);
{error, Value} ->
build_fault_response(-118,
"Invalid authentication data: ~p",
[Value]);
Auth ->
handler(State#state{get_auth = false, auth = Auth#{ip => IP, caller_module => ?MODULE}},
{call, Method, Arguments})
@ -341,15 +338,10 @@ handler(_State,
handler(State, {call, Command, []}) ->
handler(State, {call, Command, [{struct, []}]});
handler(State,
{call, Command, [{struct, AttrL}]} = Payload) ->
case ejabberd_commands:get_command_format(Command, State#state.auth) of
{error, command_unknown} ->
build_fault_response(-112, "Unknown call: ~p",
[Payload]);
{ArgsF, ResultF} ->
try_do_command(State#state.access_commands,
State#state.auth, Command, AttrL, ArgsF, ResultF)
end;
{call, Command, [{struct, AttrL}]}) ->
{ArgsF, ResultF} = ejabberd_commands:get_command_format(Command, State#state.auth),
try_do_command(State#state.access_commands,
State#state.auth, Command, AttrL, ArgsF, ResultF);
%% If no other guard matches
handler(_State, Payload) ->
build_fault_response(-112, "Unknown call: ~p",
@ -400,14 +392,8 @@ build_fault_response(Code, ParseString, ParseArgs) ->
do_command(AccessCommands, Auth, Command, AttrL, ArgsF,
ResultF) ->
ArgsFormatted = format_args(AttrL, ArgsF),
Auth2 = case AccessCommands of
V when is_list(V) ->
Auth#{extra_permissions => AccessCommands};
_ ->
Auth
end,
Result =
ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth2),
Auth2 = Auth#{extra_permissions => AccessCommands},
Result = ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth2),
ResultFormatted = format_result(Result, ResultF),
{command_result, ResultFormatted}.
@ -555,17 +541,6 @@ make_status(false) -> 1;
make_status(error) -> 1;
make_status(_) -> 1.
transform_listen_option({access_commands, ACOpts}, Opts) ->
NewACOpts = lists:map(
fun({AName, ACmds, AOpts}) ->
{AName, [{commands, ACmds}, {options, AOpts}]};
(Opt) ->
Opt
end, ACOpts),
[{access_commands, NewACOpts}|Opts];
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
listen_opt_type(access_commands) ->
fun(Opts) ->
lists:map(

View File

@ -25,14 +25,12 @@
-module(eldap_utils).
-behaviour(ejabberd_config).
-author('mremond@process-one.net').
-export([generate_subfilter/1, find_ldap_attrs/2, check_filter/1,
get_ldap_attr/2, get_user_part/2, make_filter/2,
get_state/2, case_insensitive_match/2, get_config/2,
decode_octet_string/3, uids_domain_subst/2, opt_type/1,
options/1]).
get_state/2, case_insensitive_match/2,
decode_octet_string/3, uids_domain_subst/2]).
-include("logger.hrl").
-include("eldap.hrl").
@ -160,110 +158,54 @@ get_state(Server, Module) ->
%% we look from alias domain (%d) and make the substitution
%% with the actual host domain
%% This help when you need to configure many virtual domains.
-spec uids_domain_subst(binary(), [{binary(), binary()}]) ->
-spec uids_domain_subst(binary(), [{binary(), binary()}]) ->
[{binary(), binary()}].
uids_domain_subst(Host, UIDs) ->
lists:map(fun({U,V}) ->
{U, eldap_filter:do_sub(V,[{<<"%d">>, Host}])};
(A) -> A
(A) -> A
end,
UIDs).
-spec get_config(binary(), list()) -> eldap_config().
get_config(Host, Opts) ->
Servers = get_opt(ldap_servers, Host, Opts, [<<"localhost">>]),
Backups = get_opt(ldap_backups, Host, Opts, []),
Encrypt = get_opt(ldap_encrypt, Host, Opts, none),
TLSVerify = get_opt(ldap_tls_verify, Host, Opts, false),
TLSCertFile = get_opt(ldap_tls_certfile, Host, Opts),
TLSCAFile = get_opt(ldap_tls_cacertfile, Host, Opts),
TLSDepth = get_opt(ldap_tls_depth, Host, Opts),
Port = case get_opt(ldap_port, Host, Opts) of
undefined ->
case Encrypt of
tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT
end;
P ->
P
end,
RootDN = get_opt(ldap_rootdn, Host, Opts, <<"">>),
Password = get_opt(ldap_password, Host, Opts, <<"">>),
Base = get_opt(ldap_base, Host, Opts, <<"">>),
OldDerefAliases = get_opt(deref_aliases, Host, Opts, unspecified),
DerefAliases =
if OldDerefAliases == unspecified ->
get_opt(ldap_deref_aliases, Host, Opts, never);
true ->
?WARNING_MSG("Option 'deref_aliases' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 'ldap_deref_aliases' instead.", []),
OldDerefAliases
end,
#eldap_config{servers = Servers,
backups = Backups,
tls_options = [{encrypt, Encrypt},
{tls_verify, TLSVerify},
{tls_certfile, TLSCertFile},
{tls_cacertfile, TLSCAFile},
{tls_depth, TLSDepth}],
port = Port,
dn = RootDN,
password = Password,
base = Base,
deref_aliases = DerefAliases}.
get_opt(Opt, Host, Opts) ->
get_opt(Opt, Host, Opts, undefined).
get_opt(Opt, Host, Opts, Default) ->
case proplists:get_value(Opt, Opts) of
undefined -> ejabberd_config:get_option({Opt, Host}, Default);
Value -> Value
end.
%%----------------------------------------
%%----------------------------------------
%% Borrowed from asn1rt_ber_bin_v2.erl
%%----------------------------------------
%%% The tag-number for universal types
-define(N_BOOLEAN, 1).
-define(N_INTEGER, 2).
-define(N_BOOLEAN, 1).
-define(N_INTEGER, 2).
-define(N_BIT_STRING, 3).
-define(N_OCTET_STRING, 4).
-define(N_NULL, 5).
-define(N_OBJECT_IDENTIFIER, 6).
-define(N_OBJECT_DESCRIPTOR, 7).
-define(N_EXTERNAL, 8).
-define(N_REAL, 9).
-define(N_ENUMERATED, 10).
-define(N_EMBEDDED_PDV, 11).
-define(N_SEQUENCE, 16).
-define(N_SET, 17).
-define(N_NULL, 5).
-define(N_OBJECT_IDENTIFIER, 6).
-define(N_OBJECT_DESCRIPTOR, 7).
-define(N_EXTERNAL, 8).
-define(N_REAL, 9).
-define(N_ENUMERATED, 10).
-define(N_EMBEDDED_PDV, 11).
-define(N_SEQUENCE, 16).
-define(N_SET, 17).
-define(N_NumericString, 18).
-define(N_PrintableString, 19).
-define(N_TeletexString, 20).
-define(N_VideotexString, 21).
-define(N_IA5String, 22).
-define(N_UTCTime, 23).
-define(N_GeneralizedTime, 24).
-define(N_UTCTime, 23).
-define(N_GeneralizedTime, 24).
-define(N_GraphicString, 25).
-define(N_VisibleString, 26).
-define(N_GeneralString, 27).
-define(N_UniversalString, 28).
-define(N_BMPString, 30).
decode_octet_string(Buffer, Range, Tags) ->
decode_octet_string(Buffer, Range, Tags) ->
% NewTags = new_tags(HasTag,#tag{class=?UNIVERSAL,number=?N_OCTET_STRING}),
decode_restricted_string(Buffer, Range, Tags).
decode_restricted_string(Tlv, Range, TagsIn) ->
Val = match_tags(Tlv, TagsIn),
Val2 =
Val2 =
case Val of
PartList = [_H|_T] -> % constructed val
collect_parts(PartList);
@ -287,12 +229,12 @@ check_and_convert_restricted_string(Val, Range) ->
NewVal;
{{Lb,_Ub},_Ext=[Min|_]} when StrLen >= Lb; StrLen >= Min ->
NewVal;
{{Lb1,Ub1},{Lb2,Ub2}} when StrLen >= Lb1, StrLen =< Ub1;
{{Lb1,Ub1},{Lb2,Ub2}} when StrLen >= Lb1, StrLen =< Ub1;
StrLen =< Ub2, StrLen >= Lb2 ->
NewVal;
StrLen -> % fixed length constraint
NewVal;
{_,_} ->
{_,_} ->
exit({error,{asn1,{length,Range,Val}}});
_Len when is_integer(_Len) ->
exit({error,{asn1,{length,Range,Val}}});
@ -300,9 +242,9 @@ check_and_convert_restricted_string(Val, Range) ->
NewVal
end.
%%----------------------------------------
%% Decode the in buffer to bits
%%----------------------------------------
%%----------------------------------------
%% Decode the in buffer to bits
%%----------------------------------------
match_tags({T,V},[T]) ->
V;
match_tags({T,V}, [T|Tt]) ->
@ -328,91 +270,7 @@ collect_parts([{_T,V}|Rest],Acc) ->
collect_parts([],Acc) ->
list_to_binary(lists:reverse(Acc)).
collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc);
collect_parts_bit([],Acc,Uacc) ->
list_to_binary([Uacc|lists:reverse(Acc)]).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(deref_aliases) ->
fun(unspecified) -> unspecified;
(never) -> never;
(searching) -> searching;
(finding) -> finding;
(always) -> always
end;
opt_type(ldap_backups) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_base) -> fun iolist_to_binary/1;
opt_type(ldap_deref_aliases) ->
fun (never) -> never;
(searching) -> searching;
(finding) -> finding;
(always) -> always
end;
opt_type(ldap_encrypt) ->
fun (tls) -> tls;
(starttls) -> starttls;
(none) -> none
end;
opt_type(ldap_password) -> fun iolist_to_binary/1;
opt_type(ldap_port) ->
fun(undefined) -> undefined;
(I) when is_integer(I), I > 0 -> I
end;
opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
opt_type(ldap_servers) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_tls_certfile) ->
fun(undefined) -> undefined;
(S) -> binary_to_list(ejabberd_pkix:try_certfile(S))
end;
opt_type(ldap_tls_cacertfile) ->
fun(undefined) -> undefined;
(S) -> binary_to_list(misc:try_read_file(S))
end;
opt_type(ldap_tls_depth) ->
fun(undefined) -> undefined;
(I) when is_integer(I), I >= 0 -> I
end;
opt_type(ldap_tls_verify) ->
fun (hard) -> hard;
(soft) -> soft;
(false) -> false
end;
opt_type(ldap_filter) ->
fun(<<"">>) -> <<"">>;
(F) -> check_filter(F)
end;
opt_type(ldap_uids) ->
fun (Us) ->
lists:map(fun ({U, P}) ->
{iolist_to_binary(U), iolist_to_binary(P)};
({U}) -> {iolist_to_binary(U)};
(U) -> {iolist_to_binary(U)}
end,
lists:flatten(Us))
end;
opt_type(_) ->
[deref_aliases, ldap_backups, ldap_base, ldap_uids,
ldap_deref_aliases, ldap_encrypt, ldap_password,
ldap_port, ldap_rootdn, ldap_servers, ldap_filter,
ldap_tls_certfile, ldap_tls_cacertfile, ldap_tls_depth,
ldap_tls_verify].
options(_) ->
[{deref_aliases, unspecified},
{ldap_backups, []},
{ldap_base, <<"">>},
{ldap_uids, [{<<"uid">>, <<"%u">>}]},
{ldap_deref_aliases, never},
{ldap_encrypt, none},
{ldap_password, <<"">>},
{ldap_port, undefined},
{ldap_rootdn, <<"">>},
{ldap_servers, [<<"localhost">>]},
{ldap_filter, <<"">>},
{ldap_tls_certfile, undefined},
{ldap_tls_cacertfile, undefined},
{ldap_tls_depth, undefined},
{ldap_tls_verify, false}].

View File

@ -22,9 +22,10 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(elixir_logger_backend).
-ifdef(ELIXIR_ENABLED).
-behaviour(gen_event).
-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
@ -57,7 +58,7 @@ handle_event({log, LagerMsg}, State) ->
notify(Mode, {MsgLevel, GroupLeader, {'Elixir.Logger', Message, Timestamp, Metadata}}),
{ok, State};
_ ->
{ok, State}
{ok, State}
end;
handle_event(_Msg, State) ->
{ok, State}.
@ -110,7 +111,7 @@ timestamp(Time, UTCLog) ->
false -> calendar:now_to_local_time(Time)
end,
{Date, {Hours, Minutes, Seconds, Micro div 1000}}.
severity_to_level(debug) -> debug;
severity_to_level(info) -> info;
@ -120,3 +121,5 @@ severity_to_level(error) -> error;
severity_to_level(critical) -> error;
severity_to_level(alert) -> error;
severity_to_level(emergency) -> error.
-endif.

View File

@ -25,7 +25,6 @@
-module(ext_mod).
-behaviour(ejabberd_config).
-behaviour(gen_server).
-author("Christophe Romain <christophe.romain@process-one.net>").
@ -34,7 +33,7 @@
installed_command/0, installed/0, installed/1,
install/1, uninstall/1, upgrade/0, upgrade/1, add_paths/0,
add_sources/1, add_sources/2, del_sources/1, modules_dir/0,
config_dir/0, opt_type/1, get_commands_spec/0]).
config_dir/0, get_commands_spec/0]).
-export([compile_erlang_file/2, compile_elixir_file/2]).
@ -221,7 +220,7 @@ install(Package) when is_binary(Package) ->
case compile_and_install(Module, Attrs) of
ok ->
code:add_patha(module_ebin_dir(Module)),
ejabberd_config:reload_file(),
ejabberd_config:reload(),
case erlang:function_exported(Module, post_install, 0) of
true -> Module:post_install();
_ -> ok
@ -243,12 +242,12 @@ uninstall(Package) when is_binary(Package) ->
_ -> ok
end,
[catch gen_mod:stop_module(Host, Module)
|| Host <- ejabberd_config:get_myhosts()],
|| Host <- ejabberd_option:hosts()],
code:purge(Module),
code:delete(Module),
code:del_path(module_ebin_dir(Module)),
delete_path(module_lib_dir(Module)),
ejabberd_config:reload_file();
ejabberd_config:reload();
false ->
{error, not_installed}
end.
@ -464,7 +463,7 @@ short_spec({Module, Attrs}) when is_atom(Module), is_list(Attrs) ->
{Module, proplists:get_value(summary, Attrs, "")}.
is_contrib_allowed() ->
ejabberd_config:get_option(allow_contrib_modules, true).
ejabberd_option:allow_contrib_modules().
%% -- build functions
@ -597,6 +596,7 @@ compile_erlang_file(Dest, File, ErlOptions) ->
{error, E, W} -> {error, {compilation_failed, File, E, W}}
end.
-ifdef(ELIXIR_ENABLED).
compile_elixir_file(Dest, File) when is_list(Dest) and is_list(File) ->
compile_elixir_file(list_to_binary(Dest), list_to_binary(File));
@ -606,6 +606,10 @@ compile_elixir_file(Dest, File) ->
catch
_ -> {error, {compilation_failed, File}}
end.
-else.
compile_elixir_file(_, File) ->
{error, {compilation_failed, File}}.
-endif.
install(Module, Spec, SrcDir, LibDir) ->
{ok, CurDir} = file:get_cwd(),
@ -682,11 +686,3 @@ format({Key, Val}) when is_binary(Val) ->
{Key, binary_to_list(Val)};
format({Key, Val}) -> % TODO: improve Yaml parsing
{Key, Val}.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(allow_contrib_modules) ->
fun (false) -> false;
(no) -> false;
(_) -> true
end;
opt_type(_) -> [allow_contrib_modules].

View File

@ -78,11 +78,11 @@ remove_user(User, Server, Password) ->
-spec prog_name(binary()) -> string() | undefined.
prog_name(Host) ->
ejabberd_config:get_option({extauth_program, Host}).
ejabberd_option:extauth_program(Host).
-spec pool_name(binary()) -> atom().
pool_name(Host) ->
case ejabberd_config:get_option({extauth_pool_name, Host}) of
case ejabberd_option:extauth_pool_name(Host) of
undefined ->
list_to_atom("extauth_pool_" ++ binary_to_list(Host));
Name ->
@ -95,7 +95,7 @@ worker_name(Pool, N) ->
-spec pool_size(binary()) -> pos_integer().
pool_size(Host) ->
case ejabberd_config:get_option({extauth_pool_size, Host}) of
case ejabberd_option:extauth_pool_size(Host) of
undefined ->
try erlang:system_info(logical_processors)
catch _:_ -> 1

View File

@ -27,12 +27,9 @@
-author('alexey@process-one.net').
-behaviour(ejabberd_config).
%% API
-export([add_iq_handler/5, remove_iq_handler/3, handle/1, handle/2,
check_type/1, transform_module_options/1,
opt_type/1, start/1, get_features/2]).
start/1, get_features/2]).
%% Deprecated functions
-export([add_iq_handler/6, handle/5, iqdisc/1]).
-deprecated([{add_iq_handler, 6}, {handle, 5}, {iqdisc, 1}]).
@ -135,29 +132,10 @@ process_iq(Module, Function, #iq{lang = Lang, sub_els = [El]} = IQ) ->
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
end.
-spec check_type(any()) -> no_queue.
check_type(_Type) ->
?WARNING_MSG("Option 'iqdisc' is deprecated and has no effect anymore", []),
no_queue.
-spec iqdisc(binary() | global) -> no_queue.
iqdisc(_Host) ->
no_queue.
-spec transform_module_options([{atom(), any()}]) -> [{atom(), any()}].
transform_module_options(Opts) ->
lists:map(
fun({iqdisc, {queues, N}}) ->
{iqdisc, N};
(Opt) ->
Opt
end, Opts).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(iqdisc) -> fun check_type/1;
opt_type(_) -> [iqdisc].
%%====================================================================
%% Deprecated API
%%====================================================================

File diff suppressed because it is too large Load Diff

View File

@ -184,7 +184,7 @@
{result, {[pubsubItem()], undefined | rsm_set()}}.
-callback get_last_items(nodeIdx(), jid(), undefined | rsm_set()) ->
{result, {[pubsubItem()], undefined | rsm_set()}}.
{result, [pubsubItem()]}.
-callback get_item(NodeIdx :: nodeIdx(),
ItemId :: itemId(),

View File

@ -96,7 +96,7 @@
Parents :: [nodeId()]) ->
{ok, NodeIdx::nodeIdx()} |
{error, stanza_error()} |
{error, {virtual, {host(), nodeId()}}}.
{error, {virtual, {host(), nodeId()} | nodeId()}}.
-callback delete_node(Host :: host(),
NodeId :: nodeId()) ->

View File

@ -39,7 +39,8 @@
css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0,
read_css/1, read_img/1, read_js/1, read_lua/1, try_url/1,
intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0,
is_mucsub_message/1, best_match/2]).
is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4,
parse_ip_mask/1, match_ip_mask/3]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
@ -206,10 +207,10 @@ hex_to_base64(Hex) ->
url_encode(A) ->
url_encode(A, <<>>).
-spec expand_keyword(binary(), binary(), binary()) -> binary().
-spec expand_keyword(iodata(), iodata(), iodata()) -> binary().
expand_keyword(Keyword, Input, Replacement) ->
Parts = binary:split(Input, Keyword, [global]),
str:join(Parts, Replacement).
re:replace(Input, Keyword, Replacement,
[{return, binary}, global]).
binary_to_atom(Bin) ->
erlang:binary_to_atom(Bin, utf8).
@ -412,7 +413,7 @@ format_val(Term) ->
_ -> [io_lib:nl(), S]
end.
-spec cancel_timer(reference()) -> ok.
-spec cancel_timer(reference() | undefined) -> ok.
cancel_timer(TRef) when is_reference(TRef) ->
case erlang:cancel_timer(TRef) of
false ->
@ -425,18 +426,106 @@ cancel_timer(TRef) when is_reference(TRef) ->
cancel_timer(_) ->
ok.
-spec best_match(atom(), [atom()]) -> atom().
-spec best_match(atom(), [atom()]) -> atom();
(binary(), [binary()]) -> binary().
best_match(Pattern, []) ->
Pattern;
best_match(Pattern, Opts) ->
String = atom_to_list(Pattern),
F = if is_atom(Pattern) -> fun atom_to_list/1;
is_binary(Pattern) -> fun binary_to_list/1
end,
String = F(Pattern),
{Ds, _} = lists:mapfoldl(
fun(Opt, Cache) ->
{Distance, Cache1} = ld(String, atom_to_list(Opt), Cache),
{Distance, Cache1} = ld(String, F(Opt), Cache),
{{Distance, Opt}, Cache1}
end, #{}, Opts),
element(2, lists:min(Ds)).
-spec pmap(fun((T1) -> T2), [T1]) -> [T2].
pmap(Fun, [_,_|_] = List) ->
Self = self(),
lists:map(
fun({Pid, Ref}) ->
receive
{Pid, Ret} ->
receive
{'DOWN', Ref, _, _, _} ->
Ret
end;
{'DOWN', Ref, _, _, Reason} ->
exit(Reason)
end
end, [spawn_monitor(
fun() -> Self ! {self(), Fun(X)} end)
|| X <- List]);
pmap(Fun, List) ->
lists:map(Fun, List).
-spec peach(fun((T) -> any()), [T]) -> ok.
peach(Fun, [_,_|_] = List) ->
Self = self(),
lists:foreach(
fun({Pid, Ref}) ->
receive
Pid ->
receive
{'DOWN', Ref, _, _, _} ->
ok
end;
{'DOWN', Ref, _, _, Reason} ->
exit(Reason)
end
end, [spawn_monitor(
fun() -> Fun(X), Self ! self() end)
|| X <- List]);
peach(Fun, List) ->
lists:foreach(Fun, List).
format_exception(Level, Class, Reason, Stacktrace) ->
erl_error:format_exception(
Level, Class, Reason, Stacktrace,
fun(_M, _F, _A) -> false end,
fun(Term, I) ->
io_lib:print(Term, I, 80, -1)
end).
-spec parse_ip_mask(binary()) -> {ok, {inet:ip4_address(), 0..32}} |
{ok, {inet:ip6_address(), 0..128}} |
error.
parse_ip_mask(S) ->
case econf:validate(econf:ip_mask(), S) of
{ok, _} = Ret -> Ret;
_ -> error
end.
-spec match_ip_mask(inet:ip_address(), inet:ip_address(), 0..128) -> boolean().
match_ip_mask({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) ->
IPInt = ip_to_integer(IP),
NetInt = ip_to_integer(Net),
M = bnot (1 bsl (32 - Mask) - 1),
IPInt band M =:= NetInt band M;
match_ip_mask({_, _, _, _, _, _, _, _} = IP,
{_, _, _, _, _, _, _, _} = Net, Mask) ->
IPInt = ip_to_integer(IP),
NetInt = ip_to_integer(Net),
M = bnot (1 bsl (128 - Mask) - 1),
IPInt band M =:= NetInt band M;
match_ip_mask({_, _, _, _} = IP,
{0, 0, 0, 0, 0, 16#FFFF, _, _} = Net, Mask) ->
IPInt = ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}) + ip_to_integer(IP),
NetInt = ip_to_integer(Net),
M = bnot (1 bsl (128 - Mask) - 1),
IPInt band M =:= NetInt band M;
match_ip_mask({0, 0, 0, 0, 0, 16#FFFF, _, _} = IP,
{_, _, _, _} = Net, Mask) ->
IPInt = ip_to_integer(IP) - ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}),
NetInt = ip_to_integer(Net),
M = bnot (1 bsl (32 - Mask) - 1),
IPInt band M =:= NetInt band M;
match_ip_mask(_, _, _) ->
false.
%%%===================================================================
%%% Internal functions
%%%===================================================================
@ -515,3 +604,11 @@ ld([_|ST] = S, [_|TT] = T, Cache) ->
L = 1 + lists:min([L1, L2, L3]),
{L, maps:put({S, T}, L, C3)}
end.
-spec ip_to_integer(inet:ip_address()) -> non_neg_integer().
ip_to_integer({IP1, IP2, IP3, IP4}) ->
IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
IP8}) ->
IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
bor IP5 bsl 16 bor IP6 bsl 16 bor IP7 bsl 16 bor IP8.

View File

@ -93,8 +93,7 @@ reload(_Host, _NewOpts, _OldOpts) ->
get_local_commands(Acc, _From,
#jid{server = Server, lserver = LServer} = _To, <<"">>,
Lang) ->
Display = gen_mod:get_module_opt(LServer, ?MODULE,
report_commands_node),
Display = mod_adhoc_opt:report_commands_node(LServer),
case Display of
false -> Acc;
_ ->
@ -121,8 +120,7 @@ get_local_commands(Acc, _From, _To, _Node, _Lang) ->
get_sm_commands(Acc, _From,
#jid{lserver = LServer} = To, <<"">>, Lang) ->
Display = gen_mod:get_module_opt(LServer, ?MODULE,
report_commands_node),
Display = mod_adhoc_opt:report_commands_node(LServer),
case Display of
false -> Acc;
_ ->
@ -275,7 +273,7 @@ depends(_Host, _Opts) ->
[].
mod_opt_type(report_commands_node) ->
fun (B) when is_boolean(B) -> B end.
econf:bool().
mod_options(_Host) ->
[{report_commands_node, false}].

13
src/mod_adhoc_opt.erl Normal file
View File

@ -0,0 +1,13 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_adhoc_opt).
-export([report_commands_node/1]).
-spec report_commands_node(gen_mod:opts() | global | binary()) -> boolean().
report_commands_node(Opts) when is_map(Opts) ->
gen_mod:get_opt(report_commands_node, Opts);
report_commands_node(Host) ->
gen_mod:get_module_opt(Host, mod_adhoc, report_commands_node).

View File

@ -1534,7 +1534,7 @@ stats(Name) ->
case Name of
<<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000);
<<"processes">> -> length(erlang:processes());
<<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:count_users(Host) + Sum end, 0, ejabberd_config:get_myhosts());
<<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:count_users(Host) + Sum end, 0, ejabberd_option:hosts());
<<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list());
<<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list())
end.

View File

@ -81,7 +81,7 @@ update_sql() ->
_ ->
update_sql(Host)
end
end, ejabberd_config:get_myhosts()),
end, ejabberd_option:hosts()),
ok.
-record(state, {host :: binary(),
@ -90,7 +90,7 @@ update_sql() ->
update_sql(Host) ->
LHost = jid:nameprep(Host),
DBType = ejabberd_config:get_option({sql_type, LHost}, undefined),
DBType = ejabberd_option:sql_type(LHost),
IsSupported =
case DBType of
pgsql -> true;

View File

@ -85,8 +85,8 @@ stop(Host) ->
gen_mod:stop_child(?MODULE, Host).
reload(Host, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
if NewMod /= OldMod ->
NewMod:init(Host, NewOpts);
true ->
@ -102,7 +102,7 @@ depends(_Host, _Opts) ->
%%====================================================================
init([Host, Opts]) ->
process_flag(trap_exit, true),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod = gen_mod:db_mod(Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
@ -663,7 +663,7 @@ announce_motd(#message{to = To} = Packet) ->
announce_motd(To#jid.lserver, Packet).
announce_all_hosts_motd(Packet) ->
Hosts = ejabberd_config:get_myhosts(),
Hosts = ejabberd_option:hosts(),
[announce_motd(Host, Packet) || Host <- Hosts].
announce_motd(Host, Packet) ->
@ -678,7 +678,7 @@ announce_motd_update(#message{to = To} = Packet) ->
announce_motd_update(To#jid.lserver, Packet).
announce_all_hosts_motd_update(Packet) ->
Hosts = ejabberd_config:get_myhosts(),
Hosts = ejabberd_option:hosts(),
[announce_motd_update(Host, Packet) || Host <- Hosts].
announce_motd_update(LServer, Packet) ->
@ -696,7 +696,7 @@ announce_all_hosts_motd_delete(_Packet) ->
fun(Host) ->
Mod = gen_mod:db_mod(Host, ?MODULE),
delete_motd(Mod, Host)
end, ejabberd_config:get_myhosts()).
end, ejabberd_option:hosts()).
-spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}.
send_motd({_, #{pres_last := _}} = Acc) ->
@ -829,7 +829,7 @@ send_announcement_to_all(Host, SubjectS, BodyS) ->
-spec get_access(global | binary()) -> atom().
get_access(Host) ->
gen_mod:get_module_opt(Host, ?MODULE, access).
mod_announce_opt:access(Host).
-spec add_store_hint(stanza()) -> stanza().
add_store_hint(El) ->
@ -853,9 +853,9 @@ init_cache(Mod, Host, Opts) ->
-spec cache_opts(gen_mod:opts()) -> [proplists:property()].
cache_opts(Opts) ->
MaxSize = gen_mod:get_opt(cache_size, Opts),
CacheMissed = gen_mod:get_opt(cache_missed, Opts),
LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of
MaxSize = mod_announce_opt:cache_size(Opts),
CacheMissed = mod_announce_opt:cache_missed(Opts),
LifeTime = case mod_announce_opt:cache_life_time(Opts) of
infinity -> infinity;
I -> timer:seconds(I)
end,
@ -865,7 +865,7 @@ cache_opts(Opts) ->
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(Host);
false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache)
false -> mod_announce_opt:use_cache(Host)
end.
-spec cache_nodes(module(), binary()) -> [node()].
@ -897,19 +897,23 @@ import(LServer, {sql, _}, DBType, Tab, List) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, Tab, List).
mod_opt_type(access) -> fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end.
mod_opt_type(access) ->
econf:acl();
mod_opt_type(db_type) ->
econf:well_known(db_type, ?MODULE);
mod_opt_type(use_cache) ->
econf:well_known(use_cache, ?MODULE);
mod_opt_type(cache_size) ->
econf:well_known(cache_size, ?MODULE);
mod_opt_type(cache_missed) ->
econf:well_known(cache_missed, ?MODULE);
mod_opt_type(cache_life_time) ->
econf:well_known(cache_life_time, ?MODULE).
mod_options(Host) ->
[{access, none},
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{use_cache, ejabberd_config:use_cache(Host)},
{cache_size, ejabberd_config:cache_size(Host)},
{cache_missed, ejabberd_config:cache_missed(Host)},
{cache_life_time, ejabberd_config:cache_life_time(Host)}].
{use_cache, ejabberd_option:use_cache(Host)},
{cache_size, ejabberd_option:cache_size(Host)},
{cache_missed, ejabberd_option:cache_missed(Host)},
{cache_life_time, ejabberd_option:cache_life_time(Host)}].

View File

@ -98,10 +98,10 @@ set_motd_user(LUser, LServer) ->
end,
transaction(F).
need_transform(#motd{server = S}) when is_list(S) ->
need_transform({motd, S, _}) when is_list(S) ->
?INFO_MSG("Mnesia table 'motd' will be converted to binary", []),
true;
need_transform(#motd_users{us = {U, S}}) when is_list(U) orelse is_list(S) ->
need_transform({motd_users, {U, S}, _}) when is_list(U) orelse is_list(S) ->
?INFO_MSG("Mnesia table 'motd_users' will be converted to binary", []),
true;
need_transform(_) ->

48
src/mod_announce_opt.erl Normal file
View File

@ -0,0 +1,48 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_announce_opt).
-export([access/1]).
-export([cache_life_time/1]).
-export([cache_missed/1]).
-export([cache_size/1]).
-export([db_type/1]).
-export([use_cache/1]).
-spec access(gen_mod:opts() | global | binary()) -> 'none' | acl:acl().
access(Opts) when is_map(Opts) ->
gen_mod:get_opt(access, Opts);
access(Host) ->
gen_mod:get_module_opt(Host, mod_announce, access).
-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer().
cache_life_time(Opts) when is_map(Opts) ->
gen_mod:get_opt(cache_life_time, Opts);
cache_life_time(Host) ->
gen_mod:get_module_opt(Host, mod_announce, cache_life_time).
-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean().
cache_missed(Opts) when is_map(Opts) ->
gen_mod:get_opt(cache_missed, Opts);
cache_missed(Host) ->
gen_mod:get_module_opt(Host, mod_announce, cache_missed).
-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer().
cache_size(Opts) when is_map(Opts) ->
gen_mod:get_opt(cache_size, Opts);
cache_size(Host) ->
gen_mod:get_module_opt(Host, mod_announce, cache_size).
-spec db_type(gen_mod:opts() | global | binary()) -> atom().
db_type(Opts) when is_map(Opts) ->
gen_mod:get_opt(db_type, Opts);
db_type(Host) ->
gen_mod:get_module_opt(Host, mod_announce, db_type).
-spec use_cache(gen_mod:opts() | global | binary()) -> boolean().
use_cache(Opts) when is_map(Opts) ->
gen_mod:get_opt(use_cache, Opts);
use_cache(Host) ->
gen_mod:get_module_opt(Host, mod_announce, use_cache).

View File

@ -26,7 +26,6 @@
-behaviour(mod_announce).
-compile([{parse_transform, ejabberd_sql_pt}]).
%% API
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,

View File

@ -35,7 +35,8 @@
-include("logger.hrl").
-include("pubsub.hrl").
-type convert_rules() :: {default | eimp:img_type(), eimp:img_type()}.
-opaque convert_rule() :: {default | eimp:img_type(), eimp:img_type()}.
-export_type([convert_rule/0]).
%%%===================================================================
%%% API
@ -75,7 +76,7 @@ pubsub_publish_item(LServer, ?NS_AVATAR_METADATA,
#avatar_meta{info = []} ->
delete_vcard_avatar(From);
#avatar_meta{info = Info} ->
Rules = get_converting_rules(LServer),
Rules = mod_avatar_opt:convert(LServer),
case get_meta_info(Info, Rules) of
#avatar_info{type = MimeType, id = ID, url = <<"">>} = I ->
case get_avatar_data(Host, ID) of
@ -168,7 +169,7 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec get_meta_info([avatar_info()], convert_rules()) -> avatar_info().
-spec get_meta_info([avatar_info()], [convert_rule()]) -> avatar_info().
get_meta_info(Info, Rules) ->
case lists:foldl(
fun(_, #avatar_info{} = Acc) ->
@ -317,7 +318,7 @@ publish_avatar(#iq{from = JID} = IQ, Meta, MimeType, Data, ItemID) ->
{error, eimp:error_reason() | base64_error} |
pass.
convert_avatar(LUser, LServer, VCard) ->
case get_converting_rules(LServer) of
case mod_avatar_opt:convert(LServer) of
[] ->
pass;
Rules ->
@ -329,19 +330,19 @@ convert_avatar(LUser, LServer, VCard) ->
end
end.
-spec convert_avatar(binary(), binary(), binary(), convert_rules()) ->
{ok, eimp:img_type(), binary()} |
-spec convert_avatar(binary(), binary(), binary(), [convert_rule()]) ->
{ok, binary(), binary()} |
{error, eimp:error_reason()} |
pass.
convert_avatar(LUser, LServer, Data, Rules) ->
Type = get_type(Data),
NewType = convert_to_type(Type, Rules),
if NewType == undefined orelse Type == NewType ->
if NewType == undefined ->
pass;
true ->
?DEBUG("Converting avatar of ~s@~s: ~s -> ~s",
[LUser, LServer, Type, NewType]),
RateLimit = gen_mod:get_module_opt(LServer, ?MODULE, rate_limit),
RateLimit = mod_avatar_opt:rate_limit(LServer),
Opts = [{limit_by, {LUser, LServer}},
{rate_limit, RateLimit}],
case eimp:convert(Data, NewType, Opts) of
@ -401,15 +402,11 @@ stop_with_error(Lang, Reason) ->
Txt = eimp:format_error(Reason),
{stop, xmpp:err_internal_server_error(Txt, Lang)}.
-spec get_converting_rules(binary()) -> convert_rules().
get_converting_rules(LServer) ->
gen_mod:get_module_opt(LServer, ?MODULE, convert).
-spec get_type(binary()) -> eimp:img_type() | unknown.
get_type(Data) ->
eimp:get_type(Data).
-spec convert_to_type(eimp:img_type() | unknown, convert_rules()) ->
-spec convert_to_type(eimp:img_type() | unknown, [convert_rule()]) ->
eimp:img_type() | undefined.
convert_to_type(unknown, _Rules) ->
undefined;
@ -417,6 +414,8 @@ convert_to_type(Type, Rules) ->
case proplists:get_value(Type, Rules) of
undefined ->
proplists:get_value(default, Rules);
Type ->
undefined;
T ->
T
end.
@ -435,38 +434,23 @@ decode_mime_type(MimeType) ->
encode_mime_type(Type) ->
<<"image/", (atom_to_binary(Type, latin1))/binary>>.
-spec fail(atom()) -> no_return().
fail(Format) ->
FormatS = case Format of
webp -> "WebP";
png -> "PNG";
jpeg -> "JPEG";
gif -> "GIF";
_ -> ""
end,
if FormatS /= "" ->
?WARNING_MSG("ejabberd is not compiled with ~s support", [FormatS]);
true ->
ok
end,
erlang:error(badarg).
mod_opt_type({convert, From}) ->
fun(To) when is_atom(To), To /= From ->
case eimp:is_supported(From) orelse From == default of
false ->
fail(From);
true ->
case eimp:is_supported(To) orelse To == undefined of
false -> fail(To);
true -> To
end
end
end;
mod_opt_type(convert) ->
Formats = eimp:supported_formats(),
econf:and_then(
fun(_) when Formats == [] ->
econf:fail(eimp_error);
(V) ->
V
end,
econf:options(
maps:from_list(
[{Type, econf:enum(Formats)}
|| Type <- [default|Formats]])));
mod_opt_type(rate_limit) ->
fun(I) when is_integer(I), I > 0 -> I end.
econf:pos_int().
-spec mod_options(binary()) -> [{convert, [?MODULE:convert_rule()]} |
{atom(), any()}].
mod_options(_) ->
[{rate_limit, 10},
{convert,
[{T, undefined} || T <- [default|eimp:supported_formats()]]}].
{convert, []}].

20
src/mod_avatar_opt.erl Normal file
View File

@ -0,0 +1,20 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_avatar_opt).
-export([convert/1]).
-export([rate_limit/1]).
-spec convert(gen_mod:opts() | global | binary()) -> [mod_avatar:convert_rule()].
convert(Opts) when is_map(Opts) ->
gen_mod:get_opt(convert, Opts);
convert(Host) ->
gen_mod:get_module_opt(Host, mod_avatar, convert).
-spec rate_limit(gen_mod:opts() | global | binary()) -> pos_integer().
rate_limit(Opts) when is_map(Opts) ->
gen_mod:get_opt(rate_limit, Opts);
rate_limit(Host) ->
gen_mod:get_module_opt(Host, mod_avatar, rate_limit).

View File

@ -90,8 +90,8 @@ filter_subscription(Acc, #presence{meta = #{captcha := passed}}) ->
filter_subscription(Acc, #presence{from = From, to = To, lang = Lang,
id = SID, type = subscribe} = Pres) ->
LServer = To#jid.lserver,
case gen_mod:get_module_opt(LServer, ?MODULE, drop) andalso
gen_mod:get_module_opt(LServer, ?MODULE, captcha) andalso
case mod_block_strangers_opt:drop(LServer) andalso
mod_block_strangers_opt:captcha(LServer) andalso
need_check(Pres) of
true ->
case check_subscription(From, To) of
@ -106,7 +106,7 @@ filter_subscription(Acc, #presence{from = From, to = To, lang = Lang,
Msg = #message{from = BTo, to = From,
id = ID, body = Body,
sub_els = CaptchaEls},
case gen_mod:get_module_opt(LServer, ?MODULE, log) of
case mod_block_strangers_opt:log(LServer) of
true ->
?INFO_MSG("Challenge subscription request "
"from stranger ~s to ~s with "
@ -151,8 +151,8 @@ check_message(#message{from = From, to = To, lang = Lang} = Msg) ->
true ->
case check_subscription(From, To) of
false ->
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log),
Drop = mod_block_strangers_opt:drop(LServer),
Log = mod_block_strangers_opt:log(LServer),
if
Log ->
?INFO_MSG("~s message from stranger ~s to ~s",
@ -199,8 +199,8 @@ need_check(Pkt) ->
_ ->
false
end,
AllowLocalUsers = gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users),
Access = gen_mod:get_module_opt(LServer, ?MODULE, access),
AllowLocalUsers = mod_block_strangers_opt:allow_local_users(LServer),
Access = mod_block_strangers_opt:access(LServer),
not (IsSelf orelse IsEmpty
orelse acl:match_rule(LServer, Access, From) == allow
orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>)
@ -215,7 +215,7 @@ check_subscription(From, To) ->
false;
false ->
%% Check if the contact's server is in the roster
gen_mod:get_module_opt(LocalServer, ?MODULE, allow_transports)
mod_block_strangers_opt:allow_transports(LocalServer)
andalso mod_roster:is_subscribed(jid:make(RemoteServer), To);
true ->
true
@ -230,19 +230,18 @@ sets_bare_member({U, S, <<"">>} = LBJID, Set) ->
depends(_Host, _Opts) ->
[].
mod_opt_type(drop) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(log) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(allow_local_users) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(allow_transports) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(captcha) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(access) ->
fun acl:access_rules_validator/1.
econf:acl();
mod_opt_type(drop) ->
econf:bool();
mod_opt_type(log) ->
econf:bool();
mod_opt_type(captcha) ->
econf:bool();
mod_opt_type(allow_local_users) ->
econf:bool();
mod_opt_type(allow_transports) ->
econf:bool().
mod_options(_) ->
[{access, none},

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