25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-22 16:20:52 +01:00

Use new configuration validator

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

View File

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

View File

@ -12,22 +12,10 @@
### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY ******* ### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY *******
### ******************************************************* ### *******************************************************
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description. ### 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: hosts:
- "localhost" - localhost
loglevel: 4 loglevel: 4
log_rotate_size: 10485760 log_rotate_size: 10485760
@ -36,8 +24,8 @@ log_rotate_count: 1
log_rate_limit: 100 log_rate_limit: 100
certfiles: certfiles:
- "/etc/letsencrypt/live/localhost/fullchain.pem" - /etc/letsencrypt/live/localhost/fullchain.pem
- "/etc/letsencrypt/live/localhost/privkey.pem" - /etc/letsencrypt/live/localhost/privkey.pem
listen: listen:
- -
@ -84,25 +72,25 @@ acl:
user_regexp: "" user_regexp: ""
loopback: loopback:
ip: ip:
- "127.0.0.0/8" - 127.0.0.0/8
- "::1/128" - ::1/128
access_rules: access_rules:
local: local:
- allow: local allow: local
c2s: c2s:
- deny: blocked deny: blocked
- allow allow: all
announce: announce:
- allow: admin allow: admin
configure: configure:
- allow: admin allow: admin
muc_create: muc_create:
- allow: local allow: local
pubsub_createnode: pubsub_createnode:
- allow: local allow: local
trusted_network: trusted_network:
- allow: loopback allow: loopback
api_permissions: api_permissions:
"console commands": "console commands":
@ -112,26 +100,26 @@ api_permissions:
what: "*" what: "*"
"admin access": "admin access":
who: who:
- access: access:
- allow: allow:
- acl: loopback acl: loopback
- acl: admin acl: admin
- oauth: oauth:
- scope: "ejabberd:admin" scope: "ejabberd:admin"
- access: access:
- allow: allow:
- acl: loopback acl: loopback
- acl: admin acl: admin
what: what:
- "*" - "*"
- "!stop" - "!stop"
- "!start" - "!start"
"public commands": "public commands":
who: who:
- ip: "127.0.0.1/8" ip: 127.0.0.1/8
what: what:
- "status" - status
- "connected_users_number" - connected_users_number
shaper: shaper:
normal: 1000 normal: 1000
@ -140,11 +128,11 @@ shaper:
shaper_rules: shaper_rules:
max_user_sessions: 10 max_user_sessions: 10
max_user_offline_messages: max_user_offline_messages:
- 5000: admin 5000: admin
- 100 100: all
c2s_shaper: c2s_shaper:
- none: admin none: admin
- normal normal: all
s2s_shaper: fast s2s_shaper: fast
modules: modules:
@ -163,7 +151,7 @@ modules:
mod_fail2ban: {} mod_fail2ban: {}
mod_http_api: {} mod_http_api: {}
mod_http_upload: mod_http_upload:
put_url: "https://@HOST@:5443/upload" put_url: https://@HOST@:5443/upload
mod_last: {} mod_last: {}
mod_mam: mod_mam:
## Mnesia is limited to 2GB, better to use an SQL backend ## Mnesia is limited to 2GB, better to use an SQL backend
@ -196,11 +184,11 @@ modules:
mod_pubsub: mod_pubsub:
access_createnode: pubsub_createnode access_createnode: pubsub_createnode
plugins: plugins:
- "flat" - flat
- "pep" - pep
force_node_config: force_node_config:
## Avoid buggy clients to make their bookmarks public ## Avoid buggy clients to make their bookmarks public
"storage:bookmarks": storage:bookmarks:
access_model: whitelist access_model: whitelist
mod_push: {} mod_push: {}
mod_push_keepalive: {} mod_push_keepalive: {}

View File

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

View File

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

View File

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

View File

@ -17,19 +17,5 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-compile([{parse_transform, ejabberd_sql_pt}]).
-define(SQL_MARK, sql__mark_). -include("ejabberd_sql.hrl").
-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}).

View File

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

View File

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

View File

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

View File

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

View File

@ -26,11 +26,12 @@
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.36"}}}, {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.36"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.3.4"}}}, {xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.3.4"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.19"}}}, {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"}}}, {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.5"}}}, {p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.5"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.2"}}}, {pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.2"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.8.4"}}}, {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"}}}, {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, 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"}}}}, {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.29"}}}},

View File

@ -3,6 +3,7 @@
-module('ELDAPv3'). -module('ELDAPv3').
-compile(nowarn_unused_vars). -compile(nowarn_unused_vars).
-dialyzer(no_match).
-include("ELDAPv3.hrl"). -include("ELDAPv3.hrl").
-asn1_info([{vsn,'2.0.1'}, -asn1_info([{vsn,'2.0.1'},
{module,'ELDAPv3'}, {module,'ELDAPv3'},

File diff suppressed because it is too large Load Diff

529
src/econf.erl Normal file
View File

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

View File

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

View File

@ -29,17 +29,13 @@
-include("logger.hrl"). -include("logger.hrl").
-behaviour(gen_server). -behaviour(gen_server).
-behaviour(ejabberd_config).
%% API %% API
-export([start_link/0, -export([start_link/0,
parse_api_permissions/1,
can_access/2, can_access/2,
invalidate/0, invalidate/0,
opt_type/1, validator/0,
show_current_definitions/0, show_current_definitions/0]).
register_permission_addon/2,
unregister_permission_addon/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([init/1,
@ -51,16 +47,29 @@
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-record(state, { -record(state,
definitions = none, {definitions = none :: none | [definition()]}).
fragments_generators = []
}). -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 %%% API
%%%=================================================================== %%%===================================================================
-spec can_access(atom(), caller_info()) -> allow | deny.
-spec can_access(atom(), map()) -> allow | deny.
can_access(Cmd, CallerInfo) -> can_access(Cmd, CallerInfo) ->
gen_server:call(?MODULE, {can_access, Cmd, CallerInfo}). gen_server:call(?MODULE, {can_access, Cmd, CallerInfo}).
@ -68,65 +77,24 @@ can_access(Cmd, CallerInfo) ->
invalidate() -> invalidate() ->
gen_server:cast(?MODULE, invalidate). gen_server:cast(?MODULE, invalidate).
-spec register_permission_addon(atom(), fun()) -> ok. -spec show_current_definitions() -> [definition()].
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().
show_current_definitions() -> show_current_definitions() ->
gen_server:call(?MODULE, 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() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
-spec init([]) -> {ok, state()}.
%%--------------------------------------------------------------------
%% @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.
init([]) -> init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, invalidate, 90), ejabberd_hooks:add(config_reloaded, ?MODULE, invalidate, 90),
{ok, #state{}}. {ok, #state{}}.
%%-------------------------------------------------------------------- -spec handle_call({can_access, atom(), caller_info()} |
%% @private show_current_definitions | term(),
%% @doc term(), state()) -> {reply, term(), state()}.
%% 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{}}.
handle_call({can_access, Cmd, CallerInfo}, _From, State) -> handle_call({can_access, Cmd, CallerInfo}, _From, State) ->
CallerModule = maps:get(caller_module, CallerInfo, none), CallerModule = maps:get(caller_module, CallerInfo, none),
Host = maps:get(caller_host, CallerInfo, global), Host = maps:get(caller_host, CallerInfo, global),
@ -134,123 +102,61 @@ handle_call({can_access, Cmd, CallerInfo}, _From, State) ->
{State2, Defs0} = get_definitions(State), {State2, Defs0} = get_definitions(State),
Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0, Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0,
Res = lists:foldl( Res = lists:foldl(
fun({Name, _} = Def, none) -> fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of
true -> true ->
?DEBUG("Command '~p' execution allowed by rule '~s' (CallerInfo=~p)", [Cmd, Name, CallerInfo]), ?DEBUG("Command '~p' execution allowed by rule "
allow; "'~s' (CallerInfo=~p)", [Cmd, Name, CallerInfo]),
_ -> allow;
none _ ->
end; none
(_, Val) -> end;
Val (_, Val) ->
end, none, Defs), Val
end, none, Defs),
Res2 = case Res of Res2 = case Res of
allow -> allow; allow -> allow;
_ -> _ ->
?DEBUG("Command '~p' execution denied (CallerInfo=~p)", [Cmd, CallerInfo]), ?DEBUG("Command '~p' execution denied "
"(CallerInfo=~p)", [Cmd, CallerInfo]),
deny deny
end, end,
{reply, Res2, State2}; {reply, Res2, State2};
handle_call(show_current_definitions, _From, State) -> handle_call(show_current_definitions, _From, State) ->
{State2, Defs} = get_definitions(State), {State2, Defs} = get_definitions(State),
{reply, Defs, State2}; {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) -> handle_call(_Request, _From, State) ->
{reply, ok, State}. {reply, ok, State}.
%%-------------------------------------------------------------------- -spec handle_cast(invalidate | term(), state()) -> {noreply, 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{}}.
handle_cast(invalidate, State) -> handle_cast(invalidate, State) ->
{noreply, State#state{definitions = none}}; {noreply, State#state{definitions = none}};
handle_cast(_Request, State) -> handle_cast(_Request, State) ->
{noreply, 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) -> handle_info(_Info, State) ->
{noreply, 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) -> terminate(_Reason, _State) ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, invalidate, 90). 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) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
-spec get_definitions(state()) -> {state(), [definition()]}.
-spec get_definitions(#state{}) -> {#state{}, any()}.
get_definitions(#state{definitions = Defs} = State) when Defs /= none -> get_definitions(#state{definitions = Defs} = State) when Defs /= none ->
{State, Defs}; {State, Defs};
get_definitions(#state{definitions = none, fragments_generators = Gens} = State) -> get_definitions(#state{definitions = none} = State) ->
DefaultOptions = [{<<"admin access">>, ApiPerms = ejabberd_option:api_permissions(),
{[],
[{acl,{acl,admin}},
{oauth,[<<"ejabberd:admin">>],[{acl,{acl,admin}}]}],
{all, [start, stop]}}}],
ApiPerms = ejabberd_config:get_option(api_permissions, DefaultOptions),
AllCommands = ejabberd_commands:get_commands_definition(), AllCommands = ejabberd_commands:get_commands_definition(),
Frags = lists:foldl(
fun({_Name, Generator}, Acc) ->
Acc ++ Generator()
end, [], Gens),
NDefs0 = lists:map( NDefs0 = lists:map(
fun({Name, {From, Who, {Add, Del}}}) -> fun({Name, {From, Who, {Add, Del}}}) ->
Cmds = filter_commands_with_permissions(AllCommands, Add, Del), Cmds = filter_commands_with_permissions(AllCommands, Add, Del),
{Name, {From, Who, Cmds}} {Name, {From, Who, Cmds}}
end, ApiPerms ++ Frags), end, ApiPerms),
NDefs = case lists:keyfind(<<"console commands">>, 1, NDefs0) of NDefs = case lists:keyfind(<<"console commands">>, 1, NDefs0) of
false -> false ->
[{<<"console commands">>, [{<<"console commands">>,
@ -262,6 +168,8 @@ get_definitions(#state{definitions = none, fragments_generators = Gens} = State)
end, end,
{State#state{definitions = NDefs}, NDefs}. {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) -> matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInfo) ->
case What == all orelse lists:member(Cmd, What) of case What == all orelse lists:member(Cmd, What) of
true -> true ->
@ -271,25 +179,29 @@ matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInf
true -> true ->
Scope = maps:get(oauth_scope, CallerInfo, none), Scope = maps:get(oauth_scope, CallerInfo, none),
lists:any( lists:any(
fun({access, Access}) when Scope == none -> fun({access, Access}) when Scope == none ->
acl:access_matches(Access, CallerInfo, Host) == allow; acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Acl}) when Scope == none -> ({acl, Name} = Acl) when Scope == none, is_atom(Name) ->
acl:acl_rule_matches(Acl, CallerInfo, Host); acl:match_acl(Host, Acl, CallerInfo);
({oauth, Scopes, List}) when Scope /= none -> ({acl, Acl}) when Scope == none ->
case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of acl:match_acl(Host, Acl, CallerInfo);
true -> ({oauth, {Scopes, List}}) when Scope /= none ->
lists:any( case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of
fun({access, Access}) -> true ->
acl:access_matches(Access, CallerInfo, Host) == allow; lists:any(
({acl, Acl}) -> fun({access, Access}) ->
acl:acl_rule_matches(Acl, CallerInfo, Host) acl:match_rule(Host, Access, CallerInfo) == allow;
end, List); ({acl, Name} = Acl) when is_atom(Name) ->
_ -> acl:match_acl(Host, Acl, CallerInfo);
false ({acl, Acl}) ->
end; acl:match_acl(Host, Acl, CallerInfo)
(_) -> end, List);
false _ ->
end, Who); false
end;
(_) ->
false
end, Who);
_ -> _ ->
false false
end; end;
@ -297,12 +209,15 @@ matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInf
false false
end. end.
-spec filter_commands_with_permissions([#ejabberd_commands{}], what(), what()) -> [atom()].
filter_commands_with_permissions(AllCommands, Add, Del) -> filter_commands_with_permissions(AllCommands, Add, Del) ->
CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []), CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []),
CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []), CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []),
lists:map(fun(#ejabberd_commands{name = N}) -> N end, lists:map(fun(#ejabberd_commands{name = N}) -> N end,
CommandsAdd -- CommandsDel). CommandsAdd -- CommandsDel).
-spec filter_commands_with_patterns([#ejabberd_commands{}], what(),
[#ejabberd_commands{}]) -> [#ejabberd_commands{}].
filter_commands_with_patterns([], _Patterns, Acc) -> filter_commands_with_patterns([], _Patterns, Acc) ->
Acc; Acc;
filter_commands_with_patterns([C | CRest], Patterns, 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) filter_commands_with_patterns(CRest, Patterns, Acc)
end. end.
-spec command_matches_patterns(#ejabberd_commands{}, what()) -> boolean().
command_matches_patterns(_, all) -> command_matches_patterns(_, all) ->
true; true;
command_matches_patterns(_, none) -> command_matches_patterns(_, none) ->
@ -332,125 +248,26 @@ command_matches_patterns(C, [_ | Tail]) ->
command_matches_patterns(C, Tail). command_matches_patterns(C, Tail).
%%%=================================================================== %%%===================================================================
%%% Options parsing code %%% Validators
%%%=================================================================== %%%===================================================================
-spec parse_what([binary()]) -> {what(), what()}.
parse_api_permissions(Data) when is_list(Data) -> parse_what(Defs) ->
[parse_api_permission(Name, Args) || {Name, Args} <- Data]. {A, D} =
lists:foldl(
parse_api_permission(Name, Args0) -> fun(Def, {Add, Del}) ->
Args = lists:flatten(Args0), case parse_single_what(Def) of
{From, Who, What} = case key_split(Args, [{from, []}, {who, none}, {what, []}]) of {error, Err} ->
{error, Msg} -> econf:fail({invalid_syntax, [Err, ": ", Def]});
report_error(<<"~s inside api_permission '~s' section">>, [Msg, Name]); all ->
Val -> Val {case Add of none -> none; _ -> all end, Del};
end, {neg, all} ->
{Name, {parse_from(Name, From), parse_who(Name, Who, oauth), parse_what(Name, What)}}. {none, all};
{neg, Value} ->
parse_from(_Name, Module) when is_atom(Module) -> {Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end};
[Module]; Value ->
parse_from(Name, Modules) when is_list(Modules) -> {case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del}
lists:map( end
fun(Module) when is_atom(Module) -> end, {[], []}, Defs),
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),
case {A, D} of case {A, D} of
{[], _} -> {[], _} ->
{none, all}; {none, all};
@ -458,11 +275,9 @@ parse_what(Name, Defs) when is_list(Defs) ->
{A2, none}; {A2, none};
V -> V ->
V V
end; end.
parse_what(Name, Val) ->
report_error(<<"Invalid value '~p' used inside 'what' section for api_permission '~s'">>,
[Val, Name]).
-spec parse_single_what(binary()) -> atom() | {neg, atom()} | {tag, atom()} | {error, string()}.
parse_single_what(<<"*">>) -> parse_single_what(<<"*">>) ->
all; all;
parse_single_what(<<"!*">>) -> parse_single_what(<<"!*">>) ->
@ -470,7 +285,7 @@ parse_single_what(<<"!*">>) ->
parse_single_what(<<"!", Rest/binary>>) -> parse_single_what(<<"!", Rest/binary>>) ->
case parse_single_what(Rest) of case parse_single_what(Rest) of
{neg, _} -> {neg, _} ->
{error, <<"Double negation">>}; {error, "double negation"};
{error, _} = Err -> {error, _} = Err ->
Err; Err;
V -> V ->
@ -485,71 +300,78 @@ parse_single_what(<<"[tag:", Rest/binary>>) ->
V when is_atom(V) -> V when is_atom(V) ->
{tag, V}; {tag, V};
_ -> _ ->
{error, <<"Invalid tag">>} {error, "invalid tag"}
end; end;
_ -> _ ->
{error, <<"Invalid tag">>} {error, "invalid tag"}
end; end;
parse_single_what(Binary) when is_binary(Binary) -> parse_single_what(B) ->
case is_valid_command_name(Binary) of case re:run(B, "^[a-z0-9_\\-]*$") of
true -> nomatch -> {error, "invalid command"};
binary_to_atom(Binary, latin1); _ -> binary_to_atom(B, 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])
end. end.
report_error(Format, Args) -> validator(Map, Opts) ->
throw({invalid_syntax, (str:format(Format, Args))}). 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) -> validator(from) ->
{error, (str:format(Format, Args))}. 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) -> validator() ->
fun parse_api_permissions/1; econf:map(
opt_type(_) -> econf:binary(),
[api_permissions]. econf:and_then(
econf:options(
#{from => validator(from),
what => validator(what),
who => validator(who)}),
fun(Os) ->
{proplists:get_value(from, Os, []),
proplists:get_value(who, Os, none),
proplists:get_value(what, Os, [])}
end),
[unique]).

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -211,7 +211,6 @@
-author('badlop@process-one.net'). -author('badlop@process-one.net').
-behaviour(gen_server). -behaviour(gen_server).
-behaviour(ejabberd_config).
-define(DEFAULT_VERSION, 1000000). -define(DEFAULT_VERSION, 1000000).
@ -225,11 +224,8 @@
get_command_definition/2, get_command_definition/2,
get_tags_commands/0, get_tags_commands/0,
get_tags_commands/1, get_tags_commands/1,
get_exposed_commands/0,
register_commands/1, register_commands/1,
unregister_commands/1, unregister_commands/1,
expose_commands/1,
opt_type/1,
get_commands_spec/0, get_commands_spec/0,
get_commands_definition/0, get_commands_definition/0,
get_commands_definition/1, get_commands_definition/1,
@ -245,6 +241,8 @@
-define(POLICY_ACCESS, '$policy'). -define(POLICY_ACCESS, '$policy').
-type auth() :: {binary(), binary(), binary() | {oauth, binary()}, boolean()} | map().
-record(state, {}). -record(state, {}).
get_commands_spec() -> get_commands_spec() ->
@ -292,7 +290,6 @@ init([]) ->
{attributes, record_info(fields, ejabberd_commands)}, {attributes, record_info(fields, ejabberd_commands)},
{type, bag}]), {type, bag}]),
register_commands(get_commands_spec()), register_commands(get_commands_spec()),
ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0),
{ok, #state{}}. {ok, #state{}}.
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
@ -338,29 +335,7 @@ unregister_commands(Commands) ->
mnesia:dirty_delete_object(Command) mnesia:dirty_delete_object(Command)
end, end,
Commands), Commands),
ejabberd_access_permissions:invalidate(), 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.
-spec list_commands() -> [{atom(), [aterm()], string()}]. -spec list_commands() -> [{atom(), [aterm()], string()}].
@ -378,20 +353,6 @@ list_commands(Version) ->
args = Args, args = Args,
desc = Desc} <- Commands]. 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()}. -spec get_command_format(atom()) -> {[aterm()], rterm()}.
%% @doc Get the format of arguments and result of a command. %% @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) ->
get_command_format(Name, Auth, ?DEFAULT_VERSION). get_command_format(Name, Auth, ?DEFAULT_VERSION).
-spec get_command_format(atom(), -spec get_command_format(atom(), noauth | admin | auth(), integer()) -> {[aterm()], rterm()}.
{binary(), binary(), binary(), boolean()} |
noauth | admin,
integer()) ->
{[aterm()], rterm()}.
get_command_format(Name, Auth, Version) -> get_command_format(Name, Auth, Version) ->
Admin = is_admin(Name, Auth, #{}), Admin = is_admin(Name, Auth, #{}),
#ejabberd_commands{args = Args, #ejabberd_commands{args = Args,
@ -422,12 +378,6 @@ get_command_format(Name, Auth, Version) ->
{Args, Result} {Args, Result}
end. 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(). -spec get_command_definition(atom()) -> ejabberd_commands().
%% @doc Get the definition record of a command. %% @doc Get the definition record of a command.
@ -533,95 +483,12 @@ get_tags_commands(Version) ->
%% ----------------------------- %% -----------------------------
%% Access verification %% Access verification
%% ----------------------------- %% -----------------------------
-spec is_admin(atom(), admin | noauth | auth(), map()) -> boolean().
-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).
is_admin(_Name, admin, _Extra) -> is_admin(_Name, admin, _Extra) ->
true; true;
is_admin(_Name, {_User, _Server, _, false}, _Extra) -> is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
false; false;
is_admin(_Name, Map, _extra) when is_map(Map) -> is_admin(_Name, Map, _extra) when is_map(Map) ->
true; true;
is_admin(Name, Auth, Extra) -> is_admin(_Name, _Auth, _Extra) ->
{ACLInfo, Server} = case Auth of false.
{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].

File diff suppressed because it is too large Load Diff

View File

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

View File

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

46
src/ejabberd_db_sup.erl Normal file
View File

@ -0,0 +1,46 @@
%%%-------------------------------------------------------------------
%%% Created : 13 June 2019 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_db_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
%%%===================================================================
%%% API functions
%%%===================================================================
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================
init([]) ->
{ok, {{one_for_one, 10, 1}, []}}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

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

View File

@ -25,17 +25,15 @@
-module(ejabberd_http). -module(ejabberd_http).
-behaviour(ejabberd_listener). -behaviour(ejabberd_listener).
-behaviour(ejabberd_config).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
%% External exports %% External exports
-export([start/3, start_link/3, -export([start/3, start_link/3,
accept/1, receive_headers/1, recv_file/2, accept/1, receive_headers/1, recv_file/2,
transform_listen_option/2, listen_opt_type/1, listen_opt_type/1, listen_options/0]).
listen_options/0]).
-export([init/3, opt_type/1]). -export([init/3]).
-include("logger.hrl"). -include("logger.hrl").
-include("xmpp.hrl"). -include("xmpp.hrl").
@ -112,9 +110,10 @@ init(SockMod, Socket, Opts) ->
false -> [compression_none | TLSOpts1]; false -> [compression_none | TLSOpts1];
true -> TLSOpts1 true -> TLSOpts1
end, end,
TLSOpts3 = case get_certfile(Opts) of TLSOpts3 = case ejabberd_pkix:get_certfile(
undefined -> TLSOpts2; ejabberd_config:get_myname()) of
CertFile -> [{certfile, CertFile}|TLSOpts2] error -> TLSOpts2;
{ok, CertFile} -> [{certfile, CertFile}|TLSOpts2]
end, end,
TLSOpts = [verify_none | TLSOpts3], TLSOpts = [verify_none | TLSOpts3],
{SockMod1, Socket1} = if TLSEnabled -> {SockMod1, Socket1} = if TLSEnabled ->
@ -124,30 +123,8 @@ init(SockMod, Socket, Opts) ->
{fast_tls, TLSSocket}; {fast_tls, TLSSocket};
true -> {SockMod, Socket} true -> {SockMod, Socket}
end, 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), SockPeer = proplists:get_value(sock_peer_name, Opts, none),
DefinedHandlers = proplists:get_value(request_handlers, Opts, []), RequestHandlers = proplists:get_value(request_handlers, Opts, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC,
?DEBUG("S: ~p~n", [RequestHandlers]), ?DEBUG("S: ~p~n", [RequestHandlers]),
DefaultHost = proplists:get_value(default_host, Opts), DefaultHost = proplists:get_value(default_host, Opts),
@ -557,7 +534,7 @@ analyze_ip_xff(IP, [], _Host) -> IP;
analyze_ip_xff({IPLast, Port}, XFF, Host) -> analyze_ip_xff({IPLast, Port}, XFF, Host) ->
[ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++ [ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++
[misc:ip_to_list(IPLast)], [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, IPClient = case is_ipchain_trusted(ProxiesIPs,
TrustedProxies) TrustedProxies)
of of
@ -581,7 +558,7 @@ is_ipchain_trusted(UserIPs, Masks) ->
{ok, IP2} -> {ok, IP2} ->
lists:any( lists:any(
fun({Mask, MaskLen}) -> fun({Mask, MaskLen}) ->
acl:ip_matches_mask(IP2, Mask, MaskLen) misc:match_ip_mask(IP2, Mask, MaskLen)
end, Masks); end, Masks);
_ -> _ ->
false false
@ -803,7 +780,7 @@ rest_dir(N, Path, <<_H, T/binary>>) -> rest_dir(N, Path, T).
expand_custom_headers(Headers) -> expand_custom_headers(Headers) ->
lists:map(fun({K, V}) -> lists:map(fun({K, V}) ->
{K, misc:expand_keyword(<<"@VERSION@">>, V, {K, misc:expand_keyword(<<"@VERSION@">>, V,
ejabberd_config:get_version())} ejabberd_option:version())}
end, Headers). end, Headers).
code_to_phrase(100) -> <<"Continue">>; code_to_phrase(100) -> <<"Continue">>;
@ -851,7 +828,7 @@ code_to_phrase(503) -> <<"Service Unavailable">>;
code_to_phrase(504) -> <<"Gateway Timeout">>; code_to_phrase(504) -> <<"Gateway Timeout">>;
code_to_phrase(505) -> <<"HTTP Version Not Supported">>. 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>>) -> parse_auth(<<"Basic ", Auth64/binary>>) ->
try base64:decode(Auth64) of try base64:decode(Auth64) of
Auth -> Auth ->
@ -927,150 +904,32 @@ normalize_path([_Parent, <<"..">>|Path], Norm) ->
normalize_path([Part | Path], Norm) -> normalize_path([Part | Path], Norm) ->
normalize_path(Path, [Part|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) -> listen_opt_type(tag) ->
fun(B) when is_binary(B) -> B end; econf:binary();
listen_opt_type(request_handlers) -> listen_opt_type(request_handlers) ->
fun(Hs) -> econf:and_then(
Hs1 = lists:map(fun econf:map(
({Mod, Path}) when is_atom(Mod) -> {Path, Mod}; econf:binary(),
({Path, Mod}) -> {Path, Mod} econf:beam([[{socket_handoff, 3}, {process, 2}]])),
end, Hs), fun(L) ->
Hs2 = [{str:tokens( [{str:tokens(Path, <<"/">>), Mod} || {Path, Mod} <- L]
iolist_to_binary(Path), <<"/">>), end);
Mod} || {Path, Mod} <- Hs1],
[{Path, prepare_request_module(Mod)} || {Path, Mod} <- Hs2]
end;
listen_opt_type(default_host) -> listen_opt_type(default_host) ->
fun iolist_to_binary/1; econf:domain();
listen_opt_type(custom_headers) -> 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() -> listen_options() ->
[{certfile, undefined}, [{ciphers, undefined},
{ciphers, undefined},
{dhfile, undefined}, {dhfile, undefined},
{cafile, undefined}, {cafile, undefined},
{protocol_options, undefined}, {protocol_options, undefined},
{tls, false}, {tls, false},
{tls_compression, false}, {tls_compression, false},
{captcha, false},
{register, false},
{web_admin, false},
{http_bind, false},
{xmlrpc, false},
{request_handlers, []}, {request_handlers, []},
{tag, <<>>}, {tag, <<>>},
{default_host, undefined}, {default_host, undefined},

View File

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

View File

@ -70,7 +70,7 @@ dispatch(_) ->
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
init([]) -> init([]) ->
ets:new(?MODULE, [named_table, ordered_set, public]), _ = ets:new(?MODULE, [named_table, ordered_set, public]),
{ok, #state{}}. {ok, #state{}}.
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
@ -166,7 +166,7 @@ decode_id(_) ->
-spec calc_checksum(binary()) -> binary(). -spec calc_checksum(binary()) -> binary().
calc_checksum(Data) -> 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>>)). base64:encode(crypto:hash(sha, <<Data/binary, Key/binary>>)).
-spec callback(atom() | pid(), #iq{} | timeout, term()) -> any(). -spec callback(atom() | pid(), #iq{} | timeout, term()) -> any().

View File

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

View File

@ -106,7 +106,7 @@ get_features(Host) ->
init([]) -> init([]) ->
process_flag(trap_exit, true), 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_up, ?MODULE, host_up, 10),
ejabberd_hooks:add(host_down, ?MODULE, host_down, 100), ejabberd_hooks:add(host_down, ?MODULE, host_down, 100),
gen_iq_handler:start(?MODULE), gen_iq_handler:start(?MODULE),
@ -126,7 +126,7 @@ handle_info(Info, State) ->
{noreply, State}. {noreply, State}.
terminate(_Reason, _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_up, ?MODULE, host_up, 10),
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 100), ejabberd_hooks:delete(host_down, ?MODULE, host_down, 100),
ok. ok.

View File

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

View File

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

View File

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

View File

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

View File

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

655
src/ejabberd_old_config.erl Normal file
View File

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

1058
src/ejabberd_option.erl Normal file

File diff suppressed because it is too large Load Diff

757
src/ejabberd_options.erl Normal file
View File

@ -0,0 +1,757 @@
%%%----------------------------------------------------------------------
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_options).
-behaviour(ejabberd_config).
-export([opt_type/1, options/0, globals/0]).
-ifdef(NEW_SQL_SCHEMA).
-define(USE_NEW_SQL_SCHEMA_DEFAULT, true).
-else.
-define(USE_NEW_SQL_SCHEMA_DEFAULT, false).
-endif.
-include_lib("kernel/include/inet.hrl").
%%%===================================================================
%%% API
%%%===================================================================
-spec opt_type(atom()) -> econf:validator().
opt_type(access_rules) ->
acl:validator(access_rules);
opt_type(acl) ->
acl:validator(acl);
opt_type(acme) ->
econf:options(
#{ca_url => econf:url(),
contact => econf:binary("^[a-zA-Z]+:[^:]+$")},
[unique, {return, map}]);
opt_type(allow_contrib_modules) ->
econf:bool();
opt_type(allow_multiple_connections) ->
econf:bool();
opt_type(anonymous_protocol) ->
econf:enum([sasl_anon, login_anon, both]);
opt_type(api_permissions) ->
ejabberd_access_permissions:validator();
opt_type(append_host_config) ->
econf:map(
econf:and_then(
econf:domain(),
econf:enum(ejabberd_option:hosts())),
validator());
opt_type(auth_cache_life_time) ->
econf:pos_int(infinity);
opt_type(auth_cache_missed) ->
econf:bool();
opt_type(auth_cache_size) ->
econf:pos_int(infinity);
opt_type(auth_method) ->
econf:list_or_single(econf:db_type(ejabberd_auth));
opt_type(auth_password_format) ->
econf:enum([plain, scram]);
opt_type(auth_use_cache) ->
econf:bool();
opt_type(c2s_cafile) ->
econf:file();
opt_type(c2s_ciphers) ->
econf:binary();
opt_type(c2s_dhfile) ->
econf:file();
opt_type(c2s_protocol_options) ->
econf:and_then(
econf:list(econf:binary(), [unique]),
fun concat_tls_protocol_options/1);
opt_type(c2s_tls_compression) ->
econf:bool();
opt_type(ca_file) ->
econf:pem();
opt_type(cache_life_time) ->
econf:pos_int(infinity);
opt_type(cache_missed) ->
econf:bool();
opt_type(cache_size) ->
econf:pos_int(infinity);
opt_type(captcha_cmd) ->
econf:file();
opt_type(captcha_host) ->
econf:binary();
opt_type(captcha_limit) ->
econf:pos_int(infinity);
opt_type(certfiles) ->
econf:list(econf:binary());
opt_type(cluster_backend) ->
econf:db_type(ejabberd_cluster);
opt_type(cluster_nodes) ->
econf:list(econf:atom(), [unique]);
opt_type(default_db) ->
econf:enum([mnesia, riak, sql]);
opt_type(default_ram_db) ->
econf:enum([mnesia, riak, sql, redis]);
opt_type(define_macro) ->
econf:any();
opt_type(disable_sasl_mechanisms) ->
econf:list_or_single(
econf:and_then(
econf:binary(),
fun str:to_upper/1));
opt_type(domain_balancing) ->
econf:map(
econf:domain(),
econf:options(
#{component_number => econf:int(2, 1000),
type => econf:enum([random, source, destination,
bare_source, bare_destination])},
[{required, [component_number]}, {return, map}, unique]),
[{return, map}]);
opt_type(ext_api_path_oauth) ->
econf:binary();
opt_type(ext_api_http_pool_size) ->
econf:pos_int();
opt_type(ext_api_url) ->
econf:url();
opt_type(ext_api_headers) ->
econf:binary();
opt_type(extauth_pool_name) ->
econf:binary();
opt_type(extauth_pool_size) ->
econf:pos_int();
opt_type(extauth_program) ->
econf:string();
opt_type(fqdn) ->
econf:list_or_single(econf:domain());
opt_type(hide_sensitive_log_data) ->
econf:bool();
opt_type(host_config) ->
econf:map(
econf:and_then(
econf:domain(),
econf:enum(ejabberd_option:hosts())),
validator());
opt_type(hosts) ->
econf:non_empty(econf:list(econf:domain(), [unique]));
opt_type(include_config_file) ->
econf:any();
opt_type(language) ->
econf:lang();
opt_type(ldap_backups) ->
econf:list(econf:domain(), [unique]);
opt_type(ldap_base) ->
econf:binary();
opt_type(ldap_deref_aliases) ->
econf:enum([never, searching, finding, always]);
opt_type(ldap_dn_filter) ->
econf:and_then(
econf:non_empty(
econf:map(
econf:ldap_filter(),
econf:list(econf:binary()))),
fun hd/1);
opt_type(ldap_encrypt) ->
econf:enum([tls, starttls, none]);
opt_type(ldap_filter) ->
econf:ldap_filter();
opt_type(ldap_password) ->
econf:binary();
opt_type(ldap_port) ->
econf:port();
opt_type(ldap_rootdn) ->
econf:binary();
opt_type(ldap_servers) ->
econf:list(econf:domain(), [unique]);
opt_type(ldap_tls_cacertfile) ->
econf:pem();
opt_type(ldap_tls_certfile) ->
econf:pem();
opt_type(ldap_tls_depth) ->
econf:non_neg_int();
opt_type(ldap_tls_verify) ->
econf:enum([hard, soft, false]);
opt_type(ldap_uids) ->
econf:either(
econf:list(
econf:and_then(
econf:binary(),
fun(U) -> {U, <<"%u">>} end)),
econf:map(econf:binary(), econf:binary(), [unique]));
opt_type(listen) ->
ejabberd_listener:validator();
opt_type(log_rate_limit) ->
econf:non_neg_int();
opt_type(log_rotate_count) ->
econf:non_neg_int();
opt_type(log_rotate_date) ->
econf:string("^(\\$((D(([0-9])|(1[0-9])|(2[0-3])))|"
"(((W[0-6])|(M(([1-2][0-9])|(3[0-1])|([1-9]))))"
"(D(([0-9])|(1[0-9])|(2[0-3])))?)))?$");
opt_type(log_rotate_size) ->
econf:non_neg_int();
opt_type(loglevel) ->
econf:int(0, 5);
opt_type(max_fsm_queue) ->
econf:pos_int();
opt_type(modules) ->
econf:map(econf:atom(), econf:any());
opt_type(negotiation_timeout) ->
econf:timeout(second);
opt_type(net_ticktime) ->
econf:pos_int();
opt_type(new_sql_schema) ->
econf:bool();
opt_type(oauth_access) ->
econf:acl();
opt_type(oauth_cache_life_time) ->
econf:pos_int(infinity);
opt_type(oauth_cache_missed) ->
econf:bool();
opt_type(oauth_cache_size) ->
econf:pos_int(infinity);
opt_type(oauth_db_type) ->
econf:db_type(ejabberd_oauth);
opt_type(oauth_expire) ->
econf:non_neg_int();
opt_type(oauth_use_cache) ->
econf:bool();
opt_type(oom_killer) ->
econf:bool();
opt_type(oom_queue) ->
econf:pos_int();
opt_type(oom_watermark) ->
econf:int(1, 99);
opt_type(outgoing_s2s_families) ->
econf:and_then(
econf:non_empty(
econf:list(econf:enum([ipv4, ipv6]), [unique])),
fun(L) ->
lists:map(
fun(ipv4) -> inet;
(ipv6) -> inet6
end, L)
end);
opt_type(outgoing_s2s_port) ->
econf:port();
opt_type(outgoing_s2s_timeout) ->
econf:timeout(second, infinity);
opt_type(pam_service) ->
econf:binary();
opt_type(pam_userinfotype) ->
econf:enum([username, jid]);
opt_type(pgsql_users_number_estimate) ->
econf:bool();
opt_type(queue_dir) ->
econf:directory(write);
opt_type(queue_type) ->
econf:enum([ram, file]);
opt_type(redis_connect_timeout) ->
econf:timeout(second);
opt_type(redis_db) ->
econf:non_neg_int();
opt_type(redis_password) ->
econf:string();
opt_type(redis_pool_size) ->
econf:pos_int();
opt_type(redis_port) ->
econf:port();
opt_type(redis_queue_type) ->
econf:enum([ram, file]);
opt_type(redis_server) ->
econf:string();
opt_type(registration_timeout) ->
econf:pos_int(infinity);
opt_type(resource_conflict) ->
econf:enum([setresource, closeold, closenew, acceptnew]);
opt_type(riak_cacertfile) ->
econf:and_then(econf:pem(), econf:string());
opt_type(riak_password) ->
econf:string();
opt_type(riak_pool_size) ->
econf:pos_int();
opt_type(riak_port) ->
econf:port();
opt_type(riak_server) ->
econf:string();
opt_type(riak_start_interval) ->
econf:timeout(second);
opt_type(riak_username) ->
econf:string();
opt_type(route_subdomains) ->
econf:enum([s2s, local]);
opt_type(router_cache_life_time) ->
econf:pos_int(infinity);
opt_type(router_cache_missed) ->
econf:bool();
opt_type(router_cache_size) ->
econf:pos_int(infinity);
opt_type(router_db_type) ->
econf:db_type(ejabberd_router);
opt_type(router_use_cache) ->
econf:bool();
opt_type(rpc_timeout) ->
econf:timeout(second);
opt_type(s2s_access) ->
econf:acl();
opt_type(s2s_cafile) ->
econf:pem();
opt_type(s2s_ciphers) ->
econf:binary();
opt_type(s2s_dhfile) ->
econf:file();
opt_type(s2s_dns_retries) ->
econf:non_neg_int();
opt_type(s2s_dns_timeout) ->
econf:timeout(second, infinity);
opt_type(s2s_max_retry_delay) ->
econf:pos_int();
opt_type(s2s_protocol_options) ->
econf:and_then(
econf:list(econf:binary(), [unique]),
fun concat_tls_protocol_options/1);
opt_type(s2s_queue_type) ->
econf:enum([ram, file]);
opt_type(s2s_timeout) ->
econf:timeout(second, infinity);
opt_type(s2s_tls_compression) ->
econf:bool();
opt_type(s2s_use_starttls) ->
econf:either(
econf:bool(),
econf:enum([optional, required]));
opt_type(s2s_zlib) ->
econf:and_then(
econf:bool(),
fun(false) -> false;
(true) ->
ejabberd:start_app(ezlib),
true
end);
opt_type(shaper) ->
ejabberd_shaper:validator(shaper);
opt_type(shaper_rules) ->
ejabberd_shaper:validator(shaper_rules);
opt_type(sm_cache_life_time) ->
econf:pos_int(infinity);
opt_type(sm_cache_missed) ->
econf:bool();
opt_type(sm_cache_size) ->
econf:pos_int(infinity);
opt_type(sm_db_type) ->
econf:db_type(ejabberd_sm);
opt_type(sm_use_cache) ->
econf:bool();
opt_type(sql_connect_timeout) ->
econf:timeout(second);
opt_type(sql_database) ->
econf:binary();
opt_type(sql_keepalive_interval) ->
econf:timeout(second);
opt_type(sql_password) ->
econf:binary();
opt_type(sql_pool_size) ->
econf:pos_int();
opt_type(sql_port) ->
econf:port();
opt_type(sql_query_timeout) ->
econf:timeout(second);
opt_type(sql_queue_type) ->
econf:enum([ram, file]);
opt_type(sql_server) ->
econf:binary();
opt_type(sql_ssl) ->
econf:bool();
opt_type(sql_ssl_cafile) ->
econf:pem();
opt_type(sql_ssl_certfile) ->
econf:pem();
opt_type(sql_ssl_verify) ->
econf:bool();
opt_type(sql_start_interval) ->
econf:timeout(second);
opt_type(sql_type) ->
econf:enum([mysql, pgsql, sqlite, mssql, odbc]);
opt_type(sql_username) ->
econf:binary();
opt_type(trusted_proxies) ->
econf:either(all, econf:list(econf:ip_mask()));
opt_type(use_cache) ->
econf:bool();
opt_type(validate_stream) ->
econf:bool();
opt_type(version) ->
econf:binary();
opt_type(websocket_origin) ->
econf:list(
econf:and_then(
econf:and_then(
econf:binary_sep("\\s+"),
econf:list(econf:url(), [unique])),
fun(L) -> str:join(L, <<" ">>) end),
[unique]);
opt_type(websocket_ping_interval) ->
econf:timeout(second);
opt_type(websocket_timeout) ->
econf:timeout(second).
%% We only define the types of options that cannot be derived
%% automatically by tools/opt_type.sh script
-spec options() -> [{s2s_protocol_options, undefined | binary()} |
{c2s_protocol_options, undefined | binary()} |
{websocket_origin, [binary()]} |
{disable_sasl_mechanisms, [binary()]} |
{s2s_zlib, boolean()} |
{listen, [ejabberd_listener:listener()]} |
{modules, [{module(), gen_mod:opts(), integer()}]} |
{ldap_uids, [{binary(), binary()}]} |
{ldap_dn_filter, {binary(), [binary()]}} |
{outgoing_s2s_families, [inet | inet6, ...]} |
{acl, [{atom(), [acl:acl_rule()]}]} |
{access_rules, [{atom(), acl:access()}]} |
{shaper, #{atom() => ejabberd_shaper:shaper_rate()}} |
{shaper_rules, [{atom(), [ejabberd_shaper:shaper_rule()]}]} |
{api_permissions, [ejabberd_access_permissions:permission()]} |
{append_host_config, [{binary(), any()}]} |
{host_config, [{binary(), any()}]} |
{define_macro, any()} |
{include_config_file, any()} |
{atom(), any()}].
options() ->
[%% Top-priority options
hosts,
{loglevel, 4},
{cache_life_time, 3600},
{cache_missed, true},
{cache_size, 1000},
{use_cache, true},
{default_db, mnesia},
{default_ram_db, mnesia},
{queue_type, ram},
%% Other options
{acl, []},
{access_rules, []},
{acme, #{}},
{allow_contrib_modules, true},
{allow_multiple_connections, false},
{anonymous_protocol, sasl_anon},
{api_permissions,
[{<<"admin access">>,
{[],
[{acl, admin},
{oauth, {[<<"ejabberd:admin">>], [{acl, admin}]}}],
{all, [start, stop]}}}]},
{append_host_config, []},
{auth_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{auth_cache_missed,
fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end},
{auth_cache_size,
fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end},
{auth_method,
fun(Host) -> [ejabberd_config:default_db(Host, ejabberd_auth)] end},
{auth_password_format, plain},
{auth_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{c2s_cafile, undefined},
{c2s_ciphers, undefined},
{c2s_dhfile, undefined},
{c2s_protocol_options, undefined},
{c2s_tls_compression, undefined},
{ca_file, iolist_to_binary(pkix:get_cafile())},
{captcha_cmd, undefined},
{captcha_host, <<"">>},
{captcha_limit, infinity},
{certfiles, undefined},
{cluster_backend, mnesia},
{cluster_nodes, []},
{define_macro, []},
{disable_sasl_mechanisms, []},
{domain_balancing, #{}},
{ext_api_headers, <<>>},
{ext_api_http_pool_size, 100},
{ext_api_path_oauth, <<"/oauth">>},
{ext_api_url, <<"http://localhost/api">>},
{extauth_pool_name, undefined},
{extauth_pool_size, undefined},
{extauth_program, undefined},
{fqdn, fun fqdn/1},
{hide_sensitive_log_data, false},
{host_config, []},
{include_config_file, []},
{language, <<"en">>},
{ldap_backups, []},
{ldap_base, <<"">>},
{ldap_deref_aliases, never},
{ldap_dn_filter, {undefined, []}},
{ldap_encrypt, none},
{ldap_filter, <<"">>},
{ldap_password, <<"">>},
{ldap_port,
fun(Host) ->
case ejabberd_option:ldap_encrypt(Host) of
tls -> 636;
_ -> 389
end
end},
{ldap_rootdn, <<"">>},
{ldap_servers, [<<"localhost">>]},
{ldap_tls_cacertfile, undefined},
{ldap_tls_certfile, undefined},
{ldap_tls_depth, undefined},
{ldap_tls_verify, false},
{ldap_uids, [{<<"uid">>, <<"%u">>}]},
{listen, []},
{log_rate_limit, undefined},
{log_rotate_count, undefined},
{log_rotate_date, undefined},
{log_rotate_size, undefined},
{max_fsm_queue, undefined},
{modules, []},
{negotiation_timeout, timer:seconds(30)},
{net_ticktime, 60},
{new_sql_schema, ?USE_NEW_SQL_SCHEMA_DEFAULT},
{oauth_access, none},
{oauth_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{oauth_cache_missed,
fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end},
{oauth_cache_size,
fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end},
{oauth_db_type,
fun(Host) -> ejabberd_config:default_db(Host, ejabberd_oauth) end},
{oauth_expire, 4294967},
{oauth_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{oom_killer, true},
{oom_queue, 10000},
{oom_watermark, 80},
{outgoing_s2s_families, [inet, inet6]},
{outgoing_s2s_port, 5269},
{outgoing_s2s_timeout, timer:seconds(10)},
{pam_service, <<"ejabberd">>},
{pam_userinfotype, username},
{pgsql_users_number_estimate, false},
{queue_dir, undefined},
{redis_connect_timeout, timer:seconds(1)},
{redis_db, 0},
{redis_password, ""},
{redis_pool_size, 10},
{redis_port, 6379},
{redis_queue_type,
fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end},
{redis_server, "localhost"},
{registration_timeout, 600},
{resource_conflict, acceptnew},
{riak_cacertfile, nil},
{riak_password, nil},
{riak_pool_size, 10},
{riak_port, 8087},
{riak_server, "127.0.0.1"},
{riak_start_interval, timer:seconds(30)},
{riak_username, nil},
{route_subdomains, local},
{router_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{router_cache_missed,
fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end},
{router_cache_size,
fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end},
{router_db_type,
fun(Host) -> ejabberd_config:default_ram_db(Host, ejabberd_router) end},
{router_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{rpc_timeout, timer:seconds(5)},
{s2s_access, all},
{s2s_cafile, undefined},
{s2s_ciphers, undefined},
{s2s_dhfile, undefined},
{s2s_dns_retries, 2},
{s2s_dns_timeout, timer:seconds(10)},
{s2s_max_retry_delay, 300},
{s2s_protocol_options, undefined},
{s2s_queue_type,
fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end},
{s2s_timeout, timer:minutes(10)},
{s2s_tls_compression, undefined},
{s2s_use_starttls, false},
{s2s_zlib, false},
{shaper, #{}},
{shaper_rules, []},
{sm_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{sm_cache_missed,
fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end},
{sm_cache_size,
fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end},
{sm_db_type,
fun(Host) -> ejabberd_config:default_ram_db(Host, ejabberd_sm) end},
{sm_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{sql_type, undefined},
{sql_connect_timeout, timer:seconds(5)},
{sql_database, undefined},
{sql_keepalive_interval, undefined},
{sql_password, <<"">>},
{sql_pool_size,
fun(Host) ->
case ejabberd_config:get_option({sql_type, Host}) of
sqlite -> 1;
_ -> 10
end
end},
{sql_port,
fun(Host) ->
case ejabberd_config:get_option({sql_type, Host}) of
mssql -> 1433;
mysql -> 3306;
pgsql -> 5432;
_ -> undefined
end
end},
{sql_query_timeout, timer:seconds(60)},
{sql_queue_type,
fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end},
{sql_server, <<"localhost">>},
{sql_ssl, false},
{sql_ssl_cafile, undefined},
{sql_ssl_certfile, undefined},
{sql_ssl_verify, false},
{sql_start_interval, timer:seconds(30)},
{sql_username, <<"ejabberd">>},
{trusted_proxies, []},
{validate_stream, false},
{version, fun version/1},
{websocket_origin, []},
{websocket_ping_interval, timer:seconds(60)},
{websocket_timeout, timer:minutes(5)}].
-spec globals() -> [atom()].
globals() ->
[acme,
allow_contrib_modules,
api_permissions,
append_host_config,
auth_cache_life_time,
auth_cache_missed,
auth_cache_size,
ca_file,
captcha_cmd,
captcha_host,
captcha_limit,
certfiles,
cluster_backend,
cluster_nodes,
domain_balancing,
ext_api_path_oauth,
fqdn,
hosts,
host_config,
listen,
loglevel,
log_rate_limit,
log_rotate_count,
log_rotate_date,
log_rotate_size,
negotiation_timeout,
net_ticktime,
new_sql_schema,
node_start,
oauth_cache_life_time,
oauth_cache_missed,
oauth_cache_size,
oauth_db_type,
oauth_expire,
oauth_use_cache,
oom_killer,
oom_queue,
oom_watermark,
queue_dir,
redis_connect_timeout,
redis_db,
redis_password,
redis_pool_size,
redis_port,
redis_queue_type,
redis_server,
registration_timeout,
riak_cacertfile,
riak_password,
riak_pool_size,
riak_port,
riak_server,
riak_start_interval,
riak_username,
router_cache_life_time,
router_cache_missed,
router_cache_size,
router_db_type,
router_use_cache,
rpc_timeout,
s2s_max_retry_delay,
shaper,
sm_cache_life_time,
sm_cache_missed,
sm_cache_size,
version,
websocket_origin,
websocket_ping_interval,
websocket_timeout].
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec validator() -> econf:validator().
validator() ->
Disallowed = ejabberd_config:globals(),
{Validators, Required} = ejabberd_config:validators(Disallowed),
econf:options(
Validators,
[{disallowed, Required ++ Disallowed}, unique]).
-spec fqdn(global | binary()) -> [binary()].
fqdn(global) ->
{ok, Hostname} = inet:gethostname(),
case inet:gethostbyname(Hostname) of
{ok, #hostent{h_name = FQDN}} ->
case jid:nameprep(iolist_to_binary(FQDN)) of
error -> [];
Domain -> [Domain]
end;
{error, _} ->
[]
end;
fqdn(_) ->
ejabberd_config:get_option(fqdn).
-spec version(global | binary()) -> binary().
version(global) ->
case application:get_env(ejabberd, custom_vsn) of
{ok, Vsn0} when is_list(Vsn0) ->
list_to_binary(Vsn0);
{ok, Vsn1} when is_binary(Vsn1) ->
Vsn1;
_ ->
case application:get_key(ejabberd, vsn) of
undefined -> <<"">>;
{ok, Vsn} -> list_to_binary(Vsn)
end
end;
version(_) ->
ejabberd_config:get_option(version).
-spec concat_tls_protocol_options([binary()]) -> binary().
concat_tls_protocol_options(Opts) ->
str:join(Opts, <<"|">>).

View File

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

View File

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

View File

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

View File

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

View File

@ -23,41 +23,41 @@
-module(ejabberd_redis_sup). -module(ejabberd_redis_sup).
-behaviour(supervisor). -behaviour(supervisor).
-behaviour(ejabberd_config).
%% API %% API
-export([start_link/0, get_pool_size/0, -export([start/0, start_link/0]).
host_up/1, config_reloaded/0, opt_type/1]). -export([get_pool_size/0, config_reloaded/0]).
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
-include("logger.hrl"). -include("logger.hrl").
-define(DEFAULT_POOL_SIZE, 10).
%%%=================================================================== %%%===================================================================
%%% API functions %%% 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() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). 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() -> config_reloaded() ->
case is_redis_configured() of case is_started() of
true -> true ->
ejabberd:start_app(eredis),
lists:foreach( lists:foreach(
fun(Spec) -> fun(Spec) ->
supervisor:start_child(?MODULE, Spec) supervisor:start_child(?MODULE, Spec)
@ -65,17 +65,15 @@ config_reloaded() ->
PoolSize = get_pool_size(), PoolSize = get_pool_size(),
lists:foreach( lists:foreach(
fun({Id, _, _, _}) when Id > PoolSize -> fun({Id, _, _, _}) when Id > PoolSize ->
supervisor:terminate_child(?MODULE, Id), case supervisor:terminate_child(?MODULE, Id) of
supervisor:delete_child(?MODULE, Id); ok -> supervisor:delete_child(?MODULE, Id);
_ -> ok
end;
(_) -> (_) ->
ok ok
end, supervisor:which_children(?MODULE)); end, supervisor:which_children(?MODULE));
false -> false ->
lists:foreach( ok
fun({Id, _, _, _}) ->
supervisor:terminate_child(?MODULE, Id),
supervisor:delete_child(?MODULE, Id)
end, supervisor:which_children(?MODULE))
end. end.
%%%=================================================================== %%%===================================================================
@ -83,36 +81,11 @@ config_reloaded() ->
%%%=================================================================== %%%===================================================================
init([]) -> init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 20), ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 20),
ejabberd_hooks:add(host_up, ?MODULE, host_up, 20), {ok, {{one_for_one, 500, 1}, get_specs()}}.
Specs = case is_redis_configured() of
true ->
ejabberd:start_app(eredis),
get_specs();
false ->
[]
end,
{ok, {{one_for_one, 500, 1}, Specs}}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% 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() -> get_specs() ->
lists:map( lists:map(
fun(I) -> fun(I) ->
@ -121,24 +94,7 @@ get_specs() ->
end, lists:seq(1, get_pool_size())). end, lists:seq(1, get_pool_size())).
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) -> is_started() ->
binary_to_list(iolist_to_binary(IOList)). whereis(?MODULE) /= undefined.
-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].

View File

@ -125,13 +125,12 @@ sh_to_awk_3(<<"]", Sh/binary>>, false) ->
sh_to_awk_3(<<C:8, Sh/binary>>, UpArrow) -> sh_to_awk_3(<<C:8, Sh/binary>>, UpArrow) ->
[C|sh_to_awk_3(Sh, UpArrow)]; [C|sh_to_awk_3(Sh, UpArrow)];
sh_to_awk_3(<<>>, true) -> sh_to_awk_3(<<>>, true) ->
[$^|sh_to_awk_1([])]; [$^|sh_to_awk_1(<<>>)];
sh_to_awk_3(<<>>, false) -> 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. %% 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; 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($") -> true; sh_special_char($") -> true;
sh_special_char(_C) -> false. sh_special_char(_C) -> false.

View File

@ -520,8 +520,13 @@ make_invalid_object(Val) ->
(str:format("Invalid object: ~p", [Val])). (str:format("Invalid object: ~p", [Val])).
get_random_pid() -> get_random_pid() ->
PoolPid = ejabberd_riak_sup:get_random_pid(), case ejabberd_riak_sup:start() of
get_riak_pid(PoolPid). ok ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
get_riak_pid(PoolPid);
{error, _} = Err ->
Err
end.
get_riak_pid(PoolPid) -> get_riak_pid(PoolPid) ->
case catch gen_server:call(PoolPid, get_pid) of case catch gen_server:call(PoolPid, get_pid) of

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,9 +22,11 @@
-module(ejabberd_s2s_in). -module(ejabberd_s2s_in).
-behaviour(xmpp_stream_in). -behaviour(xmpp_stream_in).
-behaviour(ejabberd_listener). -behaviour(ejabberd_listener).
-dialyzer([{no_fail_call, [stop/1, process_closed/2]},
{no_return, process_closed/2}]).
%% ejabberd_listener callbacks %% 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 %% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2, -export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
@ -252,11 +254,11 @@ init([State, Opts]) ->
false -> [compression_none | TLSOpts1]; false -> [compression_none | TLSOpts1];
true -> TLSOpts1 true -> TLSOpts1
end, end,
Timeout = ejabberd_config:negotiation_timeout(), Timeout = ejabberd_option:negotiation_timeout(),
State1 = State#{tls_options => TLSOpts2, State1 = State#{tls_options => TLSOpts2,
auth_domains => sets:new(), auth_domains => sets:new(),
xmlns => ?NS_SERVER, xmlns => ?NS_SERVER,
lang => ejabberd_config:get_mylang(), lang => ejabberd_option:language(),
server => ejabberd_config:get_myname(), server => ejabberd_config:get_myname(),
lserver => ejabberd_config:get_myname(), lserver => ejabberd_config:get_myname(),
server_host => ejabberd_config:get_myname(), server_host => ejabberd_config:get_myname(),
@ -337,20 +339,11 @@ set_idle_timeout(State) ->
-spec change_shaper(state(), binary()) -> state(). -spec change_shaper(state(), binary()) -> state().
change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State, change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
RServer) -> 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)). 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() -> listen_options() ->
[{shaper, none}, [{shaper, none},
{certfile, undefined},
{ciphers, undefined}, {ciphers, undefined},
{dhfile, undefined}, {dhfile, undefined},
{cafile, undefined}, {cafile, undefined},

View File

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

View File

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

View File

@ -1,10 +1,4 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% File : ejabberd_shaper.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Functions to control connections traffic
%%% Created : 9 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne %%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%% %%%
%%% This program is free software; you can redistribute it and/or %%% 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. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_shaper). -module(ejabberd_shaper).
-behaviour(gen_server). -behaviour(gen_server).
-behaviour(ejabberd_config).
-author('alexey@process-one.net'). -export([start_link/0, new/1, update/2, match/3, get_max_rate/1]).
-export([reload_from_config/0]).
-export([start_link/0, new/1, update/2, -export([validator/1, shaper_rules_validator/0]).
get_max_rate/1, transform_options/1, load_from_config/0,
opt_type/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-include("logger.hrl"). -include("logger.hrl").
-record(shaper, {name :: {atom(), global}, -type state() :: #{hosts := [binary()]}.
maxrate :: integer(),
burst_size :: integer()}).
-record(state, {}).
-type shaper() :: none | p1_shaper:state(). -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()}. -spec start_link() -> {ok, pid()} | {error, any()}.
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 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([]) -> init([]) ->
ejabberd_mnesia:create(?MODULE, shaper, create_tabs(),
[{ram_copies, [node()]}, Hosts = ejabberd_option:hosts(),
{local_content, true}, load_from_config([], Hosts),
{attributes, record_info(fields, shaper)}]), ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20),
ejabberd_hooks:add(config_reloaded, ?MODULE, load_from_config, 20), {ok, #{hosts => Hosts}}.
load_from_config(),
{ok, #state{}}.
handle_call(_Request, _From, State) -> -spec handle_call(term(), term(), state()) -> {reply, ok, state()} | {noreply, state()}.
Reply = ok, handle_call(reload_from_config, _, #{hosts := OldHosts} = State) ->
{reply, Reply, State}. NewHosts = ejabberd_option:hosts(),
load_from_config(OldHosts, NewHosts),
handle_cast(_Msg, State) -> {reply, ok, State#{hosts => NewHosts}};
handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {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}. {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) -> 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) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
-spec load_from_config() -> ok | {error, any()}. %%%===================================================================
load_from_config() -> %%% Internal functions
Shapers = ejabberd_config:get_option(shaper, []), %%%===================================================================
case mnesia:transaction( %%%===================================================================
fun() -> %%% Table management
lists:foreach( %%%===================================================================
fun({Name, MaxRate, BurstSize}) -> -spec load_from_config([binary()], [binary()]) -> ok.
mnesia:write( load_from_config(OldHosts, NewHosts) ->
#shaper{name = {Name, global}, ?DEBUG("Loading shaper rules from config", []),
maxrate = MaxRate, Shapers = ejabberd_option:shaper(),
burst_size = BurstSize}) ets:insert(shaper, maps:to_list(Shapers)),
end, ets:insert(
Shapers) shaper_rules,
end) of lists:flatmap(
{atomic, ok} -> fun(Host) ->
ok; lists:flatmap(
Err -> fun({Name, List}) ->
{error, Err} 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. end.
-spec get_max_rate(atom()) -> none | non_neg_integer(). -spec read_shaper(atom() | shaper_rate()) -> none | shaper_rate().
get_max_rate(none) -> read_shaper(Name) when is_atom(Name), Name /= none, Name /= infinity ->
none; case ets:lookup(shaper, Name) of
get_max_rate(Name) -> [{_, Rate}] -> Rate;
case ets:lookup(shaper, {Name, global}) of [] -> none
[#shaper{maxrate = R}] -> end;
R; read_shaper(Rate) ->
[] -> Rate.
none
end.
-spec new(atom()) -> shaper(). %%%===================================================================
new(none) -> %%% Validators
none; %%%===================================================================
new(Name) -> shaper_name() ->
case ets:lookup(shaper, {Name, global}) of econf:either(
[#shaper{maxrate = R, burst_size = B}] -> econf:and_then(
p1_shaper:new(R, B); econf:atom(),
[] -> fun(infinite) -> infinity;
none (unlimited) -> infinity;
end. (A) -> A
end),
econf:pos_int()).
-spec update(shaper(), integer()) -> {shaper(), integer()}. shaper_validator() ->
update(none, _Size) -> {none, 0}; econf:either(
update(Shaper, Size) -> econf:and_then(
Result = p1_shaper:update(Shaper, Size), econf:options(
?DEBUG("Shaper update:~n~s =>~n~s", #{rate => econf:pos_int(),
[p1_shaper:pp(Shaper), p1_shaper:pp(Result)]), burst_size => econf:pos_int()},
Result. [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) -> -spec resolve_shapers(atom(), [shaper_rule()], #{atom() => shaper_rate()}) -> [shaper_rate_rule()].
[{shaper, [{Name, N}]} | Opts]; resolve_shapers(ShaperRule, Rules, Shapers) ->
transform_options({shaper, Name, none}, Opts) -> lists:filtermap(
[{shaper, [{Name, none}]} | Opts]; fun({Name, Rule}) when is_atom(Name), Name /= none, Name /= infinity ->
transform_options({shaper, List}, Opts) when is_list(List) -> try {true, {maps:get(Name, Shapers), Rule}}
R = lists:map( catch _:{badkey, _} ->
fun({Name, Args}) when is_list(Args) -> ?WARNING_MSG(
MaxRate = proplists:get_value(rate, Args, 1000), "Shaper rule '~s' refers to unknown shaper: ~s",
BurstSize = proplists:get_value(burst_size, Args, MaxRate), [ShaperRule, Name]),
{Name, MaxRate, BurstSize}; false
({Name, Val}) -> end;
{Name, Val, Val} (_) ->
end, List), true
[{shaper, R} | Opts]; end, Rules).
transform_options(Opt, Opts) ->
[Opt | Opts].
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(shaper) -> fun(V) -> V end;
opt_type(_) -> [shaper].

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -37,12 +37,11 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-module(ejabberd_websocket). -module(ejabberd_websocket).
-behaviour(ejabberd_config).
-protocol({rfc, 6455}). -protocol({rfc, 6455}).
-author('ecestari@process-one.net'). -author('ecestari@process-one.net').
-export([socket_handoff/5, opt_type/1]). -export([socket_handoff/5]).
-include("logger.hrl"). -include("logger.hrl").
@ -429,27 +428,4 @@ websocket_close(Socket, WsHandleLoopPid, SocketMode, _CloseCode) ->
SocketMode:close(Socket). SocketMode:close(Socket).
get_origin() -> get_origin() ->
ejabberd_config:get_option(websocket_origin, []). ejabberd_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].

View File

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

View File

@ -25,14 +25,12 @@
-module(eldap_utils). -module(eldap_utils).
-behaviour(ejabberd_config).
-author('mremond@process-one.net'). -author('mremond@process-one.net').
-export([generate_subfilter/1, find_ldap_attrs/2, check_filter/1, -export([generate_subfilter/1, find_ldap_attrs/2, check_filter/1,
get_ldap_attr/2, get_user_part/2, make_filter/2, get_ldap_attr/2, get_user_part/2, make_filter/2,
get_state/2, case_insensitive_match/2, get_config/2, get_state/2, case_insensitive_match/2,
decode_octet_string/3, uids_domain_subst/2, opt_type/1, decode_octet_string/3, uids_domain_subst/2]).
options/1]).
-include("logger.hrl"). -include("logger.hrl").
-include("eldap.hrl"). -include("eldap.hrl").
@ -170,62 +168,6 @@ uids_domain_subst(Host, UIDs) ->
end, end,
UIDs). 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 %% Borrowed from asn1rt_ber_bin_v2.erl
%%---------------------------------------- %%----------------------------------------
@ -332,87 +274,3 @@ collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc); collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc);
collect_parts_bit([],Acc,Uacc) -> collect_parts_bit([],Acc,Uacc) ->
list_to_binary([Uacc|lists:reverse(Acc)]). list_to_binary([Uacc|lists:reverse(Acc)]).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(deref_aliases) ->
fun(unspecified) -> unspecified;
(never) -> never;
(searching) -> searching;
(finding) -> finding;
(always) -> always
end;
opt_type(ldap_backups) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_base) -> fun iolist_to_binary/1;
opt_type(ldap_deref_aliases) ->
fun (never) -> never;
(searching) -> searching;
(finding) -> finding;
(always) -> always
end;
opt_type(ldap_encrypt) ->
fun (tls) -> tls;
(starttls) -> starttls;
(none) -> none
end;
opt_type(ldap_password) -> fun iolist_to_binary/1;
opt_type(ldap_port) ->
fun(undefined) -> undefined;
(I) when is_integer(I), I > 0 -> I
end;
opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
opt_type(ldap_servers) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_tls_certfile) ->
fun(undefined) -> undefined;
(S) -> binary_to_list(ejabberd_pkix:try_certfile(S))
end;
opt_type(ldap_tls_cacertfile) ->
fun(undefined) -> undefined;
(S) -> binary_to_list(misc:try_read_file(S))
end;
opt_type(ldap_tls_depth) ->
fun(undefined) -> undefined;
(I) when is_integer(I), I >= 0 -> I
end;
opt_type(ldap_tls_verify) ->
fun (hard) -> hard;
(soft) -> soft;
(false) -> false
end;
opt_type(ldap_filter) ->
fun(<<"">>) -> <<"">>;
(F) -> check_filter(F)
end;
opt_type(ldap_uids) ->
fun (Us) ->
lists:map(fun ({U, P}) ->
{iolist_to_binary(U), iolist_to_binary(P)};
({U}) -> {iolist_to_binary(U)};
(U) -> {iolist_to_binary(U)}
end,
lists:flatten(Us))
end;
opt_type(_) ->
[deref_aliases, ldap_backups, ldap_base, ldap_uids,
ldap_deref_aliases, ldap_encrypt, ldap_password,
ldap_port, ldap_rootdn, ldap_servers, ldap_filter,
ldap_tls_certfile, ldap_tls_cacertfile, ldap_tls_depth,
ldap_tls_verify].
options(_) ->
[{deref_aliases, unspecified},
{ldap_backups, []},
{ldap_base, <<"">>},
{ldap_uids, [{<<"uid">>, <<"%u">>}]},
{ldap_deref_aliases, never},
{ldap_encrypt, none},
{ldap_password, <<"">>},
{ldap_port, undefined},
{ldap_rootdn, <<"">>},
{ldap_servers, [<<"localhost">>]},
{ldap_filter, <<"">>},
{ldap_tls_certfile, undefined},
{ldap_tls_cacertfile, undefined},
{ldap_tls_depth, undefined},
{ldap_tls_verify, false}].

View File

@ -22,9 +22,10 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(elixir_logger_backend). -module(elixir_logger_backend).
-ifdef(ELIXIR_ENABLED).
-behaviour(gen_event). -behaviour(gen_event).
-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
@ -120,3 +121,5 @@ severity_to_level(error) -> error;
severity_to_level(critical) -> error; severity_to_level(critical) -> error;
severity_to_level(alert) -> error; severity_to_level(alert) -> error;
severity_to_level(emergency) -> error. severity_to_level(emergency) -> error.
-endif.

View File

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

View File

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

View File

@ -27,12 +27,9 @@
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(ejabberd_config).
%% API %% API
-export([add_iq_handler/5, remove_iq_handler/3, handle/1, handle/2, -export([add_iq_handler/5, remove_iq_handler/3, handle/1, handle/2,
check_type/1, transform_module_options/1, start/1, get_features/2]).
opt_type/1, start/1, get_features/2]).
%% Deprecated functions %% Deprecated functions
-export([add_iq_handler/6, handle/5, iqdisc/1]). -export([add_iq_handler/6, handle/5, iqdisc/1]).
-deprecated([{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)) xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
end. 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. -spec iqdisc(binary() | global) -> no_queue.
iqdisc(_Host) -> iqdisc(_Host) ->
no_queue. 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 %% Deprecated API
%%==================================================================== %%====================================================================

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -39,7 +39,8 @@
css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0, 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, 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, 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 %% Deprecated functions
-export([decode_base64/1, encode_base64/1]). -export([decode_base64/1, encode_base64/1]).
@ -206,10 +207,10 @@ hex_to_base64(Hex) ->
url_encode(A) -> url_encode(A) ->
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) -> expand_keyword(Keyword, Input, Replacement) ->
Parts = binary:split(Input, Keyword, [global]), re:replace(Input, Keyword, Replacement,
str:join(Parts, Replacement). [{return, binary}, global]).
binary_to_atom(Bin) -> binary_to_atom(Bin) ->
erlang:binary_to_atom(Bin, utf8). erlang:binary_to_atom(Bin, utf8).
@ -412,7 +413,7 @@ format_val(Term) ->
_ -> [io_lib:nl(), S] _ -> [io_lib:nl(), S]
end. end.
-spec cancel_timer(reference()) -> ok. -spec cancel_timer(reference() | undefined) -> ok.
cancel_timer(TRef) when is_reference(TRef) -> cancel_timer(TRef) when is_reference(TRef) ->
case erlang:cancel_timer(TRef) of case erlang:cancel_timer(TRef) of
false -> false ->
@ -425,18 +426,106 @@ cancel_timer(TRef) when is_reference(TRef) ->
cancel_timer(_) -> cancel_timer(_) ->
ok. ok.
-spec best_match(atom(), [atom()]) -> atom(). -spec best_match(atom(), [atom()]) -> atom();
(binary(), [binary()]) -> binary().
best_match(Pattern, []) -> best_match(Pattern, []) ->
Pattern; Pattern;
best_match(Pattern, Opts) -> 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( {Ds, _} = lists:mapfoldl(
fun(Opt, Cache) -> fun(Opt, Cache) ->
{Distance, Cache1} = ld(String, atom_to_list(Opt), Cache), {Distance, Cache1} = ld(String, F(Opt), Cache),
{{Distance, Opt}, Cache1} {{Distance, Opt}, Cache1}
end, #{}, Opts), end, #{}, Opts),
element(2, lists:min(Ds)). 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 %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -515,3 +604,11 @@ ld([_|ST] = S, [_|TT] = T, Cache) ->
L = 1 + lists:min([L1, L2, L3]), L = 1 + lists:min([L1, L2, L3]),
{L, maps:put({S, T}, L, C3)} {L, maps:put({S, T}, L, C3)}
end. end.
-spec ip_to_integer(inet:ip_address()) -> non_neg_integer().
ip_to_integer({IP1, IP2, IP3, IP4}) ->
IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
IP8}) ->
IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
bor IP5 bsl 16 bor IP6 bsl 16 bor IP7 bsl 16 bor IP8.

View File

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

13
src/mod_adhoc_opt.erl Normal file
View File

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

View File

@ -1534,7 +1534,7 @@ stats(Name) ->
case Name of case Name of
<<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000); <<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000);
<<"processes">> -> length(erlang:processes()); <<"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()); <<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list());
<<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list()) <<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list())
end. end.

View File

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

View File

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

View File

@ -98,10 +98,10 @@ set_motd_user(LUser, LServer) ->
end, end,
transaction(F). 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", []), ?INFO_MSG("Mnesia table 'motd' will be converted to binary", []),
true; 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", []), ?INFO_MSG("Mnesia table 'motd_users' will be converted to binary", []),
true; true;
need_transform(_) -> need_transform(_) ->

48
src/mod_announce_opt.erl Normal file
View File

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

View File

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

View File

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

20
src/mod_avatar_opt.erl Normal file
View File

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

View File

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

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