mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-24 17:29:28 +01:00
Use new configuration validator
This commit is contained in:
parent
d48c067681
commit
a02cff0e78
11
Makefile.in
11
Makefile.in
@ -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
|
||||
|
@ -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: {}
|
||||
|
@ -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()},
|
||||
|
@ -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}).
|
||||
|
@ -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.
|
||||
|
@ -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").
|
||||
|
@ -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)}).
|
||||
|
@ -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),
|
||||
|
@ -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}).
|
||||
|
||||
|
@ -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
|
||||
}).
|
||||
|
@ -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"}}}},
|
||||
|
293
src/ELDAPv3.erl
293
src/ELDAPv3.erl
File diff suppressed because it is too large
Load Diff
998
src/acl.erl
998
src/acl.erl
File diff suppressed because it is too large
Load Diff
529
src/econf.erl
Normal file
529
src/econf.erl
Normal 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)).
|
@ -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.".
|
||||
|
@ -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]).
|
||||
|
@ -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].
|
||||
|
@ -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]),
|
||||
|
@ -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.
|
||||
|
@ -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].
|
||||
|
@ -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].
|
||||
|
@ -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].
|
||||
|
@ -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].
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
@ -25,8 +25,6 @@
|
||||
|
||||
-module(ejabberd_auth_riak).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
@ -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].
|
||||
|
@ -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) ->
|
||||
|
@ -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}].
|
||||
|
@ -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).
|
||||
|
@ -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].
|
||||
|
@ -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().
|
||||
|
@ -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
517
src/ejabberd_config_transformer.erl
Normal file
517
src/ejabberd_config_transformer.erl
Normal 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, []))},
|
||||
[]).
|
@ -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
46
src/ejabberd_db_sup.erl
Normal 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
|
||||
%%%===================================================================
|
@ -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.
|
||||
|
@ -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},
|
||||
|
@ -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.
|
||||
|
@ -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().
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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].
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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].
|
||||
|
@ -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
655
src/ejabberd_old_config.erl
Normal 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
1058
src/ejabberd_option.erl
Normal file
File diff suppressed because it is too large
Load Diff
757
src/ejabberd_options.erl
Normal file
757
src/ejabberd_options.erl
Normal 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, <<"|">>).
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
@ -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].
|
||||
|
@ -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) ->
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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].
|
||||
|
@ -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].
|
||||
|
@ -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) ->
|
||||
|
@ -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...", []),
|
||||
|
@ -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,
|
||||
|
@ -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].
|
||||
|
@ -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},
|
||||
|
@ -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].
|
||||
|
@ -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},
|
||||
|
@ -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).
|
||||
|
@ -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}].
|
||||
|
@ -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].
|
||||
|
@ -24,7 +24,6 @@
|
||||
|
||||
-module(ejabberd_sm_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_sm).
|
||||
|
||||
|
@ -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].
|
||||
|
@ -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}
|
||||
|
@ -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].
|
||||
|
@ -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},
|
||||
|
@ -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),
|
||||
|
@ -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
@ -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().
|
||||
|
@ -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(
|
||||
|
@ -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}].
|
||||
|
@ -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.
|
||||
|
@ -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].
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
%%====================================================================
|
||||
|
931
src/gen_mod.erl
931
src/gen_mod.erl
File diff suppressed because it is too large
Load Diff
@ -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(),
|
||||
|
@ -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()) ->
|
||||
|
113
src/misc.erl
113
src/misc.erl
@ -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.
|
||||
|
@ -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
13
src/mod_adhoc_opt.erl
Normal 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).
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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)}].
|
||||
|
@ -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
48
src/mod_announce_opt.erl
Normal 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).
|
||||
|
@ -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,
|
||||
|
@ -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
20
src/mod_avatar_opt.erl
Normal 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).
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user