Compare commits
242 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 72b0fb49e8 | |||
| 111aa83f5e | |||
| 78fa9e08a5 | |||
| 3c1e4f0dfd | |||
| 4add262090 | |||
| 76eba3647a | |||
| 2ef58a33a9 | |||
| d02d7b2b6a | |||
| 90ea3ca361 | |||
| bf45c9eeee | |||
| a9c6748ec7 | |||
| 4982639d05 | |||
| c5c394e929 | |||
| 6ea7153e31 | |||
| 2a49f8cae7 | |||
| 674a8039ef | |||
| 4bf8ce7681 | |||
| 19ad6e6145 | |||
| 39640b67c7 | |||
| fb2603d3cd | |||
| 4a49dfecf3 | |||
| 42e6f72ee9 | |||
| 3c58a93eb8 | |||
| a080322055 | |||
| fd365b2893 | |||
| fad088a3c4 | |||
| 91865c66c0 | |||
| 7a74a4836a | |||
| 72445bb374 | |||
| 984c4cf6bd | |||
| 2a8005e47f | |||
| 7781f39b74 | |||
| e5fd1ee4f6 | |||
| 9ff7257287 | |||
| 12f74b4aa7 | |||
| fede85c9bd | |||
| 839490b0d9 | |||
| dbc0498279 | |||
| c183092aa4 | |||
| 5d4f8bcf0d | |||
| d7ad99f147 | |||
| 4b0d71d402 | |||
| b4a430541d | |||
| bfa61eaa46 | |||
| 68555ff466 | |||
| caf2c20210 | |||
| 1485b56211 | |||
| 2c70c572c8 | |||
| d4d1941133 | |||
| 814b80c644 | |||
| 4332dddbc4 | |||
| 57aeef74d5 | |||
| 12b58b9870 | |||
| caf7b54305 | |||
| c5d9d35e7b | |||
| ffbe97d988 | |||
| bdfef09c0f | |||
| dd38bef8b1 | |||
| 6983dfa21f | |||
| cbfab687e8 | |||
| c2753cd51c | |||
| 5458d8bfcb | |||
| 7748dd4e5d | |||
| 0c0c6465ba | |||
| b5a90be3cb | |||
| 1d317e8068 | |||
| 8f8c499cfa | |||
| 9fcb81dea9 | |||
| 490a758050 | |||
| f79ac6874e | |||
| 655cbf6055 | |||
| 483ef09263 | |||
| 33e0283f0d | |||
| 673a654c47 | |||
| 48c88b61b6 | |||
| fca2f24231 | |||
| 8bc3dc9c49 | |||
| 749033598d | |||
| f6e960d326 | |||
| 786bd4f26c | |||
| 5f48d2641b | |||
| 1a62d4e04b | |||
| 6b38d19085 | |||
| 661b041302 | |||
| 368b202144 | |||
| caaf02eaa0 | |||
| 32de9a56a5 | |||
| febbc2bb5a | |||
| 71f27ee7d4 | |||
| c718cbbd9f | |||
| 12c0d888b1 | |||
| 4220a2b98c | |||
| de9f80f2ce | |||
| be3a4acb55 | |||
| 3820aaa421 | |||
| e300f8095d | |||
| b31c0d9e2e | |||
| 8e04a7ef4d | |||
| 16b1d8541a | |||
| 0737958b45 | |||
| 024124decb | |||
| 88ac1dc56b | |||
| 8be1d49961 | |||
| 10d4c16a97 | |||
| ce0d1704c6 | |||
| 2e28d06744 | |||
| 3446aba753 | |||
| 75366ca2fd | |||
| f56cff925c | |||
| 94461948db | |||
| 0b438d09d1 | |||
| 06bf8cb032 | |||
| 1794dd19d0 | |||
| 1b5c50a384 | |||
| a9b456ccb3 | |||
| 2ebdd8915e | |||
| e54400a8e4 | |||
| 065f5272e6 | |||
| 751be3cca6 | |||
| cd0244eb71 | |||
| f029488260 | |||
| eeeb190680 | |||
| 95ff94b054 | |||
| 7744339347 | |||
| 2efa8677c9 | |||
| c928956d73 | |||
| 7ddeac38b6 | |||
| 3a8da27d86 | |||
| 52d45604ba | |||
| 804190e4a8 | |||
| 4b9613e8fe | |||
| b2f53fb962 | |||
| c91c5aa352 | |||
| 6f2b0179e7 | |||
| 8583958268 | |||
| d1425f0d78 | |||
| f1138baa80 | |||
| 1fb1e8721b | |||
| 0a09f27373 | |||
| 7b308e0d41 | |||
| 9004608181 | |||
| 26bce5dee3 | |||
| 34cf693231 | |||
| 0e61e57ed9 | |||
| 34cbed54cd | |||
| 4ccc40bce5 | |||
| 53f3a45803 | |||
| 858d880675 | |||
| a4f213837e | |||
| 5173de503c | |||
| 8a7b31ca63 | |||
| f8d2589ee5 | |||
| 78d4200f05 | |||
| 60803f5780 | |||
| 5c3074c0fb | |||
| 4789ddf1ee | |||
| 41c3751fa1 | |||
| 8305cc293b | |||
| 4d5eab6662 | |||
| 3a1fc6fb66 | |||
| 5c1db176a9 | |||
| 0503d899cf | |||
| 0093326f7d | |||
| 9ef52b8c64 | |||
| d201f013b2 | |||
| 5352037680 | |||
| bbb90b9928 | |||
| 9c27f31d72 | |||
| e7843bf92b | |||
| db240413ab | |||
| 8e883a76e3 | |||
| 622bff23a4 | |||
| be0dd51e51 | |||
| fc2b7018cc | |||
| 17f87eb899 | |||
| 1ade88402c | |||
| f252b9d489 | |||
| 1d3959b5a2 | |||
| e81302dc79 | |||
| 9e68c4c0d9 | |||
| 1981e13326 | |||
| fffae97940 | |||
| 49658e1655 | |||
| c55319c81e | |||
| 13ead140f4 | |||
| ca329826cb | |||
| 14b53fbcb0 | |||
| b055c2a13a | |||
| 639e9fab4e | |||
| c958fa2f06 | |||
| 30e814dd4b | |||
| 8c16fdf59f | |||
| a2f0e157bc | |||
| 2a9dd548b5 | |||
| 3f3ecad981 | |||
| 70452ba25a | |||
| 1b02c5fbf3 | |||
| 9d87a4a6d4 | |||
| f6ba91ff97 | |||
| 420ae65590 | |||
| 8f72c27b88 | |||
| 4f009e64fc | |||
| ba74c1c367 | |||
| ba2680df61 | |||
| 2529acc36c | |||
| ff199a323d | |||
| 64bb371285 | |||
| 9bd446e519 | |||
| 792f47b4bd | |||
| be2a9e35ae | |||
| 068db1a2d9 | |||
| 4717d64d7a | |||
| f7f40cf9a6 | |||
| bcf07fd032 | |||
| ff4a0e1808 | |||
| 51238bff83 | |||
| 86d5cf6d6c | |||
| b2ffa1db96 | |||
| 0ea0ba3004 | |||
| d6700bdc5b | |||
| 13c6430341 | |||
| 12a8d915cd | |||
| 6cb60aaff5 | |||
| 575ef9c619 | |||
| 5e5328da4a | |||
| 6da07d78b5 | |||
| 0c0ce17bc0 | |||
| 8a6c51290a | |||
| 671bc4e573 | |||
| 07196b6c62 | |||
| 53e1100cc4 | |||
| 47050db6b8 | |||
| d54f211514 | |||
| b46ed7044a | |||
| b202004862 | |||
| 8700a3401e | |||
| 82082f1799 | |||
| 3493a87469 | |||
| 6a10916dda | |||
| 36164d9446 | |||
| 45321fa2e2 | |||
| cebdfb6523 |
+2
-1
@@ -184,7 +184,8 @@ install: all copy-files
|
||||
-e "s*{{sysconfdir}}*@sysconfdir@*" \
|
||||
-e "s*{{localstatedir}}*@localstatedir@*" \
|
||||
-e "s*{{docdir}}*@docdir@*" \
|
||||
-e "s*{{erl}}*@ERL@*" ejabberdctl.template \
|
||||
-e "s*{{erl}}*@ERL@*" \
|
||||
-e "s*{{epmd}}*@EPMD@*" ejabberdctl.template \
|
||||
> ejabberdctl.example
|
||||
[ -f $(ETCDIR)/ejabberdctl.cfg ] \
|
||||
&& $(INSTALL) -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new \
|
||||
|
||||
@@ -30,6 +30,7 @@ fi
|
||||
|
||||
AC_PATH_TOOL(ERL, erl, , [${extra_erl_path}$PATH])
|
||||
AC_PATH_TOOL(ERLC, erlc, , [${extra_erl_path}$PATH])
|
||||
AC_PATH_TOOL(EPMD, epmd, , [${extra_erl_path}$PATH])
|
||||
|
||||
AC_ERLANG_NEED_ERL
|
||||
AC_ERLANG_NEED_ERLC
|
||||
|
||||
@@ -242,9 +242,7 @@ print_usage() ->
|
||||
print_po_header(File) ->
|
||||
MsgProps = get_msg_header_props(File),
|
||||
{Language, [LastT | AddT]} = prepare_props(MsgProps),
|
||||
application:load(ejabberd),
|
||||
{ok, Version} = application:get_key(ejabberd, vsn),
|
||||
print_po_header(Version, Language, LastT, AddT).
|
||||
print_po_header(Language, LastT, AddT).
|
||||
|
||||
get_msg_header_props(File) ->
|
||||
{ok, F} = file:open(File, [read]),
|
||||
@@ -274,12 +272,11 @@ prepare_props(MsgProps) ->
|
||||
Authors = proplists:get_all_values("Author:", MsgProps),
|
||||
{Language, Authors}.
|
||||
|
||||
print_po_header(Version, Language, LastTranslator, AdditionalTranslatorsList) ->
|
||||
print_po_header(Language, LastTranslator, AdditionalTranslatorsList) ->
|
||||
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
|
||||
HeaderString =
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
"\"Project-Id-Version: " ++ Version ++ "\\n\"\n"
|
||||
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
|
||||
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
|
||||
++ AdditionalTranslatorsString ++
|
||||
|
||||
@@ -157,7 +157,7 @@ extract_lang_srcmsg2po ()
|
||||
echo $MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
|
||||
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
|
||||
|
||||
@@ -176,7 +176,7 @@ extract_lang_src2pot ()
|
||||
echo "" >>$MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
|
||||
|
||||
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
|
||||
|
||||
@@ -12,6 +12,13 @@ ExecStop=@ctlscriptpath@/ejabberdctl stop
|
||||
ExecReload=@ctlscriptpath@/ejabberdctl reload_config
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
# The CAP_DAC_OVERRIDE capability is required for pam authentication to work
|
||||
CapabilityBoundingSet=CAP_DAC_OVERRIDE
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=full
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
+47
-45
@@ -408,14 +408,14 @@ acl:
|
||||
##
|
||||
## admin:
|
||||
## user:
|
||||
## - "aleksey": "localhost"
|
||||
## - "ermine": "example.org"
|
||||
## - "aleksey@localhost"
|
||||
## - "ermine@example.org"
|
||||
##
|
||||
## Blocked users
|
||||
##
|
||||
## blocked:
|
||||
## user:
|
||||
## - "baduser": "example.org"
|
||||
## - "baduser@example.org"
|
||||
## - "test"
|
||||
|
||||
## Local users: don't modify this.
|
||||
@@ -431,7 +431,7 @@ acl:
|
||||
## - "jabber.org"
|
||||
## aleksey:
|
||||
## user:
|
||||
## - "aleksey": "jabber.ru"
|
||||
## - "aleksey@jabber.ru"
|
||||
## test:
|
||||
## user_regexp: "^test"
|
||||
## user_glob: "test*"
|
||||
@@ -459,61 +459,61 @@ acl:
|
||||
## acl:
|
||||
## admin:
|
||||
## user:
|
||||
## - "bob-local": "localhost"
|
||||
## - "bob-local@localhost"
|
||||
|
||||
###. ============
|
||||
###' SHAPER RULES
|
||||
|
||||
shaper_rules:
|
||||
## Maximum number of simultaneous sessions allowed for a single user:
|
||||
max_user_sessions: 10
|
||||
## Maximum number of offline messages that users can have:
|
||||
max_user_offline_messages:
|
||||
- 5000: admin
|
||||
- 100
|
||||
## For C2S connections, all users except admins use the "normal" shaper
|
||||
c2s_shaper:
|
||||
- none: admin
|
||||
- normal
|
||||
## All S2S connections use the "fast" shaper
|
||||
s2s_shaper: fast
|
||||
|
||||
###. ============
|
||||
###' ACCESS RULES
|
||||
access:
|
||||
## Maximum number of simultaneous sessions allowed for a single user:
|
||||
max_user_sessions:
|
||||
all: 10
|
||||
## Maximum number of offline messages that users can have:
|
||||
max_user_offline_messages:
|
||||
admin: 5000
|
||||
all: 100
|
||||
access_rules:
|
||||
## This rule allows access only for local users:
|
||||
local:
|
||||
local: allow
|
||||
local:
|
||||
- allow: local
|
||||
## Only non-blocked users can use c2s connections:
|
||||
c2s:
|
||||
blocked: deny
|
||||
all: allow
|
||||
## For C2S connections, all users except admins use the "normal" shaper
|
||||
c2s_shaper:
|
||||
admin: none
|
||||
all: normal
|
||||
## All S2S connections use the "fast" shaper
|
||||
s2s_shaper:
|
||||
all: fast
|
||||
c2s:
|
||||
- deny: blocked
|
||||
- allow
|
||||
## Only admins can send announcement messages:
|
||||
announce:
|
||||
admin: allow
|
||||
announce:
|
||||
- allow: admin
|
||||
## Only admins can use the configuration interface:
|
||||
configure:
|
||||
admin: allow
|
||||
## Admins of this server are also admins of the MUC service:
|
||||
muc_admin:
|
||||
admin: allow
|
||||
- allow: admin
|
||||
## Only accounts of the local ejabberd server can create rooms:
|
||||
muc_create:
|
||||
local: allow
|
||||
## All users are allowed to use the MUC service:
|
||||
muc:
|
||||
all: allow
|
||||
- allow: local
|
||||
## Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
pubsub_createnode:
|
||||
local: allow
|
||||
- allow: local
|
||||
## In-band registration allows registration of any possible username.
|
||||
## To disable in-band registration, replace 'allow' with 'deny'.
|
||||
register:
|
||||
all: allow
|
||||
- allow
|
||||
## Only allow to register from localhost
|
||||
trusted_network:
|
||||
loopback: allow
|
||||
- allow: loopback
|
||||
## Do not establish S2S connections with bad servers
|
||||
## s2s:
|
||||
## bad_servers: deny
|
||||
## all: allow
|
||||
## - deny:
|
||||
## - ip: "XXX.XXX.XXX.XXX/32"
|
||||
## - deny:
|
||||
## - ip: "XXX.XXX.XXX.XXX/32"
|
||||
## - allow
|
||||
|
||||
## By default the frequency of account registrations from the same IP
|
||||
## is limited to 1 account every 10 minutes. To disable, specify: infinity
|
||||
@@ -526,10 +526,10 @@ access:
|
||||
## "localhost":
|
||||
## access:
|
||||
## c2s:
|
||||
## admin: allow
|
||||
## all: deny
|
||||
## - allow: admin
|
||||
## - deny
|
||||
## register:
|
||||
## all: deny
|
||||
## - deny
|
||||
|
||||
###. ================
|
||||
###' DEFAULT LANGUAGE
|
||||
@@ -590,10 +590,12 @@ modules:
|
||||
mod_last: {}
|
||||
mod_muc:
|
||||
## host: "conference.@HOST@"
|
||||
access: muc
|
||||
access:
|
||||
- allow
|
||||
access_admin:
|
||||
- allow: admin
|
||||
access_create: muc_create
|
||||
access_persistent: muc_create
|
||||
access_admin: muc_admin
|
||||
## mod_muc_log: {}
|
||||
## mod_multicast: {}
|
||||
mod_offline:
|
||||
|
||||
@@ -13,7 +13,7 @@ ERLANG_NODE=ejabberd@localhost
|
||||
SCRIPT_DIR=`cd ${0%/*} && pwd`
|
||||
ERL={{erl}}
|
||||
IEX={{bindir}}/iex
|
||||
EPMD={{bindir}}/epmd
|
||||
EPMD={{epmd}}
|
||||
INSTALLUSER={{installuser}}
|
||||
ERL_LIBS={{libdir}}
|
||||
|
||||
@@ -212,7 +212,7 @@ iexdebug()
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "ERL_PATH=$\"$ERL\" $CMD"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start interactive server
|
||||
@@ -441,6 +441,6 @@ case "${ARGS[0]}" in
|
||||
'ping'*) ping ${ARGS[1]};;
|
||||
'etop') etop;;
|
||||
'started') wait_for_status 0 30 2;; # wait 30x2s before timeout
|
||||
'stopped') wait_for_status 3 15 2 && stop_epmd;; # wait 15x2s before timeout
|
||||
'stopped') wait_for_status 3 30 2 && stop_epmd;; # wait 30x2s before timeout
|
||||
*) ctl "${ARGS[@]}";;
|
||||
esac
|
||||
|
||||
@@ -26,6 +26,25 @@
|
||||
{tuple, [rterm()]} | {list, rterm()} |
|
||||
rescode | restuple.
|
||||
|
||||
-type oauth_scope() :: atom().
|
||||
|
||||
%% ejabberd_commands OAuth ReST ACL definition:
|
||||
%% Two fields exist that are used to control access on a command from ReST API:
|
||||
%% 1. Policy
|
||||
%% If policy is:
|
||||
%% - restricted: command is not exposed as OAuth Rest API.
|
||||
%% - admin: Command is allowed for user that have Admin Rest command enabled by access rule: commands_admin_access
|
||||
%% - user: Command might be called by any server user.
|
||||
%% - open: Command can be called by anyone.
|
||||
%%
|
||||
%% Policy is just used to control who can call the command. A specific additional access rules can be performed, as
|
||||
%% defined by access option.
|
||||
%% Access option can be a list of:
|
||||
%% - {Module, accessName, DefaultValue}: Reference and existing module access to limit who can use the command.
|
||||
%% - AccessRule name: direct name of the access rule to check in config file.
|
||||
%% TODO: Access option could be atom command (not a list). In the case, User performing the command, will be added as first parameter
|
||||
%% to command, so that the command can perform additional check.
|
||||
|
||||
-record(ejabberd_commands,
|
||||
{name :: atom(),
|
||||
tags = [] :: [atom()] | '_' | '$2',
|
||||
@@ -36,19 +55,25 @@
|
||||
function :: atom() | '_',
|
||||
args = [] :: [aterm()] | '_' | '$1' | '$2',
|
||||
policy = restricted :: open | restricted | admin | user,
|
||||
%% access is: [accessRuleName] or [{Module, AccessOption, DefaultAccessRuleName}]
|
||||
access = [] :: [{atom(),atom(),atom()}|atom()],
|
||||
result = {res, rescode} :: rterm() | '_' | '$2',
|
||||
args_desc = none :: none | [string()] | '_',
|
||||
result_desc = none :: none | string() | '_',
|
||||
args_example = none :: none | [any()] | '_',
|
||||
result_example = none :: any()}).
|
||||
|
||||
%% TODO Fix me: Type is not up to date
|
||||
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
|
||||
tags :: [atom()],
|
||||
desc :: string(),
|
||||
longdesc :: string(),
|
||||
version :: integer(),
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
args :: [aterm()],
|
||||
policy :: open | restricted | admin | user,
|
||||
access :: [{atom(),atom(),atom()}|atom()],
|
||||
result :: rterm()}.
|
||||
|
||||
%% @type ejabberd_commands() = #ejabberd_commands{
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 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.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(oauth_token, {
|
||||
token = <<"">> :: binary() | '_',
|
||||
us = {<<"">>, <<"">>} :: {binary(), binary()} | '_',
|
||||
scope = [] :: [binary()] | '_',
|
||||
expire :: integer() | '$1'
|
||||
}).
|
||||
@@ -1,12 +1,13 @@
|
||||
-ifndef(EJABBERD_SM_HRL).
|
||||
-define(EJABBERD_SM_HRL, true).
|
||||
|
||||
-record(session, {sid, usr, us, priority, info}).
|
||||
-record(session, {sid, usr, us, priority, info = []}).
|
||||
-record(session_counter, {vhost, count}).
|
||||
-type sid() :: {erlang:timestamp(), pid()}.
|
||||
-type ip() :: {inet:ip_address(), inet:port_number()} | undefined.
|
||||
-type info() :: [{conn, atom()} | {ip, ip()} | {node, atom()}
|
||||
| {oor, boolean()} | {auth_module, atom()}].
|
||||
| {oor, boolean()} | {auth_module, atom()}
|
||||
| {num_stanzas_in, non_neg_integer()}].
|
||||
-type prio() :: undefined | integer().
|
||||
|
||||
-endif.
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
members_by_default = true :: boolean(),
|
||||
members_only = false :: boolean(),
|
||||
allow_user_invites = false :: boolean(),
|
||||
allow_subscription = false :: boolean(),
|
||||
password_protected = false :: boolean(),
|
||||
password = <<"">> :: binary(),
|
||||
anonymous = true :: boolean(),
|
||||
@@ -76,6 +77,8 @@
|
||||
jid :: jid(),
|
||||
nick :: binary(),
|
||||
role :: role(),
|
||||
is_subscriber = false :: boolean(),
|
||||
subscriptions = [] :: [binary()],
|
||||
last_presence :: xmlel()
|
||||
}).
|
||||
|
||||
|
||||
@@ -164,3 +164,11 @@
|
||||
-define(NS_MIX_NODES_PARTICIPANTS, <<"urn:xmpp:mix:nodes:participants">>).
|
||||
-define(NS_MIX_NODES_SUBJECT, <<"urn:xmpp:mix:nodes:subject">>).
|
||||
-define(NS_MIX_NODES_CONFIG, <<"urn:xmpp:mix:nodes:config">>).
|
||||
-define(NS_MUCSUB, <<"urn:xmpp:mucsub:0">>).
|
||||
-define(NS_MUCSUB_NODES_PRESENCE, <<"urn:xmpp:mucsub:nodes:presence">>).
|
||||
-define(NS_MUCSUB_NODES_MESSAGES, <<"urn:xmpp:mucsub:nodes:messages">>).
|
||||
-define(NS_MUCSUB_NODES_PARTICIPANTS, <<"urn:xmpp:mucsub:nodes:participants">>).
|
||||
-define(NS_MUCSUB_NODES_AFFILIATIONS, <<"urn:xmpp:mucsub:nodes:affiliations">>).
|
||||
-define(NS_MUCSUB_NODES_SUBJECT, <<"urn:xmpp:mucsub:nodes:subject">>).
|
||||
-define(NS_MUCSUB_NODES_CONFIG, <<"urn:xmpp:mucsub:nodes:config">>).
|
||||
-define(NS_MUCSUB_NODES_SYSTEM, <<"urn:xmpp:mucsub:nodes:system">>).
|
||||
|
||||
@@ -93,7 +93,12 @@
|
||||
|
||||
-type(subOptions() :: [mod_pubsub:subOption(),...]).
|
||||
|
||||
-type(pubOption() ::
|
||||
{Option::binary(),
|
||||
Values::[binary()]
|
||||
}).
|
||||
|
||||
-type(pubOptions() :: [mod_pubsub:pubOption()]).
|
||||
|
||||
-type(affiliation() :: 'none'
|
||||
| 'owner'
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ defmodule ExUnit.CTFormatter do
|
||||
|
||||
use GenEvent
|
||||
|
||||
import ExUnit.Formatter, only: [format_time: 2, format_filters: 2, format_test_failure: 5,
|
||||
import ExUnit.Formatter, only: [format_time: 2, format_test_failure: 5,
|
||||
format_test_case_failure: 5]
|
||||
|
||||
def init(opts) do
|
||||
|
||||
@@ -7,15 +7,20 @@ defmodule ModPresenceDemo do
|
||||
Ejabberd.Hooks.add(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
def stop(host) do
|
||||
info('Stopping ejabberd module Presence Demo')
|
||||
Ejabberd.Hooks.delete(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
def on_presence(user, _server, _resource, _packet) do
|
||||
info('Receive presence for #{user}')
|
||||
:none
|
||||
end
|
||||
end
|
||||
|
||||
# gen_mod callbacks
|
||||
def depends(_host, _opts), do: []
|
||||
def mod_opt_type(_), do: []
|
||||
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "16.04.0",
|
||||
version: "16.06.0",
|
||||
description: description,
|
||||
elixir: "~> 1.2",
|
||||
elixirc_paths: ["lib"],
|
||||
@@ -11,6 +11,8 @@ defmodule Ejabberd.Mixfile do
|
||||
compilers: [:asn1] ++ Mix.compilers,
|
||||
erlc_options: erlc_options,
|
||||
erlc_paths: ["asn1", "src"],
|
||||
# Elixir tests are starting the part of ejabberd they need
|
||||
aliases: [test: "test --no-start"],
|
||||
package: package,
|
||||
deps: deps]
|
||||
end
|
||||
@@ -38,7 +40,7 @@ defmodule Ejabberd.Mixfile do
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[{:lager, "~> 3.0"},
|
||||
[{:lager, "~> 3.0.0"},
|
||||
{:p1_utils, "~> 1.0"},
|
||||
{:cache_tab, "~> 1.0"},
|
||||
{:stringprep, "~> 1.0"},
|
||||
@@ -51,12 +53,17 @@ defmodule Ejabberd.Mixfile do
|
||||
{:p1_oauth2, "~> 0.6.1"},
|
||||
{:p1_xmlrpc, "~> 1.15"},
|
||||
{:p1_mysql, "~> 1.0"},
|
||||
{:p1_pgsql, "~> 1.0"},
|
||||
{:p1_pgsql, "~> 1.1"},
|
||||
{:sqlite3, "~> 1.1"},
|
||||
{:ezlib, "~> 1.0"},
|
||||
{:iconv, "~> 1.0"},
|
||||
{:eredis, "~> 1.0"},
|
||||
{:exrm, "~> 1.0.0-rc7", only: :dev}]
|
||||
{:exrm, "~> 1.0.0", only: :dev},
|
||||
# relx is used by exrm. Lock version as for now, ejabberd doesn not compile fine with
|
||||
# version 3.20:
|
||||
{:relx, "~> 3.19.0", only: :dev},
|
||||
{:meck, "~> 0.8.4", only: :test},
|
||||
{:moka, github: "processone/moka", tag: "1.0.5b", only: :test}]
|
||||
end
|
||||
|
||||
defp package do
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
%{"bbmustache": {:hex, :bbmustache, "1.0.4"},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.2"},
|
||||
"cf": {:hex, :cf, "0.2.1"},
|
||||
"eredis": {:hex, :eredis, "1.0.8"},
|
||||
"erlware_commons": {:hex, :erlware_commons, "0.19.0"},
|
||||
"esip": {:hex, :esip, "1.0.4"},
|
||||
"exrm": {:hex, :exrm, "1.0.3"},
|
||||
"ezlib": {:hex, :ezlib, "1.0.1"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.3"},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.11"},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.3"},
|
||||
"getopt": {:hex, :getopt, "0.8.2"},
|
||||
"goldrush": {:hex, :goldrush, "0.1.7"},
|
||||
"iconv": {:hex, :iconv, "1.0.0"},
|
||||
"jiffy": {:hex, :jiffy, "0.14.7"},
|
||||
"lager": {:hex, :lager, "3.0.2"},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.1"},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.0"},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.3"},
|
||||
"p1_xmlrpc": {:hex, :p1_xmlrpc, "1.15.1"},
|
||||
"providers": {:hex, :providers, "1.6.0"},
|
||||
"relx": {:hex, :relx, "3.19.0"},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.3"},
|
||||
"stun": {:hex, :stun, "1.0.3"}}
|
||||
%{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.3", "0e3c40dde2fe2a6a4db241d7583cea0cc1bcf29e546a0a22f15b75366b2f336e", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
|
||||
"cf": {:hex, :cf, "0.2.1", "69d0b1349fd4d7d4dc55b7f407d29d7a840bf9a1ef5af529f1ebe0ce153fc2ab", [:rebar3], []},
|
||||
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
|
||||
"erlware_commons": {:hex, :erlware_commons, "0.19.0", "7b43caf2c91950c5f60dc20451e3c3afba44d3d4f7f27bcdc52469285a5a3e70", [:rebar3], [{:cf, "0.2.1", [hex: :cf, optional: false]}]},
|
||||
"esip": {:hex, :esip, "1.0.7", "f75f6a5cac6814e506f0ff96141fbe276dee3261fca1471c8edfdde25b74f877", [:rebar3], [{:fast_tls, "1.0.6", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}, {:stun, "1.0.6", [hex: :stun, optional: false]}]},
|
||||
"exrm": {:hex, :exrm, "1.0.8", "5aa8990cdfe300282828b02cefdc339e235f7916388ce99f9a1f926a9271a45d", [:mix], [{:relx, "~> 3.5", [hex: :relx, optional: false]}]},
|
||||
"ezlib": {:hex, :ezlib, "1.0.1", "add8b2770a1a70c174aaea082b4a8668c0c7fdb03ee6cc81c6c68d3a6c3d767d", [:rebar3], []},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.6", "750a74aabb05056f0f222910f0955883649e6c5d67df6ca504ff676160d22b89", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.14", "23d4de66e645bca1d8a557444e83062cc42f235d7a7e2d4072d525bac3986f04", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.5", "a67772c75abb84181c6c9899e1f988b08ac214ea0d764ff1f9889bb7e27f74d4", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
|
||||
"getopt": {:hex, :getopt, "0.8.2", "b17556db683000ba50370b16c0619df1337e7af7ecbf7d64fbf8d1d6bce3109b", [:rebar], []},
|
||||
"goldrush": {:hex, :goldrush, "0.1.7", "349a351d17c71c2fdaa18a6c2697562abe136fec945f147b381f0cf313160228", [:rebar3], []},
|
||||
"iconv": {:hex, :iconv, "1.0.1", "dbb8700070577e7a021a095cc5ead221069a0c4034bfadca2516c1f1109ee7fd", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
|
||||
"jiffy": {:hex, :jiffy, "0.14.7", "9f33b893edd6041ceae03bc1e50b412e858cc80b46f3d7535a7a9940a79a1c37", [:rebar, :make], []},
|
||||
"lager": {:hex, :lager, "3.0.2", "25dc81bc3659b62f5ab9bd073e97ddd894fc4c242019fccef96f3889d7366c97", [:rebar3], [{:goldrush, "0.1.7", [hex: :goldrush, optional: false]}]},
|
||||
"meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:rebar, :make], []},
|
||||
"moka": {:git, "https://github.com/processone/moka.git", "768efea96443c57125e6247dbebee687f17be149", [tag: "1.0.5b"]},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.1", "d2be1cfc71bb4f1391090b62b74c3f5cb8e7a45b0076b8cb290cd6b2856c581b", [:rebar3], []},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.0", "ca525c42878eac095e5feb19563acc9915c845648f48fdec7ba6266c625d4ac7", [:rebar3], []},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.4", "7face65db102b5d1ebe7ad3c7517c5ee8cfbe174c6658e3affbb00eb66e06787", [:rebar3], []},
|
||||
"p1_xmlrpc": {:hex, :p1_xmlrpc, "1.15.1", "a382b62dc21bb372281c2488f99294d84f2b4020ed0908a1c4ad710ace3cf35a", [:rebar3], []},
|
||||
"providers": {:hex, :providers, "1.6.0", "db0e2f9043ae60c0155205fcd238d68516331d0e5146155e33d1e79dc452964a", [:rebar3], [{:getopt, "0.8.2", [hex: :getopt, optional: false]}]},
|
||||
"relx": {:hex, :relx, "3.19.0", "286dd5244b4786f56aac75d5c8e2d1fb4cfd306810d4ec8548f3ae1b3aadb8f7", [:rebar3], [{:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:erlware_commons, "0.19.0", [hex: :erlware_commons, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:providers, "1.6.0", [hex: :providers, optional: false]}]},
|
||||
"samerlib": {:git, "https://github.com/processone/samerlib", "9158f65d18ec63f8b409543b6fb46dd5fce46160", [tag: "0.8.0b"]},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
|
||||
"stringprep": {:hex, :stringprep, "1.0.5", "f29395275c35af5051b29bf875b44ac632dc4d0287880f0e143b536c61fd0ed5", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
|
||||
"stun": {:hex, :stun, "1.0.6", "1ca9dea574e09f60971bd8de9cb7e34f327cbf435462cf56aa30f05c1ee2f231", [:rebar3], [{:fast_tls, "1.0.6", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]}}
|
||||
|
||||
+15
-14
@@ -7,15 +7,15 @@
|
||||
%%% Created : 1 May 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.0.2"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.4"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.2"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.3"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.3"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.3"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.3"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.4"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.3"}}},
|
||||
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.5"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.3"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.6"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.5"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.14"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.6"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.7"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.5"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.7"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
|
||||
{p1_xmlrpc, ".*", {git, "https://github.com/processone/p1_xmlrpc", {tag, "1.15.1"}}},
|
||||
@@ -34,17 +34,17 @@
|
||||
"527722d12d0433b837cdb92a60900c2cb5df8942"}}},
|
||||
%% Forces correct dependency for riakc and allow using newer meck version)
|
||||
{if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang",
|
||||
"908a24fda4a46776a5135db60ca071e3d783f9f6"}}}, % for riak_pb-2.1.0.7
|
||||
"13f9bfb9b27d216e8e033b0e0a9a29097ed923dd"}}}, % for riak_pb-2.1.0.7
|
||||
{if_var_true, riak, {protobuffs, ".*", {git, "https://github.com/basho/erlang_protobuffs",
|
||||
"6e7fc924506e2dc166a6170e580ce1d95ebbd5bd"}}}, % for riak_pb-2.1.0.7 with correct meck dependency
|
||||
%% Elixir support, needed to run tests
|
||||
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
|
||||
{tag, "v1.1.1"}}}},
|
||||
{tag, {if_version_above, "17", "v1.2.6", "v1.1.1"}}}}},
|
||||
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
|
||||
{if_var_true, elixir, {rebar_elixir_plugin, ".*",
|
||||
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}},
|
||||
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
|
||||
{tag, "1.0.0"}}}},
|
||||
{tag, "1.0.1"}}}},
|
||||
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.4"}}}},
|
||||
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
|
||||
@@ -68,7 +68,7 @@
|
||||
ezlib,
|
||||
iconv]}}.
|
||||
|
||||
{erl_first_files, ["src/ejabberd_config.erl"]}.
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl"]}.
|
||||
|
||||
{erl_opts, [nowarn_deprecated_function,
|
||||
{if_var_false, debug, no_debug_info},
|
||||
@@ -113,11 +113,12 @@
|
||||
{if_var_false, iconv, "(\"iconv\":_/_)"},
|
||||
{if_var_false, odbc, "(\"odbc\":_/_)"},
|
||||
{if_var_false, sqlite, "(\"sqlite3\":_/_)"},
|
||||
{if_var_false, elixir, "(\"Elixir.Logger.*\":_/_)"},
|
||||
{if_var_false, redis, "(\"eredis\":_/_)"}]}.
|
||||
|
||||
{eunit_compile_opts, [{i, "tools"}]}.
|
||||
|
||||
{cover_enabled, true}.
|
||||
{if_version_above, "17", {cover_enabled, true}}.
|
||||
{cover_export_enabled, true}.
|
||||
|
||||
{post_hook_configure, [{"fast_tls", []},
|
||||
|
||||
+41
-6
@@ -19,7 +19,7 @@ ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) ->
|
||||
[{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg]
|
||||
end
|
||||
end,
|
||||
ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end.
|
||||
ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end,
|
||||
|
||||
Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config")) of
|
||||
{ok, Terms} ->
|
||||
@@ -28,15 +28,50 @@ Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config"))
|
||||
[]
|
||||
end,
|
||||
|
||||
ProcessSingleVar = fun(F, Var, Tail) ->
|
||||
case F(F, [Var], []) of
|
||||
[] -> Tail;
|
||||
[Val] -> [Val | Tail]
|
||||
end
|
||||
end,
|
||||
|
||||
ProcessVars = fun(_F, [], Acc) ->
|
||||
lists:reverse(Acc);
|
||||
(F, [{Type, Ver, Value} | Tail], Acc) when
|
||||
Type == if_version_above orelse
|
||||
Type == if_version_below ->
|
||||
SysVer = erlang:system_info(otp_release),
|
||||
Include = if Type == if_version_above ->
|
||||
SysVer > Ver;
|
||||
true ->
|
||||
SysVer < Ver
|
||||
end,
|
||||
if Include ->
|
||||
F(F, Tail, ProcessSingleVar(F, Value, Acc));
|
||||
true ->
|
||||
F(F, Tail, Acc)
|
||||
end;
|
||||
(F, [{Type, Ver, Value, ElseValue} | Tail], Acc) when
|
||||
Type == if_version_above orelse
|
||||
Type == if_version_below ->
|
||||
SysVer = erlang:system_info(otp_release),
|
||||
Include = if Type == if_version_above ->
|
||||
SysVer > Ver;
|
||||
true ->
|
||||
SysVer < Ver
|
||||
end,
|
||||
if Include ->
|
||||
F(F, Tail, ProcessSingleVar(F, Value, Acc));
|
||||
true ->
|
||||
F(F, Tail, ProcessSingleVar(F, ElseValue, Acc))
|
||||
end;
|
||||
(F, [{Type, Var, Value} | Tail], Acc) when
|
||||
Type == if_var_true orelse
|
||||
Type == if_var_false ->
|
||||
Flag = Type == if_var_true,
|
||||
case proplists:get_bool(Var, Cfg) of
|
||||
V when V == Flag ->
|
||||
F(F, Tail, [Value | Acc]);
|
||||
F(F, Tail, ProcessSingleVar(F, Value, Acc));
|
||||
_ ->
|
||||
F(F, Tail, Acc)
|
||||
end;
|
||||
@@ -45,7 +80,7 @@ ProcessVars = fun(_F, [], Acc) ->
|
||||
Type == if_var_no_match ->
|
||||
case proplists:get_value(Var, Cfg) of
|
||||
V when V == Match ->
|
||||
F(F, Tail, [Value | Acc]);
|
||||
F(F, Tail, ProcessSingleVar(F, Value, Acc));
|
||||
_ ->
|
||||
F(F, Tail, Acc)
|
||||
end;
|
||||
@@ -122,8 +157,8 @@ Conf5 = case lists:keytake(floating_deps, 1, Conf3) of
|
||||
end,
|
||||
|
||||
%% When running Travis test, upload test coverage result to coveralls:
|
||||
Conf6 = case os:getenv("TRAVIS") of
|
||||
"true" ->
|
||||
Conf6 = case {lists:keyfind(cover_enabled, 1, Conf5), os:getenv("TRAVIS")} of
|
||||
{{cover_enabled, true}, "true"} ->
|
||||
JobId = os:getenv("TRAVIS_JOB_ID"),
|
||||
CfgTemp = ModCfg(Conf5, [deps], fun(V) -> [{coveralls, ".*", {git, "https://github.com/markusn/coveralls-erl.git", "master"}}|V] end, []),
|
||||
ModCfg(CfgTemp, [post_hooks], fun(V) -> V ++ [{ct, "echo '\n%%! -pa ebin/ deps/coveralls/ebin\nmain(_)->{ok,F}=file:open(\"erlang.json\",[write]),io:fwrite(F,\"~s\",[coveralls:convert_file(\"logs/all.coverdata\", \""++JobId++"\", \"travis-ci\")]).' > getcover.erl"},
|
||||
@@ -132,7 +167,7 @@ Conf6 = case os:getenv("TRAVIS") of
|
||||
Conf5
|
||||
end,
|
||||
|
||||
%io:format("ejabberd configuration:~n ~p~n", [Conf5]),
|
||||
%io:format("ejabberd configuration:~n ~p~n", [Conf6]),
|
||||
|
||||
Conf6.
|
||||
|
||||
|
||||
@@ -313,3 +313,10 @@ CREATE TABLE sm (
|
||||
CREATE UNIQUE INDEX i_sm_sid ON sm(usec, pid);
|
||||
CREATE INDEX i_sm_node ON sm(node);
|
||||
CREATE INDEX i_sm_username ON sm(username);
|
||||
|
||||
CREATE TABLE oauth_token (
|
||||
token text NOT NULL PRIMARY KEY,
|
||||
jid text NOT NULL,
|
||||
scope text NOT NULL,
|
||||
expire bigint NOT NULL
|
||||
);
|
||||
|
||||
@@ -480,3 +480,13 @@ ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE [dbo].[pubsub_state] CHECK CONSTRAINT [pubsub_state_ibfk_1];
|
||||
|
||||
CREATE TABLE [dbo].[oauth_token] (
|
||||
[token] [varchar] (250) NOT NULL,
|
||||
[jid] [text] NOT NULL,
|
||||
[scope] [text] NOT NULL,
|
||||
[expire] [bigint] NOT NULL,
|
||||
CONSTRAINT [oauth_token_PRIMARY] PRIMARY KEY CLUSTERED
|
||||
(
|
||||
[token] ASC
|
||||
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||
) TEXTIMAGE_ON [PRIMARY];
|
||||
|
||||
+8
-1
@@ -275,7 +275,7 @@ CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt(subid(3
|
||||
CREATE TABLE muc_room (
|
||||
name text NOT NULL,
|
||||
host text NOT NULL,
|
||||
opts text NOT NULL,
|
||||
opts mediumtext NOT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
@@ -328,3 +328,10 @@ CREATE TABLE sm (
|
||||
CREATE UNIQUE INDEX i_sid ON sm(usec, pid(75));
|
||||
CREATE INDEX i_node ON sm(node(75));
|
||||
CREATE INDEX i_username ON sm(username);
|
||||
|
||||
CREATE TABLE oauth_token (
|
||||
token varchar(191) NOT NULL PRIMARY KEY,
|
||||
jid text NOT NULL,
|
||||
scope text NOT NULL,
|
||||
expire bigint NOT NULL
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
@@ -330,3 +330,12 @@ CREATE TABLE sm (
|
||||
CREATE UNIQUE INDEX i_sm_sid ON sm USING btree (usec, pid);
|
||||
CREATE INDEX i_sm_node ON sm USING btree (node);
|
||||
CREATE INDEX i_sm_username ON sm USING btree (username);
|
||||
|
||||
CREATE TABLE oauth_token (
|
||||
token text NOT NULL,
|
||||
jid text NOT NULL,
|
||||
scope text NOT NULL,
|
||||
expire bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX i_oauth_token_token ON oauth_token USING btree (token);
|
||||
|
||||
+320
-130
@@ -29,12 +29,14 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, to_record/3, add/3, add_list/3,
|
||||
add_local/3, add_list_local/3, load_from_config/0,
|
||||
match_rule/3, match_access/4, match_acl/3, transform_options/1,
|
||||
opt_type/1]).
|
||||
|
||||
-export([add_access/3, clear/0]).
|
||||
-export([start/0, add/3, add_list/3, add_local/3, add_list_local/3,
|
||||
load_from_config/0, match_rule/3, any_rules_allowed/3,
|
||||
transform_options/1, opt_type/1, acl_rule_matches/3,
|
||||
acl_rule_verify/1, access_matches/3,
|
||||
transform_access_rules_config/1,
|
||||
parse_ip_netmask/1,
|
||||
access_rules_validator/1, shaper_rules_validator/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -92,12 +94,6 @@ start() ->
|
||||
load_from_config(),
|
||||
ok.
|
||||
|
||||
-spec to_record(binary(), atom(), aclspec()) -> acl().
|
||||
|
||||
to_record(Host, ACLName, ACLSpec) ->
|
||||
#acl{aclname = {ACLName, Host},
|
||||
aclspec = normalize_spec(ACLSpec)}.
|
||||
|
||||
-spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}.
|
||||
|
||||
add(Host, ACLName, ACLSpec) ->
|
||||
@@ -188,6 +184,10 @@ load_from_config() ->
|
||||
{acl, Host}, fun(V) -> V end, []),
|
||||
AccessRules = ejabberd_config:get_option(
|
||||
{access, Host}, fun(V) -> V end, []),
|
||||
AccessRulesNew = ejabberd_config:get_option(
|
||||
{access_rules, Host}, fun(V) -> V end, []),
|
||||
ShaperRules = ejabberd_config:get_option(
|
||||
{shaper_rules, Host}, fun(V) -> V end, []),
|
||||
lists:foreach(
|
||||
fun({ACLName, SpecList}) ->
|
||||
lists:foreach(
|
||||
@@ -201,10 +201,21 @@ load_from_config() ->
|
||||
add(Host, ACLName, {ACLType, ACLSpecs})
|
||||
end, lists:flatten(SpecList))
|
||||
end, ACLs),
|
||||
lists:foreach(
|
||||
fun({Access, Rules}) ->
|
||||
NRules = lists:map(fun({ACL, Type}) ->
|
||||
{Type, [{acl, ACL}]}
|
||||
end, Rules),
|
||||
add_access(Host, Access, NRules ++ [{deny, [all]}])
|
||||
end, AccessRules),
|
||||
lists:foreach(
|
||||
fun({Access, Rules}) ->
|
||||
add_access(Host, Access, Rules)
|
||||
end, AccessRules)
|
||||
end, AccessRulesNew),
|
||||
lists:foreach(
|
||||
fun({Access, Rules}) ->
|
||||
add_access(Host, Access, Rules)
|
||||
end, ShaperRules)
|
||||
end, Hosts).
|
||||
|
||||
%% Delete all previous set ACLs and Access rules
|
||||
@@ -225,19 +236,28 @@ nameprep(S) ->
|
||||
resourceprep(S) ->
|
||||
jid:resourceprep(b(S)).
|
||||
|
||||
split_user_server(Str, NormFunUsr, NormFunSrv) ->
|
||||
case binary:split(Str, <<"@">>) of
|
||||
[U, S] ->
|
||||
{NormFunUsr(U), NormFunSrv(S)};
|
||||
_ ->
|
||||
NormFunUsr(Str)
|
||||
end.
|
||||
|
||||
normalize_spec(Spec) ->
|
||||
case Spec of
|
||||
all -> all;
|
||||
none -> none;
|
||||
{acl, N} -> {acl, N};
|
||||
{user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}};
|
||||
{user, U} -> {user, nodeprep(U)};
|
||||
{user, U} -> {user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
|
||||
{shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
|
||||
{shared_group, G} -> {shared_group, b(G)};
|
||||
{shared_group, G} -> {shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
|
||||
{user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}};
|
||||
{user_regexp, UR} -> {user_regexp, b(UR)};
|
||||
{user_regexp, UR} -> {user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
|
||||
{node_regexp, {UR, SR}} -> {node_regexp, {b(UR), b(SR)}};
|
||||
{user_glob, {UR, S}} -> {user_glob, {b(UR), nameprep(S)}};
|
||||
{user_glob, UR} -> {user_glob, b(UR)};
|
||||
{user_glob, UR} -> {user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
|
||||
{node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}};
|
||||
{server, S} -> {server, nameprep(S)};
|
||||
{resource, R} -> {resource, resourceprep(R)};
|
||||
@@ -255,130 +275,215 @@ normalize_spec(Spec) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec match_access(global | binary(), access_name(),
|
||||
jid() | ljid() | inet:ip_address(),
|
||||
atom()) -> any().
|
||||
-spec any_rules_allowed(global | binary(), access_name(),
|
||||
jid() | ljid() | inet:ip_address()) -> boolean().
|
||||
|
||||
match_access(_Host, all, _JID, _Default) ->
|
||||
allow;
|
||||
match_access(_Host, none, _JID, _Default) ->
|
||||
deny;
|
||||
match_access(_Host, {user, UserPattern}, JID, Default) ->
|
||||
match_user_spec({user, UserPattern}, JID, Default);
|
||||
match_access(Host, AccessRule, JID, _Default) ->
|
||||
match_rule(Host, AccessRule, JID).
|
||||
any_rules_allowed(Host, Access, Entity) ->
|
||||
lists:any(fun (Rule) ->
|
||||
allow == acl:match_rule(Host, Rule, Entity)
|
||||
end,
|
||||
Access).
|
||||
|
||||
-spec match_rule(global | binary(), access_name(),
|
||||
jid() | ljid() | inet:ip_address()) -> any().
|
||||
|
||||
match_rule(_Host, all, _JID) ->
|
||||
allow;
|
||||
match_rule(_Host, none, _JID) ->
|
||||
deny;
|
||||
match_rule(Host, Access, IP) when tuple_size(IP) == 4;
|
||||
tuple_size(IP) == 8 ->
|
||||
access_matches(Access, #{ip => IP}, Host);
|
||||
match_rule(Host, Access, JID) ->
|
||||
GAccess = ets:lookup(access, {Access, global}),
|
||||
LAccess = if Host /= global ->
|
||||
ets:lookup(access, {Access, Host});
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
case GAccess ++ LAccess of
|
||||
[] ->
|
||||
deny;
|
||||
AccessList ->
|
||||
Rules = lists:flatmap(
|
||||
fun(#access{rules = Rs}) ->
|
||||
Rs
|
||||
end, AccessList),
|
||||
match_acls(Rules, JID, Host)
|
||||
end.
|
||||
access_matches(Access, #{usr => jid:tolower(JID)}, Host).
|
||||
|
||||
match_acls([], _, _Host) -> deny;
|
||||
match_acls([{ACL, Access} | ACLs], JID, Host) ->
|
||||
case match_acl(ACL, JID, Host) of
|
||||
true -> Access;
|
||||
_ -> match_acls(ACLs, JID, Host)
|
||||
end.
|
||||
-spec acl_rule_verify(aclspec()) -> boolean().
|
||||
|
||||
-spec match_acl(atom(),
|
||||
jid() | ljid() | inet:ip_address(),
|
||||
binary()) -> boolean().
|
||||
|
||||
match_acl(all, _JID, _Host) ->
|
||||
acl_rule_verify(all) ->
|
||||
true;
|
||||
match_acl(none, _JID, _Host) ->
|
||||
acl_rule_verify(none) ->
|
||||
true;
|
||||
acl_rule_verify({ip, {{A,B,C,D}, Mask}})
|
||||
when is_integer(A), is_integer(B), is_integer(C), is_integer(D),
|
||||
A >= 0, A =< 255, B >= 0, B =< 255, C >= 0, C =< 255, D >= 0, D =< 255,
|
||||
is_integer(Mask), Mask >= 0, Mask =< 32 ->
|
||||
true;
|
||||
acl_rule_verify({ip, {{A,B,C,D,E,F,G,H}, Mask}}) when
|
||||
is_integer(A), is_integer(B), is_integer(C), is_integer(D),
|
||||
is_integer(E), is_integer(F), is_integer(G), is_integer(H),
|
||||
A >= 0, A =< 65535, B >= 0, B =< 65535, C >= 0, C =< 65535, D >= 0, D =< 65535,
|
||||
E >= 0, E =< 65535, F >= 0, F =< 65535, G >= 0, G =< 65535, H >= 0, H =< 65535,
|
||||
is_integer(Mask), Mask >= 0, Mask =< 64 ->
|
||||
true;
|
||||
acl_rule_verify({user, {U, S}}) when is_binary(U), is_binary(S) ->
|
||||
true;
|
||||
acl_rule_verify({user, U}) when is_binary(U) ->
|
||||
true;
|
||||
acl_rule_verify({server, S}) when is_binary(S) ->
|
||||
true;
|
||||
acl_rule_verify({resource, R}) when is_binary(R) ->
|
||||
true;
|
||||
acl_rule_verify({shared_group, {G, H}}) when is_binary(G), is_binary(H) ->
|
||||
true;
|
||||
acl_rule_verify({shared_group, G}) when is_binary(G) ->
|
||||
true;
|
||||
acl_rule_verify({user_regexp, {UR, S}}) when is_binary(UR), is_binary(S) ->
|
||||
true;
|
||||
acl_rule_verify({user_regexp, UR}) when is_binary(UR) ->
|
||||
true;
|
||||
acl_rule_verify({server_regexp, SR}) when is_binary(SR) ->
|
||||
true;
|
||||
acl_rule_verify({resource_regexp, RR}) when is_binary(RR) ->
|
||||
true;
|
||||
acl_rule_verify({node_regexp, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
|
||||
true;
|
||||
acl_rule_verify({user_glob, {UR, S}}) when is_binary(UR), is_binary(S) ->
|
||||
true;
|
||||
acl_rule_verify({user_glob, UR}) when is_binary(UR) ->
|
||||
true;
|
||||
acl_rule_verify({server_glob, SR}) when is_binary(SR) ->
|
||||
true;
|
||||
acl_rule_verify({resource_glob, RR}) when is_binary(RR) ->
|
||||
true;
|
||||
acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
|
||||
true;
|
||||
acl_rule_verify(_Spec) ->
|
||||
false.
|
||||
invalid_syntax(Msg, Data) ->
|
||||
throw({invalid_syntax, iolist_to_binary(io_lib:format(Msg, Data))}).
|
||||
|
||||
acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) ->
|
||||
acl_rules_verify(Rest, true);
|
||||
acl_rules_verify([{acl, Name} = Rule | _Rest], false) when is_atom(Name) ->
|
||||
invalid_syntax(<<"Using acl: rules not allowed: ~p">>, [Rule]);
|
||||
acl_rules_verify([Rule | Rest], AllowAcl) ->
|
||||
case acl_rule_verify(Rule) of
|
||||
false ->
|
||||
invalid_syntax(<<"Invalid rule: ~p">>, [Rule]);
|
||||
true ->
|
||||
acl_rules_verify(Rest, AllowAcl)
|
||||
end;
|
||||
acl_rules_verify([], _AllowAcl) ->
|
||||
true;
|
||||
acl_rules_verify(Rules, _AllowAcl) ->
|
||||
invalid_syntax(<<"Not a acl rules list: ~p">>, [Rules]).
|
||||
|
||||
|
||||
|
||||
all_acl_rules_matches([], _Data, _Host) ->
|
||||
false;
|
||||
match_acl(ACL, IP, Host) when tuple_size(IP) == 4;
|
||||
tuple_size(IP) == 8 ->
|
||||
lists:any(
|
||||
fun(#acl{aclspec = {ip, {Net, Mask}}}) ->
|
||||
is_ip_match(IP, Net, Mask);
|
||||
(_) ->
|
||||
false
|
||||
end, get_aclspecs(ACL, Host));
|
||||
match_acl(ACL, JID, Host) ->
|
||||
{User, Server, Resource} = jid:tolower(JID),
|
||||
lists:any(
|
||||
fun(#acl{aclspec = Spec}) ->
|
||||
case Spec of
|
||||
all -> true;
|
||||
{user, {U, S}} -> U == User andalso S == Server;
|
||||
{user, U} ->
|
||||
U == User andalso
|
||||
lists:member(Server, ?MYHOSTS);
|
||||
{server, S} -> S == Server;
|
||||
{resource, R} -> R == Resource;
|
||||
{shared_group, {G, H}} ->
|
||||
Mod = loaded_shared_roster_module(H),
|
||||
Mod:is_user_in_group({User, Server}, G, H);
|
||||
{shared_group, G} ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({User, Server}, G, Host);
|
||||
{user_regexp, {UR, S}} ->
|
||||
S == Server andalso is_regexp_match(User, UR);
|
||||
{user_regexp, UR} ->
|
||||
lists:member(Server, ?MYHOSTS)
|
||||
andalso is_regexp_match(User, UR);
|
||||
{server_regexp, SR} ->
|
||||
is_regexp_match(Server, SR);
|
||||
{resource_regexp, RR} ->
|
||||
is_regexp_match(Resource, RR);
|
||||
{node_regexp, {UR, SR}} ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_glob, {UR, S}} ->
|
||||
S == Server andalso is_glob_match(User, UR);
|
||||
{user_glob, UR} ->
|
||||
lists:member(Server, ?MYHOSTS)
|
||||
andalso is_glob_match(User, UR);
|
||||
{server_glob, SR} -> is_glob_match(Server, SR);
|
||||
{resource_glob, RR} ->
|
||||
is_glob_match(Resource, RR);
|
||||
{node_glob, {UR, SR}} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
WrongSpec ->
|
||||
?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
|
||||
"config file and reload it with the override_a"
|
||||
"cls option enabled",
|
||||
[WrongSpec]),
|
||||
false
|
||||
end
|
||||
end,
|
||||
get_aclspecs(ACL, Host)).
|
||||
all_acl_rules_matches(Rules, Data, Host) ->
|
||||
all_acl_rules_matches2(Rules, Data, Host).
|
||||
|
||||
all_acl_rules_matches2([Rule | Tail], Data, Host) ->
|
||||
case acl_rule_matches(Rule, Data, Host) of
|
||||
true ->
|
||||
all_acl_rules_matches2(Tail, Data, Host);
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
all_acl_rules_matches2([], _Data, _Host) ->
|
||||
true.
|
||||
|
||||
any_acl_rules_matches([], _Data, _Host) ->
|
||||
false;
|
||||
any_acl_rules_matches([Rule|Tail], Data, Host) ->
|
||||
case acl_rule_matches(Rule, Data, Host) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
any_acl_rules_matches(Tail, Data, Host)
|
||||
end.
|
||||
|
||||
-spec acl_rule_matches(aclspec(), any(), global|binary()) -> boolean().
|
||||
|
||||
acl_rule_matches(all, _Data, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({acl, all}, _Data, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({acl, Name}, Data, Host) ->
|
||||
ACLs = get_aclspecs(Name, Host),
|
||||
RawACLs = lists:map(fun(#acl{aclspec = R}) -> R end, ACLs),
|
||||
any_acl_rules_matches(RawACLs, Data, Host);
|
||||
acl_rule_matches({ip, {Net, Mask}}, #{ip := {IP, _Port}}, _Host) ->
|
||||
is_ip_match(IP, Net, Mask);
|
||||
acl_rule_matches({ip, {Net, Mask}}, #{ip := IP}, _Host) ->
|
||||
is_ip_match(IP, Net, Mask);
|
||||
acl_rule_matches({user, {U, S}}, #{usr := {U, S, _}}, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({user, U}, #{usr := {U, S, _}}, _Host) ->
|
||||
lists:member(S, ?MYHOSTS);
|
||||
acl_rule_matches({server, S}, #{usr := {_, S, _}}, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({resource, R}, #{usr := {_, _, R}}, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({shared_group, {G, H}}, #{usr := {U, S, _}}, _Host) ->
|
||||
Mod = loaded_shared_roster_module(H),
|
||||
Mod:is_user_in_group({U, S}, G, H);
|
||||
acl_rule_matches({shared_group, G}, #{usr := {U, S, _}}, Host) ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({U, S}, G, Host);
|
||||
acl_rule_matches({user_regexp, {UR, S}}, #{usr := {U, S, _}}, _Host) ->
|
||||
is_regexp_match(U, UR);
|
||||
acl_rule_matches({user_regexp, UR}, #{usr := {U, S, _}}, _Host) ->
|
||||
lists:member(S, ?MYHOSTS) andalso is_regexp_match(U, UR);
|
||||
acl_rule_matches({server_regexp, SR}, #{usr := {_, S, _}}, _Host) ->
|
||||
is_regexp_match(S, SR);
|
||||
acl_rule_matches({resource_regexp, RR}, #{usr := {_, _, R}}, _Host) ->
|
||||
is_regexp_match(R, RR);
|
||||
acl_rule_matches({node_regexp, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
|
||||
is_regexp_match(U, UR) andalso is_regexp_match(S, SR);
|
||||
acl_rule_matches({user_glob, {UR, S}}, #{usr := {U, S, _}}, _Host) ->
|
||||
is_glob_match(U, UR);
|
||||
acl_rule_matches({user_glob, UR}, #{usr := {U, S, _}}, _Host) ->
|
||||
lists:member(S, ?MYHOSTS) andalso is_glob_match(U, UR);
|
||||
acl_rule_matches({server_glob, SR}, #{usr := {_, S, _}}, _Host) ->
|
||||
is_glob_match(S, SR);
|
||||
acl_rule_matches({resource_glob, RR}, #{usr := {_, _, R}}, _Host) ->
|
||||
is_glob_match(R, RR);
|
||||
acl_rule_matches({node_glob, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
|
||||
is_glob_match(U, UR) andalso is_glob_match(S, SR);
|
||||
acl_rule_matches(_ACL, _Data, _Host) ->
|
||||
false.
|
||||
|
||||
-spec access_matches(atom()|list(), any(), global|binary()) -> any().
|
||||
access_matches(all, _Data, _Host) ->
|
||||
allow;
|
||||
access_matches(none, _Data, _Host) ->
|
||||
deny;
|
||||
access_matches(Name, Data, Host) when is_atom(Name) ->
|
||||
GAccess = ets:lookup(access, {Name, global}),
|
||||
LAccess =
|
||||
if Host /= global -> ets:lookup(access, {Name, Host});
|
||||
true -> []
|
||||
end,
|
||||
case GAccess ++ LAccess of
|
||||
[] ->
|
||||
deny;
|
||||
AccessList ->
|
||||
Rules = lists:flatmap(
|
||||
fun(#access{rules = Rs}) ->
|
||||
Rs
|
||||
end, AccessList),
|
||||
access_rules_matches(Rules, Data, Host)
|
||||
end;
|
||||
access_matches(Rules, Data, Host) when is_list(Rules) ->
|
||||
access_rules_matches(Rules, Data, Host).
|
||||
|
||||
|
||||
-spec access_rules_matches(list(), any(), global|binary()) -> any().
|
||||
|
||||
access_rules_matches(AR, Data, Host) ->
|
||||
access_rules_matches(AR, Data, Host, deny).
|
||||
|
||||
access_rules_matches([{Type, Acls} | Rest], Data, Host, Default) ->
|
||||
case all_acl_rules_matches(Acls, Data, Host) of
|
||||
false ->
|
||||
access_rules_matches(Rest, Data, Host, Default);
|
||||
true ->
|
||||
Type
|
||||
end;
|
||||
access_rules_matches([], _Data, _Host, Default) ->
|
||||
Default.
|
||||
|
||||
get_aclspecs(ACL, Host) ->
|
||||
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
|
||||
|
||||
|
||||
match_user_spec(Spec, JID, Default) ->
|
||||
case do_match_user_spec(Spec, jid:tolower(JID)) of
|
||||
true -> Default;
|
||||
false -> deny
|
||||
end.
|
||||
|
||||
do_match_user_spec({user, {U, S}}, {User, Server, _Resource}) ->
|
||||
U == User andalso S == Server.
|
||||
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
|
||||
|
||||
is_regexp_match(String, RegExp) ->
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
@@ -450,6 +555,63 @@ parse_ip_netmask(S) ->
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
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) ->
|
||||
normalize_spec({Type, hd(lists:flatten(Args))});
|
||||
(V) -> normalize_spec(V)
|
||||
end, lists:flatten(Rules)),
|
||||
{Res, T};
|
||||
transform_access_rules_config2({Res, Rule}) ->
|
||||
{Res, [Rule]}.
|
||||
|
||||
access_rules_validator(Name) when is_atom(Name) ->
|
||||
Name;
|
||||
access_rules_validator(Rules0) ->
|
||||
Rules = transform_access_rules_config(Rules0),
|
||||
access_shaper_rules_validator(Rules, fun(allow) -> true;
|
||||
(deny) -> true;
|
||||
(_) -> false
|
||||
end),
|
||||
throw({replace_with, Rules}).
|
||||
|
||||
|
||||
shaper_rules_validator(Name) when is_atom(Name) ->
|
||||
Name;
|
||||
shaper_rules_validator(Rules0) ->
|
||||
Rules = transform_access_rules_config(Rules0),
|
||||
access_shaper_rules_validator(Rules, fun(V) when is_atom(V) -> true;
|
||||
(V2) when is_integer(V2) -> true;
|
||||
(_) -> false
|
||||
end),
|
||||
throw({replace_with, Rules}).
|
||||
|
||||
access_shaper_rules_validator([{Type, Acls} = Rule | Rest], RuleTypeCheck) ->
|
||||
case RuleTypeCheck(Type) of
|
||||
true ->
|
||||
case acl_rules_verify(Acls, true) of
|
||||
true ->
|
||||
access_shaper_rules_validator(Rest, RuleTypeCheck);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
false ->
|
||||
invalid_syntax(<<"Invalid rule type: ~p in rule ~p">>, [Type, Rule])
|
||||
end;
|
||||
access_shaper_rules_validator([], _RuleTypeCheck) ->
|
||||
true;
|
||||
access_shaper_rules_validator(Value, _RuleTypeCheck) ->
|
||||
invalid_syntax(<<"Not a rule definition: ~p">>, [Value]).
|
||||
|
||||
|
||||
transform_options(Opts) ->
|
||||
Opts1 = lists:foldl(fun transform_options/2, [], Opts),
|
||||
{ACLOpts, Opts2} = lists:mapfoldl(
|
||||
@@ -464,6 +626,18 @@ transform_options(Opts) ->
|
||||
(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 = ejabberd_config:collect_options(lists:flatten(ACLOpts)),
|
||||
AccessOpts1 = case ejabberd_config:collect_options(
|
||||
lists:flatten(AccessOpts)) of
|
||||
@@ -477,7 +651,21 @@ transform_options(Opts) ->
|
||||
[] -> [];
|
||||
L2 -> [{acl, L2}]
|
||||
end,
|
||||
ACLOpts2 ++ AccessOpts1 ++ Opts3.
|
||||
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_options({acl, Name, Type}, Opts) ->
|
||||
T = case Type of
|
||||
@@ -508,5 +696,7 @@ transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
opt_type(access) -> fun (V) -> V end;
|
||||
opt_type(access_rules) -> fun (V) -> V end;
|
||||
opt_type(shaper_rules) -> fun (V) -> V end;
|
||||
opt_type(acl) -> fun (V) -> V end;
|
||||
opt_type(_) -> [access, acl].
|
||||
opt_type(_) -> [access, acl, access_rules, shaper_rules].
|
||||
|
||||
+5
-18
@@ -41,13 +41,7 @@
|
||||
%% Parse an ad-hoc request. Return either an adhoc_request record or
|
||||
%% an {error, ErrorType} tuple.
|
||||
%%
|
||||
-spec(parse_request/1 ::
|
||||
(
|
||||
IQ :: iq_request())
|
||||
-> adhoc_response()
|
||||
%%
|
||||
| {error, _}
|
||||
).
|
||||
-spec parse_request(IQ :: iq_request()) -> adhoc_response() | {error, _}.
|
||||
|
||||
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
|
||||
?DEBUG("entering parse_request...", []),
|
||||
@@ -88,12 +82,9 @@ find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
|
||||
%% record, filling in values for language, node and session id from
|
||||
%% the request.
|
||||
%%
|
||||
-spec(produce_response/2 ::
|
||||
(
|
||||
Adhoc_Request :: adhoc_request(),
|
||||
Adhoc_Response :: adhoc_response())
|
||||
-> Xmlel::xmlel()
|
||||
).
|
||||
-spec produce_response(Adhoc_Request :: adhoc_request(),
|
||||
Adhoc_Response :: adhoc_response()) ->
|
||||
Xmlel::xmlel().
|
||||
|
||||
%% Produce a <command/> node to use as response from an adhoc_response
|
||||
%% record.
|
||||
@@ -104,11 +95,7 @@ produce_response(#adhoc_request{lang = Lang, node = Node, sessionid = SessionID}
|
||||
}).
|
||||
|
||||
%%
|
||||
-spec(produce_response/1 ::
|
||||
(
|
||||
Adhoc_Response::adhoc_response())
|
||||
-> Xmlel::xmlel()
|
||||
).
|
||||
-spec produce_response(Adhoc_Response::adhoc_response()) -> Xmlel::xmlel().
|
||||
|
||||
produce_response(
|
||||
#adhoc_response{
|
||||
|
||||
+5
-23
@@ -88,13 +88,8 @@ start() ->
|
||||
ok.
|
||||
|
||||
%%
|
||||
-spec(register_mechanism/3 ::
|
||||
(
|
||||
Mechanim :: mechanism(),
|
||||
Module :: module(),
|
||||
PasswordType :: password_type())
|
||||
-> any()
|
||||
).
|
||||
-spec register_mechanism(Mechanim :: mechanism(), Module :: module(),
|
||||
PasswordType :: password_type()) -> any().
|
||||
|
||||
register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
case is_disabled(Mechanism) of
|
||||
@@ -139,11 +134,7 @@ check_credentials(_State, Props) ->
|
||||
_LUser -> ok
|
||||
end.
|
||||
|
||||
-spec(listmech/1 ::
|
||||
(
|
||||
Host ::binary())
|
||||
-> Mechanisms::mechanisms()
|
||||
).
|
||||
-spec listmech(Host ::binary()) -> Mechanisms::mechanisms().
|
||||
|
||||
listmech(Host) ->
|
||||
Mechs = ets:select(sasl_mechanism,
|
||||
@@ -213,12 +204,7 @@ server_step(State, ClientIn) ->
|
||||
%% Remove the anonymous mechanism from the list if not enabled for the given
|
||||
%% host
|
||||
%%
|
||||
-spec(filter_anonymous/2 ::
|
||||
(
|
||||
Host :: binary(),
|
||||
Mechs :: mechanisms())
|
||||
-> mechanisms()
|
||||
).
|
||||
-spec filter_anonymous(Host :: binary(), Mechs :: mechanisms()) -> mechanisms().
|
||||
|
||||
filter_anonymous(Host, Mechs) ->
|
||||
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
|
||||
@@ -226,11 +212,7 @@ filter_anonymous(Host, Mechs) ->
|
||||
false -> Mechs -- [<<"ANONYMOUS">>]
|
||||
end.
|
||||
|
||||
-spec(is_disabled/1 ::
|
||||
(
|
||||
Mechanism :: mechanism())
|
||||
-> boolean()
|
||||
).
|
||||
-spec is_disabled(Mechanism :: mechanism()) -> boolean().
|
||||
|
||||
is_disabled(Mechanism) ->
|
||||
Disabled = ejabberd_config:get_option(
|
||||
|
||||
@@ -53,11 +53,11 @@
|
||||
check_password = fun(_, _, _, _, _) -> false end :: check_password_fun(),
|
||||
auth_module :: atom(),
|
||||
host = <<"">> :: binary(),
|
||||
hostfqdn = <<"">> :: binary()}).
|
||||
hostfqdn = <<"">> :: binary() | [binary()]}).
|
||||
|
||||
start(_Opts) ->
|
||||
Fqdn = get_local_fqdn(),
|
||||
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
|
||||
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p",
|
||||
[Fqdn]),
|
||||
cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
|
||||
digest).
|
||||
@@ -183,16 +183,16 @@ is_digesturi_valid(DigestURICase, JabberDomain,
|
||||
DigestURI = stringprep:tolower(DigestURICase),
|
||||
case catch str:tokens(DigestURI, <<"/">>) of
|
||||
[<<"xmpp">>, Host] ->
|
||||
IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(Host == JabberDomain) or IsHostFqdn;
|
||||
[<<"xmpp">>, Host, ServName] ->
|
||||
IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(ServName == JabberDomain) and IsHostFqdn;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_host_fqdn(Host, [Letter | _Tail] = Fqdn) when not is_list(Letter) ->
|
||||
is_host_fqdn(Host, Fqdn) when is_binary(Fqdn) ->
|
||||
Host == Fqdn;
|
||||
is_host_fqdn(_Host, []) ->
|
||||
false;
|
||||
@@ -204,6 +204,7 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
|
||||
get_local_fqdn() ->
|
||||
case catch get_local_fqdn2() of
|
||||
Str when is_binary(Str) -> Str;
|
||||
List when is_list(List) -> List;
|
||||
_ ->
|
||||
<<"unknown-fqdn, please configure fqdn "
|
||||
"option in ejabberd.yml!">>
|
||||
@@ -211,9 +212,11 @@ get_local_fqdn() ->
|
||||
|
||||
get_local_fqdn2() ->
|
||||
case ejabberd_config:get_option(
|
||||
fqdn, fun iolist_to_binary/1) of
|
||||
fqdn, fun(X) -> X end) of
|
||||
ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
|
||||
ConfiguredFqdn;
|
||||
[A | _] = ConfiguredFqdns when is_binary(A) ->
|
||||
ConfiguredFqdns;
|
||||
undefined ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
{ok, {hostent, Fqdn, _, _, _, _}} =
|
||||
|
||||
@@ -51,7 +51,7 @@ mech_step(State, ClientIn) ->
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, ejabberd_oauth}]};
|
||||
false ->
|
||||
_ ->
|
||||
{error, <<"not-authorized">>, User}
|
||||
end;
|
||||
_ -> {error, <<"bad-protocol">>}
|
||||
|
||||
+9
-11
@@ -87,6 +87,7 @@ get_commands_spec() ->
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = reopen_log, tags = [logs, server],
|
||||
desc = "Reopen the log files",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = reopen_log,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = rotate_log, tags = [logs, server],
|
||||
@@ -129,6 +130,8 @@ get_commands_spec() ->
|
||||
|
||||
#ejabberd_commands{name = register, tags = [accounts],
|
||||
desc = "Register a user",
|
||||
policy = admin,
|
||||
access = [{mod_register, access, configure}],
|
||||
module = ?MODULE, function = register,
|
||||
args = [{user, binary}, {host, binary}, {password, binary}],
|
||||
result = {res, restuple}},
|
||||
@@ -166,7 +169,7 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = list_cluster, tags = [cluster],
|
||||
desc = "List nodes that are part of the cluster handled by Node",
|
||||
module = ?MODULE, function = list_cluster,
|
||||
args = [],
|
||||
args = [],
|
||||
result = {nodes, {list, {node, atom}}}},
|
||||
|
||||
#ejabberd_commands{name = import_file, tags = [mnesia],
|
||||
@@ -192,10 +195,6 @@ get_commands_spec() ->
|
||||
module = ejabberd_piefxis, function = export_host,
|
||||
args = [{dir, string}, {host, string}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = export_sql, tags = [mnesia, sql],
|
||||
desc = "Export all tables as SQL queries to a file",
|
||||
module = ejd2sql, function = export,
|
||||
args = [{host, string}, {file, string}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
|
||||
desc = "Export all tables as SQL queries to a file",
|
||||
module = ejd2sql, function = delete,
|
||||
@@ -224,11 +223,11 @@ get_commands_spec() ->
|
||||
desc = "Delete offline messages older than DAYS",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
|
||||
|
||||
#ejabberd_commands{name = export2sql, tags = [mnesia],
|
||||
desc = "Export virtual host information from Mnesia tables to SQL files",
|
||||
module = ejd2sql, function = export,
|
||||
args = [{host, string}, {directory, string}],
|
||||
args = [{host, string}, {file, string}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_master, tags = [mnesia],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
@@ -382,13 +381,12 @@ register(User, Host, Password) ->
|
||||
{atomic, ok} ->
|
||||
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
|
||||
{atomic, exists} ->
|
||||
String = io_lib:format("User ~s@~s already registered at node ~p",
|
||||
[User, Host, node()]),
|
||||
{exists, String};
|
||||
Msg = io_lib:format("User ~s@~s already registered", [User, Host]),
|
||||
{error, conflict, 10090, Msg};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
|
||||
[User, Host, node(), Reason]),
|
||||
{cannot_register, String}
|
||||
{error, cannot_register, 10001, String}
|
||||
end.
|
||||
|
||||
unregister(User, Host) ->
|
||||
|
||||
@@ -45,6 +45,7 @@ start(normal, _Args) ->
|
||||
write_pid_file(),
|
||||
jid:start(),
|
||||
start_apps(),
|
||||
start_elixir_application(),
|
||||
ejabberd:check_app(ejabberd),
|
||||
randoms:start(),
|
||||
db_init(),
|
||||
@@ -63,6 +64,7 @@ start(normal, _Args) ->
|
||||
Sup = ejabberd_sup:start_link(),
|
||||
ejabberd_rdbms:start(),
|
||||
ejabberd_riak_sup:start(),
|
||||
ejabberd_redis:start(),
|
||||
ejabberd_sm:start(),
|
||||
cyrsasl:start(),
|
||||
% Profiling
|
||||
@@ -83,9 +85,9 @@ start(_, _) ->
|
||||
%% before shutting down the processes of the application.
|
||||
prep_stop(State) ->
|
||||
ejabberd_listener:stop_listeners(),
|
||||
gen_mod:stop_modules(),
|
||||
ejabberd_admin:stop(),
|
||||
broadcast_c2s_shutdown(),
|
||||
gen_mod:stop_modules(),
|
||||
timer:sleep(5000),
|
||||
State.
|
||||
|
||||
@@ -236,3 +238,9 @@ opt_type(modules) ->
|
||||
Mods)
|
||||
end;
|
||||
opt_type(_) -> [cluster_nodes, loglevel, modules, net_ticktime].
|
||||
|
||||
start_elixir_application() ->
|
||||
case application:ensure_started(elixir) of
|
||||
ok -> ok;
|
||||
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
|
||||
end.
|
||||
|
||||
@@ -78,13 +78,15 @@ store_type() -> external.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> check_password_extauth(User, AuthzId, Server, Password);
|
||||
{true, CacheTime} ->
|
||||
check_password_cache(User, AuthzId, Server, Password, CacheTime)
|
||||
end
|
||||
false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false ->
|
||||
check_password_extauth(User, AuthzId, Server, Password);
|
||||
{true, CacheTime} ->
|
||||
check_password_cache(User, AuthzId, Server, Password,
|
||||
CacheTime)
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
|
||||
@@ -118,16 +118,15 @@ store_type() -> external.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
if Password == <<"">> -> false;
|
||||
false;
|
||||
true ->
|
||||
case catch check_password_ldap(User, Server, Password)
|
||||
of
|
||||
{'EXIT', _} -> false;
|
||||
Result -> Result
|
||||
end
|
||||
end
|
||||
if Password == <<"">> -> false;
|
||||
true ->
|
||||
case catch check_password_ldap(User, Server, Password) of
|
||||
{'EXIT', _} -> false;
|
||||
Result -> Result
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
-module(ejabberd_auth_mnesia).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
@@ -43,6 +45,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
@@ -88,51 +91,48 @@ store_type() ->
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ -> false
|
||||
end
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}] when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
Passwd = jlib:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
Passwd = jlib:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
@@ -473,12 +473,22 @@ is_password_scram_valid(Password, Scram) ->
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
when LServer == Host,
|
||||
is_binary(Password) ->
|
||||
[?SQL("delete from users where username=%(LUser)s;"),
|
||||
?SQL("insert into users(username, password) "
|
||||
"values (%(LUser)s, %(Password)s);")];
|
||||
(Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Pass = ejabberd_sql:escape(Password),
|
||||
[[<<"delete from users where username='">>, Username, <<"';">>],
|
||||
[<<"insert into users(username, password) "
|
||||
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
|
||||
StoredKey = Scram#scram.storedkey,
|
||||
ServerKey = Scram#scram.serverkey,
|
||||
Salt = Scram#scram.salt,
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
[?SQL("delete from users where username=%(LUser)s;"),
|
||||
?SQL("insert into users(username, password, serverkey, salt, "
|
||||
"iterationcount) "
|
||||
"values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
|
||||
" %(Salt)s, %(IterationCount)d);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
-module(ejabberd_auth_riak).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
@@ -42,6 +44,7 @@
|
||||
-export([passwd_schema/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
@@ -290,12 +293,10 @@ is_password_scram_valid(Password, Scram) ->
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Pass = ejabberd_sql:escape(Password),
|
||||
[[<<"delete from users where username='">>, Username, <<"';">>],
|
||||
[<<"insert into users(username, password) "
|
||||
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
|
||||
when LServer == Host ->
|
||||
[?SQL("delete from users where username=%(LUser)s;"),
|
||||
?SQL("insert into users(username, password) "
|
||||
"values (%(LUser)s, %(Password)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
+34
-26
@@ -25,6 +25,8 @@
|
||||
|
||||
-module(ejabberd_auth_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
@@ -43,6 +45,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
@@ -85,7 +88,7 @@ check_password(User, AuthzId, Server, Password) ->
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
iterationcount = IterationCount},
|
||||
is_password_scram_valid(Password, Scram);
|
||||
is_password_scram_valid_stored(Password, Scram, LUser, LServer);
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
@@ -414,6 +417,15 @@ password_to_scram(Password, IterationCount) ->
|
||||
salt = jlib:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) ->
|
||||
?INFO_MSG("Apparently, SQL auth method and scram password formatting are "
|
||||
"enabled, but the password of user '~s' in the 'users' table is not "
|
||||
"scrammed. You may want to execute this command: "
|
||||
"ejabberdctl convert_to_scram ~s", [LUser, LServer]),
|
||||
false;
|
||||
is_password_scram_valid_stored(Password, Scram, _, _) ->
|
||||
is_password_scram_valid(Password, Scram).
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = jlib:decode_base64(Scram#scram.salt),
|
||||
@@ -425,19 +437,15 @@ is_password_scram_valid(Password, Scram) ->
|
||||
|
||||
-define(BATCH_SIZE, 1000).
|
||||
|
||||
set_password_scram_t(Username,
|
||||
set_password_scram_t(LUser,
|
||||
StoredKey, ServerKey, Salt, IterationCount) ->
|
||||
sql_queries:update_t(<<"users">>,
|
||||
[<<"username">>,
|
||||
<<"password">>,
|
||||
<<"serverkey">>,
|
||||
<<"salt">>,
|
||||
<<"iterationcount">>],
|
||||
[Username, StoredKey,
|
||||
ServerKey, Salt,
|
||||
IterationCount],
|
||||
[<<"username='">>, Username,
|
||||
<<"'">>]).
|
||||
?SQL_UPSERT_T(
|
||||
"users",
|
||||
["!username=%(LUser)s",
|
||||
"password=%(StoredKey)s",
|
||||
"serverkey=%(ServerKey)s",
|
||||
"salt=%(Salt)s",
|
||||
"iterationcount=%(IterationCount)d"]).
|
||||
|
||||
convert_to_scram(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
@@ -447,24 +455,24 @@ convert_to_scram(Server) ->
|
||||
{error, {incorrect_server_name, Server}};
|
||||
true ->
|
||||
F = fun () ->
|
||||
BatchSize = ?BATCH_SIZE,
|
||||
case ejabberd_sql:sql_query_t(
|
||||
[<<"select username, password from users where "
|
||||
"iterationcount=0 limit ">>,
|
||||
integer_to_binary(?BATCH_SIZE),
|
||||
<<";">>]) of
|
||||
{selected, [<<"username">>, <<"password">>], []} ->
|
||||
?SQL("select @(username)s, @(password)s"
|
||||
" from users"
|
||||
" where iterationcount=0"
|
||||
" limit %(BatchSize)d")) of
|
||||
{selected, []} ->
|
||||
ok;
|
||||
{selected, [<<"username">>, <<"password">>], Rs} ->
|
||||
{selected, Rs} ->
|
||||
lists:foreach(
|
||||
fun([LUser, Password]) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
fun({LUser, Password}) ->
|
||||
Scram = password_to_scram(Password),
|
||||
set_password_scram_t(
|
||||
Username,
|
||||
ejabberd_sql:escape(Scram#scram.storedkey),
|
||||
ejabberd_sql:escape(Scram#scram.serverkey),
|
||||
ejabberd_sql:escape(Scram#scram.salt),
|
||||
integer_to_binary(Scram#scram.iterationcount)
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
Scram#scram.serverkey,
|
||||
Scram#scram.salt,
|
||||
Scram#scram.iterationcount
|
||||
)
|
||||
end, Rs),
|
||||
continue;
|
||||
|
||||
+139
-116
@@ -104,7 +104,6 @@
|
||||
ip,
|
||||
aux_fields = [],
|
||||
csi_state = active,
|
||||
csi_queue = [],
|
||||
mgmt_state,
|
||||
mgmt_xmlns,
|
||||
mgmt_queue,
|
||||
@@ -167,27 +166,32 @@
|
||||
(Xmlns == ?NS_STREAM_MGMT_2) or
|
||||
(Xmlns == ?NS_STREAM_MGMT_3)).
|
||||
|
||||
-define(MGMT_FAILED(Condition, Xmlns),
|
||||
-define(MGMT_FAILED(Condition, Attrs),
|
||||
#xmlel{name = <<"failed">>,
|
||||
attrs = [{<<"xmlns">>, Xmlns}],
|
||||
attrs = Attrs,
|
||||
children = [#xmlel{name = Condition,
|
||||
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
|
||||
children = []}]}).
|
||||
|
||||
-define(MGMT_BAD_REQUEST(Xmlns),
|
||||
?MGMT_FAILED(<<"bad-request">>, Xmlns)).
|
||||
|
||||
-define(MGMT_ITEM_NOT_FOUND(Xmlns),
|
||||
?MGMT_FAILED(<<"item-not-found">>, Xmlns)).
|
||||
?MGMT_FAILED(<<"bad-request">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_SERVICE_UNAVAILABLE(Xmlns),
|
||||
?MGMT_FAILED(<<"service-unavailable">>, Xmlns)).
|
||||
?MGMT_FAILED(<<"service-unavailable">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_UNEXPECTED_REQUEST(Xmlns),
|
||||
?MGMT_FAILED(<<"unexpected-request">>, Xmlns)).
|
||||
?MGMT_FAILED(<<"unexpected-request">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_UNSUPPORTED_VERSION(Xmlns),
|
||||
?MGMT_FAILED(<<"unsupported-version">>, Xmlns)).
|
||||
?MGMT_FAILED(<<"unsupported-version">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_ITEM_NOT_FOUND(Xmlns),
|
||||
?MGMT_FAILED(<<"item-not-found">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_ITEM_NOT_FOUND_H(Xmlns, NumStanzasIn),
|
||||
?MGMT_FAILED(<<"item-not-found">>,
|
||||
[{<<"xmlns">>, Xmlns},
|
||||
{<<"h">>, jlib:integer_to_binary(NumStanzasIn)}])).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@@ -255,14 +259,10 @@ close(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed).
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
init([{SockMod, Socket}, Opts]) ->
|
||||
Access = case lists:keysearch(access, 1, Opts) of
|
||||
{value, {_, A}} -> A;
|
||||
_ -> all
|
||||
end,
|
||||
Shaper = case lists:keysearch(shaper, 1, Opts) of
|
||||
{value, {_, S}} -> S;
|
||||
_ -> none
|
||||
end,
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun acl:access_rules_validator/1, all),
|
||||
Shaper = gen_mod:get_opt(shaper, Opts,
|
||||
fun acl:shaper_rules_validator/1, none),
|
||||
XMLSocket = case lists:keysearch(xml_socket, 1, Opts) of
|
||||
{value, {_, XS}} -> XS;
|
||||
_ -> false
|
||||
@@ -364,11 +364,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
%% avoid possible DoS/flood attacks
|
||||
<<"">>
|
||||
end,
|
||||
StreamVersion = case fxml:get_attr_s(<<"version">>, Attrs) of
|
||||
<<"1.0">> ->
|
||||
<<"1.0">>;
|
||||
_ ->
|
||||
<<"">>
|
||||
end,
|
||||
IsBlacklistedIP = is_ip_blacklisted(StateData#state.ip, Lang),
|
||||
case lists:member(Server, ?MYHOSTS) of
|
||||
true when IsBlacklistedIP == false ->
|
||||
change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)),
|
||||
case fxml:get_attr_s(<<"version">>, Attrs) of
|
||||
case StreamVersion of
|
||||
<<"1.0">> ->
|
||||
send_header(StateData, Server, <<"1.0">>, DefaultLang),
|
||||
case StateData#state.authenticated of
|
||||
@@ -534,11 +540,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
{true, LogReason, ReasonT} = IsBlacklistedIP,
|
||||
?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
|
||||
[jlib:ip_to_list(IP), LogReason]),
|
||||
send_header(StateData, Server, <<"">>, DefaultLang),
|
||||
send_header(StateData, Server, StreamVersion, DefaultLang),
|
||||
send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
|
||||
send_header(StateData, ?MYNAME, StreamVersion, DefaultLang),
|
||||
send_element(StateData, ?HOST_UNKNOWN_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
@@ -620,9 +626,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
{auth, _ID, set, {U, P, D, R}} ->
|
||||
JID = jid:make(U, StateData#state.server, R),
|
||||
case JID /= error andalso
|
||||
acl:match_rule(StateData#state.server,
|
||||
StateData#state.access, JID)
|
||||
== allow
|
||||
acl:access_matches(StateData#state.access,
|
||||
#{usr => jid:split(JID), ip => StateData#state.ip},
|
||||
StateData#state.server) == allow
|
||||
of
|
||||
true ->
|
||||
DGen = fun (PW) ->
|
||||
@@ -1053,7 +1059,11 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
|
||||
children =
|
||||
[{xmlcdata,
|
||||
jid:to_string(JID)}]}]}]},
|
||||
send_element(StateData3, jlib:iq_to_xml(Res)),
|
||||
try
|
||||
send_element(StateData3, jlib:iq_to_xml(Res))
|
||||
catch exit:normal ->
|
||||
close(self())
|
||||
end,
|
||||
fsm_next_state_pack(
|
||||
session_established,
|
||||
StateData3);
|
||||
@@ -1095,8 +1105,10 @@ open_session(StateData) ->
|
||||
R = StateData#state.resource,
|
||||
JID = StateData#state.jid,
|
||||
Lang = StateData#state.lang,
|
||||
case acl:match_rule(StateData#state.server,
|
||||
StateData#state.access, JID) of
|
||||
IP = StateData#state.ip,
|
||||
case acl:access_matches(StateData#state.access,
|
||||
#{usr => jid:split(JID), ip => IP},
|
||||
StateData#state.server) of
|
||||
allow ->
|
||||
?INFO_MSG("(~w) Opened session for ~s",
|
||||
[StateData#state.socket, jid:to_string(JID)]),
|
||||
@@ -1143,7 +1155,7 @@ session_established({xmlstreamelement,
|
||||
#xmlel{name = <<"active">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
|
||||
StateData) ->
|
||||
NewStateData = csi_queue_flush(StateData),
|
||||
NewStateData = csi_flush_queue(StateData),
|
||||
fsm_next_state(session_established, NewStateData#state{csi_state = active});
|
||||
session_established({xmlstreamelement,
|
||||
#xmlel{name = <<"inactive">>,
|
||||
@@ -1277,7 +1289,7 @@ wait_for_resume({xmlstreamelement, _El} = Event, StateData) ->
|
||||
wait_for_resume(timeout, StateData) ->
|
||||
?DEBUG("Timed out waiting for resumption of stream for ~s",
|
||||
[jid:to_string(StateData#state.jid)]),
|
||||
{stop, normal, StateData};
|
||||
{stop, normal, StateData#state{mgmt_state = timeout}};
|
||||
wait_for_resume(Event, StateData) ->
|
||||
?DEBUG("Ignoring event while waiting for resumption: ~p", [Event]),
|
||||
fsm_next_state(wait_for_resume, StateData).
|
||||
@@ -1306,7 +1318,7 @@ handle_sync_event({resume_session, Time}, _From, _StateName,
|
||||
StateData#state.user,
|
||||
StateData#state.server,
|
||||
StateData#state.resource),
|
||||
{stop, normal, {ok, StateData}, StateData#state{mgmt_state = resumed}};
|
||||
{stop, normal, {resume, StateData}, StateData#state{mgmt_state = resumed}};
|
||||
handle_sync_event({resume_session, _Time}, _From, StateName,
|
||||
StateData) ->
|
||||
{reply, {error, <<"Previous session not found">>}, StateName, StateData};
|
||||
@@ -1563,6 +1575,12 @@ handle_info({route, From, To,
|
||||
{true, Attrs,
|
||||
StateData};
|
||||
deny ->
|
||||
Err =
|
||||
jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
Err),
|
||||
{false, Attrs,
|
||||
StateData}
|
||||
end;
|
||||
@@ -1614,11 +1632,18 @@ handle_info({route, From, To,
|
||||
<<"groupchat">> -> ok;
|
||||
<<"headline">> -> ok;
|
||||
_ ->
|
||||
Err =
|
||||
jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From,
|
||||
Err)
|
||||
case fxml:get_subtag_with_xmlns(Packet,
|
||||
<<"x">>,
|
||||
?NS_MUC_USER)
|
||||
of
|
||||
false ->
|
||||
Err =
|
||||
jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From,
|
||||
Err);
|
||||
_ -> ok
|
||||
end
|
||||
end,
|
||||
{false, Attrs, StateData}
|
||||
end;
|
||||
@@ -1720,6 +1745,13 @@ handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
|
||||
fsm_next_state(StateName, StateData);
|
||||
handle_info(dont_ask_offline, StateName, StateData) ->
|
||||
fsm_next_state(StateName, StateData#state{ask_offline = false});
|
||||
handle_info({_Ref, {resume, OldStateData}}, StateName, StateData) ->
|
||||
%% This happens if the resume_session/1 request timed out; the new session
|
||||
%% now receives the late response.
|
||||
?DEBUG("Received old session state for ~s after failed resumption",
|
||||
[jid:to_string(OldStateData#state.jid)]),
|
||||
handle_unacked_stanzas(OldStateData#state{mgmt_resend = false}),
|
||||
fsm_next_state(StateName, StateData);
|
||||
handle_info(Info, StateName, StateData) ->
|
||||
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
||||
fsm_next_state(StateName, StateData).
|
||||
@@ -1756,8 +1788,7 @@ terminate(_Reason, StateName, StateData) ->
|
||||
StateData#state.resource,
|
||||
<<"Replaced by new connection">>),
|
||||
presence_broadcast(StateData, From,
|
||||
StateData#state.pres_a, Packet),
|
||||
handle_unacked_stanzas(StateData);
|
||||
StateData#state.pres_a, Packet);
|
||||
_ ->
|
||||
?INFO_MSG("(~w) Close session for ~s",
|
||||
[StateData#state.socket,
|
||||
@@ -1782,8 +1813,20 @@ terminate(_Reason, StateName, StateData) ->
|
||||
presence_broadcast(StateData, From,
|
||||
StateData#state.pres_a, Packet)
|
||||
end,
|
||||
handle_unacked_stanzas(StateData)
|
||||
case StateData#state.mgmt_state of
|
||||
timeout ->
|
||||
Info = [{num_stanzas_in,
|
||||
StateData#state.mgmt_stanzas_in}],
|
||||
ejabberd_sm:set_offline_info(StateData#state.sid,
|
||||
StateData#state.user,
|
||||
StateData#state.server,
|
||||
StateData#state.resource,
|
||||
Info);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
handle_unacked_stanzas(StateData),
|
||||
bounce_messages();
|
||||
true ->
|
||||
ok
|
||||
@@ -1798,8 +1841,9 @@ terminate(_Reason, StateName, StateData) ->
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
change_shaper(StateData, JID) ->
|
||||
Shaper = acl:match_rule(StateData#state.server,
|
||||
StateData#state.shaper, JID),
|
||||
Shaper = acl:access_matches(StateData#state.shaper,
|
||||
#{usr => jid:split(JID), ip => StateData#state.ip},
|
||||
StateData#state.server),
|
||||
(StateData#state.sockmod):change_shaper(StateData#state.socket,
|
||||
Shaper).
|
||||
|
||||
@@ -1825,6 +1869,7 @@ send_text(StateData, Text) ->
|
||||
send_element(StateData, El) when StateData#state.mgmt_state == pending ->
|
||||
?DEBUG("Cannot send element while waiting for resumption: ~p", [El]);
|
||||
send_element(StateData, El) when StateData#state.xml_socket ->
|
||||
?DEBUG("Send XML on stream = ~p", [fxml:element_to_binary(El)]),
|
||||
(StateData#state.sockmod):send_xml(StateData#state.socket,
|
||||
{xmlstreamelement, El});
|
||||
send_element(StateData, El) ->
|
||||
@@ -2584,9 +2629,9 @@ stream_mgmt_enabled(#state{mgmt_state = disabled}) ->
|
||||
stream_mgmt_enabled(_StateData) ->
|
||||
true.
|
||||
|
||||
dispatch_stream_mgmt(El, StateData)
|
||||
when StateData#state.mgmt_state == active;
|
||||
StateData#state.mgmt_state == pending ->
|
||||
dispatch_stream_mgmt(El, #state{mgmt_state = MgmtState} = StateData)
|
||||
when MgmtState == active;
|
||||
MgmtState == pending ->
|
||||
perform_stream_mgmt(El, StateData);
|
||||
dispatch_stream_mgmt(El, StateData) ->
|
||||
negotiate_stream_mgmt(El, StateData).
|
||||
@@ -2717,6 +2762,8 @@ handle_resume(StateData, Attrs) ->
|
||||
case inherit_session_state(StateData, PrevID) of
|
||||
{ok, InheritedState} ->
|
||||
{ok, InheritedState, H};
|
||||
{error, Err, InH} ->
|
||||
{error, ?MGMT_ITEM_NOT_FOUND_H(Xmlns, InH), Err};
|
||||
{error, Err} ->
|
||||
{error, ?MGMT_ITEM_NOT_FOUND(Xmlns), Err}
|
||||
end;
|
||||
@@ -2753,7 +2800,7 @@ handle_resume(StateData, Attrs) ->
|
||||
#xmlel{name = <<"r">>,
|
||||
attrs = [{<<"xmlns">>, AttrXmlns}],
|
||||
children = []}),
|
||||
FlushedState = csi_queue_flush(NewState),
|
||||
FlushedState = csi_flush_queue(NewState),
|
||||
NewStateData = FlushedState#state{csi_state = active},
|
||||
?INFO_MSG("Resumed session for ~s",
|
||||
[jid:to_string(NewStateData#state.jid)]),
|
||||
@@ -2775,7 +2822,9 @@ check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H) ->
|
||||
[jid:to_string(StateData#state.jid), H, NumStanzasOut]),
|
||||
mgmt_queue_drop(StateData, H).
|
||||
|
||||
update_num_stanzas_in(#state{mgmt_state = active} = StateData, El) ->
|
||||
update_num_stanzas_in(#state{mgmt_state = MgmtState} = StateData, El)
|
||||
when MgmtState == active;
|
||||
MgmtState == pending ->
|
||||
NewNum = case {is_stanza(El), StateData#state.mgmt_stanzas_in} of
|
||||
{true, 4294967295} ->
|
||||
0;
|
||||
@@ -2830,16 +2879,17 @@ check_queue_length(#state{mgmt_queue = Queue,
|
||||
StateData
|
||||
end.
|
||||
|
||||
handle_unacked_stanzas(StateData, F)
|
||||
when StateData#state.mgmt_state == active;
|
||||
StateData#state.mgmt_state == pending ->
|
||||
handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F)
|
||||
when MgmtState == active;
|
||||
MgmtState == pending;
|
||||
MgmtState == timeout ->
|
||||
Queue = StateData#state.mgmt_queue,
|
||||
case queue:len(Queue) of
|
||||
0 ->
|
||||
ok;
|
||||
N ->
|
||||
?INFO_MSG("~B stanzas were not acknowledged by ~s",
|
||||
[N, jid:to_string(StateData#state.jid)]),
|
||||
?DEBUG("~B stanza(s) were not acknowledged by ~s",
|
||||
[N, jid:to_string(StateData#state.jid)]),
|
||||
lists:foreach(
|
||||
fun({_, Time, #xmlel{attrs = Attrs} = El}) ->
|
||||
From_s = fxml:get_attr_s(<<"from">>, Attrs),
|
||||
@@ -2852,9 +2902,10 @@ handle_unacked_stanzas(StateData, F)
|
||||
handle_unacked_stanzas(_StateData, _F) ->
|
||||
ok.
|
||||
|
||||
handle_unacked_stanzas(StateData)
|
||||
when StateData#state.mgmt_state == active;
|
||||
StateData#state.mgmt_state == pending ->
|
||||
handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
|
||||
when MgmtState == active;
|
||||
MgmtState == pending;
|
||||
MgmtState == timeout ->
|
||||
ResendOnTimeout =
|
||||
case StateData#state.mgmt_resend of
|
||||
Resend when is_boolean(Resend) ->
|
||||
@@ -2880,7 +2931,7 @@ handle_unacked_stanzas(StateData)
|
||||
end;
|
||||
false ->
|
||||
fun(From, To, El, _Time) ->
|
||||
Txt = <<"User session not found">>,
|
||||
Txt = <<"User session terminated">>,
|
||||
Err =
|
||||
jlib:make_error_reply(
|
||||
El,
|
||||
@@ -2892,7 +2943,7 @@ handle_unacked_stanzas(StateData)
|
||||
?DEBUG("Dropping presence stanza from ~s",
|
||||
[jid:to_string(From)]);
|
||||
(From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
|
||||
Txt = <<"User session not found">>,
|
||||
Txt = <<"User session terminated">>,
|
||||
Err = jlib:make_error_reply(
|
||||
El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
@@ -2915,6 +2966,9 @@ handle_unacked_stanzas(StateData)
|
||||
[StateData, From,
|
||||
StateData#state.jid, El]) of
|
||||
true ->
|
||||
?DEBUG("Dropping archived message stanza from ~s",
|
||||
[fxml:get_attr_s(<<"from">>,
|
||||
El#xmlel.attrs)]),
|
||||
ok;
|
||||
false ->
|
||||
ReRoute(From, To, El, Time)
|
||||
@@ -2956,11 +3010,21 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
|
||||
{term, {R, Time}} ->
|
||||
case ejabberd_sm:get_session_pid(U, S, R) of
|
||||
none ->
|
||||
{error, <<"Previous session PID not found">>};
|
||||
case ejabberd_sm:get_offline_info(Time, U, S, R) of
|
||||
none ->
|
||||
{error, <<"Previous session PID not found">>};
|
||||
Info ->
|
||||
case proplists:get_value(num_stanzas_in, Info) of
|
||||
undefined ->
|
||||
{error, <<"Previous session timed out">>};
|
||||
H ->
|
||||
{error, <<"Previous session timed out">>, H}
|
||||
end
|
||||
end;
|
||||
OldPID ->
|
||||
OldSID = {Time, OldPID},
|
||||
case catch resume_session(OldSID) of
|
||||
{ok, OldStateData} ->
|
||||
{resume, OldStateData} ->
|
||||
NewSID = {Time, self()}, % Old time, new PID
|
||||
Priority = case OldStateData#state.pres_last of
|
||||
undefined ->
|
||||
@@ -2985,7 +3049,6 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
|
||||
privacy_list = OldStateData#state.privacy_list,
|
||||
aux_fields = OldStateData#state.aux_fields,
|
||||
csi_state = OldStateData#state.csi_state,
|
||||
csi_queue = OldStateData#state.csi_queue,
|
||||
mgmt_xmlns = OldStateData#state.mgmt_xmlns,
|
||||
mgmt_queue = OldStateData#state.mgmt_queue,
|
||||
mgmt_timeout = OldStateData#state.mgmt_timeout,
|
||||
@@ -3003,7 +3066,7 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
|
||||
end.
|
||||
|
||||
resume_session({Time, PID}) ->
|
||||
(?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 5000).
|
||||
(?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 15000).
|
||||
|
||||
make_resume_id(StateData) ->
|
||||
{Time, _} = StateData#state.sid,
|
||||
@@ -3018,65 +3081,25 @@ add_resent_delay_info(#state{server = From}, El, Time) ->
|
||||
%%% XEP-0352
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData,
|
||||
csi_filter_stanza(#state{csi_state = CsiState, server = Server} = StateData,
|
||||
Stanza) ->
|
||||
Action = ejabberd_hooks:run_fold(csi_filter_stanza,
|
||||
StateData#state.server,
|
||||
send, [Stanza]),
|
||||
?DEBUG("Going to ~p stanza for inactive client ~p",
|
||||
[Action, jid:to_string(JID)]),
|
||||
case Action of
|
||||
queue -> csi_queue_add(StateData, Stanza);
|
||||
drop -> StateData;
|
||||
send ->
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
StateData1 = csi_queue_send(StateData, From),
|
||||
StateData2 = send_stanza(StateData1#state{csi_state = active},
|
||||
Stanza),
|
||||
StateData2#state{csi_state = CsiState}
|
||||
end.
|
||||
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_filter_stanza, Server,
|
||||
{StateData, [Stanza]},
|
||||
[Server, Stanza]),
|
||||
StateData2 = lists:foldl(fun(CurStanza, AccState) ->
|
||||
send_stanza(AccState, CurStanza)
|
||||
end, StateData1#state{csi_state = active},
|
||||
Stanzas),
|
||||
StateData2#state{csi_state = CsiState}.
|
||||
|
||||
csi_queue_add(#state{csi_queue = Queue} = StateData, Stanza) ->
|
||||
case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of
|
||||
true -> csi_queue_add(csi_queue_flush(StateData), Stanza);
|
||||
false ->
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
NewQueue = lists:keystore(From, 1, Queue, {From, p1_time_compat:timestamp(), Stanza}),
|
||||
StateData#state{csi_queue = NewQueue}
|
||||
end.
|
||||
|
||||
csi_queue_send(#state{csi_queue = Queue, csi_state = CsiState, server = Host} =
|
||||
StateData, From) ->
|
||||
case lists:keytake(From, 1, Queue) of
|
||||
{value, {From, Time, Stanza}, NewQueue} ->
|
||||
NewStanza = jlib:add_delay_info(Stanza, Host, Time,
|
||||
<<"Client Inactive">>),
|
||||
NewStateData = send_stanza(StateData#state{csi_state = active},
|
||||
NewStanza),
|
||||
NewStateData#state{csi_queue = NewQueue, csi_state = CsiState};
|
||||
false -> StateData
|
||||
end.
|
||||
|
||||
csi_queue_flush(#state{csi_queue = Queue, csi_state = CsiState, jid = JID,
|
||||
server = Host} = StateData) ->
|
||||
?DEBUG("Flushing CSI queue for ~s", [jid:to_string(JID)]),
|
||||
NewStateData =
|
||||
lists:foldl(fun({_From, Time, Stanza}, AccState) ->
|
||||
NewStanza =
|
||||
jlib:add_delay_info(Stanza, Host, Time,
|
||||
<<"Client Inactive">>),
|
||||
send_stanza(AccState, NewStanza)
|
||||
end, StateData#state{csi_state = active}, Queue),
|
||||
NewStateData#state{csi_queue = [], csi_state = CsiState}.
|
||||
|
||||
%% Make sure we won't push too many messages to the XEP-0198 queue when the
|
||||
%% client becomes 'active' again. Otherwise, the client might not manage to
|
||||
%% acknowledge the message flood in time. Also, don't let the queue grow to
|
||||
%% more than 100 stanzas.
|
||||
csi_max_queue(#state{mgmt_max_queue = infinity}) -> 100;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) when Max > 200 -> 100;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) when Max < 2 -> 1;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) -> Max div 2.
|
||||
csi_flush_queue(#state{csi_state = CsiState, server = Server} = StateData) ->
|
||||
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_flush_queue, Server,
|
||||
{StateData, []}, [Server]),
|
||||
StateData2 = lists:foldl(fun(CurStanza, AccState) ->
|
||||
send_stanza(AccState, CurStanza)
|
||||
end, StateData1#state{csi_state = active},
|
||||
Stanzas),
|
||||
StateData2#state{csi_state = CsiState}.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% JID Set memory footprint reduction code
|
||||
|
||||
+220
-146
@@ -218,18 +218,20 @@
|
||||
get_command_format/1,
|
||||
get_command_format/2,
|
||||
get_command_format/3,
|
||||
get_command_policy/1,
|
||||
get_command_policy_and_scope/1,
|
||||
get_command_definition/1,
|
||||
get_command_definition/2,
|
||||
get_tags_commands/0,
|
||||
get_tags_commands/1,
|
||||
get_commands/0,
|
||||
get_exposed_commands/0,
|
||||
register_commands/1,
|
||||
unregister_commands/1,
|
||||
unregister_commands/1,
|
||||
expose_commands/1,
|
||||
execute_command/2,
|
||||
execute_command/3,
|
||||
execute_command/4,
|
||||
execute_command/5,
|
||||
execute_command/6,
|
||||
opt_type/1,
|
||||
get_commands_spec/0
|
||||
]).
|
||||
@@ -274,10 +276,10 @@ get_commands_spec() ->
|
||||
init() ->
|
||||
mnesia:delete_table(ejabberd_commands),
|
||||
mnesia:create_table(ejabberd_commands,
|
||||
[{ram_copies, [node()]},
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, ejabberd_commands)},
|
||||
{type, bag}]),
|
||||
{attributes, record_info(fields, ejabberd_commands)},
|
||||
{type, bag}]),
|
||||
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
|
||||
register_commands(get_commands_spec()).
|
||||
|
||||
@@ -286,12 +288,14 @@ init() ->
|
||||
%% @doc Register ejabberd commands.
|
||||
%% If a command is already registered, a warning is printed and the
|
||||
%% old command is preserved.
|
||||
%% A registered command is not directly available to be called through
|
||||
%% ejabberd ReST API. It need to be exposed to be available through API.
|
||||
register_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
% XXX check if command exists
|
||||
mnesia:dirty_write(Command)
|
||||
% ?DEBUG("This command is already defined:~n~p", [Command])
|
||||
%% XXX check if command exists
|
||||
mnesia:dirty_write(Command)
|
||||
%% ?DEBUG("This command is already defined:~n~p", [Command])
|
||||
end,
|
||||
Commands).
|
||||
|
||||
@@ -305,6 +309,25 @@ unregister_commands(Commands) ->
|
||||
end,
|
||||
Commands).
|
||||
|
||||
%% @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_local_option(commands, [{add_commands, Names}]) of
|
||||
{aborted, Reason} ->
|
||||
{error, Reason};
|
||||
{atomic, Result} ->
|
||||
Result
|
||||
end.
|
||||
|
||||
-spec list_commands() -> [{atom(), [aterm()], string()}].
|
||||
|
||||
%% @doc Get a list of all the available commands, arguments and description.
|
||||
@@ -318,8 +341,8 @@ list_commands() ->
|
||||
list_commands(Version) ->
|
||||
Commands = get_commands_definition(Version),
|
||||
[{Name, Args, Desc} || #ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc} <- Commands].
|
||||
args = Args,
|
||||
desc = Desc} <- Commands].
|
||||
|
||||
|
||||
-spec list_commands_policy(integer()) ->
|
||||
@@ -330,10 +353,10 @@ list_commands(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].
|
||||
#ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc,
|
||||
policy = Policy} <- Commands].
|
||||
|
||||
-spec get_command_format(atom()) -> {[aterm()], rterm()}.
|
||||
|
||||
@@ -352,30 +375,36 @@ get_command_format(Name, Auth) ->
|
||||
{[aterm()], rterm()}.
|
||||
|
||||
get_command_format(Name, Auth, Version) ->
|
||||
Admin = is_admin(Name, Auth),
|
||||
Admin = is_admin(Name, Auth, #{}),
|
||||
#ejabberd_commands{args = Args,
|
||||
result = Result,
|
||||
policy = Policy} =
|
||||
get_command_definition(Name, Version),
|
||||
policy = Policy} =
|
||||
get_command_definition(Name, Version),
|
||||
case Policy of
|
||||
user when Admin;
|
||||
Auth == noauth ->
|
||||
{[{user, binary}, {server, binary} | Args], Result};
|
||||
_ ->
|
||||
{Args, Result}
|
||||
user when Admin;
|
||||
Auth == noauth ->
|
||||
{[{user, binary}, {server, binary} | Args], Result};
|
||||
_ ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
-spec get_command_policy(atom()) -> {ok, open|user|admin|restricted} | {error, command_not_found}.
|
||||
-spec get_command_policy_and_scope(atom()) -> {ok, open|user|admin|restricted, [oauth_scope()]} | {error, command_not_found}.
|
||||
|
||||
%% @doc return command policy.
|
||||
get_command_policy(Name) ->
|
||||
get_command_policy_and_scope(Name) ->
|
||||
case get_command_definition(Name) of
|
||||
#ejabberd_commands{policy = Policy} ->
|
||||
{ok, Policy};
|
||||
#ejabberd_commands{policy = Policy} = Cmd ->
|
||||
{ok, Policy, cmd_scope(Cmd)};
|
||||
command_not_found ->
|
||||
{error, command_not_found}
|
||||
end.
|
||||
|
||||
%% The oauth scopes for a command are the command name itself,
|
||||
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
|
||||
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
|
||||
[erlang:atom_to_binary(Name,utf8)] ++ [<<"ejabberd:user">> || Policy == user] ++ [<<"ejabberd:admin">> || Policy == admin].
|
||||
|
||||
|
||||
-spec get_command_definition(atom()) -> ejabberd_commands().
|
||||
|
||||
%% @doc Get the definition record of a command.
|
||||
@@ -387,16 +416,16 @@ get_command_definition(Name) ->
|
||||
%% @doc Get the definition record of a command in a given API version.
|
||||
get_command_definition(Name, Version) ->
|
||||
case lists:reverse(
|
||||
lists:sort(
|
||||
mnesia:dirty_select(
|
||||
ejabberd_commands,
|
||||
ets:fun2ms(
|
||||
fun(#ejabberd_commands{name = N, version = V} = C)
|
||||
when N == Name, V =< Version ->
|
||||
{V, C}
|
||||
end)))) of
|
||||
[{_, Command} | _ ] -> Command;
|
||||
_E -> throw(unknown_command)
|
||||
lists:sort(
|
||||
mnesia:dirty_select(
|
||||
ejabberd_commands,
|
||||
ets:fun2ms(
|
||||
fun(#ejabberd_commands{name = N, version = V} = C)
|
||||
when N == Name, V =< Version ->
|
||||
{V, C}
|
||||
end)))) of
|
||||
[{_, Command} | _ ] -> Command;
|
||||
_E -> throw({error, unknown_command})
|
||||
end.
|
||||
|
||||
-spec get_commands_definition(integer()) -> [ejabberd_commands()].
|
||||
@@ -404,20 +433,20 @@ get_command_definition(Name, Version) ->
|
||||
% @doc Returns all commands for a given API version
|
||||
get_commands_definition(Version) ->
|
||||
L = lists:reverse(
|
||||
lists:sort(
|
||||
mnesia:dirty_select(
|
||||
ejabberd_commands,
|
||||
ets:fun2ms(
|
||||
fun(#ejabberd_commands{name = Name, version = V} = C)
|
||||
when V =< Version ->
|
||||
{Name, V, C}
|
||||
end)))),
|
||||
lists:sort(
|
||||
mnesia:dirty_select(
|
||||
ejabberd_commands,
|
||||
ets:fun2ms(
|
||||
fun(#ejabberd_commands{name = Name, version = V} = C)
|
||||
when V =< Version ->
|
||||
{Name, V, C}
|
||||
end)))),
|
||||
F = fun({_Name, _V, Command}, []) ->
|
||||
[Command];
|
||||
({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) ->
|
||||
Acc;
|
||||
({_Name, _V, Command}, Acc) -> [Command | Acc]
|
||||
end,
|
||||
[Command];
|
||||
({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) ->
|
||||
Acc;
|
||||
({_Name, _V, Command}, Acc) -> [Command | Acc]
|
||||
end,
|
||||
lists:foldl(F, [], L).
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm
|
||||
@@ -426,7 +455,7 @@ get_commands_definition(Version) ->
|
||||
%% @doc Execute a command.
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided
|
||||
%% no_auth_provided | access_rules_unauthorized
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command(Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
@@ -487,38 +516,64 @@ execute_command(AccessCommands, Auth, Name, Arguments) ->
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided | access_rules_unauthorized
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
|
||||
Auth = case is_admin(Name, Auth1) of
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
|
||||
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
|
||||
Auth = case is_admin(Name, Auth1, CallerInfo) of
|
||||
true -> admin;
|
||||
false -> Auth1
|
||||
end,
|
||||
TokenJID = oauth_token_user(Auth1),
|
||||
Command = get_command_definition(Name, Version),
|
||||
AccessCommands = get_access_commands(AccessCommands1, Version),
|
||||
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
|
||||
ok -> execute_command2(Auth, Command, Arguments)
|
||||
AccessCommands = get_all_access_commands(AccessCommands1),
|
||||
|
||||
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
|
||||
ok -> execute_check_policy(Auth, TokenJID, Command, Arguments)
|
||||
end.
|
||||
|
||||
execute_command2(
|
||||
_Auth, #ejabberd_commands{policy = open} = Command, Arguments) ->
|
||||
execute_command2(Command, Arguments);
|
||||
execute_command2(
|
||||
_Auth, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
|
||||
execute_command2(Command, Arguments);
|
||||
execute_command2(
|
||||
_Auth, #ejabberd_commands{policy = admin} = Command, Arguments) ->
|
||||
execute_command2(Command, Arguments);
|
||||
execute_command2(
|
||||
admin, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_command2(Command, Arguments);
|
||||
execute_command2(
|
||||
noauth, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_command2(Command, Arguments);
|
||||
execute_command2(
|
||||
{User, Server, _, _}, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_command2(Command, [User, Server | Arguments]).
|
||||
|
||||
execute_command2(Command, Arguments) ->
|
||||
execute_check_policy(
|
||||
_Auth, _JID, #ejabberd_commands{policy = open} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
_Auth, _JID, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
_Auth, JID, #ejabberd_commands{policy = admin} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, Arguments);
|
||||
execute_check_policy(
|
||||
admin, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, Arguments);
|
||||
execute_check_policy(
|
||||
noauth, _JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
{User, Server, _, _}, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, [User, Server | Arguments]).
|
||||
|
||||
execute_check_access(_FromJID, #ejabberd_commands{access = []} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_access(undefined, _Command, _Arguments) ->
|
||||
throw({error, access_rules_unauthorized});
|
||||
execute_check_access(FromJID, #ejabberd_commands{access = AccessRefs} = Command, Arguments) ->
|
||||
%% TODO Review: Do we have smarter / better way to check rule on other Host than global ?
|
||||
Host = global,
|
||||
Rules = lists:map(fun({Mod, AccessName, Default}) ->
|
||||
gen_mod:get_module_opt(Host, Mod,
|
||||
AccessName, fun(A) -> A end, Default);
|
||||
(Default) ->
|
||||
Default
|
||||
end, AccessRefs),
|
||||
case acl:any_rules_allowed(Host, Rules, FromJID) of
|
||||
true ->
|
||||
do_execute_command(Command, Arguments);
|
||||
false ->
|
||||
throw({error, access_rules_unauthorized})
|
||||
end.
|
||||
|
||||
do_execute_command(Command, Arguments) ->
|
||||
Module = Command#ejabberd_commands.module,
|
||||
Function = Command#ejabberd_commands.function,
|
||||
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
|
||||
@@ -573,9 +628,9 @@ get_tags_commands(Version) ->
|
||||
%% At least one AccessCommand must be satisfied.
|
||||
%% It may throw {error, Error} where:
|
||||
%% Error = account_unprivileged | invalid_account_data
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments, _CallerInfo) ->
|
||||
ok;
|
||||
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
|
||||
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerInfo) ->
|
||||
Command =
|
||||
case {Command1#ejabberd_commands.policy, Auth} of
|
||||
{user, {_, _, _, _}} ->
|
||||
@@ -588,31 +643,31 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
|
||||
Command1
|
||||
end,
|
||||
AccessCommandsAllowed =
|
||||
lists:filter(
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Command, Access, Auth) of
|
||||
true ->
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
({Access, Commands}) ->
|
||||
ArgumentRestrictions = [],
|
||||
case check_access(Command, Access, Auth) of
|
||||
true ->
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
AccessCommands),
|
||||
lists:filter(
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Command, Access, Auth, CallerInfo) of
|
||||
true ->
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
({Access, Commands}) ->
|
||||
ArgumentRestrictions = [],
|
||||
case check_access(Command, Access, Auth, CallerInfo) of
|
||||
true ->
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
AccessCommands),
|
||||
case AccessCommandsAllowed of
|
||||
[] -> throw({error, account_unprivileged});
|
||||
L when is_list(L) -> ok
|
||||
[] -> throw({error, account_unprivileged});
|
||||
L when is_list(L) -> ok
|
||||
end.
|
||||
|
||||
-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
|
||||
@@ -623,11 +678,11 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
|
||||
check_auth(_Command, noauth) ->
|
||||
no_auth_provided;
|
||||
check_auth(Command, {User, Server, {oauth, Token}, _}) ->
|
||||
Scope = erlang:atom_to_binary(Command#ejabberd_commands.name, utf8),
|
||||
case ejabberd_oauth:check_token(User, Server, Scope, Token) of
|
||||
ScopeList = cmd_scope(Command),
|
||||
case ejabberd_oauth:check_token(User, Server, ScopeList, Token) of
|
||||
true ->
|
||||
{ok, User, Server};
|
||||
false ->
|
||||
_ ->
|
||||
throw({error, invalid_account_data})
|
||||
end;
|
||||
check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
|
||||
@@ -637,31 +692,38 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
|
||||
_ -> throw({error, invalid_account_data})
|
||||
end.
|
||||
|
||||
check_access(Command, ?POLICY_ACCESS, _)
|
||||
check_access(Command, ?POLICY_ACCESS, _, _)
|
||||
when Command#ejabberd_commands.policy == open ->
|
||||
true;
|
||||
check_access(_Command, _Access, admin) ->
|
||||
check_access(_Command, _Access, admin, _) ->
|
||||
true;
|
||||
check_access(_Command, _Access, {_User, _Server, _, true}) ->
|
||||
check_access(_Command, _Access, {_User, _Server, _, true}, _) ->
|
||||
false;
|
||||
check_access(Command, Access, Auth)
|
||||
check_access(Command, Access, Auth, CallerInfo)
|
||||
when Access =/= ?POLICY_ACCESS;
|
||||
Command#ejabberd_commands.policy == open;
|
||||
Command#ejabberd_commands.policy == user ->
|
||||
case check_auth(Command, Auth) of
|
||||
{ok, User, Server} ->
|
||||
check_access2(Access, User, Server);
|
||||
check_access2(Access, CallerInfo#{usr => jid:split(jid:make(User, Server, <<>>))}, Server);
|
||||
no_auth_provided ->
|
||||
case Command#ejabberd_commands.policy of
|
||||
user ->
|
||||
false;
|
||||
_ ->
|
||||
check_access2(Access, CallerInfo, global)
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
check_access(_Command, _Access, _Auth) ->
|
||||
check_access(_Command, _Access, _Auth, _CallerInfo) ->
|
||||
false.
|
||||
|
||||
check_access2(?POLICY_ACCESS, _User, _Server) ->
|
||||
check_access2(?POLICY_ACCESS, _CallerInfo, _Server) ->
|
||||
true;
|
||||
check_access2(Access, User, Server) ->
|
||||
check_access2(Access, AccessInfo, Server) ->
|
||||
%% Check this user has access permission
|
||||
case acl:match_rule(Server, Access, jid:make(User, Server, <<"">>)) of
|
||||
case acl:access_matches(Access, AccessInfo, Server) of
|
||||
allow -> true;
|
||||
deny -> false
|
||||
end.
|
||||
@@ -669,9 +731,9 @@ check_access2(Access, User, Server) ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
Method, Arguments) ->
|
||||
case Commands==all orelse lists:member(Method, Commands) of
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions,
|
||||
Arguments);
|
||||
false -> false
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions,
|
||||
Arguments);
|
||||
false -> false
|
||||
end.
|
||||
|
||||
check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
|
||||
@@ -694,19 +756,23 @@ tag_arguments(ArgsDefs, Args) ->
|
||||
Args).
|
||||
|
||||
|
||||
%% Get commands for all version
|
||||
get_all_access_commands(AccessCommands) ->
|
||||
get_access_commands(AccessCommands, ?DEFAULT_VERSION).
|
||||
|
||||
get_access_commands(undefined, Version) ->
|
||||
Cmds = get_commands(Version),
|
||||
Cmds = get_exposed_commands(Version),
|
||||
[{?POLICY_ACCESS, Cmds, []}];
|
||||
get_access_commands(AccessCommands, _Version) ->
|
||||
AccessCommands.
|
||||
|
||||
get_commands() ->
|
||||
get_commands(?DEFAULT_VERSION).
|
||||
get_commands(Version) ->
|
||||
get_exposed_commands() ->
|
||||
get_exposed_commands(?DEFAULT_VERSION).
|
||||
get_exposed_commands(Version) ->
|
||||
Opts0 = ejabberd_config:get_option(
|
||||
commands,
|
||||
fun(V) when is_list(V) -> V end,
|
||||
[]),
|
||||
[]),
|
||||
Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
|
||||
CommandsList = list_commands_policy(Version),
|
||||
OpenCmds = [N || {N, _, _, open} <- CommandsList],
|
||||
@@ -716,50 +782,58 @@ get_commands(Version) ->
|
||||
Cmds =
|
||||
lists:foldl(
|
||||
fun([{add_commands, L}], Acc) ->
|
||||
Cmds = case L of
|
||||
open -> OpenCmds;
|
||||
restricted -> RestrictedCmds;
|
||||
admin -> AdminCmds;
|
||||
user -> UserCmds;
|
||||
_ when is_list(L) -> L
|
||||
end,
|
||||
Cmds = expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds),
|
||||
lists:usort(Cmds ++ Acc);
|
||||
([{remove_commands, L}], Acc) ->
|
||||
Cmds = case L of
|
||||
open -> OpenCmds;
|
||||
restricted -> RestrictedCmds;
|
||||
admin -> AdminCmds;
|
||||
user -> UserCmds;
|
||||
_ when is_list(L) -> L
|
||||
end,
|
||||
Cmds = expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds),
|
||||
Acc -- Cmds;
|
||||
(_, Acc) -> Acc
|
||||
end, AdminCmds ++ UserCmds, Opts),
|
||||
end, [], Opts),
|
||||
Cmds.
|
||||
|
||||
is_admin(_Name, noauth) ->
|
||||
false;
|
||||
is_admin(_Name, admin) ->
|
||||
%% 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_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).
|
||||
|
||||
oauth_token_user(noauth) ->
|
||||
undefined;
|
||||
oauth_token_user(admin) ->
|
||||
undefined;
|
||||
oauth_token_user({User, Server, _, _}) ->
|
||||
jid:make(User, Server, <<>>).
|
||||
|
||||
is_admin(_Name, admin, _Extra) ->
|
||||
true;
|
||||
is_admin(_Name, {_User, _Server, _, false}) ->
|
||||
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
|
||||
false;
|
||||
is_admin(Name, {User, Server, _, true} = Auth) ->
|
||||
is_admin(Name, Auth, Extra) ->
|
||||
{ACLInfo, Server} = case Auth of
|
||||
{U, S, _, _} ->
|
||||
{Extra#{usr=>jid:split(jid:make(U, S, <<>>))}, S};
|
||||
_ ->
|
||||
{Extra, global}
|
||||
end,
|
||||
AdminAccess = ejabberd_config:get_option(
|
||||
commands_admin_access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(V) -> V end,
|
||||
none),
|
||||
case acl:match_rule(Server, AdminAccess,
|
||||
jid:make(User, Server, <<"">>)) of
|
||||
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.
|
||||
|
||||
opt_type(commands_admin_access) ->
|
||||
fun(A) when is_atom(A) -> A end;
|
||||
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].
|
||||
|
||||
+70
-27
@@ -30,7 +30,7 @@
|
||||
add_global_option/2, add_local_option/2,
|
||||
get_global_option/2, get_local_option/2,
|
||||
get_global_option/3, get_local_option/3,
|
||||
get_option/2, get_option/3, add_option/2,
|
||||
get_option/2, get_option/3, add_option/2, has_option/1,
|
||||
get_vh_by_auth_method/1, is_file_readable/1,
|
||||
get_version/0, get_myhosts/0, get_mylang/0,
|
||||
prepare_opt_val/4, convert_table_to_binary/5,
|
||||
@@ -90,7 +90,7 @@ hosts_to_start(State) ->
|
||||
|
||||
%% @private
|
||||
%% At the moment, these functions are mainly used to setup unit tests.
|
||||
-spec(start/2 :: (Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok).
|
||||
-spec start(Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok.
|
||||
start(Hosts, Opts) ->
|
||||
mnesia_init(),
|
||||
set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})).
|
||||
@@ -220,13 +220,11 @@ env_binary_to_list(Application, Parameter) ->
|
||||
%% in which the options 'include_config_file' were parsed
|
||||
%% and the terms in those files were included.
|
||||
%% @spec(iolist()) -> [term()]
|
||||
get_plain_terms_file(File) ->
|
||||
get_plain_terms_file(File, [{include_files, true}]).
|
||||
|
||||
get_plain_terms_file(File, Opts) when is_binary(File) ->
|
||||
get_plain_terms_file(binary_to_list(File), Opts);
|
||||
get_plain_terms_file(File1, Opts) ->
|
||||
File = get_absolute_path(File1),
|
||||
DontStopOnError = lists:member(dont_halt_on_error, Opts),
|
||||
case consult(File) of
|
||||
{ok, Terms} ->
|
||||
BinTerms1 = strings_to_binary(Terms),
|
||||
@@ -246,9 +244,21 @@ get_plain_terms_file(File1, Opts) ->
|
||||
false ->
|
||||
BinTerms
|
||||
end;
|
||||
{error, Reason} ->
|
||||
{error, enoent, Reason} ->
|
||||
case DontStopOnError of
|
||||
true ->
|
||||
?WARNING_MSG(Reason, []),
|
||||
[];
|
||||
_ ->
|
||||
?ERROR_MSG(Reason, []),
|
||||
exit_or_halt(Reason)
|
||||
end;
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG(Reason, []),
|
||||
case DontStopOnError of
|
||||
true -> [];
|
||||
_ -> exit_or_halt(Reason)
|
||||
end
|
||||
end.
|
||||
|
||||
consult(File) ->
|
||||
@@ -262,17 +272,29 @@ consult(File) ->
|
||||
{error, Err} ->
|
||||
Msg1 = "Cannot load " ++ File ++ ": ",
|
||||
Msg2 = fast_yaml:format_error(Err),
|
||||
case Err of
|
||||
enoent ->
|
||||
{error, enoent, Msg1 ++ Msg2};
|
||||
_ ->
|
||||
{error, Msg1 ++ Msg2}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
{ok, Terms};
|
||||
{error, enoent} ->
|
||||
{error, enoent};
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
{error, describe_config_problem(File, Reason, LineNumber)};
|
||||
{error, Reason} ->
|
||||
case Reason of
|
||||
enoent ->
|
||||
{error, enoent, describe_config_problem(File, Reason)};
|
||||
_ ->
|
||||
{error, describe_config_problem(File, Reason)}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
parserl(<<"> ", Term/binary>>) ->
|
||||
@@ -473,8 +495,8 @@ include_config_files(Terms) ->
|
||||
include_config_file(File, Opts)
|
||||
end, lists:flatten(FileOpts)),
|
||||
|
||||
M1 = merge_configs(transform_terms(Terms1), #{}),
|
||||
M2 = merge_configs(transform_terms(Terms2), M1),
|
||||
M1 = merge_configs(Terms1, #{}),
|
||||
M2 = merge_configs(Terms2, M1),
|
||||
maps_to_lists(M2).
|
||||
|
||||
transform_include_option({include_config_file, File}) when is_list(File) ->
|
||||
@@ -488,7 +510,7 @@ transform_include_option({include_config_file, Filename, Options}) ->
|
||||
{Filename, Options}.
|
||||
|
||||
include_config_file(Filename, Options) ->
|
||||
Included_terms = get_plain_terms_file(Filename),
|
||||
Included_terms = get_plain_terms_file(Filename, [{include_files, true}, dont_halt_on_error]),
|
||||
Disallow = proplists:get_value(disallow, Options, []),
|
||||
Included_terms2 = delete_disallowed(Disallow, Included_terms),
|
||||
Allow_only = proplists:get_value(allow_only, Options, all),
|
||||
@@ -745,22 +767,32 @@ add_option(Opt, Val) ->
|
||||
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
|
||||
|
||||
prepare_opt_val(Opt, Val, F, Default) ->
|
||||
Res = case F of
|
||||
{Mod, Fun} ->
|
||||
catch Mod:Fun(Val);
|
||||
_ ->
|
||||
catch F(Val)
|
||||
end,
|
||||
case Res of
|
||||
{'EXIT', _} ->
|
||||
Call = case F of
|
||||
{Mod, Fun} ->
|
||||
fun() -> Mod:Fun(Val) end;
|
||||
_ ->
|
||||
fun() -> F(Val) end
|
||||
end,
|
||||
try Call() of
|
||||
Res ->
|
||||
Res
|
||||
catch {replace_with, NewRes} ->
|
||||
NewRes;
|
||||
{invalid_syntax, Error} ->
|
||||
?WARNING_MSG("incorrect value '~s' of option '~s', "
|
||||
"using '~s' as fallback: ~s",
|
||||
[format_term(Val),
|
||||
format_term(Opt),
|
||||
format_term(Default),
|
||||
Error]),
|
||||
Default;
|
||||
_:_ ->
|
||||
?WARNING_MSG("incorrect value '~s' of option '~s', "
|
||||
"using '~s' as fallback",
|
||||
[format_term(Val),
|
||||
format_term(Opt),
|
||||
format_term(Default)]),
|
||||
Default;
|
||||
_ ->
|
||||
Res
|
||||
Default
|
||||
end.
|
||||
|
||||
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
|
||||
@@ -813,6 +845,10 @@ get_option(Opt, F, Default) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec has_option(atom() | {atom(), global | binary()}) -> any().
|
||||
has_option(Opt) ->
|
||||
get_option(Opt, fun(_) -> true end, false).
|
||||
|
||||
init_module_db_table(Modules) ->
|
||||
catch ets:new(module_db, [named_table, public, bag]),
|
||||
%% Dirty hack for mod_pubsub
|
||||
@@ -879,19 +915,26 @@ get_modules_with_options() ->
|
||||
|
||||
validate_opts(#state{opts = Opts} = State) ->
|
||||
ModOpts = get_modules_with_options(),
|
||||
NewOpts = lists:filter(
|
||||
fun(#local_config{key = {Opt, _Host}, value = Val}) ->
|
||||
NewOpts = lists:filtermap(
|
||||
fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
|
||||
case dict:find(Opt, ModOpts) of
|
||||
{ok, [Mod|_]} ->
|
||||
VFun = Mod:opt_type(Opt),
|
||||
case catch VFun(Val) of
|
||||
{'EXIT', _} ->
|
||||
try VFun(Val) of
|
||||
_ ->
|
||||
true
|
||||
catch {replace_with, NewVal} ->
|
||||
{true, In#local_config{value = NewVal}};
|
||||
{invalid_syntax, Error} ->
|
||||
?ERROR_MSG("ignoring option '~s' with "
|
||||
"invalid value: ~p: ~s",
|
||||
[Opt, Val, Error]),
|
||||
false;
|
||||
_:_ ->
|
||||
?ERROR_MSG("ignoring option '~s' with "
|
||||
"invalid value: ~p",
|
||||
[Opt, Val]),
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
?ERROR_MSG("unknown option '~s' will be likely"
|
||||
|
||||
+11
-3
@@ -212,7 +212,7 @@ process(["help" | Mode], Version) ->
|
||||
end;
|
||||
|
||||
process(["--version", Arg | Args], _) ->
|
||||
Version =
|
||||
Version =
|
||||
try
|
||||
list_to_integer(Arg)
|
||||
catch _:_ ->
|
||||
@@ -239,7 +239,7 @@ process2(["--auth", User, Server, Pass | Args], AccessCommands, Version) ->
|
||||
process2(Args, AccessCommands, {list_to_binary(User), list_to_binary(Server),
|
||||
list_to_binary(Pass), true}, Version);
|
||||
process2(Args, AccessCommands, Version) ->
|
||||
process2(Args, AccessCommands, admin, Version).
|
||||
process2(Args, AccessCommands, noauth, Version).
|
||||
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ call_command([CmdString | Args], Auth, AccessCommands, Version) ->
|
||||
{ArgsFormat, ResultFormat} ->
|
||||
case (catch format_args(Args, ArgsFormat)) of
|
||||
ArgsFormatted when is_list(ArgsFormatted) ->
|
||||
Result = ejabberd_commands:execute_command(AccessCommands,
|
||||
Result = ejabberd_commands:execute_command(AccessCommands,
|
||||
Auth, Command,
|
||||
ArgsFormatted,
|
||||
Version),
|
||||
@@ -374,6 +374,12 @@ format_arg2(Arg, Parse)->
|
||||
format_result({error, ErrorAtom}, _) ->
|
||||
{io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)};
|
||||
|
||||
%% An error should always be allowed to return extended error to help with API.
|
||||
%% Extended error is of the form:
|
||||
%% {error, type :: atom(), code :: int(), Desc :: string()}
|
||||
format_result({error, ErrorAtom, Code, _Msg}, _) ->
|
||||
{io_lib:format("Error: ~p", [ErrorAtom]), make_status(Code)};
|
||||
|
||||
format_result(Atom, {_Name, atom}) ->
|
||||
io_lib:format("~p", [Atom]);
|
||||
|
||||
@@ -433,6 +439,8 @@ format_result(404, {_Name, _}) ->
|
||||
|
||||
make_status(ok) -> ?STATUS_SUCCESS;
|
||||
make_status(true) -> ?STATUS_SUCCESS;
|
||||
make_status(Code) when is_integer(Code), Code > 255 -> ?STATUS_ERROR;
|
||||
make_status(Code) when is_integer(Code), Code > 0 -> Code;
|
||||
make_status(_Error) -> ?STATUS_ERROR.
|
||||
|
||||
get_list_commands(Version) ->
|
||||
|
||||
@@ -763,7 +763,8 @@ parse_auth(<<"Basic ", Auth64/binary>>) ->
|
||||
undefined;
|
||||
Pos ->
|
||||
{User, <<$:, Pass/binary>>} = erlang:split_binary(Auth, Pos-1),
|
||||
{User, Pass}
|
||||
PassUtf8 = unicode:characters_to_binary(binary_to_list(Pass), utf8),
|
||||
{User, PassUtf8}
|
||||
end;
|
||||
parse_auth(<<"Bearer ", SToken/binary>>) ->
|
||||
Token = str:strip(SToken),
|
||||
|
||||
+10
-1
@@ -271,7 +271,16 @@ do_route(From, To, Packet) ->
|
||||
#xmlel{name = Name} = Packet,
|
||||
case Name of
|
||||
<<"iq">> -> process_iq(From, To, Packet);
|
||||
<<"message">> -> ok;
|
||||
<<"message">> ->
|
||||
#xmlel{attrs = Attrs} = Packet,
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
<<"presence">> -> ok;
|
||||
_ -> ok
|
||||
end;
|
||||
|
||||
+323
-81
@@ -47,6 +47,8 @@
|
||||
process/2,
|
||||
opt_type/1]).
|
||||
|
||||
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -54,27 +56,114 @@
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("ejabberd_web_admin.hrl").
|
||||
-include("ejabberd_oauth.hrl").
|
||||
|
||||
-record(oauth_token, {
|
||||
token = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
scope = [] :: [binary()],
|
||||
expire :: integer()
|
||||
}).
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
-define(EXPIRE, 3600).
|
||||
|
||||
%% There are two ways to obtain an oauth token:
|
||||
%% * Using the web form/api results in the token being generated in behalf of the user providing the user/pass
|
||||
%% * Using the command line and oauth_issue_token command, the token is generated in behalf of ejabberd' sysadmin
|
||||
%% (as it has access to ejabberd command line).
|
||||
|
||||
-define(EXPIRE, 31536000).
|
||||
|
||||
start() ->
|
||||
init_db(mnesia, ?MYNAME),
|
||||
DBMod = get_db_backend(),
|
||||
DBMod:init(),
|
||||
MaxSize =
|
||||
ejabberd_config:get_option(
|
||||
oauth_cache_size,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1000),
|
||||
LifeTime =
|
||||
ejabberd_config:get_option(
|
||||
oauth_cache_life_time,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
timer:hours(1) div 1000),
|
||||
cache_tab:new(oauth_token,
|
||||
[{max_size, MaxSize}, {life_time, LifeTime}]),
|
||||
Expire = expire(),
|
||||
application:set_env(oauth2, backend, ejabberd_oauth),
|
||||
application:set_env(oauth2, expiry_time, Expire),
|
||||
application:start(oauth2),
|
||||
ChildSpec = {?MODULE, {?MODULE, start_link, []},
|
||||
temporary, 1000, worker, [?MODULE]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec),
|
||||
ejabberd_commands:register_commands(get_commands_spec()),
|
||||
ok.
|
||||
|
||||
|
||||
get_commands_spec() ->
|
||||
[
|
||||
#ejabberd_commands{name = oauth_issue_token, tags = [oauth],
|
||||
desc = "Issue an oauth token for the given jid",
|
||||
module = ?MODULE, function = oauth_issue_token,
|
||||
args = [{jid, string},{ttl, integer}, {scopes, string}],
|
||||
policy = restricted,
|
||||
args_example = ["user@server.com", "connected_users_number;muc_online_rooms"],
|
||||
args_desc = ["List of scopes to allow, separated by ';'"],
|
||||
result = {result, {tuple, [{token, string}, {scopes, string}, {expires_in, string}]}}
|
||||
},
|
||||
#ejabberd_commands{name = oauth_list_tokens, tags = [oauth],
|
||||
desc = "List oauth tokens, their user and scope, and how many seconds remain until expirity",
|
||||
module = ?MODULE, function = oauth_list_tokens,
|
||||
args = [],
|
||||
policy = restricted,
|
||||
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
|
||||
},
|
||||
#ejabberd_commands{name = oauth_list_scopes, tags = [oauth],
|
||||
desc = "List scopes that can be granted to tokens generated through the command line, together with the commands they allow",
|
||||
module = ?MODULE, function = oauth_list_scopes,
|
||||
args = [],
|
||||
policy = restricted,
|
||||
result = {scopes, {list, {scope, {tuple, [{scope, string}, {commands, string}]}}}}
|
||||
},
|
||||
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
|
||||
desc = "Revoke authorization for a token",
|
||||
module = ?MODULE, function = oauth_revoke_token,
|
||||
args = [{token, string}],
|
||||
policy = restricted,
|
||||
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}},
|
||||
result_desc = "List of remaining tokens"
|
||||
}
|
||||
].
|
||||
|
||||
oauth_issue_token(Jid, TTLSeconds, ScopesString) ->
|
||||
Scopes = [list_to_binary(Scope) || Scope <- string:tokens(ScopesString, ";")],
|
||||
case jid:from_string(list_to_binary(Jid)) of
|
||||
#jid{luser =Username, lserver = Server} ->
|
||||
case oauth2:authorize_password({Username, Server}, Scopes, admin_generated) of
|
||||
{ok, {_Ctx,Authorization}} ->
|
||||
{ok, {_AppCtx2, Response}} = oauth2:issue_token(Authorization, [{expiry_time, TTLSeconds}]),
|
||||
{ok, AccessToken} = oauth2_response:access_token(Response),
|
||||
{ok, VerifiedScope} = oauth2_response:scope(Response),
|
||||
{AccessToken, VerifiedScope, integer_to_list(TTLSeconds) ++ " seconds"};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end;
|
||||
error ->
|
||||
{error, "Invalid JID: " ++ Jid}
|
||||
end.
|
||||
|
||||
oauth_list_tokens() ->
|
||||
Tokens = mnesia:dirty_match_object(#oauth_token{_ = '_'}),
|
||||
{MegaSecs, Secs, _MiniSecs} = os:timestamp(),
|
||||
TS = 1000000 * MegaSecs + Secs,
|
||||
[{Token, jid:to_string(jid:make(U,S,<<>>)), Scope, integer_to_list(Expires - TS) ++ " seconds"} ||
|
||||
#oauth_token{token=Token, scope=Scope, us= {U,S},expire=Expires} <- Tokens].
|
||||
|
||||
|
||||
oauth_revoke_token(Token) ->
|
||||
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
|
||||
oauth_list_tokens().
|
||||
|
||||
oauth_list_scopes() ->
|
||||
[ {Scope, string:join([atom_to_list(Cmd) || Cmd <- Cmds], ",")} || {Scope, Cmds} <- dict:to_list(get_cmd_scopes())].
|
||||
|
||||
|
||||
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
@@ -91,15 +180,8 @@ handle_cast(_Msg, State) -> {noreply, State}.
|
||||
handle_info(clean, State) ->
|
||||
{MegaSecs, Secs, MiniSecs} = os:timestamp(),
|
||||
TS = 1000000 * MegaSecs + Secs,
|
||||
F = fun() ->
|
||||
Ts = mnesia:select(
|
||||
oauth_token,
|
||||
[{#oauth_token{expire = '$1', _ = '_'},
|
||||
[{'<', '$1', TS}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun mnesia:delete_object/1, Ts)
|
||||
end,
|
||||
mnesia:async_dirty(F),
|
||||
DBMod = get_db_backend(),
|
||||
DBMod:clean(TS),
|
||||
erlang:send_after(trunc(expire() * 1000 * (1 + MiniSecs / 1000000)),
|
||||
self(), clean),
|
||||
{noreply, State};
|
||||
@@ -110,35 +192,30 @@ terminate(_Reason, _State) -> ok.
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
|
||||
init_db(mnesia, _Host) ->
|
||||
mnesia:create_table(oauth_token,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, oauth_token)}]),
|
||||
mnesia:add_table_copy(oauth_token, node(), disc_copies);
|
||||
init_db(_, _) ->
|
||||
ok.
|
||||
|
||||
|
||||
get_client_identity(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
|
||||
|
||||
verify_redirection_uri(_, _, Ctx) -> {ok, Ctx}.
|
||||
|
||||
authenticate_user({User, Server}, {password, Password} = Ctx) ->
|
||||
authenticate_user({User, Server}, Ctx) ->
|
||||
case jid:make(User, Server, <<"">>) of
|
||||
#jid{} = JID ->
|
||||
Access =
|
||||
ejabberd_config:get_option(
|
||||
{oauth_access, JID#jid.lserver},
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
none),
|
||||
case acl:match_rule(JID#jid.lserver, Access, JID) of
|
||||
allow ->
|
||||
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
|
||||
true ->
|
||||
{ok, {Ctx, {user, User, Server}}};
|
||||
false ->
|
||||
{error, badpass}
|
||||
case Ctx of
|
||||
{password, Password} ->
|
||||
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
|
||||
true ->
|
||||
{ok, {Ctx, {user, User, Server}}};
|
||||
false ->
|
||||
{error, badpass}
|
||||
end;
|
||||
admin_generated ->
|
||||
{ok, {Ctx, {user, User, Server}}}
|
||||
end;
|
||||
deny ->
|
||||
{error, badpass}
|
||||
@@ -150,8 +227,8 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
|
||||
authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
|
||||
|
||||
verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
|
||||
Cmds = ejabberd_commands:get_commands(),
|
||||
Cmds1 = [sasl_auth | Cmds],
|
||||
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
|
||||
@@ -164,64 +241,133 @@ verify_resowner_scope(_, _, _) ->
|
||||
{error, badscope}.
|
||||
|
||||
|
||||
get_cmd_scopes() ->
|
||||
ScopeMap = lists:foldl(fun(Cmd, Accum) ->
|
||||
case ejabberd_commands:get_command_policy_and_scope(Cmd) of
|
||||
{ok, Policy, Scopes} when Policy =/= restricted ->
|
||||
lists:foldl(fun(Scope, Accum2) ->
|
||||
dict:append(Scope, Cmd, Accum2)
|
||||
end, Accum, Scopes);
|
||||
_ -> Accum
|
||||
end end, dict:new(), ejabberd_commands:get_exposed_commands()),
|
||||
ScopeMap.
|
||||
|
||||
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are
|
||||
%% made available.
|
||||
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
|
||||
% RegisteredScope = dict:fetch_keys(get_cmd_scopes()),
|
||||
% 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.
|
||||
|
||||
|
||||
|
||||
|
||||
-spec seconds_since_epoch(integer()) -> non_neg_integer().
|
||||
seconds_since_epoch(Diff) ->
|
||||
{Mega, Secs, _} = os:timestamp(),
|
||||
Mega * 1000000 + Secs + Diff.
|
||||
|
||||
|
||||
associate_access_code(_AccessCode, _Context, AppContext) ->
|
||||
%put(?ACCESS_CODE_TABLE, AccessCode, Context),
|
||||
{ok, AppContext}.
|
||||
|
||||
associate_access_token(AccessToken, Context, AppContext) ->
|
||||
{user, User, Server} =
|
||||
proplists:get_value(<<"resource_owner">>, Context, <<"">>),
|
||||
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
|
||||
Expire = case proplists:get_value(expiry_time, AppContext, undefined) of
|
||||
undefined ->
|
||||
proplists:get_value(<<"expiry_time">>, Context, 0);
|
||||
ExpiresIn ->
|
||||
%% There is no clean way in oauth2 lib to actually override the TTL of the generated token.
|
||||
%% It always pass the global configured value. Here we use the app context to pass the per-case
|
||||
%% ttl if we want to override it.
|
||||
seconds_since_epoch(ExpiresIn)
|
||||
end,
|
||||
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
|
||||
Scope = proplists:get_value(<<"scope">>, Context, []),
|
||||
Expire = proplists:get_value(<<"expiry_time">>, Context, 0),
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
R = #oauth_token{
|
||||
token = AccessToken,
|
||||
us = {LUser, LServer},
|
||||
us = {jid:nodeprep(User), jid:nodeprep(Server)},
|
||||
scope = Scope,
|
||||
expire = Expire
|
||||
},
|
||||
mnesia:dirty_write(R),
|
||||
store(R),
|
||||
{ok, AppContext}.
|
||||
|
||||
associate_refresh_token(_RefreshToken, _Context, AppContext) ->
|
||||
%put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
|
||||
{ok, AppContext}.
|
||||
|
||||
|
||||
check_token(User, Server, Scope, Token) ->
|
||||
check_token(User, Server, ScopeList, Token) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case catch mnesia:dirty_read(oauth_token, Token) of
|
||||
[#oauth_token{us = {LUser, LServer},
|
||||
scope = TokenScope,
|
||||
expire = Expire}] ->
|
||||
case lookup(Token) of
|
||||
{ok, #oauth_token{us = {LUser, LServer},
|
||||
scope = TokenScope,
|
||||
expire = Expire}} ->
|
||||
{MegaSecs, Secs, _} = os:timestamp(),
|
||||
TS = 1000000 * MegaSecs + Secs,
|
||||
oauth2_priv_set:is_member(
|
||||
Scope, oauth2_priv_set:new(TokenScope)) andalso
|
||||
Expire > TS;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_token(Scope, Token) ->
|
||||
case catch mnesia:dirty_read(oauth_token, Token) of
|
||||
[#oauth_token{us = {LUser, LServer},
|
||||
scope = TokenScope,
|
||||
expire = Expire}] ->
|
||||
{MegaSecs, Secs, _} = os:timestamp(),
|
||||
TS = 1000000 * MegaSecs + Secs,
|
||||
case oauth2_priv_set:is_member(
|
||||
Scope, oauth2_priv_set:new(TokenScope)) andalso
|
||||
Expire > TS of
|
||||
true -> {ok, LUser, LServer};
|
||||
false -> false
|
||||
if
|
||||
Expire > TS ->
|
||||
TokenScopeSet = oauth2_priv_set:new(TokenScope),
|
||||
lists:any(fun(Scope) ->
|
||||
oauth2_priv_set:is_member(Scope, TokenScopeSet) end,
|
||||
ScopeList);
|
||||
true ->
|
||||
{false, expired}
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
{false, not_found}
|
||||
end.
|
||||
|
||||
check_token(ScopeList, Token) ->
|
||||
case lookup(Token) of
|
||||
{ok, #oauth_token{us = US,
|
||||
scope = TokenScope,
|
||||
expire = Expire}} ->
|
||||
{MegaSecs, Secs, _} = os:timestamp(),
|
||||
TS = 1000000 * MegaSecs + Secs,
|
||||
if
|
||||
Expire > TS ->
|
||||
TokenScopeSet = oauth2_priv_set:new(TokenScope),
|
||||
case lists:any(fun(Scope) ->
|
||||
oauth2_priv_set:is_member(Scope, TokenScopeSet) end,
|
||||
ScopeList) of
|
||||
true -> {ok, user, US};
|
||||
false -> {false, no_matching_scope}
|
||||
end;
|
||||
true ->
|
||||
{false, expired}
|
||||
end;
|
||||
_ ->
|
||||
{false, not_found}
|
||||
end.
|
||||
|
||||
|
||||
store(R) ->
|
||||
cache_tab:insert(
|
||||
oauth_token, R#oauth_token.token, R,
|
||||
fun() ->
|
||||
DBMod = get_db_backend(),
|
||||
DBMod:store(R)
|
||||
end).
|
||||
|
||||
lookup(Token) ->
|
||||
cache_tab:lookup(
|
||||
oauth_token, Token,
|
||||
fun() ->
|
||||
DBMod = get_db_backend(),
|
||||
case DBMod:lookup(Token) of
|
||||
#oauth_token{} = R -> {ok, R};
|
||||
_ -> error
|
||||
end
|
||||
end).
|
||||
|
||||
|
||||
expire() ->
|
||||
ejabberd_config:get_option(
|
||||
@@ -250,12 +396,9 @@ process(_Handlers,
|
||||
?XAE(<<"form">>,
|
||||
[{<<"action">>, <<"authorization_token">>},
|
||||
{<<"method">>, <<"post">>}],
|
||||
[?LABEL(<<"username">>, [?CT(<<"User">>), ?C(<<": ">>)]),
|
||||
[?LABEL(<<"username">>, [?CT(<<"User (jid)">>), ?C(<<": ">>)]),
|
||||
?INPUTID(<<"text">>, <<"username">>, <<"">>),
|
||||
?BR,
|
||||
?LABEL(<<"server">>, [?CT(<<"Server">>), ?C(<<": ">>)]),
|
||||
?INPUTID(<<"text">>, <<"server">>, <<"">>),
|
||||
?BR,
|
||||
?LABEL(<<"password">>, [?CT(<<"Password">>), ?C(<<": ">>)]),
|
||||
?INPUTID(<<"password">>, <<"password">>, <<"">>),
|
||||
?INPUT(<<"hidden">>, <<"response_type">>, ResponseType),
|
||||
@@ -264,6 +407,15 @@ process(_Handlers,
|
||||
?INPUT(<<"hidden">>, <<"scope">>, Scope),
|
||||
?INPUT(<<"hidden">>, <<"state">>, State),
|
||||
?BR,
|
||||
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?CT(<<": ">>)]),
|
||||
?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}],
|
||||
[
|
||||
?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>),
|
||||
?XAC(<<"option">>, [{<<"value">>, <<"86400">>}],<<"1 Day">>),
|
||||
?XAC(<<"option">>, [{<<"value">>, <<"2592000">>}],<<"1 Month">>),
|
||||
?XAC(<<"option">>, [{<<"selected">>, <<"selected">>},{<<"value">>, <<"31536000">>}],<<"1 Year">>),
|
||||
?XAC(<<"option">>, [{<<"value">>, <<"315360000">>}],<<"10 Years">>)]),
|
||||
?BR,
|
||||
?INPUTT(<<"submit">>, <<"">>, <<"Accept">>)
|
||||
]),
|
||||
Top =
|
||||
@@ -307,11 +459,16 @@ process(_Handlers,
|
||||
ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
|
||||
RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
|
||||
SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
|
||||
Username = proplists:get_value(<<"username">>, Q, <<"">>),
|
||||
Server = proplists:get_value(<<"server">>, Q, <<"">>),
|
||||
StringJID = proplists:get_value(<<"username">>, Q, <<"">>),
|
||||
#jid{user = Username, server = Server} = jid:from_string(StringJID),
|
||||
Password = proplists:get_value(<<"password">>, Q, <<"">>),
|
||||
State = proplists:get_value(<<"state">>, Q, <<"">>),
|
||||
Scope = str:tokens(SScope, <<" ">>),
|
||||
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
|
||||
ExpiresIn = case TTL of
|
||||
<<>> -> undefined;
|
||||
_ -> jlib:binary_to_integer(TTL)
|
||||
end,
|
||||
case oauth2:authorize_password({Username, Server},
|
||||
ClientId,
|
||||
RedirectURI,
|
||||
@@ -319,10 +476,18 @@ process(_Handlers,
|
||||
{password, Password}) of
|
||||
{ok, {_AppContext, Authorization}} ->
|
||||
{ok, {_AppContext2, Response}} =
|
||||
oauth2:issue_token(Authorization, none),
|
||||
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
|
||||
{ok, AccessToken} = oauth2_response:access_token(Response),
|
||||
{ok, Type} = oauth2_response:token_type(Response),
|
||||
{ok, Expires} = oauth2_response:expires_in(Response),
|
||||
%%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have
|
||||
%%per-case expirity time.
|
||||
Expires = case ExpiresIn of
|
||||
undefined ->
|
||||
{ok, Ex} = oauth2_response:expires_in(Response),
|
||||
Ex;
|
||||
_ ->
|
||||
ExpiresIn
|
||||
end,
|
||||
{ok, VerifiedScope} = oauth2_response:scope(Response),
|
||||
%oauth2_wrq:redirected_access_token_response(ReqData,
|
||||
% RedirectURI,
|
||||
@@ -351,11 +516,82 @@ process(_Handlers,
|
||||
}],
|
||||
ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])}
|
||||
end;
|
||||
process(_Handlers,
|
||||
#request{method = 'POST', q = Q, lang = _Lang,
|
||||
path = [_, <<"token">>]}) ->
|
||||
case proplists:get_value(<<"grant_type">>, Q, <<"">>) of
|
||||
<<"password">> ->
|
||||
SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
|
||||
StringJID = proplists:get_value(<<"username">>, Q, <<"">>),
|
||||
#jid{user = Username, server = Server} = jid:from_string(StringJID),
|
||||
Password = proplists:get_value(<<"password">>, Q, <<"">>),
|
||||
Scope = str:tokens(SScope, <<" ">>),
|
||||
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
|
||||
ExpiresIn = case TTL of
|
||||
<<>> -> undefined;
|
||||
_ -> jlib:binary_to_integer(TTL)
|
||||
end,
|
||||
case oauth2:authorize_password({Username, Server},
|
||||
Scope,
|
||||
{password, Password}) of
|
||||
{ok, {_AppContext, Authorization}} ->
|
||||
{ok, {_AppContext2, Response}} =
|
||||
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
|
||||
{ok, AccessToken} = oauth2_response:access_token(Response),
|
||||
{ok, Type} = oauth2_response:token_type(Response),
|
||||
%%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have
|
||||
%%per-case expirity time.
|
||||
Expires = case ExpiresIn of
|
||||
undefined ->
|
||||
{ok, Ex} = oauth2_response:expires_in(Response),
|
||||
Ex;
|
||||
_ ->
|
||||
ExpiresIn
|
||||
end,
|
||||
{ok, VerifiedScope} = oauth2_response:scope(Response),
|
||||
json_response(200, {[
|
||||
{<<"access_token">>, AccessToken},
|
||||
{<<"token_type">>, Type},
|
||||
{<<"scope">>, str:join(VerifiedScope, <<" ">>)},
|
||||
{<<"expires_in">>, Expires}]});
|
||||
{error, Error} when is_atom(Error) ->
|
||||
json_error(400, <<"invalid_grant">>, Error)
|
||||
end;
|
||||
_OtherGrantType ->
|
||||
json_error(400, <<"unsupported_grant_type">>, unsupported_grant_type)
|
||||
end;
|
||||
|
||||
process(_Handlers, _Request) ->
|
||||
ejabberd_web:error(not_found).
|
||||
|
||||
-spec get_db_backend() -> module().
|
||||
|
||||
get_db_backend() ->
|
||||
DBType = ejabberd_config:get_option(
|
||||
oauth_db_type,
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end,
|
||||
mnesia),
|
||||
list_to_atom("ejabberd_oauth_" ++ atom_to_list(DBType)).
|
||||
|
||||
|
||||
%% Headers as per RFC 6749
|
||||
json_response(Code, Body) ->
|
||||
{Code, [{<<"Content-Type">>, <<"application/json;charset=UTF-8">>},
|
||||
{<<"Cache-Control">>, <<"no-store">>},
|
||||
{<<"Pragma">>, <<"no-cache">>}],
|
||||
jiffy:encode(Body)}.
|
||||
|
||||
%% OAauth error are defined in:
|
||||
%% https://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-5.2
|
||||
json_error(Code, Error, Reason) ->
|
||||
Desc = json_error_desc(Reason),
|
||||
Body = {[{<<"error">>, Error},
|
||||
{<<"error_description">>, Desc}]},
|
||||
json_response(Code, Body).
|
||||
|
||||
json_error_desc(access_denied) -> <<"Access denied">>;
|
||||
json_error_desc(unsupported_grant_type) -> <<"Unsupported grant type">>;
|
||||
json_error_desc(invalid_scope) -> <<"Invalid scope">>.
|
||||
|
||||
web_head() ->
|
||||
[?XA(<<"meta">>, [{<<"http-equiv">>, <<"X-UA-Compatible">>},
|
||||
@@ -469,7 +705,7 @@ css() ->
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.container > .section {
|
||||
.container > .section {
|
||||
background: #424A55;
|
||||
}
|
||||
|
||||
@@ -486,5 +722,11 @@ logo() ->
|
||||
opt_type(oauth_expire) ->
|
||||
fun(I) when is_integer(I), I >= 0 -> I end;
|
||||
opt_type(oauth_access) ->
|
||||
fun(A) when is_atom(A) -> A end;
|
||||
opt_type(_) -> [oauth_expire, oauth_access].
|
||||
fun acl:access_rules_validator/1;
|
||||
opt_type(oauth_db_type) ->
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(oauth_cache_life_time) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(oauth_cache_size) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(_) -> [oauth_expire, oauth_access, oauth_db_type].
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_oauth_mnesia.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : OAUTH2 mnesia backend
|
||||
%%% Created : 20 Jul 2016 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_mnesia).
|
||||
|
||||
-export([init/0,
|
||||
store/1,
|
||||
lookup/1,
|
||||
clean/1]).
|
||||
|
||||
-include("ejabberd_oauth.hrl").
|
||||
|
||||
init() ->
|
||||
mnesia:create_table(oauth_token,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, oauth_token)}]),
|
||||
mnesia:add_table_copy(oauth_token, node(), disc_copies),
|
||||
ok.
|
||||
|
||||
store(R) ->
|
||||
mnesia:dirty_write(R).
|
||||
|
||||
lookup(Token) ->
|
||||
case catch mnesia:dirty_read(oauth_token, Token) of
|
||||
[R] ->
|
||||
R;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
clean(TS) ->
|
||||
F = fun() ->
|
||||
Ts = mnesia:select(
|
||||
oauth_token,
|
||||
[{#oauth_token{expire = '$1', _ = '_'},
|
||||
[{'<', '$1', TS}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun mnesia:delete_object/1, Ts)
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_oauth_sql.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : OAUTH2 SQL backend
|
||||
%%% Created : 27 Jul 2016 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-export([init/0,
|
||||
store/1,
|
||||
lookup/1,
|
||||
clean/1]).
|
||||
|
||||
-include("ejabberd_oauth.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
init() ->
|
||||
ok.
|
||||
|
||||
store(R) ->
|
||||
Token = R#oauth_token.token,
|
||||
{User, Server} = R#oauth_token.us,
|
||||
SJID = jid:to_string({User, Server, <<"">>}),
|
||||
Scope = str:join(R#oauth_token.scope, <<" ">>),
|
||||
Expire = R#oauth_token.expire,
|
||||
?SQL_UPSERT(
|
||||
?MYNAME,
|
||||
"oauth_token",
|
||||
["!token=%(Token)s",
|
||||
"jid=%(SJID)s",
|
||||
"scope=%(Scope)s",
|
||||
"expire=%(Expire)d"]).
|
||||
|
||||
lookup(Token) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
?MYNAME,
|
||||
?SQL("select @(jid)s, @(scope)s, @(expire)d"
|
||||
" from oauth_token where token=%(Token)s")) of
|
||||
{selected, [{SJID, Scope, Expire}]} ->
|
||||
JID = jid:from_string(SJID),
|
||||
US = {JID#jid.luser, JID#jid.lserver},
|
||||
#oauth_token{token = Token,
|
||||
us = US,
|
||||
scope = str:tokens(Scope, <<" ">>),
|
||||
expire = Expire};
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
clean(TS) ->
|
||||
ejabberd_sql:sql_query(
|
||||
?MYNAME,
|
||||
?SQL("delete from oauth_token where expire < %(TS)d")).
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 8 May 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_redis).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
%% API
|
||||
-export([start/0, start_link/0, q/1, qp/1, opt_type/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(PROCNAME, 'ejabberd_redis_client').
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
start() ->
|
||||
case lists:any(
|
||||
fun(Host) ->
|
||||
is_redis_configured(Host)
|
||||
end, ?MYHOSTS) of
|
||||
true ->
|
||||
Spec = {?MODULE, {?MODULE, start_link, []},
|
||||
permanent, 2000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, Spec);
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
q(Command) ->
|
||||
try eredis:q(?PROCNAME, Command)
|
||||
catch _:Reason -> {error, Reason}
|
||||
end.
|
||||
|
||||
qp(Pipeline) ->
|
||||
try eredis:qp(?PROCNAME, Pipeline)
|
||||
catch _:Reason -> {error, Reason}
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
process_flag(trap_exit, true),
|
||||
connect(),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(connect, State) ->
|
||||
connect(),
|
||||
{noreply, State};
|
||||
handle_info({'DOWN', _MRef, _Type, _Pid, Reason}, State) ->
|
||||
?INFO_MSG("Redis connection has failed: ~p", [Reason]),
|
||||
connect(),
|
||||
{noreply, State};
|
||||
handle_info({'EXIT', _, _}, State) ->
|
||||
{noreply, State};
|
||||
handle_info(Info, State) ->
|
||||
?INFO_MSG("unexpected info = ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
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}),
|
||||
ReconnTimeoutConfigured = ejabberd_config:has_option(
|
||||
{redis_reconnect_timeout, Host}),
|
||||
ConnTimeoutConfigured = ejabberd_config:has_option(
|
||||
{redis_connect_timeout, Host}),
|
||||
Modules = ejabberd_config:get_option(
|
||||
{modules, Host},
|
||||
fun(L) when is_list(L) -> L end, []),
|
||||
SMConfigured = ejabberd_config:get_option(
|
||||
{sm_db_type, Host},
|
||||
fun(V) -> V end) == redis,
|
||||
ModuleWithRedisDBConfigured =
|
||||
lists:any(
|
||||
fun({Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts, Module) == redis
|
||||
end, Modules),
|
||||
ServerConfigured or PortConfigured or DBConfigured or PassConfigured or
|
||||
ReconnTimeoutConfigured or ConnTimeoutConfigured or
|
||||
SMConfigured or ModuleWithRedisDBConfigured.
|
||||
|
||||
iolist_to_list(IOList) ->
|
||||
binary_to_list(iolist_to_binary(IOList)).
|
||||
|
||||
connect() ->
|
||||
Server = ejabberd_config:get_option(redis_server,
|
||||
fun iolist_to_list/1,
|
||||
"localhost"),
|
||||
Port = ejabberd_config:get_option(redis_port,
|
||||
fun(P) when is_integer(P),
|
||||
P>0, P<65536 ->
|
||||
P
|
||||
end, 6379),
|
||||
DB = ejabberd_config:get_option(redis_db,
|
||||
fun(I) when is_integer(I), I >= 0 ->
|
||||
I
|
||||
end, 0),
|
||||
Pass = ejabberd_config:get_option(redis_password,
|
||||
fun iolist_to_list/1,
|
||||
""),
|
||||
ReconnTimeout = timer:seconds(
|
||||
ejabberd_config:get_option(
|
||||
redis_reconnect_timeout,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1)),
|
||||
ConnTimeout = timer:seconds(
|
||||
ejabberd_config:get_option(
|
||||
redis_connect_timeout,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1)),
|
||||
try case eredis:start_link(Server, Port, DB, Pass,
|
||||
ReconnTimeout, ConnTimeout) of
|
||||
{ok, Client} ->
|
||||
?INFO_MSG("Connected to Redis at ~s:~p", [Server, Port]),
|
||||
unlink(Client),
|
||||
erlang:monitor(process, Client),
|
||||
register(?PROCNAME, Client),
|
||||
{ok, Client};
|
||||
{error, Why} ->
|
||||
erlang:error(Why)
|
||||
end
|
||||
catch _:Reason ->
|
||||
Timeout = 10,
|
||||
?ERROR_MSG("Redis connection at ~s:~p has failed: ~p; "
|
||||
"reconnecting in ~p seconds",
|
||||
[Server, Port, Reason, Timeout]),
|
||||
erlang:send_after(timer:seconds(Timeout), self(), connect)
|
||||
end.
|
||||
|
||||
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_reconnect_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(redis_server) -> fun iolist_to_list/1;
|
||||
opt_type(_) ->
|
||||
[redis_connect_timeout, redis_db, redis_password,
|
||||
redis_port, redis_reconnect_timeout, redis_server].
|
||||
+26
-20
@@ -473,28 +473,34 @@ send_element(Pid, El) ->
|
||||
%%% ejabberd commands
|
||||
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = incoming_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc =
|
||||
"Number of incoming s2s connections on "
|
||||
"the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{name = outgoing_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc =
|
||||
"Number of outgoing s2s connections on "
|
||||
"the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [], result = {s2s_outgoing, integer}}].
|
||||
[#ejabberd_commands{
|
||||
name = incoming_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of incoming s2s connections on the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{
|
||||
name = outgoing_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of outgoing s2s connections on the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [], result = {s2s_outgoing, integer}}].
|
||||
|
||||
%% TODO Move those stats commands to ejabberd stats command ?
|
||||
incoming_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_in_sup)).
|
||||
supervisor_count(ejabberd_s2s_in_sup).
|
||||
|
||||
outgoing_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_out_sup)).
|
||||
supervisor_count(ejabberd_s2s_out_sup).
|
||||
|
||||
supervisor_count(Supervisor) ->
|
||||
case catch supervisor:which_children(Supervisor) of
|
||||
{'EXIT', _} -> 0;
|
||||
Result ->
|
||||
length(Result)
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Update Mnesia tables
|
||||
@@ -539,7 +545,7 @@ allow_host2(MyServer, S2SHost) ->
|
||||
allow_host1(MyHost, S2SHost) ->
|
||||
Rule = ejabberd_config:get_option(
|
||||
s2s_access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
all),
|
||||
JID = jid:make(<<"">>, S2SHost, <<"">>),
|
||||
case acl:match_rule(MyHost, Rule, JID) of
|
||||
@@ -738,5 +744,5 @@ opt_type(route_subdomains) ->
|
||||
(local) -> local
|
||||
end;
|
||||
opt_type(s2s_access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
opt_type(_) -> [route_subdomains, s2s_access].
|
||||
|
||||
@@ -224,8 +224,10 @@ wait_for_handshake({xmlstreamelement, El}, StateData) ->
|
||||
fun (H) ->
|
||||
ejabberd_router:register_route(H, ?MYNAME),
|
||||
?INFO_MSG("Route registered for service ~p~n",
|
||||
[H])
|
||||
end, dict:fetch_keys(StateData#state.host_opts)),
|
||||
[H]),
|
||||
ejabberd_hooks:run(component_connected,
|
||||
[H])
|
||||
end, dict:fetch_keys(StateData#state.host_opts)),
|
||||
{next_state, stream_established, StateData};
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
|
||||
@@ -382,7 +384,9 @@ terminate(Reason, StateName, StateData) ->
|
||||
case StateName of
|
||||
stream_established ->
|
||||
lists:foreach(fun (H) ->
|
||||
ejabberd_router:unregister_route(H)
|
||||
ejabberd_router:unregister_route(H),
|
||||
ejabberd_hooks:run(component_disconnected,
|
||||
[StateData#state.host, Reason])
|
||||
end,
|
||||
dict:fetch_keys(StateData#state.host_opts));
|
||||
_ -> ok
|
||||
|
||||
+75
-32
@@ -47,6 +47,8 @@
|
||||
set_presence/7,
|
||||
unset_presence/6,
|
||||
close_session_unset_presence/5,
|
||||
set_offline_info/5,
|
||||
get_offline_info/4,
|
||||
dirty_get_sessions_list/0,
|
||||
dirty_get_my_sessions_list/0,
|
||||
get_vh_session_list/1,
|
||||
@@ -178,14 +180,14 @@ get_user_resources(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
|
||||
|
||||
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
|
||||
|
||||
get_user_present_resources(LUser, LServer) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
[{S#session.priority, element(3, S#session.usr)}
|
||||
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
|
||||
|
||||
@@ -196,7 +198,7 @@ get_user_ip(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
undefined;
|
||||
Ss ->
|
||||
@@ -211,7 +213,7 @@ get_user_info(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
offline;
|
||||
Ss ->
|
||||
@@ -261,17 +263,45 @@ get_session_pid(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[#session{sid = {_, Pid}}] -> Pid;
|
||||
_ -> none
|
||||
end.
|
||||
|
||||
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
|
||||
|
||||
set_offline_info(SID, User, Server, Resource, Info) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
set_session(SID, LUser, LServer, LResource, undefined, [offline | Info]).
|
||||
|
||||
-spec get_offline_info(erlang:timestamp(), binary(), binary(),
|
||||
binary()) -> none | info().
|
||||
|
||||
get_offline_info(Time, User, Server, Resource) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
[#session{sid = {Time, _}, info = Info}] ->
|
||||
case proplists:get_bool(offline, Info) of
|
||||
true ->
|
||||
Info;
|
||||
false ->
|
||||
none
|
||||
end;
|
||||
_ ->
|
||||
none
|
||||
end.
|
||||
|
||||
-spec dirty_get_sessions_list() -> [ljid()].
|
||||
|
||||
dirty_get_sessions_list() ->
|
||||
lists:flatmap(
|
||||
fun(Mod) ->
|
||||
[S#session.usr || S <- Mod:get_sessions()]
|
||||
[S#session.usr || S <- online(Mod:get_sessions())]
|
||||
end, get_sm_backends()).
|
||||
|
||||
-spec dirty_get_my_sessions_list() -> [#session{}].
|
||||
@@ -279,7 +309,7 @@ dirty_get_sessions_list() ->
|
||||
dirty_get_my_sessions_list() ->
|
||||
lists:flatmap(
|
||||
fun(Mod) ->
|
||||
[S || S <- Mod:get_sessions(),
|
||||
[S || S <- online(Mod:get_sessions()),
|
||||
node(element(2, S#session.sid)) == node()]
|
||||
end, get_sm_backends()).
|
||||
|
||||
@@ -288,14 +318,14 @@ dirty_get_my_sessions_list() ->
|
||||
get_vh_session_list(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
[S#session.usr || S <- Mod:get_sessions(LServer)].
|
||||
[S#session.usr || S <- online(Mod:get_sessions(LServer))].
|
||||
|
||||
-spec get_all_pids() -> [pid()].
|
||||
|
||||
get_all_pids() ->
|
||||
lists:flatmap(
|
||||
fun(Mod) ->
|
||||
[element(2, S#session.sid) || S <- Mod:get_sessions()]
|
||||
[element(2, S#session.sid) || S <- online(Mod:get_sessions())]
|
||||
end, get_sm_backends()).
|
||||
|
||||
-spec get_vh_session_number(binary()) -> non_neg_integer().
|
||||
@@ -303,7 +333,7 @@ get_all_pids() ->
|
||||
get_vh_session_number(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
length(Mod:get_sessions(LServer)).
|
||||
length(online(Mod:get_sessions(LServer))).
|
||||
|
||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
@@ -395,6 +425,16 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
|
||||
Mod:set_session(#session{sid = SID, usr = USR, us = US,
|
||||
priority = Priority, info = Info}).
|
||||
|
||||
-spec online([#session{}]) -> [#session{}].
|
||||
|
||||
online(Sessions) ->
|
||||
lists:filter(fun is_online/1, Sessions).
|
||||
|
||||
-spec is_online(#session{}) -> boolean().
|
||||
|
||||
is_online(#session{info = Info}) ->
|
||||
not proplists:get_bool(offline, Info).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
do_route(From, To, {broadcast, _} = Packet) ->
|
||||
@@ -409,7 +449,7 @@ do_route(From, To, {broadcast, _} = Packet) ->
|
||||
_ ->
|
||||
{U, S, R} = jid:tolower(To),
|
||||
Mod = get_sm_backend(S),
|
||||
case Mod:get_sessions(U, S, R) of
|
||||
case online(Mod:get_sessions(U, S, R)) of
|
||||
[] ->
|
||||
?DEBUG("packet dropped~n", []);
|
||||
Ss ->
|
||||
@@ -511,23 +551,23 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
case Name of
|
||||
<<"message">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"chat">> -> route_message(From, To, Packet, chat);
|
||||
<<"normal">> -> route_message(From, To, Packet, normal);
|
||||
<<"">> -> route_message(From, To, Packet, normal);
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> ok;
|
||||
_ ->
|
||||
<<"groupchat">> ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
route_message(From, To, Packet, normal)
|
||||
end;
|
||||
<<"iq">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
@@ -584,8 +624,8 @@ route_message(From, To, Packet, Type) ->
|
||||
(P >= 0) and (Type == headline) ->
|
||||
LResource = jid:resourceprep(R),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer,
|
||||
LResource) of
|
||||
case online(Mod:get_sessions(LUser, LServer,
|
||||
LResource)) of
|
||||
[] ->
|
||||
ok; % Race condition
|
||||
Ss ->
|
||||
@@ -602,15 +642,12 @@ route_message(From, To, Packet, Type) ->
|
||||
case Type of
|
||||
headline -> ok;
|
||||
_ ->
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
|
||||
is_privacy_allow(From, To, Packet) of
|
||||
true ->
|
||||
case is_privacy_allow(From, To, Packet) of
|
||||
true ->
|
||||
ejabberd_hooks:run(offline_message_hook, LServer,
|
||||
[From, To, Packet]);
|
||||
false -> ok
|
||||
end;
|
||||
_ ->
|
||||
ejabberd_hooks:run(offline_message_hook, LServer,
|
||||
[From, To, Packet]);
|
||||
false ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
@@ -645,9 +682,15 @@ check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
check_max_sessions(LUser, LServer).
|
||||
|
||||
check_existing_resources(LUser, LServer, LResource) ->
|
||||
SIDs = get_resource_sessions(LUser, LServer, LResource),
|
||||
if SIDs == [] -> ok;
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer, LResource),
|
||||
{OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
|
||||
lists:foreach(fun(#session{sid = S}) ->
|
||||
Mod:delete_session(LUser, LServer, LResource, S)
|
||||
end, OfflineSs),
|
||||
if OnlineSs == [] -> ok;
|
||||
true ->
|
||||
SIDs = [SID || #session{sid = SID} <- OnlineSs],
|
||||
MaxSID = lists:max(SIDs),
|
||||
lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
|
||||
Pid ! replaced;
|
||||
@@ -666,11 +709,11 @@ get_resource_sessions(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
[S#session.sid || S <- Mod:get_sessions(LUser, LServer, LResource)].
|
||||
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
|
||||
|
||||
check_max_sessions(LUser, LServer) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
SIDs = [S#session.sid || S <- Mod:get_sessions(LUser, LServer)],
|
||||
SIDs = [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer))],
|
||||
MaxSessions = get_max_user_sessions(LUser, LServer),
|
||||
if length(SIDs) =< MaxSessions -> ok;
|
||||
true -> {_, Pid} = lists:min(SIDs), Pid ! replaced
|
||||
@@ -724,7 +767,7 @@ process_iq(From, To, Packet) ->
|
||||
|
||||
force_update_presence({LUser, LServer}) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
lists:foreach(fun (#session{sid = {_, Pid}}) ->
|
||||
Pid ! {force_update_presence, LUser, LServer}
|
||||
end,
|
||||
|
||||
+11
-47
@@ -21,48 +21,12 @@
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(PROCNAME, 'ejabberd_redis_client').
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec init() -> ok | {error, any()}.
|
||||
init() ->
|
||||
Server = ejabberd_config:get_option(redis_server,
|
||||
fun iolist_to_list/1,
|
||||
"localhost"),
|
||||
Port = ejabberd_config:get_option(redis_port,
|
||||
fun(P) when is_integer(P),
|
||||
P>0, P<65536 ->
|
||||
P
|
||||
end, 6379),
|
||||
DB = ejabberd_config:get_option(redis_db,
|
||||
fun(I) when is_integer(I), I >= 0 ->
|
||||
I
|
||||
end, 0),
|
||||
Pass = ejabberd_config:get_option(redis_password,
|
||||
fun iolist_to_list/1,
|
||||
""),
|
||||
ReconnTimeout = timer:seconds(
|
||||
ejabberd_config:get_option(
|
||||
redis_reconnect_timeout,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1)),
|
||||
ConnTimeout = timer:seconds(
|
||||
ejabberd_config:get_option(
|
||||
redis_connect_timeout,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1)),
|
||||
case eredis:start_link(Server, Port, DB, Pass,
|
||||
ReconnTimeout, ConnTimeout) of
|
||||
{ok, Client} ->
|
||||
register(?PROCNAME, Client),
|
||||
clean_table(),
|
||||
ok;
|
||||
{error, _} = Err ->
|
||||
?ERROR_MSG("failed to start redis client: ~p", [Err]),
|
||||
Err
|
||||
end.
|
||||
clean_table().
|
||||
|
||||
-spec set_session(#session{}) -> ok.
|
||||
set_session(Session) ->
|
||||
@@ -71,8 +35,8 @@ set_session(Session) ->
|
||||
SIDKey = sid_to_key(Session#session.sid),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, Session#session.sid),
|
||||
case eredis:qp(?PROCNAME, [["HSET", USKey, SIDKey, T],
|
||||
["HSET", ServKey, USSIDKey, T]]) of
|
||||
case ejabberd_redis:qp([["HSET", USKey, SIDKey, T],
|
||||
["HSET", ServKey, USSIDKey, T]]) of
|
||||
[{ok, _}, {ok, _}] ->
|
||||
ok;
|
||||
Err ->
|
||||
@@ -83,7 +47,7 @@ set_session(Session) ->
|
||||
{ok, #session{}} | {error, notfound}.
|
||||
delete_session(LUser, LServer, _LResource, SID) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
|
||||
case ejabberd_redis:q(["HGETALL", USKey]) of
|
||||
{ok, Vals} ->
|
||||
Ss = decode_session_list(Vals),
|
||||
case lists:keyfind(SID, #session.sid, Ss) of
|
||||
@@ -93,8 +57,8 @@ delete_session(LUser, LServer, _LResource, SID) ->
|
||||
SIDKey = sid_to_key(SID),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, SID),
|
||||
eredis:qp(?PROCNAME, [["HDEL", USKey, SIDKey],
|
||||
["HDEL", ServKey, USSIDKey]]),
|
||||
ejabberd_redis:qp([["HDEL", USKey, SIDKey],
|
||||
["HDEL", ServKey, USSIDKey]]),
|
||||
{ok, Session}
|
||||
end;
|
||||
Err ->
|
||||
@@ -112,7 +76,7 @@ get_sessions() ->
|
||||
-spec get_sessions(binary()) -> [#session{}].
|
||||
get_sessions(LServer) ->
|
||||
ServKey = server_to_key(LServer),
|
||||
case eredis:q(?PROCNAME, ["HGETALL", ServKey]) of
|
||||
case ejabberd_redis:q(["HGETALL", ServKey]) of
|
||||
{ok, Vals} ->
|
||||
decode_session_list(Vals);
|
||||
Err ->
|
||||
@@ -123,7 +87,7 @@ get_sessions(LServer) ->
|
||||
-spec get_sessions(binary(), binary()) -> [#session{}].
|
||||
get_sessions(LUser, LServer) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
|
||||
case ejabberd_redis:q(["HGETALL", USKey]) of
|
||||
{ok, Vals} when is_list(Vals) ->
|
||||
decode_session_list(Vals);
|
||||
Err ->
|
||||
@@ -135,7 +99,7 @@ get_sessions(LUser, LServer) ->
|
||||
[#session{}].
|
||||
get_sessions(LUser, LServer, LResource) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
|
||||
case ejabberd_redis:q(["HGETALL", USKey]) of
|
||||
{ok, Vals} when is_list(Vals) ->
|
||||
[S || S <- decode_session_list(Vals),
|
||||
element(3, S#session.usr) == LResource];
|
||||
@@ -172,7 +136,7 @@ clean_table() ->
|
||||
lists:foreach(
|
||||
fun(LServer) ->
|
||||
ServKey = server_to_key(LServer),
|
||||
case eredis:q(?PROCNAME, ["HKEYS", ServKey]) of
|
||||
case ejabberd_redis:q(["HKEYS", ServKey]) of
|
||||
{ok, []} ->
|
||||
ok;
|
||||
{ok, Vals} ->
|
||||
@@ -189,7 +153,7 @@ clean_table() ->
|
||||
SIDKey = sid_to_key(SID),
|
||||
["HDEL", USKey, SIDKey]
|
||||
end, Vals1),
|
||||
Res = eredis:qp(?PROCNAME, [Q1|Q2]),
|
||||
Res = ejabberd_redis:qp([Q1|Q2]),
|
||||
case lists:filter(
|
||||
fun({ok, _}) -> false;
|
||||
(_) -> true
|
||||
|
||||
+41
-39
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_sm_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_sm).
|
||||
|
||||
%% API
|
||||
@@ -23,18 +25,19 @@
|
||||
-include("ejabberd_sm.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec init() -> ok | {error, any()}.
|
||||
init() ->
|
||||
Node = ejabberd_sql:escape(jlib:atom_to_binary(node())),
|
||||
Node = jlib:atom_to_binary(node()),
|
||||
?INFO_MSG("Cleaning SQL SM table...", []),
|
||||
lists:foldl(
|
||||
fun(Host, ok) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of
|
||||
Host, ?SQL("delete from sm where node=%(Node)s")) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
@@ -47,20 +50,19 @@ init() ->
|
||||
|
||||
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
|
||||
priority = Priority, info = Info}) ->
|
||||
Username = ejabberd_sql:escape(U),
|
||||
Resource = ejabberd_sql:escape(R),
|
||||
InfoS = ejabberd_sql:encode_term(Info),
|
||||
InfoS = jlib:term_to_expr(Info),
|
||||
PrioS = enc_priority(Priority),
|
||||
TS = now_to_timestamp(Now),
|
||||
PidS = list_to_binary(erlang:pid_to_list(Pid)),
|
||||
Node = ejabberd_sql:escape(jlib:atom_to_binary(node(Pid))),
|
||||
case sql_queries:update(
|
||||
LServer,
|
||||
<<"sm">>,
|
||||
[<<"usec">>, <<"pid">>, <<"node">>, <<"username">>,
|
||||
<<"resource">>, <<"priority">>, <<"info">>],
|
||||
[TS, PidS, Node, Username, Resource, PrioS, InfoS],
|
||||
[<<"usec='">>, TS, <<"' and pid='">>, PidS, <<"'">>]) of
|
||||
Node = jlib:atom_to_binary(node(Pid)),
|
||||
case ?SQL_UPSERT(LServer, "sm",
|
||||
["!usec=%(TS)d",
|
||||
"!pid=%(PidS)s",
|
||||
"node=%(Node)s",
|
||||
"username=%(U)s",
|
||||
"resource=%(R)s",
|
||||
"priority=%(PrioS)s",
|
||||
"info=%(InfoS)s"]) of
|
||||
ok ->
|
||||
ok;
|
||||
Err ->
|
||||
@@ -72,14 +74,16 @@ delete_session(_LUser, LServer, _LResource, {Now, Pid}) ->
|
||||
PidS = list_to_binary(erlang:pid_to_list(Pid)),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select usec, pid, username, resource, priority, info ">>,
|
||||
<<"from sm where usec='">>, TS, <<"' and pid='">>,PidS, <<"'">>]) of
|
||||
{selected, _, [Row]} ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer, [<<"delete from sm where usec='">>,
|
||||
TS, <<"' and pid='">>, PidS, <<"'">>]),
|
||||
?SQL("select @(usec)d, @(pid)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s "
|
||||
"from sm where usec=%(TS)d and pid=%(PidS)s")) of
|
||||
{selected, [Row]} ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from sm"
|
||||
" where usec=%(TS)d and pid=%(PidS)s")),
|
||||
{ok, row_to_session(LServer, Row)};
|
||||
{selected, _, []} ->
|
||||
{selected, []} ->
|
||||
{error, notfound};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to delete from 'sm' table: ~p", [Err]),
|
||||
@@ -94,9 +98,10 @@ get_sessions() ->
|
||||
|
||||
get_sessions(LServer) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm">>]) of
|
||||
{selected, _, Rows} ->
|
||||
LServer,
|
||||
?SQL("select @(usec)d, @(pid)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s from sm")) of
|
||||
{selected, Rows} ->
|
||||
[row_to_session(LServer, Row) || Row <- Rows];
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
|
||||
@@ -104,12 +109,12 @@ get_sessions(LServer) ->
|
||||
end.
|
||||
|
||||
get_sessions(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm where ">>,
|
||||
<<"username='">>, Username, <<"'">>]) of
|
||||
{selected, _, Rows} ->
|
||||
LServer,
|
||||
?SQL("select @(usec)d, @(pid)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s from sm"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, Rows} ->
|
||||
[row_to_session(LServer, Row) || Row <- Rows];
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
|
||||
@@ -117,14 +122,12 @@ get_sessions(LUser, LServer) ->
|
||||
end.
|
||||
|
||||
get_sessions(LUser, LServer, LResource) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Resource = ejabberd_sql:escape(LResource),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm where ">>,
|
||||
<<"username='">>, Username, <<"' and resource='">>,
|
||||
Resource, <<"'">>]) of
|
||||
{selected, _, Rows} ->
|
||||
LServer,
|
||||
?SQL("select @(usec)d, @(pid)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s from sm"
|
||||
" where username=%(LUser)s and resource=%(LResource)s")) of
|
||||
{selected, Rows} ->
|
||||
[row_to_session(LServer, Row) || Row <- Rows];
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
|
||||
@@ -135,10 +138,9 @@ get_sessions(LUser, LServer, LResource) ->
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
now_to_timestamp({MSec, Sec, USec}) ->
|
||||
jlib:integer_to_binary((MSec * 1000000 + Sec) * 1000000 + USec).
|
||||
(MSec * 1000000 + Sec) * 1000000 + USec.
|
||||
|
||||
timestamp_to_now(TS) ->
|
||||
I = jlib:binary_to_integer(TS),
|
||||
timestamp_to_now(I) ->
|
||||
Head = I div 1000000,
|
||||
USec = I rem 1000000,
|
||||
MSec = Head div 1000000,
|
||||
@@ -158,7 +160,7 @@ enc_priority(undefined) ->
|
||||
enc_priority(Int) when is_integer(Int) ->
|
||||
jlib:integer_to_binary(Int).
|
||||
|
||||
row_to_session(LServer, [USec, PidS, User, Resource, PrioS, InfoS]) ->
|
||||
row_to_session(LServer, {USec, PidS, User, Resource, PrioS, InfoS}) ->
|
||||
Now = timestamp_to_now(USec),
|
||||
Pid = erlang:list_to_pid(binary_to_list(PidS)),
|
||||
Priority = dec_priority(PrioS),
|
||||
|
||||
+47
-5
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_odbc.erl
|
||||
%%% File : ejabberd_sql.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Serve ODBC connection
|
||||
%%% Purpose : Serve SQL connection
|
||||
%%% Created : 8 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@@ -39,9 +39,12 @@
|
||||
sql_query_t/1,
|
||||
sql_transaction/2,
|
||||
sql_bloc/2,
|
||||
sql_query_to_iolist/1,
|
||||
escape/1,
|
||||
standard_escape/1,
|
||||
escape_like/1,
|
||||
escape_like_arg/1,
|
||||
escape_like_arg_circumflex/1,
|
||||
to_bool/1,
|
||||
sqlite_db/1,
|
||||
sqlite_file/1,
|
||||
@@ -199,6 +202,7 @@ escape_like(S) when is_binary(S) ->
|
||||
<< <<(escape_like(C))/binary>> || <<C>> <= S >>;
|
||||
escape_like($%) -> <<"\\%">>;
|
||||
escape_like($_) -> <<"\\_">>;
|
||||
escape_like($\\) -> <<"\\\\\\\\">>;
|
||||
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C).
|
||||
|
||||
escape_like_arg(S) when is_binary(S) ->
|
||||
@@ -208,6 +212,15 @@ escape_like_arg($_) -> <<"\\_">>;
|
||||
escape_like_arg($\\) -> <<"\\\\">>;
|
||||
escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
|
||||
|
||||
escape_like_arg_circumflex(S) when is_binary(S) ->
|
||||
<< <<(escape_like_arg_circumflex(C))/binary>> || <<C>> <= S >>;
|
||||
escape_like_arg_circumflex($%) -> <<"^%">>;
|
||||
escape_like_arg_circumflex($_) -> <<"^_">>;
|
||||
escape_like_arg_circumflex($^) -> <<"^^">>;
|
||||
escape_like_arg_circumflex($[) -> <<"^[">>; % For MSSQL
|
||||
escape_like_arg_circumflex($]) -> <<"^]">>;
|
||||
escape_like_arg_circumflex(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
|
||||
|
||||
to_bool(<<"t">>) -> true;
|
||||
to_bool(<<"true">>) -> true;
|
||||
to_bool(<<"1">>) -> true;
|
||||
@@ -507,7 +520,7 @@ sql_query_internal(#sql_query{} = Query) ->
|
||||
odbc ->
|
||||
generic_sql_query(Query);
|
||||
mssql ->
|
||||
generic_sql_query(Query);
|
||||
mssql_sql_query(Query);
|
||||
pgsql ->
|
||||
Key = {?PREPARE_KEY, Query#sql_query.hash},
|
||||
case get(Key) of
|
||||
@@ -533,7 +546,7 @@ sql_query_internal(#sql_query{} = Query) ->
|
||||
mysql ->
|
||||
generic_sql_query(Query);
|
||||
sqlite ->
|
||||
generic_sql_query(Query)
|
||||
sqlite_sql_query(Query)
|
||||
end
|
||||
catch
|
||||
Class:Reason ->
|
||||
@@ -622,6 +635,32 @@ generic_escape() ->
|
||||
end
|
||||
}.
|
||||
|
||||
sqlite_sql_query(SQLQuery) ->
|
||||
sql_query_format_res(
|
||||
sql_query_internal(sqlite_sql_query_format(SQLQuery)),
|
||||
SQLQuery).
|
||||
|
||||
sqlite_sql_query_format(SQLQuery) ->
|
||||
Args = (SQLQuery#sql_query.args)(sqlite_escape()),
|
||||
(SQLQuery#sql_query.format_query)(Args).
|
||||
|
||||
sqlite_escape() ->
|
||||
#sql_escape{string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end,
|
||||
integer = fun(X) -> integer_to_binary(X) end,
|
||||
boolean = fun(true) -> <<"1">>;
|
||||
(false) -> <<"0">>
|
||||
end
|
||||
}.
|
||||
|
||||
standard_escape(S) ->
|
||||
<< <<(case Char of
|
||||
$' -> << "''" >>;
|
||||
_ -> << Char >>
|
||||
end)/binary>> || <<Char>> <= S >>.
|
||||
|
||||
mssql_sql_query(SQLQuery) ->
|
||||
sqlite_sql_query(SQLQuery).
|
||||
|
||||
pgsql_prepare(SQLQuery, State) ->
|
||||
Escape = #sql_escape{_ = fun(X) -> X end},
|
||||
N = length((SQLQuery#sql_query.args)(Escape)),
|
||||
@@ -668,6 +707,9 @@ sql_query_format_res({selected, _, Rows}, SQLQuery) ->
|
||||
sql_query_format_res(Res, _SQLQuery) ->
|
||||
Res.
|
||||
|
||||
sql_query_to_iolist(SQLQuery) ->
|
||||
generic_sql_query_format(SQLQuery).
|
||||
|
||||
%% Generate the OTP callback return tuple depending on the driver result.
|
||||
abort_on_driver_error({error, <<"query timed out">>} =
|
||||
Reply,
|
||||
@@ -748,7 +790,7 @@ pgsql_connect(Server, Port, DB, Username, Password) ->
|
||||
{port, Port},
|
||||
{as_binary, true}]) of
|
||||
{ok, Ref} ->
|
||||
pgsql:squery(Ref, [<<"alter database ">>, DB, <<" set ">>,
|
||||
pgsql:squery(Ref, [<<"alter database \"">>, DB, <<"\" set ">>,
|
||||
<<"standard_conforming_strings='off';">>]),
|
||||
pgsql:squery(Ref, [<<"set standard_conforming_strings to 'off';">>]),
|
||||
{ok, Ref};
|
||||
|
||||
+15
-9
@@ -305,20 +305,24 @@ parse_upsert(Fields) ->
|
||||
"a constant string"})
|
||||
end
|
||||
end, {[], 0}, Fields),
|
||||
%io:format("asd ~p~n", [{Fields, Fs}]),
|
||||
%io:format("upsert ~p~n", [{Fields, Fs}]),
|
||||
Fs.
|
||||
|
||||
%% key | {Update}
|
||||
parse_upsert_field([$! | S], ParamPos, Loc) ->
|
||||
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
|
||||
{Name, true, ParseState};
|
||||
{Name, key, ParseState};
|
||||
parse_upsert_field([$- | S], ParamPos, Loc) ->
|
||||
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
|
||||
{Name, {false}, ParseState};
|
||||
parse_upsert_field(S, ParamPos, Loc) ->
|
||||
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
|
||||
{Name, false, ParseState}.
|
||||
{Name, {true}, ParseState}.
|
||||
|
||||
parse_upsert_field1([], _Acc, _ParamPos, Loc) ->
|
||||
throw({error, Loc,
|
||||
"?SQL_UPSERT fields must have the "
|
||||
"following form: \"[!]name=value\""});
|
||||
"following form: \"[!-]name=value\""});
|
||||
parse_upsert_field1([$= | S], Acc, ParamPos, Loc) ->
|
||||
{lists:reverse(Acc), parse(S, ParamPos, Loc)};
|
||||
parse_upsert_field1([C | S], Acc, ParamPos, Loc) ->
|
||||
@@ -376,9 +380,9 @@ make_sql_upsert_generic(Table, ParseRes) ->
|
||||
make_sql_upsert_update(Table, ParseRes) ->
|
||||
WPairs =
|
||||
lists:flatmap(
|
||||
fun({_Field, false, _ST}) ->
|
||||
fun({_Field, {_}, _ST}) ->
|
||||
[];
|
||||
({Field, true, ST}) ->
|
||||
({Field, key, ST}) ->
|
||||
[ST#state{
|
||||
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
|
||||
}]
|
||||
@@ -386,9 +390,11 @@ make_sql_upsert_update(Table, ParseRes) ->
|
||||
Where = join_states(WPairs, " AND "),
|
||||
SPairs =
|
||||
lists:flatmap(
|
||||
fun({_Field, true, _ST}) ->
|
||||
fun({_Field, key, _ST}) ->
|
||||
[];
|
||||
({Field, false, ST}) ->
|
||||
({_Field, {false}, _ST}) ->
|
||||
[];
|
||||
({Field, {true}, ST}) ->
|
||||
[ST#state{
|
||||
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
|
||||
}]
|
||||
@@ -462,7 +468,7 @@ check_upsert(ParseRes, Pos) ->
|
||||
Set =
|
||||
lists:filter(
|
||||
fun({_Field, Match, _ST}) ->
|
||||
not Match
|
||||
Match /= key
|
||||
end, ParseRes),
|
||||
case Set of
|
||||
[] ->
|
||||
|
||||
+24
-16
@@ -74,20 +74,27 @@ get_acl_rule([<<"vhosts">>], _) ->
|
||||
%% The pages of a vhost are only accesible if the user is admin of that vhost:
|
||||
get_acl_rule([<<"server">>, VHost | _RPath], Method)
|
||||
when Method =:= 'GET' orelse Method =:= 'HEAD' ->
|
||||
{VHost, [configure, webadmin_view]};
|
||||
AC = gen_mod:get_module_opt(VHost, ejabberd_web_admin,
|
||||
access, fun(A) -> A end, configure),
|
||||
ACR = gen_mod:get_module_opt(VHost, ejabberd_web_admin,
|
||||
access_readonly, fun(A) -> A end, webadmin_view),
|
||||
{VHost, [AC, ACR]};
|
||||
get_acl_rule([<<"server">>, VHost | _RPath], 'POST') ->
|
||||
{VHost, [configure]};
|
||||
AC = gen_mod:get_module_opt(VHost, ejabberd_web_admin,
|
||||
access, fun(A) -> A end, configure),
|
||||
{VHost, [AC]};
|
||||
%% Default rule: only global admins can access any other random page
|
||||
get_acl_rule(_RPath, Method)
|
||||
when Method =:= 'GET' orelse Method =:= 'HEAD' ->
|
||||
{global, [configure, webadmin_view]};
|
||||
get_acl_rule(_RPath, 'POST') -> {global, [configure]}.
|
||||
|
||||
is_acl_match(Host, Rules, Jid) ->
|
||||
lists:any(fun (Rule) ->
|
||||
allow == acl:match_rule(Host, Rule, Jid)
|
||||
end,
|
||||
Rules).
|
||||
AC = gen_mod:get_module_opt(global, ejabberd_web_admin,
|
||||
access, fun(A) -> A end, configure),
|
||||
ACR = gen_mod:get_module_opt(global, ejabberd_web_admin,
|
||||
access_readonly, fun(A) -> A end, webadmin_view),
|
||||
{global, [AC, ACR]};
|
||||
get_acl_rule(_RPath, 'POST') ->
|
||||
AC = gen_mod:get_module_opt(global, ejabberd_web_admin,
|
||||
access, fun(A) -> A end, configure),
|
||||
{global, [AC]}.
|
||||
|
||||
%%%==================================
|
||||
%%%% Menu Items Access
|
||||
@@ -138,7 +145,7 @@ is_allowed_path([<<"admin">> | Path], JID) ->
|
||||
is_allowed_path(Path, JID);
|
||||
is_allowed_path(Path, JID) ->
|
||||
{HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'),
|
||||
is_acl_match(HostOfRule, AccessRule, JID).
|
||||
acl:any_rules_allowed(HostOfRule, AccessRule, JID).
|
||||
|
||||
%% @spec(Path) -> URL
|
||||
%% where Path = [string()]
|
||||
@@ -266,8 +273,8 @@ get_auth_account(HostOfRule, AccessRule, User, Server,
|
||||
Pass) ->
|
||||
case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
|
||||
true ->
|
||||
case is_acl_match(HostOfRule, AccessRule,
|
||||
jid:make(User, Server, <<"">>))
|
||||
case acl:any_rules_allowed(HostOfRule, AccessRule,
|
||||
jid:make(User, Server, <<"">>))
|
||||
of
|
||||
false -> {unauthorized, <<"unprivileged-account">>};
|
||||
true -> {ok, {User, Server}}
|
||||
@@ -1333,7 +1340,7 @@ parse_access_rule(Text) ->
|
||||
list_vhosts(Lang, JID) ->
|
||||
Hosts = (?MYHOSTS),
|
||||
HostsAllowed = lists:filter(fun (Host) ->
|
||||
is_acl_match(Host,
|
||||
acl:any_rules_allowed(Host,
|
||||
[configure, webadmin_view],
|
||||
JID)
|
||||
end,
|
||||
@@ -2965,7 +2972,8 @@ make_menu_item(item, 3, URI, Name, Lang) ->
|
||||
%%%==================================
|
||||
|
||||
|
||||
opt_type(access) -> fun (V) -> V end;
|
||||
opt_type(_) -> [access].
|
||||
opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
opt_type(access_readonly) -> fun acl:access_rules_validator/1;
|
||||
opt_type(_) -> [access, access_readonly].
|
||||
|
||||
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
|
||||
|
||||
@@ -373,6 +373,10 @@ try_do_command(AccessCommands, Auth, Command, AttrL,
|
||||
"The call provided additional unused "
|
||||
"arguments:~n~p",
|
||||
[ExitAtL]);
|
||||
exit:{invalid_arg_type, Arg, Type} ->
|
||||
build_fault_response(-122,
|
||||
"Parameter '~p' can't be coerced to type '~p'",
|
||||
[Arg, Type]);
|
||||
Why ->
|
||||
build_fault_response(-118,
|
||||
"A problem '~p' occurred executing the "
|
||||
@@ -472,7 +476,7 @@ format_arg(undefined, binary) -> <<>>;
|
||||
format_arg(undefined, string) -> "";
|
||||
format_arg(Arg, Format) ->
|
||||
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
|
||||
error.
|
||||
exit({invalid_arg_type, Arg, Format}).
|
||||
|
||||
process_unicode_codepoints(Str) ->
|
||||
iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
|
||||
|
||||
+18
-3
@@ -28,6 +28,7 @@
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-export([export/2, export/3, import_file/2, import/2,
|
||||
import/3, delete/1]).
|
||||
@@ -76,7 +77,12 @@ export(Server, Output, Module) ->
|
||||
IO = prepare_output(Output),
|
||||
lists:foreach(
|
||||
fun({Table, ConvertFun}) ->
|
||||
export(LServer, Table, IO, ConvertFun)
|
||||
case export(LServer, Table, IO, ConvertFun) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Failed export for module ~p: ~p",
|
||||
[Module, Reason])
|
||||
end
|
||||
end, Module:export(Server)),
|
||||
close_output(Output, IO).
|
||||
|
||||
@@ -150,7 +156,8 @@ export(LServer, Table, IO, ConvertFun) ->
|
||||
case ConvertFun(LServer, R) of
|
||||
[] ->
|
||||
Acc;
|
||||
SQL ->
|
||||
SQL1 ->
|
||||
SQL = format_queries(SQL1),
|
||||
if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 ->
|
||||
{N + 1, [SQL | SQLs]};
|
||||
true ->
|
||||
@@ -179,7 +186,7 @@ delete(LServer, Table, ConvertFun) ->
|
||||
mnesia:write_lock_table(Table),
|
||||
{_N, SQLs} =
|
||||
mnesia:foldl(
|
||||
fun(R, {N, SQLs} = Acc) ->
|
||||
fun(R, Acc) ->
|
||||
case ConvertFun(LServer, R) of
|
||||
[] ->
|
||||
Acc;
|
||||
@@ -313,3 +320,11 @@ flatten1([H|T], Acc) ->
|
||||
flatten1(T, [[H, $\n]|Acc]);
|
||||
flatten1([], Acc) ->
|
||||
Acc.
|
||||
|
||||
format_queries(SQLs) ->
|
||||
lists:map(
|
||||
fun(#sql_query{} = SQL) ->
|
||||
ejabberd_sql:sql_query_to_iolist(SQL);
|
||||
(SQL) ->
|
||||
SQL
|
||||
end, SQLs).
|
||||
|
||||
+35
-4
@@ -271,7 +271,7 @@ geturl(Url, Hdrs, UsrOpts) ->
|
||||
[U, Pass] -> [{proxy_user, U}, {proxy_password, Pass}];
|
||||
_ -> []
|
||||
end,
|
||||
case httpc:request(get, {Url, Hdrs}, Host++User++UsrOpts, []) of
|
||||
case httpc:request(get, {Url, Hdrs}, Host++User++UsrOpts++[{version, "HTTP/1.0"}], []) of
|
||||
{ok, {{_, 200, _}, Headers, Response}} ->
|
||||
{ok, Headers, Response};
|
||||
{ok, {{_, Code, _}, _Headers, Response}} ->
|
||||
@@ -484,17 +484,28 @@ compile_deps(_Module, _Spec, DestDir) ->
|
||||
filelib:ensure_dir(filename:join(Ebin, ".")),
|
||||
Result = lists:foldl(fun(Dep, Acc) ->
|
||||
Inc = filename:join(Dep, "include"),
|
||||
Lib = filename:join(Dep, "lib"),
|
||||
Src = filename:join(Dep, "src"),
|
||||
Options = [{outdir, Ebin}, {i, Inc}],
|
||||
[file:copy(App, Ebin) || App <- filelib:wildcard(Src++"/*.app")],
|
||||
Acc++[case compile:file(File, Options) of
|
||||
|
||||
%% Compile erlang files
|
||||
Acc1 = Acc ++ [case compile:file(File, Options) of
|
||||
{ok, _} -> ok;
|
||||
{ok, _, _} -> ok;
|
||||
{ok, _, _, _} -> ok;
|
||||
error -> {error, {compilation_failed, File}};
|
||||
Error -> Error
|
||||
end
|
||||
|| File <- filelib:wildcard(Src++"/*.erl")]
|
||||
|| File <- filelib:wildcard(Src++"/*.erl")],
|
||||
|
||||
%% Compile elixir files
|
||||
Acc1 ++ [case compile_elixir_file(Ebin, File) of
|
||||
{ok, _} -> ok;
|
||||
{error, File} -> {error, {compilation_failed, File}}
|
||||
end
|
||||
|| File <- filelib:wildcard(Lib ++ "/*.ex")]
|
||||
|
||||
end, [], filelib:wildcard("deps/*")),
|
||||
case lists:dropwhile(
|
||||
fun(ok) -> true;
|
||||
@@ -515,6 +526,8 @@ compile(_Module, _Spec, DestDir) ->
|
||||
verbose, report_errors, report_warnings]
|
||||
++ ExtLib,
|
||||
[file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
|
||||
|
||||
%% Compile erlang files
|
||||
Result = [case compile:file(File, Options) of
|
||||
{ok, _} -> ok;
|
||||
{ok, _, _} -> ok;
|
||||
@@ -523,14 +536,32 @@ compile(_Module, _Spec, DestDir) ->
|
||||
Error -> Error
|
||||
end
|
||||
|| File <- filelib:wildcard("src/*.erl")],
|
||||
|
||||
%% Compile elixir files
|
||||
Result1 = Result ++ [case compile_elixir_file(Ebin, File) of
|
||||
{ok, _} -> ok;
|
||||
{error, File} -> {error, {compilation_failed, File}}
|
||||
end
|
||||
|| File <- filelib:wildcard("lib/*.ex")],
|
||||
|
||||
case lists:dropwhile(
|
||||
fun(ok) -> true;
|
||||
(_) -> false
|
||||
end, Result) of
|
||||
end, Result1) of
|
||||
[] -> ok;
|
||||
[Error|_] -> Error
|
||||
end.
|
||||
|
||||
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(Dest, File) ->
|
||||
try 'Elixir.Kernel.ParallelCompiler':files_to_path([File], Dest, []) of
|
||||
[Module] -> {ok, Module}
|
||||
catch
|
||||
_ -> {error, File}
|
||||
end.
|
||||
|
||||
install(Module, Spec, DestDir) ->
|
||||
Errors = lists:dropwhile(fun({_, {ok, _}}) -> true;
|
||||
(_) -> false
|
||||
|
||||
+75
-24
@@ -52,6 +52,8 @@
|
||||
|
||||
-callback start(binary(), opts()) -> any().
|
||||
-callback stop(binary()) -> any().
|
||||
-callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
-callback depends(binary(), opts()) -> [{module(), hard | soft}].
|
||||
|
||||
-export_type([opts/0]).
|
||||
-export_type([db_type/0]).
|
||||
@@ -76,18 +78,56 @@ start_modules() ->
|
||||
|
||||
get_modules_options(Host) ->
|
||||
ejabberd_config:get_option(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
fun({M, A}) when is_atom(M), is_list(A) ->
|
||||
{M, A}
|
||||
{M, A}
|
||||
end, Mods)
|
||||
end, []).
|
||||
end, []).
|
||||
|
||||
sort_modules(Host, ModOpts) ->
|
||||
G = digraph:new([acyclic]),
|
||||
lists:foreach(
|
||||
fun({Mod, Opts}) ->
|
||||
digraph:add_vertex(G, Mod, Opts),
|
||||
Deps = try Mod:depends(Host, Opts) catch _:undef -> [] end,
|
||||
lists:foreach(
|
||||
fun({DepMod, Type}) ->
|
||||
case lists:keyfind(DepMod, 1, ModOpts) of
|
||||
false when Type == hard ->
|
||||
ErrTxt = io_lib:format(
|
||||
"failed to load module '~s' "
|
||||
"because it depends on module '~s' "
|
||||
"which is not found in the config",
|
||||
[Mod, DepMod]),
|
||||
?ERROR_MSG(ErrTxt, []),
|
||||
digraph:del_vertex(G, Mod),
|
||||
maybe_halt_ejabberd(ErrTxt);
|
||||
false when Type == soft ->
|
||||
?WARNING_MSG("module '~s' is recommended for "
|
||||
"module '~s' but is not found in "
|
||||
"the config",
|
||||
[DepMod, Mod]);
|
||||
{DepMod, DepOpts} ->
|
||||
digraph:add_vertex(G, DepMod, DepOpts),
|
||||
case digraph:add_edge(G, DepMod, Mod) of
|
||||
{error, {bad_edge, Path}} ->
|
||||
?WARNING_MSG("cyclic dependency detected "
|
||||
"between modules: ~p",
|
||||
[Path]);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end
|
||||
end, Deps)
|
||||
end, ModOpts),
|
||||
[digraph:vertex(G, V) || V <- digraph_utils:topsort(G)].
|
||||
|
||||
-spec start_modules(binary()) -> any().
|
||||
|
||||
start_modules(Host) ->
|
||||
Modules = get_modules_options(Host),
|
||||
Modules = sort_modules(Host, get_modules_options(Host)),
|
||||
lists:foreach(
|
||||
fun({Module, Opts}) ->
|
||||
start_module(Host, Module, Opts)
|
||||
@@ -120,16 +160,20 @@ start_module(Host, Module, Opts0) ->
|
||||
[Module, Host, Opts, Class, Reason,
|
||||
erlang:get_stacktrace()]),
|
||||
?CRITICAL_MSG(ErrorText, []),
|
||||
case is_app_running(ejabberd) of
|
||||
true ->
|
||||
erlang:raise(Class, Reason, erlang:get_stacktrace());
|
||||
false ->
|
||||
?CRITICAL_MSG("ejabberd initialization was aborted "
|
||||
"because a module start failed.",
|
||||
[]),
|
||||
timer:sleep(3000),
|
||||
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
|
||||
end
|
||||
maybe_halt_ejabberd(ErrorText),
|
||||
erlang:raise(Class, Reason, erlang:get_stacktrace())
|
||||
end.
|
||||
|
||||
maybe_halt_ejabberd(ErrorText) ->
|
||||
case is_app_running(ejabberd) of
|
||||
false ->
|
||||
?CRITICAL_MSG("ejabberd initialization was aborted "
|
||||
"because a module start failed.",
|
||||
[]),
|
||||
timer:sleep(3000),
|
||||
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199));
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
is_app_running(AppName) ->
|
||||
@@ -265,18 +309,25 @@ get_opt_host(Host, Opts, Default) ->
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
validate_opts(Module, Opts) ->
|
||||
lists:filter(
|
||||
lists:filtermap(
|
||||
fun({Opt, Val}) ->
|
||||
case catch Module:mod_opt_type(Opt) of
|
||||
VFun when is_function(VFun) ->
|
||||
case catch VFun(Val) of
|
||||
{'EXIT', _} ->
|
||||
try VFun(Val) of
|
||||
_ ->
|
||||
true
|
||||
catch {replace_with, NewVal} ->
|
||||
{true, {Opt, NewVal}};
|
||||
{invalid_syntax, Error} ->
|
||||
?ERROR_MSG("ignoring invalid value '~p' for "
|
||||
"option '~s' of module '~s': ~s",
|
||||
[Val, Opt, Module, Error]),
|
||||
false;
|
||||
_:_ ->
|
||||
?ERROR_MSG("ignoring invalid value '~p' for "
|
||||
"option '~s' of module '~s'",
|
||||
[Val, Opt, Module]),
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
false
|
||||
end;
|
||||
L when is_list(L) ->
|
||||
SOpts = str:join([[$', atom_to_list(A), $'] || A <- L], <<", ">>),
|
||||
@@ -301,7 +352,7 @@ validate_opts(Module, Opts) ->
|
||||
db_type(Opts, Module) when is_list(Opts) ->
|
||||
db_type(global, Opts, Module);
|
||||
db_type(Host, Module) when is_atom(Module) ->
|
||||
case Module:mod_opt_type(db_type) of
|
||||
case catch Module:mod_opt_type(db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_module_opt(Host, Module, db_type, F) of
|
||||
undefined -> ejabberd_config:default_db(Host, Module);
|
||||
@@ -314,7 +365,7 @@ db_type(Host, Module) when is_atom(Module) ->
|
||||
-spec db_type(binary(), opts(), module()) -> db_type().
|
||||
|
||||
db_type(Host, Opts, Module) ->
|
||||
case Module:mod_opt_type(db_type) of
|
||||
case catch Module:mod_opt_type(db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_opt(db_type, Opts, F) of
|
||||
undefined -> ejabberd_config:default_db(Host, Module);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
-type(pubsubState() :: mod_pubsub:pubsubState()).
|
||||
-type(pubsubItem() :: mod_pubsub:pubsubItem()).
|
||||
-type(subOptions() :: mod_pubsub:subOptions()).
|
||||
-type(pubOptions() :: mod_pubsub:pubOptions()).
|
||||
-type(affiliation() :: mod_pubsub:affiliation()).
|
||||
-type(subscription() :: mod_pubsub:subscription()).
|
||||
-type(subId() :: mod_pubsub:subId()).
|
||||
@@ -109,7 +110,8 @@
|
||||
PublishModel :: publishModel(),
|
||||
Max_Items :: non_neg_integer(),
|
||||
ItemId :: <<>> | itemId(),
|
||||
Payload :: payload()) ->
|
||||
Payload :: payload(),
|
||||
Options :: pubOptions()) ->
|
||||
{result, {default, broadcast, [itemId()]}} |
|
||||
{error, xmlel()}.
|
||||
|
||||
|
||||
+26
-2
@@ -50,11 +50,35 @@
|
||||
-spec start() -> ok.
|
||||
|
||||
start() ->
|
||||
{ok, Owner} = ets_owner(),
|
||||
SplitPattern = binary:compile_pattern([<<"@">>, <<"/">>]),
|
||||
catch ets:new(jlib, [named_table, protected, set, {keypos, 1}]),
|
||||
%% Table is public to allow ETS insert to fix / update the table even if table already exist
|
||||
%% with another owner.
|
||||
catch ets:new(jlib, [named_table, public, set, {keypos, 1}, {heir, Owner, undefined}]),
|
||||
ets:insert(jlib, {string_to_jid_pattern, SplitPattern}),
|
||||
ok.
|
||||
|
||||
ets_owner() ->
|
||||
case whereis(jlib_ets) of
|
||||
undefined ->
|
||||
Pid = spawn(fun() -> ets_keepalive() end),
|
||||
case catch register(jlib_ets, Pid) of
|
||||
true ->
|
||||
{ok, Pid};
|
||||
Error -> Error
|
||||
end;
|
||||
Pid ->
|
||||
{ok,Pid}
|
||||
end.
|
||||
|
||||
%% Process used to keep jlib ETS table alive in case the original owner dies.
|
||||
%% The table need to be public, otherwise subsequent inserts would fail.
|
||||
ets_keepalive() ->
|
||||
receive
|
||||
_ ->
|
||||
ets_keepalive()
|
||||
end.
|
||||
|
||||
-spec make(binary(), binary(), binary()) -> jid() | error.
|
||||
|
||||
make(User, Server, Resource) ->
|
||||
@@ -87,7 +111,7 @@ split(#jid{user = U, server = S, resource = R}) ->
|
||||
split(_) ->
|
||||
error.
|
||||
|
||||
-spec from_string([binary()|string()]) -> jid() | error.
|
||||
-spec from_string(binary() | string()) -> jid() | error.
|
||||
from_string(S) when is_list(S) ->
|
||||
%% We do not accept list because we want to enforce good practice of
|
||||
%% using binaries for string. However, we do not let it crash to avoid
|
||||
|
||||
+82
-69
@@ -43,7 +43,7 @@
|
||||
get_iq_namespace/1, iq_query_info/1,
|
||||
iq_query_or_response_info/1, is_iq_request_type/1,
|
||||
iq_to_xml/1, parse_xdata_submit/1,
|
||||
is_standalone_chat_state/1,
|
||||
unwrap_carbon/1, is_standalone_chat_state/1,
|
||||
add_delay_info/3, add_delay_info/4,
|
||||
timestamp_to_legacy/1, timestamp_to_iso_basic/1, timestamp_to_iso/2,
|
||||
now_to_utc_string/1, now_to_local_string/1,
|
||||
@@ -54,7 +54,8 @@
|
||||
binary_to_integer/1, binary_to_integer/2,
|
||||
integer_to_binary/1, integer_to_binary/2,
|
||||
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
|
||||
l2i/1, i2l/1, i2l/2, queue_drop_while/2]).
|
||||
l2i/1, i2l/1, i2l/2, queue_drop_while/2,
|
||||
expr_to_term/1, term_to_expr/1]).
|
||||
|
||||
%% The following functions are deprecated and will be removed soon
|
||||
%% Use corresponding functions from jid.erl instead
|
||||
@@ -306,21 +307,16 @@ get_iq_namespace(#xmlel{name = <<"iq">>, children = Els}) ->
|
||||
get_iq_namespace(_) -> <<"">>.
|
||||
|
||||
%%
|
||||
-spec(iq_query_info/1 ::
|
||||
(
|
||||
Xmlel :: xmlel())
|
||||
-> iq_request() | 'reply' | 'invalid' | 'not_iq'
|
||||
).
|
||||
-spec iq_query_info(Xmlel :: xmlel()) ->
|
||||
iq_request() | 'reply' | 'invalid' | 'not_iq'.
|
||||
|
||||
%% @spec (xmlelement()) -> iq() | reply | invalid | not_iq
|
||||
iq_query_info(El) -> iq_info_internal(El, request).
|
||||
|
||||
%%
|
||||
-spec(iq_query_or_response_info/1 ::
|
||||
(
|
||||
Xmlel :: xmlel())
|
||||
-> iq_request() | iq_reply() | 'reply' | 'invalid' | 'not_iq'
|
||||
).
|
||||
-spec iq_query_or_response_info(Xmlel :: xmlel()) ->
|
||||
iq_request() | iq_reply() |
|
||||
'reply' | 'invalid' | 'not_iq'.
|
||||
|
||||
iq_query_or_response_info(El) ->
|
||||
iq_info_internal(El, any).
|
||||
@@ -372,11 +368,7 @@ iq_type_to_string(get) -> <<"get">>;
|
||||
iq_type_to_string(result) -> <<"result">>;
|
||||
iq_type_to_string(error) -> <<"error">>.
|
||||
|
||||
-spec(iq_to_xml/1 ::
|
||||
(
|
||||
IQ :: iq())
|
||||
-> xmlel()
|
||||
).
|
||||
-spec iq_to_xml(IQ :: iq()) -> xmlel().
|
||||
|
||||
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
|
||||
if ID /= <<"">> ->
|
||||
@@ -390,13 +382,8 @@ iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
|
||||
children = SubEl}
|
||||
end.
|
||||
|
||||
-spec(parse_xdata_submit/1 ::
|
||||
(
|
||||
El :: xmlel())
|
||||
-> [{Var::binary(), Values::[binary()]}]
|
||||
%%
|
||||
| 'invalid'
|
||||
).
|
||||
-spec parse_xdata_submit(El :: xmlel()) ->
|
||||
[{Var::binary(), Values::[binary()]}] | 'invalid'.
|
||||
|
||||
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
@@ -408,12 +395,9 @@ parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
|
||||
invalid
|
||||
end.
|
||||
|
||||
-spec(parse_xdata_fields/2 ::
|
||||
(
|
||||
Xmlels :: [xmlel() | cdata()],
|
||||
Res :: [{Var::binary(), Values :: [binary()]}])
|
||||
-> [{Var::binary(), Values::[binary()]}]
|
||||
).
|
||||
-spec parse_xdata_fields(Xmlels :: [xmlel() | cdata()],
|
||||
Res :: [{Var::binary(), Values :: [binary()]}]) ->
|
||||
[{Var::binary(), Values::[binary()]}].
|
||||
|
||||
parse_xdata_fields([], Res) -> Res;
|
||||
parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
|
||||
@@ -428,12 +412,8 @@ parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
|
||||
parse_xdata_fields([_ | Els], Res) ->
|
||||
parse_xdata_fields(Els, Res).
|
||||
|
||||
-spec(parse_xdata_values/2 ::
|
||||
(
|
||||
Xmlels :: [xmlel() | cdata()],
|
||||
Res :: [binary()])
|
||||
-> [binary()]
|
||||
).
|
||||
-spec parse_xdata_values(Xmlels :: [xmlel() | cdata()],
|
||||
Res :: [binary()]) -> [binary()].
|
||||
|
||||
parse_xdata_values([], Res) -> Res;
|
||||
parse_xdata_values([#xmlel{name = <<"value">>, children = SubEls} | Els], Res) ->
|
||||
@@ -528,14 +508,64 @@ rsm_encode_count(Count, Arr) ->
|
||||
children = [{xmlcdata, i2l(Count)}]}
|
||||
| Arr].
|
||||
|
||||
-spec unwrap_carbon(xmlel()) -> xmlel().
|
||||
|
||||
unwrap_carbon(#xmlel{name = <<"message">>} = Stanza) ->
|
||||
case unwrap_carbon(Stanza, <<"sent">>) of
|
||||
#xmlel{} = Payload ->
|
||||
Payload;
|
||||
false ->
|
||||
case unwrap_carbon(Stanza, <<"received">>) of
|
||||
#xmlel{} = Payload ->
|
||||
Payload;
|
||||
false ->
|
||||
Stanza
|
||||
end
|
||||
end;
|
||||
unwrap_carbon(Stanza) -> Stanza.
|
||||
|
||||
-spec unwrap_carbon(xmlel(), binary()) -> xmlel() | false.
|
||||
|
||||
unwrap_carbon(Stanza, Direction) ->
|
||||
case fxml:get_subtag(Stanza, Direction) of
|
||||
#xmlel{name = Direction, attrs = Attrs} = El ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
NS when NS == ?NS_CARBONS_2;
|
||||
NS == ?NS_CARBONS_1 ->
|
||||
case fxml:get_subtag_with_xmlns(El, <<"forwarded">>,
|
||||
?NS_FORWARD) of
|
||||
#xmlel{children = Els} ->
|
||||
case fxml:remove_cdata(Els) of
|
||||
[#xmlel{} = Payload] ->
|
||||
Payload;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
_NS ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec is_standalone_chat_state(xmlel()) -> boolean().
|
||||
|
||||
is_standalone_chat_state(#xmlel{name = <<"message">>, children = Els}) ->
|
||||
Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els,
|
||||
fxml:get_attr_s(<<"xmlns">>, Attrs) /= ?NS_CHATSTATES,
|
||||
Name /= <<"thread">>],
|
||||
Stripped == [];
|
||||
is_standalone_chat_state(_El) -> false.
|
||||
is_standalone_chat_state(Stanza) ->
|
||||
case unwrap_carbon(Stanza) of
|
||||
#xmlel{name = <<"message">>, children = Els} ->
|
||||
IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY],
|
||||
Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els,
|
||||
not lists:member(fxml:get_attr_s(<<"xmlns">>,
|
||||
Attrs),
|
||||
IgnoreNS),
|
||||
Name /= <<"thread">>],
|
||||
Stripped == [];
|
||||
#xmlel{} ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp())
|
||||
-> xmlel().
|
||||
@@ -547,33 +577,8 @@ add_delay_info(El, From, Time) ->
|
||||
binary()) -> xmlel().
|
||||
|
||||
add_delay_info(El, From, Time, Desc) ->
|
||||
case fxml:get_subtag_with_xmlns(El, <<"delay">>, ?NS_DELAY) of
|
||||
false ->
|
||||
%% Add new tag
|
||||
DelayTag = create_delay_tag(Time, From, Desc),
|
||||
fxml:append_subtags(El, [DelayTag]);
|
||||
DelayTag ->
|
||||
%% Update existing tag
|
||||
NewDelayTag =
|
||||
case {fxml:get_tag_cdata(DelayTag), Desc} of
|
||||
{<<"">>, <<"">>} ->
|
||||
DelayTag;
|
||||
{OldDesc, <<"">>} ->
|
||||
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]};
|
||||
{<<"">>, NewDesc} ->
|
||||
DelayTag#xmlel{children = [{xmlcdata, NewDesc}]};
|
||||
{OldDesc, NewDesc} ->
|
||||
case binary:match(OldDesc, NewDesc) of
|
||||
nomatch ->
|
||||
FinalDesc = <<OldDesc/binary, ", ", NewDesc/binary>>,
|
||||
DelayTag#xmlel{children = [{xmlcdata, FinalDesc}]};
|
||||
_ ->
|
||||
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]}
|
||||
end
|
||||
end,
|
||||
NewEl = fxml:remove_subtags(El, <<"delay">>, {<<"xmlns">>, ?NS_DELAY}),
|
||||
fxml:append_subtags(NewEl, [NewDelayTag])
|
||||
end.
|
||||
DelayTag = create_delay_tag(Time, From, Desc),
|
||||
fxml:append_subtags(El, [DelayTag]).
|
||||
|
||||
-spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary())
|
||||
-> xmlel() | error.
|
||||
@@ -890,6 +895,14 @@ tuple_to_binary(T) ->
|
||||
atom_to_binary(A) ->
|
||||
erlang:atom_to_binary(A, utf8).
|
||||
|
||||
expr_to_term(Expr) ->
|
||||
Str = binary_to_list(<<Expr/binary, ".">>),
|
||||
{ok, Tokens, _} = erl_scan:string(Str),
|
||||
{ok, Term} = erl_parse:parse_term(Tokens),
|
||||
Term.
|
||||
|
||||
term_to_expr(Term) ->
|
||||
list_to_binary(io_lib:print(Term)).
|
||||
|
||||
l2i(I) when is_integer(I) -> I;
|
||||
l2i(L) when is_binary(L) -> binary_to_integer(L).
|
||||
|
||||
+4
-1
@@ -35,7 +35,7 @@
|
||||
process_sm_iq/3, get_local_commands/5,
|
||||
get_local_identity/5, get_local_features/5,
|
||||
get_sm_commands/5, get_sm_identity/5, get_sm_features/5,
|
||||
ping_item/4, ping_command/4, mod_opt_type/1]).
|
||||
ping_item/4, ping_command/4, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -284,6 +284,9 @@ ping_command(_Acc, _From, _To,
|
||||
end;
|
||||
ping_command(Acc, _From, _To, _Request) -> Acc.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(report_commands_node) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
|
||||
+37
-19
@@ -47,7 +47,7 @@
|
||||
srg_delete/2, srg_list/1, srg_get_info/2,
|
||||
srg_get_members/2, srg_user_add/4, srg_user_del/4,
|
||||
send_message/5, send_stanza/3, send_stanza_c2s/4, privacy_set/3,
|
||||
stats/1, stats/2, mod_opt_type/1, get_commands_spec/0]).
|
||||
stats/1, stats/2, mod_opt_type/1, get_commands_spec/0, depends/2]).
|
||||
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -66,6 +66,8 @@ start(_Host, _Opts) ->
|
||||
stop(_Host) ->
|
||||
ejabberd_commands:unregister_commands(get_commands_spec()).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%%
|
||||
%%% Register commands
|
||||
@@ -533,7 +535,7 @@ get_commands_spec() ->
|
||||
policy = user,
|
||||
module = mod_offline, function = count_offline_messages,
|
||||
args = [],
|
||||
result = {res, integer}},
|
||||
result = {value, integer}},
|
||||
#ejabberd_commands{name = send_message, tags = [stanza],
|
||||
desc = "Send a message to a local or remote bare of full JID",
|
||||
module = ?MODULE, function = send_message,
|
||||
@@ -861,28 +863,41 @@ connected_users_vhost(Host) ->
|
||||
|
||||
%% Code copied from ejabberd_sm.erl and customized
|
||||
dirty_get_sessions_list2() ->
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4', _ = '_'},
|
||||
[],
|
||||
[['$1', '$2', '$3', '$4']]}]).
|
||||
Ss = mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4',
|
||||
_ = '_'},
|
||||
[],
|
||||
[['$1', '$2', '$3', '$4']]}]),
|
||||
lists:filter(fun([_USR, _SID, _Priority, Info]) ->
|
||||
not proplists:get_bool(offline, Info)
|
||||
end, Ss).
|
||||
|
||||
%% Make string more print-friendly
|
||||
stringize(String) ->
|
||||
%% Replace newline characters with other code
|
||||
ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>).
|
||||
|
||||
set_presence(User, Host, Resource, Type, Show, Status, Priority)
|
||||
when is_integer(Priority) ->
|
||||
BPriority = integer_to_binary(Priority),
|
||||
set_presence(User, Host, Resource, Type, Show, Status, BPriority);
|
||||
set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
|
||||
Pid = ejabberd_sm:get_session_pid(User, Host, Resource),
|
||||
USR = jid:to_string(jid:make(User, Host, Resource)),
|
||||
US = jid:to_string(jid:make(User, Host, <<>>)),
|
||||
Message = {route_xmlstreamelement,
|
||||
{xmlel, <<"presence">>,
|
||||
[{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}],
|
||||
[{xmlel, <<"show">>, [], [{xmlcdata, Show}]},
|
||||
{xmlel, <<"status">>, [], [{xmlcdata, Status}]},
|
||||
{xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}},
|
||||
Pid ! Message.
|
||||
case ejabberd_sm:get_session_pid(User, Host, Resource) of
|
||||
none ->
|
||||
error;
|
||||
Pid ->
|
||||
USR = jid:to_string(jid:make(User, Host, Resource)),
|
||||
US = jid:to_string(jid:make(User, Host, <<>>)),
|
||||
Message = {route_xmlstreamelement,
|
||||
{xmlel, <<"presence">>,
|
||||
[{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}],
|
||||
[{xmlel, <<"show">>, [], [{xmlcdata, Show}]},
|
||||
{xmlel, <<"status">>, [], [{xmlcdata, Status}]},
|
||||
{xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}},
|
||||
Pid ! Message,
|
||||
ok
|
||||
end.
|
||||
|
||||
user_sessions_info(User, Host) ->
|
||||
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
||||
@@ -891,7 +906,9 @@ user_sessions_info(User, Host) ->
|
||||
{'EXIT', _Reason} ->
|
||||
[];
|
||||
Ss ->
|
||||
Ss
|
||||
lists:filter(fun(#session{info = Info}) ->
|
||||
not proplists:get_bool(offline, Info)
|
||||
end, Ss)
|
||||
end,
|
||||
lists:map(
|
||||
fun(Session) ->
|
||||
@@ -1154,7 +1171,8 @@ subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) -
|
||||
subscribe_roster({Name, Server, Group, Nick}, Roster);
|
||||
%% Subscribe Name2 to Name1
|
||||
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
|
||||
subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, <<"both">>, []),
|
||||
subscribe(Name1, Server1, iolist_to_binary(Name2), iolist_to_binary(Server2),
|
||||
iolist_to_binary(Nick2), iolist_to_binary(Group2), <<"both">>, []),
|
||||
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
|
||||
|
||||
push_alltoall(S, G) ->
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
-export([start/2, init/0, stop/1, export/1, import/1,
|
||||
import/3, announce/3, send_motd/1, disco_identity/5,
|
||||
disco_features/5, disco_items/5,
|
||||
disco_features/5, disco_items/5, depends/2,
|
||||
send_announcement_to_all/3, announce_commands/4,
|
||||
announce_items/4, mod_opt_type/1]).
|
||||
|
||||
@@ -74,6 +74,9 @@ start(Host, Opts) ->
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
proc_lib:spawn(?MODULE, init, [])).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_adhoc, hard}].
|
||||
|
||||
init() ->
|
||||
loop().
|
||||
|
||||
@@ -903,7 +906,7 @@ send_announcement_to_all(Host, SubjectS, BodyS) ->
|
||||
|
||||
get_access(Host) ->
|
||||
gen_mod:get_module_opt(Host, ?MODULE, access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
none).
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
@@ -920,6 +923,6 @@ import(LServer, DBType, LA) ->
|
||||
Mod:import(LServer, LA).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) -> [access, db_type].
|
||||
|
||||
+30
-35
@@ -9,6 +9,8 @@
|
||||
-module(mod_announce_sql).
|
||||
-behaviour(mod_announce).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
%% API
|
||||
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
|
||||
get_motd/1, is_motd_user/2, set_motd_user/2, import/1,
|
||||
@@ -16,6 +18,7 @@
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_announce.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -27,37 +30,35 @@ set_motd_users(LServer, USRs) ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, _S, _R}) ->
|
||||
Username = ejabberd_sql:escape(U),
|
||||
sql_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[Username, <<"">>],
|
||||
[<<"username='">>, Username, <<"'">>])
|
||||
?SQL_UPSERT_T(
|
||||
"motd",
|
||||
["!username=%(U)s",
|
||||
"xml=''"])
|
||||
end, USRs)
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
set_motd(LServer, Packet) ->
|
||||
XML = ejabberd_sql:escape(fxml:element_to_binary(Packet)),
|
||||
XML = fxml:element_to_binary(Packet),
|
||||
F = fun() ->
|
||||
sql_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[<<"">>, XML],
|
||||
[<<"username=''">>])
|
||||
?SQL_UPSERT_T(
|
||||
"motd",
|
||||
["!username=''",
|
||||
"xml=%(XML)s"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
delete_motd(LServer) ->
|
||||
F = fun() ->
|
||||
ejabberd_sql:sql_query_t([<<"delete from motd;">>])
|
||||
ejabberd_sql:sql_query_t(?SQL("delete from motd"))
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
get_motd(LServer) ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer, [<<"select xml from motd where username='';">>]) of
|
||||
{selected, [<<"xml">>], [[XML]]} ->
|
||||
LServer,
|
||||
?SQL("select @(xml)s from motd where username=''")) of
|
||||
{selected, [{XML}]} ->
|
||||
case fxml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
error;
|
||||
@@ -69,46 +70,40 @@ get_motd(LServer) ->
|
||||
end.
|
||||
|
||||
is_motd_user(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select username from motd "
|
||||
"where username='">>, Username, <<"';">>]) of
|
||||
{selected, [<<"username">>], [_|_]} ->
|
||||
LServer,
|
||||
?SQL("select @(username)s from motd"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, [_|_]} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
set_motd_user(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
F = fun() ->
|
||||
sql_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[Username, <<"">>],
|
||||
[<<"username='">>, Username, <<"'">>])
|
||||
end,
|
||||
?SQL_UPSERT_T(
|
||||
"motd",
|
||||
["!username=%(LUser)s",
|
||||
"xml=''"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
export(_Server) ->
|
||||
[{motd,
|
||||
fun(Host, #motd{server = LServer, packet = El})
|
||||
when LServer == Host ->
|
||||
[[<<"delete from motd where username='';">>],
|
||||
[<<"insert into motd(username, xml) values ('', '">>,
|
||||
ejabberd_sql:escape(fxml:element_to_binary(El)),
|
||||
<<"');">>]];
|
||||
XML = fxml:element_to_binary(El),
|
||||
[?SQL("delete from motd where username='';"),
|
||||
?SQL("insert into motd(username, xml) values ('', %(XML)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
{motd_users,
|
||||
fun(Host, #motd_users{us = {LUser, LServer}})
|
||||
when LServer == Host, LUser /= <<"">> ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
[[<<"delete from motd where username='">>, Username, <<"';">>],
|
||||
[<<"insert into motd(username, xml) values ('">>,
|
||||
Username, <<"', '');">>]];
|
||||
[?SQL("delete from motd where username=%(LUser)s;"),
|
||||
?SQL("insert into motd(username, xml) values (%(LUser)s, '');")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
-protocol({xep, 191, '1.2'}).
|
||||
|
||||
-export([start/2, stop/1, process_iq/3,
|
||||
process_iq_set/4, process_iq_get/5, mod_opt_type/1]).
|
||||
process_iq_set/4, process_iq_get/5, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -63,6 +63,9 @@ stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_BLOCKING).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_privacy, hard}].
|
||||
|
||||
process_iq(_From, _To, IQ) ->
|
||||
SubEl = IQ#iq.sub_el,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
|
||||
@@ -25,7 +25,12 @@ process_blocklist_block(LUser, LServer, Filter) ->
|
||||
Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, []} ->
|
||||
Name = <<"Blocked contacts">>,
|
||||
mod_privacy_sql:sql_add_privacy_list(LUser, Name),
|
||||
case mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Name) of
|
||||
{selected, []} ->
|
||||
mod_privacy_sql:sql_add_privacy_list(LUser, Name);
|
||||
{selected, [{_ID}]} ->
|
||||
ok
|
||||
end,
|
||||
mod_privacy_sql:sql_set_default_privacy_list(LUser, Name),
|
||||
Name;
|
||||
{selected, [{Name}]} -> Name
|
||||
|
||||
+4
-1
@@ -41,7 +41,7 @@
|
||||
import_start/2, import_stop/2]).
|
||||
|
||||
%% gen_mod callbacks
|
||||
-export([start/2, start_link/2, stop/1]).
|
||||
-export([start/2, start_link/2, stop/1, depends/2]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_info/2, handle_call/3,
|
||||
@@ -306,6 +306,9 @@ c2s_broadcast_recipients(InAcc, Host, C2SState,
|
||||
end;
|
||||
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
init([Host, Opts]) ->
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
|
||||
+19
-21
@@ -9,10 +9,13 @@
|
||||
-module(mod_caps_sql).
|
||||
-behaviour(mod_caps).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
%% API
|
||||
-export([init/2, caps_read/2, caps_write/3, export/1]).
|
||||
|
||||
-include("mod_caps.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -21,21 +24,19 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
caps_read(LServer, {Node, SubNode}) ->
|
||||
SNode = ejabberd_sql:escape(Node),
|
||||
SSubNode = ejabberd_sql:escape(SubNode),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer, [<<"select feature from caps_features where ">>,
|
||||
<<"node='">>, SNode, <<"' and subnode='">>,
|
||||
SSubNode, <<"';">>]) of
|
||||
{selected, [<<"feature">>], [[H]|_] = Fs} ->
|
||||
case catch jlib:binary_to_integer(H) of
|
||||
Int when is_integer(Int), Int>=0 ->
|
||||
{ok, Int};
|
||||
_ ->
|
||||
{ok, lists:flatten(Fs)}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
LServer,
|
||||
?SQL("select @(feature)s from caps_features where"
|
||||
" node=%(Node)s and subnode=%(SubNode)s")) of
|
||||
{selected, [{H}|_] = Fs} ->
|
||||
case catch jlib:binary_to_integer(H) of
|
||||
Int when is_integer(Int), Int>=0 ->
|
||||
{ok, Int};
|
||||
_ ->
|
||||
{ok, [F || {F} <- Fs]}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
caps_write(LServer, NodePair, Features) ->
|
||||
@@ -56,16 +57,13 @@ export(_Server) ->
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
sql_write_features_t({Node, SubNode}, Features) ->
|
||||
SNode = ejabberd_sql:escape(Node),
|
||||
SSubNode = ejabberd_sql:escape(SubNode),
|
||||
NewFeatures = if is_integer(Features) ->
|
||||
[jlib:integer_to_binary(Features)];
|
||||
true ->
|
||||
Features
|
||||
end,
|
||||
[[<<"delete from caps_features where node='">>,
|
||||
SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
|
||||
[[<<"insert into caps_features(node, subnode, feature) ">>,
|
||||
<<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
|
||||
ejabberd_sql:escape(F), <<"');">>] || F <- NewFeatures]].
|
||||
[?SQL("delete from caps_features where node=%(Node)s"
|
||||
" and subnode=%(SubNode)s;") |
|
||||
[?SQL("insert into caps_features(node, subnode, feature)"
|
||||
" values (%(Node)s, %(SubNode)s, %(F)s);") || F <- NewFeatures]].
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
-export([user_send_packet/4, user_receive_packet/5,
|
||||
iq_handler2/3, iq_handler1/3, remove_connection/4,
|
||||
is_carbon_copy/1, mod_opt_type/1]).
|
||||
is_carbon_copy/1, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -278,6 +278,9 @@ list(User, Server) ->
|
||||
Mod = gen_mod:db_mod(Server, ?MODULE),
|
||||
Mod:list(User, Server).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) -> [db_type, iqdisc].
|
||||
|
||||
+248
-53
@@ -30,24 +30,44 @@
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, add_stream_feature/2,
|
||||
filter_presence/2, filter_chat_states/2,
|
||||
mod_opt_type/1]).
|
||||
%% gen_mod callbacks.
|
||||
-export([start/2, stop/1, mod_opt_type/1, depends/2]).
|
||||
|
||||
%% ejabberd_hooks callbacks.
|
||||
-export([filter_presence/3, filter_chat_states/3, filter_pep/3, filter_other/3,
|
||||
flush_queue/2, add_stream_feature/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(CSI_QUEUE_MAX, 100).
|
||||
|
||||
-type csi_type() :: presence | chatstate | {pep, binary()}.
|
||||
-type csi_key() :: {ljid(), csi_type()}.
|
||||
-type csi_stanza() :: {csi_key(), erlang:timestamp(), xmlel()}.
|
||||
-type csi_queue() :: [csi_stanza()].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec start(binary(), gen_mod:opts()) -> ok.
|
||||
|
||||
start(Host, Opts) ->
|
||||
QueuePresence = gen_mod:get_opt(queue_presence, Opts,
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, true),
|
||||
DropChatStates = gen_mod:get_opt(drop_chat_states, Opts,
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, true),
|
||||
if QueuePresence; DropChatStates ->
|
||||
QueuePresence =
|
||||
gen_mod:get_opt(queue_presence, Opts,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true),
|
||||
QueueChatStates =
|
||||
gen_mod:get_opt(queue_chat_states, Opts,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true),
|
||||
QueuePEP =
|
||||
gen_mod:get_opt(queue_pep, Opts,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true),
|
||||
if QueuePresence; QueueChatStates; QueuePEP ->
|
||||
ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE,
|
||||
add_stream_feature, 50),
|
||||
if QueuePresence ->
|
||||
@@ -55,23 +75,150 @@ start(Host, Opts) ->
|
||||
filter_presence, 50);
|
||||
true -> ok
|
||||
end,
|
||||
if DropChatStates ->
|
||||
if QueueChatStates ->
|
||||
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_chat_states, 50);
|
||||
true -> ok
|
||||
end;
|
||||
end,
|
||||
if QueuePEP ->
|
||||
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_pep, 50);
|
||||
true -> ok
|
||||
end,
|
||||
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_other, 100),
|
||||
ejabberd_hooks:add(csi_flush_queue, Host, ?MODULE,
|
||||
flush_queue, 50);
|
||||
true -> ok
|
||||
end,
|
||||
ok.
|
||||
end.
|
||||
|
||||
-spec stop(binary()) -> ok.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_presence, 50),
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_chat_states, 50),
|
||||
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
|
||||
add_stream_feature, 50),
|
||||
ok.
|
||||
QueuePresence =
|
||||
gen_mod:get_module_opt(Host, ?MODULE, queue_presence,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true),
|
||||
QueueChatStates =
|
||||
gen_mod:get_module_opt(Host, ?MODULE, queue_chat_states,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true),
|
||||
QueuePEP =
|
||||
gen_mod:get_module_opt(Host, ?MODULE, queue_pep,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true),
|
||||
if QueuePresence; QueueChatStates; QueuePEP ->
|
||||
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
|
||||
add_stream_feature, 50),
|
||||
if QueuePresence ->
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_presence, 50);
|
||||
true -> ok
|
||||
end,
|
||||
if QueueChatStates ->
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_chat_states, 50);
|
||||
true -> ok
|
||||
end,
|
||||
if QueuePEP ->
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_pep, 50);
|
||||
true -> ok
|
||||
end,
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_other, 100),
|
||||
ejabberd_hooks:delete(csi_flush_queue, Host, ?MODULE,
|
||||
flush_queue, 50);
|
||||
true -> ok
|
||||
end.
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
|
||||
mod_opt_type(queue_presence) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(queue_chat_states) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(queue_pep) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) -> [queue_presence, queue_chat_states, queue_pep].
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% ejabberd_hooks callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec filter_presence({term(), [xmlel()]}, binary(), xmlel())
|
||||
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
|
||||
|
||||
filter_presence({C2SState, _OutStanzas} = Acc, Host,
|
||||
#xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) ->
|
||||
case fxml:get_attr(<<"type">>, Attrs) of
|
||||
{value, Type} when Type /= <<"unavailable">> ->
|
||||
Acc;
|
||||
_ ->
|
||||
?DEBUG("Got availability presence stanza", []),
|
||||
queue_add(presence, Stanza, Host, C2SState)
|
||||
end;
|
||||
filter_presence(Acc, _Host, _Stanza) -> Acc.
|
||||
|
||||
-spec filter_chat_states({term(), [xmlel()]}, binary(), xmlel())
|
||||
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
|
||||
|
||||
filter_chat_states({C2SState, _OutStanzas} = Acc, Host,
|
||||
#xmlel{name = <<"message">>} = Stanza) ->
|
||||
case jlib:is_standalone_chat_state(Stanza) of
|
||||
true ->
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
To = fxml:get_tag_attr_s(<<"to">>, Stanza),
|
||||
case {jid:from_string(From), jid:from_string(To)} of
|
||||
{#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
|
||||
%% Don't queue (carbon copies of) chat states from other
|
||||
%% resources, as they might be used to sync the state of
|
||||
%% conversations across clients.
|
||||
Acc;
|
||||
_ ->
|
||||
?DEBUG("Got standalone chat state notification", []),
|
||||
queue_add(chatstate, Stanza, Host, C2SState)
|
||||
end;
|
||||
false ->
|
||||
Acc
|
||||
end;
|
||||
filter_chat_states(Acc, _Host, _Stanza) -> Acc.
|
||||
|
||||
-spec filter_pep({term(), [xmlel()]}, binary(), xmlel())
|
||||
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
|
||||
|
||||
filter_pep({C2SState, _OutStanzas} = Acc, Host,
|
||||
#xmlel{name = <<"message">>} = Stanza) ->
|
||||
case get_pep_node(Stanza) of
|
||||
{value, Node} ->
|
||||
?DEBUG("Got PEP notification", []),
|
||||
queue_add({pep, Node}, Stanza, Host, C2SState);
|
||||
false ->
|
||||
Acc
|
||||
end;
|
||||
filter_pep(Acc, _Host, _Stanza) -> Acc.
|
||||
|
||||
-spec filter_other({term(), [xmlel()]}, binary(), xmlel())
|
||||
-> {stop, {term(), [xmlel()]}}.
|
||||
|
||||
filter_other({C2SState, _OutStanzas}, Host, Stanza) ->
|
||||
?DEBUG("Won't add stanza to CSI queue", []),
|
||||
queue_take(Stanza, Host, C2SState).
|
||||
|
||||
-spec flush_queue({term(), [xmlel()]}, binary()) -> {term(), [xmlel()]}.
|
||||
|
||||
flush_queue({C2SState, _OutStanzas}, Host) ->
|
||||
?DEBUG("Going to flush CSI queue", []),
|
||||
Queue = get_queue(C2SState),
|
||||
NewState = set_queue([], C2SState),
|
||||
{NewState, get_stanzas(Queue, Host)}.
|
||||
|
||||
-spec add_stream_feature([xmlel()], binary) -> [xmlel()].
|
||||
|
||||
add_stream_feature(Features, _Host) ->
|
||||
Feature = #xmlel{name = <<"csi">>,
|
||||
@@ -79,34 +226,82 @@ add_stream_feature(Features, _Host) ->
|
||||
children = []},
|
||||
[Feature | Features].
|
||||
|
||||
filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
|
||||
case fxml:get_attr(<<"type">>, Attrs) of
|
||||
{value, Type} when Type /= <<"unavailable">> ->
|
||||
?DEBUG("Got important presence stanza", []),
|
||||
{stop, send};
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec queue_add(csi_type(), xmlel(), binary(), term())
|
||||
-> {stop, {term(), [xmlel()]}}.
|
||||
|
||||
queue_add(Type, Stanza, Host, C2SState) ->
|
||||
case get_queue(C2SState) of
|
||||
Queue when length(Queue) >= ?CSI_QUEUE_MAX ->
|
||||
?DEBUG("CSI queue too large, going to flush it", []),
|
||||
NewState = set_queue([], C2SState),
|
||||
{stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}};
|
||||
Queue ->
|
||||
?DEBUG("Adding stanza to CSI queue", []),
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
Key = {jid:tolower(jid:from_string(From)), Type},
|
||||
Entry = {Key, p1_time_compat:timestamp(), Stanza},
|
||||
NewQueue = lists:keystore(Key, 1, Queue, Entry),
|
||||
NewState = set_queue(NewQueue, C2SState),
|
||||
{stop, {NewState, []}}
|
||||
end.
|
||||
|
||||
-spec queue_take(xmlel(), binary(), term()) -> {stop, {term(), [xmlel()]}}.
|
||||
|
||||
queue_take(Stanza, Host, C2SState) ->
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
{LUser, LServer, _LResource} = jid:tolower(jid:from_string(From)),
|
||||
{Selected, Rest} = lists:partition(
|
||||
fun({{{U, S, _R}, _Type}, _Time, _Stanza}) ->
|
||||
U == LUser andalso S == LServer
|
||||
end, get_queue(C2SState)),
|
||||
NewState = set_queue(Rest, C2SState),
|
||||
{stop, {NewState, get_stanzas(Selected, Host) ++ [Stanza]}}.
|
||||
|
||||
-spec set_queue(csi_queue(), term()) -> term().
|
||||
|
||||
set_queue(Queue, C2SState) ->
|
||||
ejabberd_c2s:set_aux_field(csi_queue, Queue, C2SState).
|
||||
|
||||
-spec get_queue(term()) -> csi_queue().
|
||||
|
||||
get_queue(C2SState) ->
|
||||
case ejabberd_c2s:get_aux_field(csi_queue, C2SState) of
|
||||
{ok, Queue} ->
|
||||
Queue;
|
||||
error ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec get_stanzas(csi_queue(), binary()) -> [xmlel()].
|
||||
|
||||
get_stanzas(Queue, Host) ->
|
||||
lists:map(fun({_Key, Time, Stanza}) ->
|
||||
jlib:add_delay_info(Stanza, Host, Time,
|
||||
<<"Client Inactive">>)
|
||||
end, Queue).
|
||||
|
||||
-spec get_pep_node(xmlel()) -> {value, binary()} | false.
|
||||
|
||||
get_pep_node(#xmlel{name = <<"message">>} = Stanza) ->
|
||||
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
case jid:from_string(From) of
|
||||
#jid{luser = <<>>} -> % It's not PEP.
|
||||
false;
|
||||
_ ->
|
||||
?DEBUG("Got availability presence stanza", []),
|
||||
{stop, queue}
|
||||
end;
|
||||
filter_presence(Action, _Stanza) -> Action.
|
||||
|
||||
filter_chat_states(_Action, #xmlel{name = <<"message">>} = Stanza) ->
|
||||
case jlib:is_standalone_chat_state(Stanza) of
|
||||
true ->
|
||||
?DEBUG("Got standalone chat state notification", []),
|
||||
{stop, drop};
|
||||
false ->
|
||||
?DEBUG("Got message stanza", []),
|
||||
{stop, send}
|
||||
end;
|
||||
filter_chat_states(Action, _Stanza) -> Action.
|
||||
|
||||
mod_opt_type(drop_chat_states) ->
|
||||
fun (true) -> true;
|
||||
(false) -> false
|
||||
end;
|
||||
mod_opt_type(queue_presence) ->
|
||||
fun (true) -> true;
|
||||
(false) -> false
|
||||
end;
|
||||
mod_opt_type(_) -> [drop_chat_states, queue_presence].
|
||||
case fxml:get_subtag_with_xmlns(Stanza, <<"event">>,
|
||||
?NS_PUBSUB_EVENT) of
|
||||
#xmlel{children = Els} ->
|
||||
case fxml:remove_cdata(Els) of
|
||||
[#xmlel{name = <<"items">>, attrs = ItemsAttrs}] ->
|
||||
fxml:get_attr(<<"node">>, ItemsAttrs);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
+35
-24
@@ -35,7 +35,8 @@
|
||||
get_local_features/5, get_local_items/5,
|
||||
adhoc_local_items/4, adhoc_local_commands/4,
|
||||
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
|
||||
adhoc_sm_items/4, adhoc_sm_commands/4, mod_opt_type/1]).
|
||||
adhoc_sm_items/4, adhoc_sm_commands/4, mod_opt_type/1,
|
||||
depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -95,6 +96,9 @@ stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_COMMANDS).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_adhoc, hard}, {mod_last, soft}].
|
||||
|
||||
%%%-----------------------------------------------------------------------
|
||||
|
||||
-define(INFO_IDENTITY(Category, Type, Name, Lang),
|
||||
@@ -1368,10 +1372,9 @@ get_form(Host, [<<"config">>, <<"access">>], Lang) ->
|
||||
[{xmlcdata, S}]}
|
||||
end,
|
||||
str:tokens(iolist_to_binary(io_lib:format("~p.",
|
||||
[ets:select(local_config,
|
||||
[{{local_config,
|
||||
{access,
|
||||
'$1',
|
||||
[ets:select(access,
|
||||
[{{access,
|
||||
{'$1',
|
||||
'$2'},
|
||||
'$3'},
|
||||
[{'==',
|
||||
@@ -1826,10 +1829,9 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
|
||||
Lang, XData) ->
|
||||
SetAccess = fun (Rs) ->
|
||||
mnesia:transaction(fun () ->
|
||||
Os = mnesia:select(local_config,
|
||||
[{{local_config,
|
||||
{access,
|
||||
'$1',
|
||||
Os = mnesia:select(access,
|
||||
[{{access,
|
||||
{'$1',
|
||||
'$2'},
|
||||
'$3'},
|
||||
[{'==',
|
||||
@@ -1843,9 +1845,8 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
|
||||
lists:foreach(fun ({access,
|
||||
Name,
|
||||
Rules}) ->
|
||||
mnesia:write({local_config,
|
||||
{access,
|
||||
Name,
|
||||
mnesia:write({access,
|
||||
{Name,
|
||||
Host},
|
||||
Rules})
|
||||
end,
|
||||
@@ -1916,19 +1917,29 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
|
||||
Xmlelement = ?SERRT_POLICY_VIOLATION(Lang, <<"has been kicked">>),
|
||||
case JID#jid.lresource of
|
||||
<<>> ->
|
||||
SIDs = mnesia:dirty_select(session,
|
||||
[{#session{sid = '$1',
|
||||
usr = {LUser, LServer, '_'},
|
||||
_ = '_'},
|
||||
[], ['$1']}]),
|
||||
[Pid ! {kick, kicked_by_admin, Xmlelement} || {_, Pid} <- SIDs];
|
||||
SIs = mnesia:dirty_select(session,
|
||||
[{#session{usr = {LUser, LServer, '_'},
|
||||
sid = '$1',
|
||||
info = '$2',
|
||||
_ = '_'},
|
||||
[], [{{'$1', '$2'}}]}]),
|
||||
Pids = [P || {{_, P}, Info} <- SIs,
|
||||
not proplists:get_bool(offline, Info)],
|
||||
lists:foreach(fun(Pid) ->
|
||||
Pid ! {kick, kicked_by_admin, Xmlelement}
|
||||
end, Pids);
|
||||
R ->
|
||||
[{_, Pid}] = mnesia:dirty_select(session,
|
||||
[{#session{sid = '$1',
|
||||
usr = {LUser, LServer, R},
|
||||
_ = '_'},
|
||||
[], ['$1']}]),
|
||||
Pid ! {kick, kicked_by_admin, Xmlelement}
|
||||
[{{_, Pid}, Info}] = mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{usr = {LUser, LServer, R},
|
||||
sid = '$1',
|
||||
info = '$2',
|
||||
_ = '_'},
|
||||
[], [{{'$1', '$2'}}]}]),
|
||||
case proplists:get_bool(offline, Info) of
|
||||
true -> ok;
|
||||
false -> Pid ! {kick, kicked_by_admin, Xmlelement}
|
||||
end
|
||||
end,
|
||||
{result, []};
|
||||
set_form(From, Host,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_local_iq/3,
|
||||
mod_opt_type/1, opt_type/1]).
|
||||
mod_opt_type/1, opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -201,6 +201,9 @@ process_get(#xmlel{name = <<"last">>, attrs = Attrs}, Lang) ->
|
||||
%% {result, };
|
||||
process_get(_, _) -> {error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) -> [iqdisc].
|
||||
|
||||
|
||||
+4
-1
@@ -39,7 +39,7 @@
|
||||
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
|
||||
get_info/5, register_feature/2, unregister_feature/2,
|
||||
register_extra_domain/2, unregister_extra_domain/2,
|
||||
transform_module_options/1, mod_opt_type/1]).
|
||||
transform_module_options/1, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -534,6 +534,9 @@ values_to_xml(Values) ->
|
||||
end,
|
||||
Values).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(extra_domains) ->
|
||||
fun (Hs) -> [iolist_to_binary(H) || H <- Hs] end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
|
||||
+5
-2
@@ -37,7 +37,7 @@
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3,
|
||||
mod_opt_type/1]).
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -63,7 +63,7 @@ start_link(Host, Opts) ->
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
temporary, 1000, worker, [?MODULE]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
@@ -200,5 +200,8 @@ do_client_version(enabled, From, To) ->
|
||||
?INFO_MSG("Information of the client: ~s~s",
|
||||
[ToS, Values_string2]).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(_) -> [host].
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3,
|
||||
mod_opt_type/1]).
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
@@ -120,6 +120,9 @@ stop(Host) ->
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
@@ -167,7 +170,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%%===================================================================
|
||||
is_whitelisted(Host, Addr) ->
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
none),
|
||||
acl:match_rule(Host, Access, Addr) == allow.
|
||||
|
||||
@@ -187,7 +190,7 @@ format_date({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||
[Hour, Minute, Second, Day, Month, Year]).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(c2s_auth_ban_lifetime) ->
|
||||
fun (T) when is_integer(T), T > 0 -> T end;
|
||||
mod_opt_type(c2s_max_auth_failures) ->
|
||||
|
||||
+137
-91
@@ -74,7 +74,7 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process/2, mod_opt_type/1]).
|
||||
-export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@@ -123,6 +123,9 @@ start(_Host, _Opts) ->
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%% ----------
|
||||
%% basic auth
|
||||
%% ----------
|
||||
@@ -130,13 +133,13 @@ stop(_Host) ->
|
||||
check_permissions(Request, Command) ->
|
||||
case catch binary_to_existing_atom(Command, utf8) of
|
||||
Call when is_atom(Call) ->
|
||||
{ok, CommandPolicy} = ejabberd_commands:get_command_policy(Call),
|
||||
check_permissions2(Request, Call, CommandPolicy);
|
||||
{ok, CommandPolicy, Scope} = ejabberd_commands:get_command_policy_and_scope(Call),
|
||||
check_permissions2(Request, Call, CommandPolicy, Scope);
|
||||
_ ->
|
||||
unauthorized_response()
|
||||
json_error(404, 40, <<"Endpoint not found.">>)
|
||||
end.
|
||||
|
||||
check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
|
||||
check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _, ScopeList)
|
||||
when HTTPAuth /= undefined ->
|
||||
Admin =
|
||||
case lists:keysearch(<<"X-Admin">>, 1, Headers) of
|
||||
@@ -156,24 +159,25 @@ check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
|
||||
false
|
||||
end;
|
||||
{oauth, Token, _} ->
|
||||
case oauth_check_token(Call, Token) of
|
||||
{ok, User, Server} ->
|
||||
case oauth_check_token(ScopeList, Token) of
|
||||
{ok, user, {User, Server}} ->
|
||||
{ok, {User, Server, {oauth, Token}, Admin}};
|
||||
false ->
|
||||
false
|
||||
{false, Reason} ->
|
||||
{false, Reason}
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end,
|
||||
case Auth of
|
||||
{ok, A} -> {allowed, Call, A};
|
||||
{false, no_matching_scope} -> outofscope_response();
|
||||
_ -> unauthorized_response()
|
||||
end;
|
||||
check_permissions2(_Request, Call, open) ->
|
||||
check_permissions2(_Request, Call, open, _Scope) ->
|
||||
{allowed, Call, noauth};
|
||||
check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
|
||||
check_permissions2(#request{ip={IP, _Port}}, Call, _Policy, _Scope) ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
|
||||
mod_opt_type(admin_ip_access),
|
||||
fun(V) -> V end,
|
||||
none),
|
||||
Res = acl:match_rule(global, Access, IP),
|
||||
case Res of
|
||||
@@ -186,19 +190,16 @@ check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
|
||||
Commands when is_list(Commands) ->
|
||||
case lists:member(Call, Commands) of
|
||||
true -> {allowed, Call, admin};
|
||||
_ -> unauthorized_response()
|
||||
_ -> outofscope_response()
|
||||
end;
|
||||
E ->
|
||||
?DEBUG("Unauthorized: ~p", [E]),
|
||||
unauthorized_response()
|
||||
_E ->
|
||||
{allowed, Call, noauth}
|
||||
end;
|
||||
check_permissions2(_Request, _Call, _Policy) ->
|
||||
check_permissions2(_Request, _Call, _Policy, _Scope) ->
|
||||
unauthorized_response().
|
||||
|
||||
oauth_check_token(Scope, Token) when is_atom(Scope) ->
|
||||
oauth_check_token(atom_to_binary(Scope, utf8), Token);
|
||||
oauth_check_token(Scope, Token) ->
|
||||
ejabberd_oauth:check_token(Scope, Token).
|
||||
oauth_check_token(ScopeList, Token) when is_list(ScopeList) ->
|
||||
ejabberd_oauth:check_token(ScopeList, Token).
|
||||
|
||||
%% ------------------
|
||||
%% command processing
|
||||
@@ -209,27 +210,27 @@ oauth_check_token(Scope, Token) ->
|
||||
process(_, #request{method = 'POST', data = <<>>}) ->
|
||||
?DEBUG("Bad Request: no data", []),
|
||||
badrequest_response(<<"Missing POST data">>);
|
||||
process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
|
||||
process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} = Req) ->
|
||||
Version = get_api_version(Req),
|
||||
try
|
||||
Args = case jiffy:decode(Data) of
|
||||
List when is_list(List) -> List;
|
||||
{List} when is_list(List) -> List;
|
||||
Other -> [Other]
|
||||
end,
|
||||
log(Call, Args, IP),
|
||||
Args = extract_args(Data),
|
||||
log(Call, Args, IPPort),
|
||||
case check_permissions(Req, Call) of
|
||||
{allowed, Cmd, Auth} ->
|
||||
{Code, Result} = handle(Cmd, Auth, Args, Version),
|
||||
json_response(Code, jiffy:encode(Result));
|
||||
Result = handle(Cmd, Auth, Args, Version, IP),
|
||||
json_format(Result);
|
||||
%% Warning: check_permission direcly formats 401 reply if not authorized
|
||||
ErrorResponse ->
|
||||
ErrorResponse
|
||||
end
|
||||
catch _:{error,{_,invalid_json}} = _Err ->
|
||||
?DEBUG("Bad Request: ~p", [_Err]),
|
||||
badrequest_response(<<"Invalid JSON input">>);
|
||||
_:_Error ->
|
||||
catch
|
||||
%% TODO We need to refactor to remove redundant error return formatting
|
||||
throw:{error, unknown_command} ->
|
||||
{404, 40, <<"Command not found.">>};
|
||||
_:{error,{_,invalid_json}} = _Err ->
|
||||
?DEBUG("Bad Request: ~p", [_Err]),
|
||||
badrequest_response(<<"Invalid JSON input">>);
|
||||
_:_Error ->
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
badrequest_response()
|
||||
end;
|
||||
@@ -243,13 +244,18 @@ process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
|
||||
log(Call, Args, IP),
|
||||
case check_permissions(Req, Call) of
|
||||
{allowed, Cmd, Auth} ->
|
||||
{Code, Result} = handle(Cmd, Auth, Args, Version),
|
||||
json_response(Code, jiffy:encode(Result));
|
||||
Result = handle(Cmd, Auth, Args, Version, IP),
|
||||
json_format(Result);
|
||||
%% Warning: check_permission direcly formats 401 reply if not authorized
|
||||
ErrorResponse ->
|
||||
ErrorResponse
|
||||
end
|
||||
catch _:_Error ->
|
||||
catch
|
||||
%% TODO We need to refactor to remove redundant error return formatting
|
||||
throw:{error, unknown_command} ->
|
||||
json_format({404, 44, <<"Command not found.">>});
|
||||
_:_Error ->
|
||||
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
badrequest_response()
|
||||
end;
|
||||
@@ -257,17 +263,26 @@ process([], #request{method = 'OPTIONS', data = <<>>}) ->
|
||||
{200, ?OPTIONS_HEADER, []};
|
||||
process(_Path, Request) ->
|
||||
?DEBUG("Bad Request: no handler ~p", [Request]),
|
||||
badrequest_response().
|
||||
json_error(400, 40, <<"Missing command name.">>).
|
||||
|
||||
%% Be tolerant to make API more easily usable from command-line pipe.
|
||||
extract_args(<<"\n">>) -> [];
|
||||
extract_args(Data) ->
|
||||
case jiffy:decode(Data) of
|
||||
List when is_list(List) -> List;
|
||||
{List} when is_list(List) -> List;
|
||||
Other -> [Other]
|
||||
end.
|
||||
|
||||
% get API version N from last "vN" element in URL path
|
||||
get_api_version(#request{path = Path}) ->
|
||||
get_api_version(lists:reverse(Path));
|
||||
get_api_version([<<"v", String/binary>> | Tail]) ->
|
||||
case catch jlib:binary_to_integer(String) of
|
||||
N when is_integer(N) ->
|
||||
N;
|
||||
_ ->
|
||||
get_api_version(Tail)
|
||||
N when is_integer(N) ->
|
||||
N;
|
||||
_ ->
|
||||
get_api_version(Tail)
|
||||
end;
|
||||
get_api_version([_Head | Tail]) ->
|
||||
get_api_version(Tail);
|
||||
@@ -278,8 +293,10 @@ get_api_version([]) ->
|
||||
%% command handlers
|
||||
%% ----------------
|
||||
|
||||
%% TODO Check accept types of request before decided format of reply.
|
||||
|
||||
% generic ejabberd command handler
|
||||
handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
|
||||
case ejabberd_commands:get_command_format(Call, Auth, Version) of
|
||||
{ArgsSpec, _} when is_list(ArgsSpec) ->
|
||||
Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
|
||||
@@ -296,7 +313,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
[{Key, undefined}|Acc]
|
||||
end, [], ArgsSpec),
|
||||
try
|
||||
handle2(Call, Auth, match(Args2, Spec), Version)
|
||||
handle2(Call, Auth, match(Args2, Spec), Version, IP)
|
||||
catch throw:not_found ->
|
||||
{404, <<"not_found">>};
|
||||
throw:{not_found, Why} when is_atom(Why) ->
|
||||
@@ -309,8 +326,10 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
{401, jlib:atom_to_binary(Why)};
|
||||
throw:{not_allowed, Msg} ->
|
||||
{401, iolist_to_binary(Msg)};
|
||||
throw:{error, account_unprivileged} ->
|
||||
{401, iolist_to_binary(<<"Unauthorized: Account Unpriviledged">>)};
|
||||
throw:{error, account_unprivileged} ->
|
||||
{403, 31, <<"Command need to be run with admin priviledge.">>};
|
||||
throw:{error, access_rules_unauthorized} ->
|
||||
{403, 32, <<"AccessRules: Account associated to token does not have the right to perform the operation.">>};
|
||||
throw:{invalid_parameter, Msg} ->
|
||||
{400, iolist_to_binary(Msg)};
|
||||
throw:{error, Why} when is_atom(Why) ->
|
||||
@@ -333,10 +352,10 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
{400, <<"Error">>}
|
||||
end.
|
||||
|
||||
handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
handle2(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
|
||||
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
|
||||
ArgsFormatted = format_args(Args, ArgsF),
|
||||
ejabberd_command(Auth, Call, ArgsFormatted, Version).
|
||||
ejabberd_command(Auth, Call, ArgsFormatted, Version, IP).
|
||||
|
||||
get_elem_delete(A, L) ->
|
||||
case proplists:get_all_values(A, L) of
|
||||
@@ -366,28 +385,33 @@ format_args(Args, ArgsFormat) ->
|
||||
L when is_list(L) -> exit({additional_unused_args, L})
|
||||
end.
|
||||
|
||||
format_arg({array, Elements},
|
||||
{list, {ElementDefName, ElementDefFormat}})
|
||||
format_arg(Elements,
|
||||
{list, {_ElementDefName, ElementDefFormat}})
|
||||
when is_list(Elements) ->
|
||||
lists:map(fun ({struct, [{ElementName, ElementValue}]}) when
|
||||
ElementDefName == ElementName ->
|
||||
format_arg(ElementValue, ElementDefFormat)
|
||||
end,
|
||||
Elements);
|
||||
format_arg({array, [{struct, Elements}]},
|
||||
{list, {ElementDefName, ElementDefFormat}})
|
||||
when is_list(Elements) ->
|
||||
lists:map(fun ({ElementName, ElementValue}) ->
|
||||
true = ElementDefName == ElementName,
|
||||
format_arg(ElementValue, ElementDefFormat)
|
||||
end,
|
||||
Elements);
|
||||
format_arg({array, [{struct, Elements}]},
|
||||
[format_arg(Element, ElementDefFormat)
|
||||
|| Element <- Elements];
|
||||
format_arg({[{Name, Value}]},
|
||||
{tuple, [{_Tuple1N, Tuple1S}, {_Tuple2N, Tuple2S}]})
|
||||
when Tuple1S == binary;
|
||||
Tuple1S == string ->
|
||||
{format_arg(Name, Tuple1S), format_arg(Value, Tuple2S)};
|
||||
format_arg({Elements},
|
||||
{tuple, ElementsDef})
|
||||
when is_list(Elements) ->
|
||||
FormattedList = format_args(Elements, ElementsDef),
|
||||
list_to_tuple(FormattedList);
|
||||
format_arg({array, Elements}, {list, ElementsDef})
|
||||
F = lists:map(fun({TElName, TElDef}) ->
|
||||
case lists:keyfind(atom_to_binary(TElName, latin1), 1, Elements) of
|
||||
{_, Value} ->
|
||||
format_arg(Value, TElDef);
|
||||
_ when TElDef == binary; TElDef == string ->
|
||||
<<"">>;
|
||||
_ ->
|
||||
?ERROR_MSG("missing field ~p in tuple ~p", [TElName, Elements]),
|
||||
throw({invalid_parameter,
|
||||
io_lib:format("Missing field ~w in tuple ~w", [TElName, Elements])})
|
||||
end
|
||||
end, ElementsDef),
|
||||
list_to_tuple(F);
|
||||
format_arg(Elements, {list, ElementsDef})
|
||||
when is_list(Elements) and is_atom(ElementsDef) ->
|
||||
[format_arg(Element, ElementsDef)
|
||||
|| Element <- Elements];
|
||||
@@ -401,7 +425,7 @@ format_arg(undefined, string) -> <<>>;
|
||||
format_arg(Arg, Format) ->
|
||||
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
|
||||
throw({invalid_parameter,
|
||||
io_lib:format("Arg ~p is not in format ~p",
|
||||
io_lib:format("Arg ~w is not in format ~w",
|
||||
[Arg, Format])}).
|
||||
|
||||
process_unicode_codepoints(Str) ->
|
||||
@@ -416,12 +440,12 @@ process_unicode_codepoints(Str) ->
|
||||
match(Args, Spec) ->
|
||||
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
|
||||
|
||||
ejabberd_command(Auth, Cmd, Args, Version) ->
|
||||
ejabberd_command(Auth, Cmd, Args, Version, IP) ->
|
||||
Access = case Auth of
|
||||
admin -> [];
|
||||
_ -> undefined
|
||||
end,
|
||||
case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version) of
|
||||
case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version, #{ip => IP}) of
|
||||
{error, Error} ->
|
||||
throw(Error);
|
||||
Res ->
|
||||
@@ -431,22 +455,24 @@ ejabberd_command(Auth, Cmd, Args, Version) ->
|
||||
format_command_result(Cmd, Auth, Result, Version) ->
|
||||
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
|
||||
case {ResultFormat, Result} of
|
||||
{{_, rescode}, V} when V == true; V == ok ->
|
||||
{200, 0};
|
||||
{{_, rescode}, _} ->
|
||||
{200, 1};
|
||||
{{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
|
||||
{200, iolist_to_binary(Text1)};
|
||||
{{_, restuple}, {_, Text2}} ->
|
||||
{500, iolist_to_binary(Text2)};
|
||||
{{_, {list, _}}, _V} ->
|
||||
{_, L} = format_result(Result, ResultFormat),
|
||||
{200, L};
|
||||
{{_, {tuple, _}}, _V} ->
|
||||
{_, T} = format_result(Result, ResultFormat),
|
||||
{200, T};
|
||||
_ ->
|
||||
{200, {[format_result(Result, ResultFormat)]}}
|
||||
{{_, rescode}, V} when V == true; V == ok ->
|
||||
{200, 0};
|
||||
{{_, rescode}, _} ->
|
||||
{200, 1};
|
||||
{_, {error, ErrorAtom, Code, Msg}} ->
|
||||
format_error_result(ErrorAtom, Code, Msg);
|
||||
{{_, restuple}, {V, Text}} when V == true; V == ok ->
|
||||
{200, iolist_to_binary(Text)};
|
||||
{{_, restuple}, {ErrorAtom, Msg}} ->
|
||||
format_error_result(ErrorAtom, 0, Msg);
|
||||
{{_, {list, _}}, _V} ->
|
||||
{_, L} = format_result(Result, ResultFormat),
|
||||
{200, L};
|
||||
{{_, {tuple, _}}, _V} ->
|
||||
{_, T} = format_result(Result, ResultFormat),
|
||||
{200, T};
|
||||
_ ->
|
||||
{200, {[format_result(Result, ResultFormat)]}}
|
||||
end.
|
||||
|
||||
format_result(Atom, {Name, atom}) ->
|
||||
@@ -484,25 +510,45 @@ format_result(Tuple, {Name, {tuple, Def}}) ->
|
||||
format_result(404, {_Name, _}) ->
|
||||
"not_found".
|
||||
|
||||
|
||||
format_error_result(conflict, Code, Msg) ->
|
||||
{409, Code, iolist_to_binary(Msg)};
|
||||
format_error_result(_ErrorAtom, Code, Msg) ->
|
||||
{500, Code, iolist_to_binary(Msg)}.
|
||||
|
||||
unauthorized_response() ->
|
||||
unauthorized_response(<<"401 Unauthorized">>).
|
||||
unauthorized_response(Body) ->
|
||||
json_response(401, jiffy:encode(Body)).
|
||||
json_error(401, 10, <<"Oauth Token is invalid or expired.">>).
|
||||
|
||||
outofscope_response() ->
|
||||
json_error(401, 11, <<"Token does not grant usage to command required scope.">>).
|
||||
|
||||
badrequest_response() ->
|
||||
badrequest_response(<<"400 Bad Request">>).
|
||||
badrequest_response(Body) ->
|
||||
json_response(400, jiffy:encode(Body)).
|
||||
|
||||
json_format({Code, Result}) ->
|
||||
json_response(Code, jiffy:encode(Result));
|
||||
json_format({HTMLCode, JSONErrorCode, Message}) ->
|
||||
json_error(HTMLCode, JSONErrorCode, Message).
|
||||
|
||||
json_response(Code, Body) when is_integer(Code) ->
|
||||
{Code, ?HEADER(?CT_JSON), Body}.
|
||||
|
||||
%% HTTPCode, JSONCode = integers
|
||||
%% message is binary
|
||||
json_error(HTTPCode, JSONCode, Message) ->
|
||||
{HTTPCode, ?HEADER(?CT_JSON),
|
||||
jiffy:encode({[{<<"status">>, <<"error">>},
|
||||
{<<"code">>, JSONCode},
|
||||
{<<"message">>, Message}]})
|
||||
}.
|
||||
|
||||
log(Call, Args, {Addr, Port}) ->
|
||||
AddrS = jlib:ip_to_list({Addr, Port}),
|
||||
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
|
||||
log(Call, Args, IP) ->
|
||||
?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
|
||||
|
||||
mod_opt_type(admin_ip_access) ->
|
||||
fun(Access) when is_atom(Access) -> Access end;
|
||||
mod_opt_type(admin_ip_access) -> fun acl:access_rules_validator/1;
|
||||
mod_opt_type(_) -> [admin_ip_access].
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process/2, mod_opt_type/1]).
|
||||
-export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -109,6 +109,8 @@ mod_opt_type(max_pause) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
mod_opt_type(_) -> [max_inactivity, max_pause].
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Help Web Page
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
%% utility for other http modules
|
||||
-export([content_type/3]).
|
||||
|
||||
-export([reopen_log/1, mod_opt_type/1]).
|
||||
-export([reopen_log/1, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -109,6 +109,9 @@ stop(Host) ->
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
|
||||
+26
-19
@@ -68,6 +68,7 @@
|
||||
-export([start_link/3,
|
||||
start/2,
|
||||
stop/1,
|
||||
depends/2,
|
||||
mod_opt_type/1]).
|
||||
|
||||
%% gen_server callbacks.
|
||||
@@ -178,7 +179,7 @@ mod_opt_type(host) ->
|
||||
mod_opt_type(name) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(access) ->
|
||||
fun(A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(max_size) ->
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
@@ -222,6 +223,11 @@ mod_opt_type(_) ->
|
||||
dir_mode, docroot, put_url, get_url, service_url, custom_headers,
|
||||
rm_on_unregister, thumbnail].
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -235,7 +241,7 @@ init({ServerHost, Opts}) ->
|
||||
fun iolist_to_binary/1,
|
||||
<<"HTTP File Upload">>),
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
local),
|
||||
MaxSize = gen_mod:get_opt(max_size, Opts,
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
@@ -321,22 +327,24 @@ init({ServerHost, Opts}) ->
|
||||
-> {reply, {ok, pos_integer(), binary(),
|
||||
pos_integer() | undefined,
|
||||
pos_integer() | undefined}, state()} |
|
||||
{reply, {error, binary()}, state()} | {noreply, state()}.
|
||||
{reply, {error, atom()}, state()} | {noreply, state()}.
|
||||
|
||||
handle_call({use_slot, Slot}, _From, #state{file_mode = FileMode,
|
||||
dir_mode = DirMode,
|
||||
get_url = GetPrefix,
|
||||
thumbnail = Thumbnail,
|
||||
docroot = DocRoot} = State) ->
|
||||
handle_call({use_slot, Slot, Size}, _From, #state{file_mode = FileMode,
|
||||
dir_mode = DirMode,
|
||||
get_url = GetPrefix,
|
||||
thumbnail = Thumbnail,
|
||||
docroot = DocRoot} = State) ->
|
||||
case get_slot(Slot, State) of
|
||||
{ok, {Size, Timer}} ->
|
||||
timer:cancel(Timer),
|
||||
NewState = del_slot(Slot, State),
|
||||
Path = str:join([DocRoot | Slot], <<$/>>),
|
||||
{reply, {ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail},
|
||||
{reply, {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail},
|
||||
NewState};
|
||||
{ok, {_WrongSize, _Timer}} ->
|
||||
{reply, {error, size_mismatch}, State};
|
||||
error ->
|
||||
{reply, {error, <<"Invalid slot">>}, State}
|
||||
{reply, {error, invalid_slot}, State}
|
||||
end;
|
||||
handle_call(get_docroot, _From, #state{docroot = DocRoot} = State) ->
|
||||
{reply, {ok, DocRoot}, State};
|
||||
@@ -406,9 +414,8 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
|
||||
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
|
||||
data = Data} = Request) ->
|
||||
{Proc, Slot} = parse_http_request(Request),
|
||||
case catch gen_server:call(Proc, {use_slot, Slot}) of
|
||||
{ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail}
|
||||
when byte_size(Data) == Size ->
|
||||
case catch gen_server:call(Proc, {use_slot, Slot, byte_size(Data)}) of
|
||||
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail} ->
|
||||
?DEBUG("Storing file from ~s for ~s: ~s",
|
||||
[?ADDR_TO_STR(IP), Host, Path]),
|
||||
case store_file(Path, Data, FileMode, DirMode,
|
||||
@@ -422,13 +429,13 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
|
||||
[Path, ?ADDR_TO_STR(IP), Host, ?FORMAT(Error)]),
|
||||
http_response(Host, 500)
|
||||
end;
|
||||
{ok, Size, Path, _FileMode, _DirMode, _GetPrefix, _Thumbnail} ->
|
||||
?INFO_MSG("Rejecting file ~s from ~s for ~s: Size is ~B, not ~B",
|
||||
[Path, ?ADDR_TO_STR(IP), Host, byte_size(Data), Size]),
|
||||
{error, size_mismatch} ->
|
||||
?INFO_MSG("Rejecting file from ~s for ~s: Unexpected size (~B)",
|
||||
[?ADDR_TO_STR(IP), Host, byte_size(Data)]),
|
||||
http_response(Host, 413);
|
||||
{error, Error} ->
|
||||
?INFO_MSG("Rejecting file from ~s for ~s: ~p",
|
||||
[?ADDR_TO_STR(IP), Host, Error]),
|
||||
{error, invalid_slot} ->
|
||||
?INFO_MSG("Rejecting file from ~s for ~s: Invalid slot",
|
||||
[?ADDR_TO_STR(IP), Host]),
|
||||
http_response(Host, 403);
|
||||
Error ->
|
||||
?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
-export([start_link/3,
|
||||
start/2,
|
||||
stop/1,
|
||||
depends/2,
|
||||
mod_opt_type/1]).
|
||||
|
||||
%% gen_server callbacks.
|
||||
@@ -99,9 +100,9 @@ stop(ServerHost) ->
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
|
||||
mod_opt_type(access_soft_quota) ->
|
||||
fun(A) when is_atom(A) -> A end;
|
||||
fun acl:shaper_rules_validator/1;
|
||||
mod_opt_type(access_hard_quota) ->
|
||||
fun(A) when is_atom(A) -> A end;
|
||||
fun acl:shaper_rules_validator/1;
|
||||
mod_opt_type(max_days) ->
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
@@ -109,6 +110,11 @@ mod_opt_type(max_days) ->
|
||||
mod_opt_type(_) ->
|
||||
[access_soft_quota, access_hard_quota, max_days].
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_http_upload, hard}].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -118,10 +124,10 @@ mod_opt_type(_) ->
|
||||
init({ServerHost, Opts}) ->
|
||||
process_flag(trap_exit, true),
|
||||
AccessSoftQuota = gen_mod:get_opt(access_soft_quota, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:shaper_rules_validator/1,
|
||||
soft_upload_quota),
|
||||
AccessHardQuota = gen_mod:get_opt(access_hard_quota, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:shaper_rules_validator/1,
|
||||
hard_upload_quota),
|
||||
MaxDays = gen_mod:get_opt(max_days, Opts,
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
@@ -245,7 +251,7 @@ terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
|
||||
?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
|
||||
ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
|
||||
handle_slot_request, 50),
|
||||
lists:foreach(fun(Timer) -> timer:cancel(Timer) end, Timers).
|
||||
lists:foreach(fun timer:cancel/1, Timers).
|
||||
|
||||
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
|
||||
|
||||
@@ -293,7 +299,7 @@ enforce_quota(UserDir, SlotSize, _OldSize, MinSize, MaxSize) ->
|
||||
{[Path | AccFiles], AccSize + Size, NewSize}
|
||||
end, {[], 0, 0}, Files),
|
||||
if OldSize + SlotSize > MaxSize ->
|
||||
lists:foreach(fun(File) -> del_file_and_dir(File) end, DelFiles),
|
||||
lists:foreach(fun del_file_and_dir/1, DelFiles),
|
||||
file:del_dir(UserDir), % In case it's empty, now.
|
||||
NewSize + SlotSize;
|
||||
true ->
|
||||
@@ -308,7 +314,7 @@ delete_old_files(UserDir, CutOff) ->
|
||||
[] ->
|
||||
ok;
|
||||
OldFiles ->
|
||||
lists:foreach(fun(File) -> del_file_and_dir(File) end, OldFiles),
|
||||
lists:foreach(fun del_file_and_dir/1, OldFiles),
|
||||
file:del_dir(UserDir) % In case it's empty, now.
|
||||
end.
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
-export([update_bl_c2s/0]).
|
||||
|
||||
-export([is_ip_in_c2s_blacklist/3, mod_opt_type/1]).
|
||||
-export([is_ip_in_c2s_blacklist/3, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -65,6 +65,9 @@ preinit(Parent, State) ->
|
||||
error:_ -> Parent ! {ok, Pid, true}
|
||||
end.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%% TODO:
|
||||
stop(_Host) -> ok.
|
||||
|
||||
|
||||
+7
-4
@@ -38,7 +38,7 @@
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3,
|
||||
mod_opt_type/1]).
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -90,7 +90,7 @@ start(Host, Opts) ->
|
||||
start_supervisor(Host),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
temporary, 1000, worker, [?MODULE]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
@@ -99,6 +99,9 @@ stop(Host) ->
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
@@ -117,7 +120,7 @@ init([Host, Opts]) ->
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
all),
|
||||
catch ets:new(irc_connection,
|
||||
[named_table, public,
|
||||
@@ -1252,7 +1255,7 @@ import(LServer, DBType, Data) ->
|
||||
Mod:import(LServer, Data).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(default_encoding) ->
|
||||
fun iolist_to_binary/1;
|
||||
|
||||
+24
-31
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_irc_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_irc).
|
||||
|
||||
%% API
|
||||
@@ -15,6 +17,7 @@
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_irc.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -23,31 +26,26 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
get_data(LServer, Host, From) ->
|
||||
LJID = jid:tolower(jid:remove_resource(From)),
|
||||
SJID = ejabberd_sql:escape(jid:to_string(LJID)),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select data from irc_custom where jid='">>,
|
||||
SJID, <<"' and host='">>, SHost,
|
||||
<<"';">>]) of
|
||||
{selected, [<<"data">>], [[SData]]} ->
|
||||
mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData));
|
||||
{'EXIT', _} -> error;
|
||||
{selected, _, _} -> empty
|
||||
LServer,
|
||||
?SQL("select @(data)s from irc_custom"
|
||||
" where jid=%(SJID)s and host=%(Host)s")) of
|
||||
{selected, [{SData}]} ->
|
||||
mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData));
|
||||
{'EXIT', _} -> error;
|
||||
{selected, _} -> empty
|
||||
end.
|
||||
|
||||
set_data(LServer, Host, From, Data) ->
|
||||
LJID = jid:tolower(jid:remove_resource(From)),
|
||||
SJID = ejabberd_sql:escape(jid:to_string(LJID)),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
SData = ejabberd_sql:encode_term(Data),
|
||||
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
|
||||
SData = jlib:term_to_expr(Data),
|
||||
F = fun () ->
|
||||
sql_queries:update_t(<<"irc_custom">>,
|
||||
[<<"jid">>, <<"host">>, <<"data">>],
|
||||
[SJID, SHost, SData],
|
||||
[<<"jid='">>, SJID, <<"' and host='">>,
|
||||
SHost, <<"'">>]),
|
||||
?SQL_UPSERT_T(
|
||||
"irc_custom",
|
||||
["!jid=%(SJID)s",
|
||||
"!host=%(Host)s",
|
||||
"data=%(SData)s"]),
|
||||
ok
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
@@ -58,17 +56,12 @@ export(_Server) ->
|
||||
data = Data}) ->
|
||||
case str:suffix(Host, IRCHost) of
|
||||
true ->
|
||||
SJID = ejabberd_sql:escape(
|
||||
jid:to_string(
|
||||
jid:make(U, S, <<"">>))),
|
||||
SIRCHost = ejabberd_sql:escape(IRCHost),
|
||||
SData = ejabberd_sql:encode_term(Data),
|
||||
[[<<"delete from irc_custom where jid='">>, SJID,
|
||||
<<"' and host='">>, SIRCHost, <<"';">>],
|
||||
[<<"insert into irc_custom(jid, host, "
|
||||
"data) values ('">>,
|
||||
SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
|
||||
<<"');">>]];
|
||||
SJID = jid:to_string(jid:make(U, S, <<"">>)),
|
||||
SData = jlib:term_to_expr(Data),
|
||||
[?SQL("delete from irc_custom"
|
||||
" where jid=%(SJID)s and host=%(IRCHost)s;"),
|
||||
?SQL("insert into irc_custom(jid, host, data)"
|
||||
" values (%(SJID)s, %(IRCHost)s, %(SData)s);")];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
|
||||
+15
-1
@@ -37,7 +37,7 @@
|
||||
process_sm_iq/3, on_presence_update/4, import/1,
|
||||
import/3, store_last_info/4, get_last_info/2,
|
||||
remove_user/2, transform_options/1, mod_opt_type/1,
|
||||
opt_type/1]).
|
||||
opt_type/1, register_user/2, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -64,12 +64,16 @@ start(Host, Opts) ->
|
||||
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_LAST, ?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(register_user, Host, ?MODULE,
|
||||
register_user, 50),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
|
||||
on_presence_update, 50).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(register_user, Host, ?MODULE,
|
||||
register_user, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:delete(unset_presence_hook, Host,
|
||||
@@ -198,6 +202,13 @@ get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) ->
|
||||
children = []}]}
|
||||
end.
|
||||
|
||||
register_user(User, Server) ->
|
||||
on_presence_update(
|
||||
User,
|
||||
Server,
|
||||
<<"RegisterResource">>,
|
||||
<<"Registered but didn't login">>).
|
||||
|
||||
on_presence_update(User, Server, _Resource, Status) ->
|
||||
TimeStamp = p1_time_compat:system_time(seconds),
|
||||
store_last_info(User, Server, TimeStamp, Status).
|
||||
@@ -244,6 +255,9 @@ transform_options({node_start, {_, _, _} = Now}, Opts) ->
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) -> [db_type, iqdisc].
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
-module(mod_last_sql).
|
||||
-behaviour(mod_last).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
%% API
|
||||
-export([init/2, get_last/2, store_last_info/4, remove_user/2,
|
||||
import/1, import/2, export/1]).
|
||||
|
||||
-include("mod_last.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -48,15 +51,9 @@ export(_Server) ->
|
||||
fun(Host, #last_activity{us = {LUser, LServer},
|
||||
timestamp = TimeStamp, status = Status})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Seconds =
|
||||
ejabberd_sql:escape(jlib:integer_to_binary(TimeStamp)),
|
||||
State = ejabberd_sql:escape(Status),
|
||||
[[<<"delete from last where username='">>, Username, <<"';">>],
|
||||
[<<"insert into last(username, seconds, "
|
||||
"state) values ('">>,
|
||||
Username, <<"', '">>, Seconds, <<"', '">>, State,
|
||||
<<"');">>]];
|
||||
[?SQL("delete from last where username=%(LUser)s;"),
|
||||
?SQL("insert into last(username, seconds, state)"
|
||||
" values (%(LUser)s, %(TimeStamp)d, %(Status)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
+93
-14
@@ -25,19 +25,19 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_mam).
|
||||
|
||||
-protocol({xep, 313, '0.4'}).
|
||||
-protocol({xep, 313, '0.5.1'}).
|
||||
-protocol({xep, 334, '0.2'}).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% API
|
||||
-export([start/2, stop/1]).
|
||||
-export([start/2, stop/1, depends/2]).
|
||||
|
||||
-export([user_send_packet/4, user_receive_packet/5,
|
||||
-export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
|
||||
process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
|
||||
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
|
||||
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
|
||||
get_commands_spec/0, msg_to_el/4]).
|
||||
get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -89,9 +89,11 @@ start(Host, Opts) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
|
||||
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
|
||||
user_receive_packet, 500),
|
||||
user_receive_packet, 88),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 500),
|
||||
user_send_packet, 88),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet_strip_tag, 500),
|
||||
ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
|
||||
muc_filter_message, 50),
|
||||
ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
|
||||
@@ -100,6 +102,12 @@ start(Host, Opts) ->
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:add(remove_room, Host, ?MODULE,
|
||||
remove_room, 50),
|
||||
ejabberd_hooks:add(get_room_config, Host, ?MODULE,
|
||||
get_room_config, 50),
|
||||
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
|
||||
set_room_option, 50),
|
||||
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
case gen_mod:get_opt(assume_mam_usage, Opts,
|
||||
@@ -128,9 +136,11 @@ init_cache(Opts) ->
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 500),
|
||||
user_send_packet, 88),
|
||||
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
|
||||
user_receive_packet, 500),
|
||||
user_receive_packet, 88),
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet_strip_tag, 500),
|
||||
ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE,
|
||||
muc_filter_message, 50),
|
||||
ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
|
||||
@@ -145,6 +155,12 @@ stop(Host) ->
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:delete(remove_room, Host, ?MODULE,
|
||||
remove_room, 50),
|
||||
ejabberd_hooks:delete(get_room_config, Host, ?MODULE,
|
||||
get_room_config, 50),
|
||||
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
|
||||
set_room_option, 50),
|
||||
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage,
|
||||
@@ -161,6 +177,9 @@ stop(Host) ->
|
||||
ejabberd_commands:unregister_commands(get_commands_spec()),
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
@@ -173,6 +192,41 @@ remove_room(LServer, Name, Host) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:remove_room(LServer, LName, LHost).
|
||||
|
||||
get_room_config(X, RoomState, _From, Lang) ->
|
||||
Config = RoomState#state.config,
|
||||
Label = <<"Enable message archiving">>,
|
||||
Var = <<"muc#roomconfig_mam">>,
|
||||
Val = case Config#config.mam of
|
||||
true -> <<"1">>;
|
||||
_ -> <<"0">>
|
||||
end,
|
||||
XField = #xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"boolean">>},
|
||||
{<<"label">>, translate:translate(Lang, Label)},
|
||||
{<<"var">>, Var}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [{xmlcdata, Val}]}]},
|
||||
X ++ [XField].
|
||||
|
||||
set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
|
||||
try
|
||||
Val = case Vals of
|
||||
[<<"0">>|_] -> false;
|
||||
[<<"false">>|_] -> false;
|
||||
[<<"1">>|_] -> true;
|
||||
[<<"true">>|_] -> true
|
||||
end,
|
||||
{#config.mam, Val}
|
||||
catch _:{case_clause, _} ->
|
||||
Txt = <<"Value of '~s' should be boolean">>,
|
||||
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
|
||||
end;
|
||||
set_room_option(Acc, _Opt, _Vals, _Lang) ->
|
||||
Acc.
|
||||
|
||||
user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
|
||||
LUser = JID#jid.luser,
|
||||
LServer = JID#jid.lserver,
|
||||
@@ -205,13 +259,30 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
|
||||
case should_archive(Pkt, LServer) of
|
||||
true ->
|
||||
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
||||
store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
|
||||
LUser, LServer, Peer, send),
|
||||
NewPkt;
|
||||
case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
|
||||
LUser, LServer, Peer, send) of
|
||||
{ok, ID} ->
|
||||
Archived = #xmlel{name = <<"archived">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_MAM_TMP},
|
||||
{<<"id">>, ID}]},
|
||||
StanzaID = #xmlel{name = <<"stanza-id">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_SID_0},
|
||||
{<<"id">>, ID}]},
|
||||
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
|
||||
NewPkt#xmlel{children = NewEls};
|
||||
_ ->
|
||||
NewPkt
|
||||
end;
|
||||
false ->
|
||||
Pkt
|
||||
end.
|
||||
|
||||
user_send_packet_strip_tag(Pkt, _C2SState, JID, _Peer) ->
|
||||
LServer = JID#jid.lserver,
|
||||
strip_my_archived_tag(Pkt, LServer).
|
||||
|
||||
muc_filter_message(Pkt, #state{config = Config} = MUCState,
|
||||
RoomJID, From, FromNick) ->
|
||||
if Config#config.mam ->
|
||||
@@ -316,7 +387,12 @@ message_is_archived(false, C2SState, Peer,
|
||||
(never) -> never
|
||||
end, never) of
|
||||
if_enabled ->
|
||||
get_prefs(LUser, LServer);
|
||||
case get_prefs(LUser, LServer) of
|
||||
#archive_prefs{} = P ->
|
||||
{ok, P};
|
||||
error ->
|
||||
error
|
||||
end;
|
||||
on_request ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
cache_tab:lookup(archive_prefs, {LUser, LServer},
|
||||
@@ -812,9 +888,10 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
|
||||
_ ->
|
||||
{Msgs, true, L}
|
||||
end;
|
||||
select(LServer, From, From, Start, End, With, RSM, MsgType) ->
|
||||
select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:select(LServer, From, From, Start, End, With, RSM, MsgType).
|
||||
Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM,
|
||||
MsgType).
|
||||
|
||||
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
|
||||
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
|
||||
@@ -955,6 +1032,8 @@ filter_by_max(_Msgs, _Junk) ->
|
||||
|
||||
limit_max(RSM, ?NS_MAM_TMP) ->
|
||||
RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
|
||||
limit_max(none, _NS) ->
|
||||
#rsm_in{max = ?DEF_PAGE_SIZE};
|
||||
limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) ->
|
||||
RSM#rsm_in{max = ?DEF_PAGE_SIZE};
|
||||
limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
|
||||
|
||||
+72
-32
@@ -16,6 +16,7 @@
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_mam.hrl").
|
||||
|
||||
-define(BIN_GREATER_THAN(A, B),
|
||||
@@ -25,6 +26,8 @@
|
||||
((A < B andalso byte_size(A) == byte_size(B))
|
||||
orelse byte_size(A) < byte_size(B))).
|
||||
|
||||
-define(TABLE_SIZE_LIMIT, 2000000000). % A bit less than 2 GiB.
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
@@ -49,37 +52,71 @@ remove_room(_LServer, LName, LHost) ->
|
||||
remove_user(LName, LHost).
|
||||
|
||||
delete_old_messages(global, TimeStamp, Type) ->
|
||||
MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
|
||||
type = MsgType} = Msg)
|
||||
when MsgTS < TimeStamp,
|
||||
MsgType == Type orelse Type == all ->
|
||||
Msg
|
||||
end),
|
||||
OldMsgs = mnesia:dirty_select(archive_msg, MS),
|
||||
lists:foreach(fun(Rec) ->
|
||||
ok = mnesia:dirty_delete_object(Rec)
|
||||
end, OldMsgs).
|
||||
mnesia:change_table_copy_type(archive_msg, node(), disc_copies),
|
||||
Result = delete_old_user_messages(mnesia:dirty_first(archive_msg), TimeStamp, Type),
|
||||
mnesia:change_table_copy_type(archive_msg, node(), disc_only_copies),
|
||||
Result.
|
||||
|
||||
delete_old_user_messages('$end_of_table', _TimeStamp, _Type) ->
|
||||
ok;
|
||||
delete_old_user_messages(User, TimeStamp, Type) ->
|
||||
F = fun() ->
|
||||
Msgs = mnesia:read(archive_msg, User),
|
||||
Keep = lists:filter(
|
||||
fun(#archive_msg{timestamp = MsgTS,
|
||||
type = MsgType}) ->
|
||||
MsgTS >= TimeStamp orelse (Type /= all andalso
|
||||
Type /= MsgType)
|
||||
end, Msgs),
|
||||
if length(Keep) < length(Msgs) ->
|
||||
mnesia:delete({archive_msg, User}),
|
||||
lists:foreach(fun(Msg) -> mnesia:write(Msg) end, Keep);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
delete_old_user_messages(mnesia:dirty_next(archive_msg, User),
|
||||
TimeStamp, Type);
|
||||
{aborted, Err} ->
|
||||
?ERROR_MSG("Cannot delete old MAM messages: ~s", [Err]),
|
||||
Err
|
||||
end.
|
||||
|
||||
extended_fields() ->
|
||||
[].
|
||||
|
||||
store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) ->
|
||||
LPeer = {PUser, PServer, _} = jid:tolower(Peer),
|
||||
TS = p1_time_compat:timestamp(),
|
||||
ID = jlib:integer_to_binary(now_to_usec(TS)),
|
||||
case mnesia:dirty_write(
|
||||
#archive_msg{us = {LUser, LServer},
|
||||
id = ID,
|
||||
timestamp = TS,
|
||||
peer = LPeer,
|
||||
bare_peer = {PUser, PServer, <<>>},
|
||||
type = Type,
|
||||
nick = Nick,
|
||||
packet = Pkt}) of
|
||||
ok ->
|
||||
{ok, ID};
|
||||
Err ->
|
||||
Err
|
||||
case {mnesia:table_info(archive_msg, disc_only_copies),
|
||||
mnesia:table_info(archive_msg, memory)} of
|
||||
{[_|_], TableSize} when TableSize > ?TABLE_SIZE_LIMIT ->
|
||||
?ERROR_MSG("MAM archives too large, won't store message for ~s@~s",
|
||||
[LUser, LServer]),
|
||||
{error, overflow};
|
||||
_ ->
|
||||
LPeer = {PUser, PServer, _} = jid:tolower(Peer),
|
||||
TS = p1_time_compat:timestamp(),
|
||||
ID = jlib:integer_to_binary(now_to_usec(TS)),
|
||||
F = fun() ->
|
||||
mnesia:write(
|
||||
#archive_msg{us = {LUser, LServer},
|
||||
id = ID,
|
||||
timestamp = TS,
|
||||
peer = LPeer,
|
||||
bare_peer = {PUser, PServer, <<>>},
|
||||
type = Type,
|
||||
nick = Nick,
|
||||
packet = Pkt})
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
{ok, ID};
|
||||
{aborted, Err} ->
|
||||
?ERROR_MSG("Cannot add message to MAM archive of ~s@~s: ~s",
|
||||
[LUser, LServer, Err]),
|
||||
Err
|
||||
end
|
||||
end.
|
||||
|
||||
write_prefs(_LUser, _LServer, Prefs, _ServerHost) ->
|
||||
@@ -101,12 +138,15 @@ select(_LServer, JidRequestor,
|
||||
SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
|
||||
{FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM),
|
||||
Count = length(Msgs),
|
||||
{lists:map(
|
||||
fun(Msg) ->
|
||||
{Msg#archive_msg.id,
|
||||
jlib:binary_to_integer(Msg#archive_msg.id),
|
||||
mod_mam:msg_to_el(Msg, MsgType, JidRequestor, JidArchive)}
|
||||
end, FilteredMsgs), IsComplete, Count}.
|
||||
Result = {lists:map(
|
||||
fun(Msg) ->
|
||||
{Msg#archive_msg.id,
|
||||
jlib:binary_to_integer(Msg#archive_msg.id),
|
||||
mod_mam:msg_to_el(Msg, MsgType, JidRequestor,
|
||||
JidArchive)}
|
||||
end, FilteredMsgs), IsComplete, Count},
|
||||
erlang:garbage_collect(),
|
||||
Result.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
|
||||
+40
-52
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_mam_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_mam).
|
||||
|
||||
%% API
|
||||
@@ -18,6 +20,7 @@
|
||||
-include("jlib.hrl").
|
||||
-include("mod_mam.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -26,13 +29,12 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
SUser = ejabberd_sql:escape(LUser),
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"delete from archive where username='">>, SUser, <<"';">>]),
|
||||
?SQL("delete from archive where username=%(LUser)s")),
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"delete from archive_prefs where username='">>, SUser, <<"';">>]).
|
||||
?SQL("delete from archive_prefs where username=%(LUser)s")).
|
||||
|
||||
remove_room(LServer, LName, LHost) ->
|
||||
LUser = jid:to_string({LName, LHost, <<>>}),
|
||||
@@ -55,7 +57,7 @@ extended_fields() ->
|
||||
|
||||
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
|
||||
TSinteger = p1_time_compat:system_time(micro_seconds),
|
||||
ID = TS = jlib:integer_to_binary(TSinteger),
|
||||
ID = jlib:integer_to_binary(TSinteger),
|
||||
SUser = case Type of
|
||||
chat -> LUser;
|
||||
groupchat -> jid:to_string({LUser, LHost, <<>>})
|
||||
@@ -67,18 +69,19 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
|
||||
jid:tolower(Peer)),
|
||||
XML = fxml:element_to_binary(Pkt),
|
||||
Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
|
||||
SType = jlib:atom_to_binary(Type),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"insert into archive (username, timestamp, "
|
||||
"peer, bare_peer, xml, txt, kind, nick) values (">>,
|
||||
<<"'">>, ejabberd_sql:escape(SUser), <<"', ">>,
|
||||
<<"'">>, TS, <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(LPeer), <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(BarePeer), <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(XML), <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(Body), <<"', ">>,
|
||||
<<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(Nick), <<"');">>]) of
|
||||
LServer,
|
||||
?SQL("insert into archive (username, timestamp,"
|
||||
" peer, bare_peer, xml, txt, kind, nick) values ("
|
||||
"%(SUser)s, "
|
||||
"%(TSinteger)d, "
|
||||
"%(LPeer)s, "
|
||||
"%(BarePeer)s, "
|
||||
"%(XML)s, "
|
||||
"%(Body)s, "
|
||||
"%(SType)s, "
|
||||
"%(Nick)s)")) of
|
||||
{updated, _} ->
|
||||
{ok, ID};
|
||||
Err ->
|
||||
@@ -89,14 +92,16 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
|
||||
never = Never,
|
||||
always = Always},
|
||||
ServerHost) ->
|
||||
SUser = ejabberd_sql:escape(LUser),
|
||||
SDefault = erlang:atom_to_binary(Default, utf8),
|
||||
SAlways = ejabberd_sql:encode_term(Always),
|
||||
SNever = ejabberd_sql:encode_term(Never),
|
||||
case update(ServerHost, <<"archive_prefs">>,
|
||||
[<<"username">>, <<"def">>, <<"always">>, <<"never">>],
|
||||
[SUser, SDefault, SAlways, SNever],
|
||||
[<<"username='">>, SUser, <<"'">>]) of
|
||||
SAlways = jlib:term_to_expr(Always),
|
||||
SNever = jlib:term_to_expr(Never),
|
||||
case ?SQL_UPSERT(
|
||||
ServerHost,
|
||||
"archive_prefs",
|
||||
["!username=%(LUser)s",
|
||||
"def=%(SDefault)s",
|
||||
"always=%(SAlways)s",
|
||||
"never=%(SNever)s"]) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
@@ -106,10 +111,9 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
|
||||
get_prefs(LUser, LServer) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select def, always, never from archive_prefs ">>,
|
||||
<<"where username='">>,
|
||||
ejabberd_sql:escape(LUser), <<"';">>]) of
|
||||
{selected, _, [[SDefault, SAlways, SNever]]} ->
|
||||
?SQL("select @(def)s, @(always)s, @(never)s from archive_prefs"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, [{SDefault, SAlways, SNever}]} ->
|
||||
Default = erlang:binary_to_existing_atom(SDefault, utf8),
|
||||
Always = ejabberd_sql:decode_term(SAlways),
|
||||
Never = ejabberd_sql:decode_term(SNever),
|
||||
@@ -208,6 +212,12 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
ODBCType = ejabberd_config:get_option(
|
||||
{sql_type, LServer},
|
||||
ejabberd_sql:opt_type(sql_type)),
|
||||
Escape =
|
||||
case ODBCType of
|
||||
mssql -> fun ejabberd_sql:standard_escape/1;
|
||||
sqlite -> fun ejabberd_sql:standard_escape/1;
|
||||
_ -> fun ejabberd_sql:escape/1
|
||||
end,
|
||||
LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql ->
|
||||
[<<" limit ">>, jlib:integer_to_binary(Max+1)];
|
||||
true ->
|
||||
@@ -223,14 +233,14 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
[];
|
||||
{text, Txt} ->
|
||||
[<<" and match (txt) against ('">>,
|
||||
ejabberd_sql:escape(Txt), <<"')">>];
|
||||
Escape(Txt), <<"')">>];
|
||||
{_, _, <<>>} ->
|
||||
[<<" and bare_peer='">>,
|
||||
ejabberd_sql:escape(jid:to_string(With)),
|
||||
Escape(jid:to_string(With)),
|
||||
<<"'">>];
|
||||
{_, _, _} ->
|
||||
[<<" and peer='">>,
|
||||
ejabberd_sql:escape(jid:to_string(With)),
|
||||
Escape(jid:to_string(With)),
|
||||
<<"'">>];
|
||||
none ->
|
||||
[]
|
||||
@@ -262,7 +272,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
SUser = ejabberd_sql:escape(User),
|
||||
SUser = Escape(User),
|
||||
|
||||
Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
|
||||
" FROM archive WHERE username='">>,
|
||||
@@ -285,25 +295,3 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
{QueryPage,
|
||||
[<<"SELECT COUNT(*) FROM archive WHERE username='">>,
|
||||
SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
|
||||
|
||||
update(LServer, Table, Fields, Vals, Where) ->
|
||||
UPairs = lists:zipwith(fun (A, B) ->
|
||||
<<A/binary, "='", B/binary, "'">>
|
||||
end,
|
||||
Fields, Vals),
|
||||
case ejabberd_sql:sql_query(LServer,
|
||||
[<<"update ">>, Table, <<" set ">>,
|
||||
join(UPairs, <<", ">>), <<" where ">>, Where,
|
||||
<<";">>])
|
||||
of
|
||||
{updated, 1} -> {updated, 1};
|
||||
_ ->
|
||||
ejabberd_sql:sql_query(LServer,
|
||||
[<<"insert into ">>, Table, <<"(">>,
|
||||
join(Fields, <<", ">>), <<") values ('">>,
|
||||
join(Vals, <<"', '">>), <<"');">>])
|
||||
end.
|
||||
|
||||
%% Almost a copy of string:join/2.
|
||||
join([], _Sep) -> [];
|
||||
join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
|
||||
|
||||
+8
-1
@@ -39,7 +39,8 @@
|
||||
s2s_send_packet, s2s_receive_packet,
|
||||
remove_user, register_user]).
|
||||
|
||||
-export([start/2, stop/1, send_metrics/4, opt_type/1]).
|
||||
-export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1,
|
||||
depends/2]).
|
||||
|
||||
-export([offline_message_hook/3,
|
||||
sm_register_connection_hook/3, sm_remove_connection_hook/3,
|
||||
@@ -59,6 +60,9 @@ stop(Host) ->
|
||||
[ejabberd_hooks:delete(Hook, Host, ?MODULE, Hook, 20)
|
||||
|| Hook <- ?HOOKS].
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%====================================================================
|
||||
%% Hooks handlers
|
||||
%%====================================================================
|
||||
@@ -126,3 +130,6 @@ send_metrics(Host, Probe, Peer, Port) ->
|
||||
|
||||
opt_type(_) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(_) ->
|
||||
[].
|
||||
|
||||
+5
-2
@@ -14,7 +14,7 @@
|
||||
%% API
|
||||
-export([start_link/2, start/2, stop/1, process_iq/3,
|
||||
disco_items/5, disco_identity/5, disco_info/5,
|
||||
disco_features/5, mod_opt_type/1]).
|
||||
disco_features/5, mod_opt_type/1, depends/2]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
@@ -44,7 +44,7 @@ start_link(Host, Opts) ->
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
temporary, 5000, worker, [?MODULE]},
|
||||
transient, 5000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
@@ -343,6 +343,9 @@ is_not_subscribed({error, ErrEl}) ->
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_pubsub, hard}].
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(_) -> [host, iqdisc].
|
||||
|
||||
+42
-19
@@ -53,7 +53,7 @@
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3,
|
||||
mod_opt_type/1]).
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -95,7 +95,7 @@ start_link(Host, Opts) ->
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
temporary, 1000, worker, [?MODULE]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
@@ -105,6 +105,9 @@ stop(Host) ->
|
||||
supervisor:delete_child(ejabberd_sup, Proc),
|
||||
{wait, Rooms}.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_mam, soft}].
|
||||
|
||||
shutdown_rooms(Host) ->
|
||||
MyHost = gen_mod:get_module_opt_host(Host, mod_muc,
|
||||
<<"conference.@HOST@">>),
|
||||
@@ -147,18 +150,10 @@ restore_room(ServerHost, Host, Name) ->
|
||||
|
||||
forget_room(ServerHost, Host, Name) ->
|
||||
LServer = jid:nameprep(ServerHost),
|
||||
remove_room_mam(LServer, Host, Name),
|
||||
ejabberd_hooks:run(remove_room, LServer, [LServer, Name, Host]),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:forget_room(LServer, Host, Name).
|
||||
|
||||
remove_room_mam(LServer, Host, Name) ->
|
||||
case gen_mod:is_loaded(LServer, mod_mam) of
|
||||
true ->
|
||||
mod_mam:remove_room(LServer, Name, Host);
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
process_iq_disco_items(Host, From, To,
|
||||
#iq{lang = Lang} = IQ) ->
|
||||
Rsm = jlib:rsm_decode(IQ),
|
||||
@@ -193,14 +188,14 @@ init([Host, Opts]) ->
|
||||
clean_table_from_bad_node(node(), MyHost),
|
||||
mnesia:subscribe(system),
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun(A) when is_atom(A) -> A end, all),
|
||||
fun acl:access_rules_validator/1, all),
|
||||
AccessCreate = gen_mod:get_opt(access_create, Opts,
|
||||
fun(A) when is_atom(A) -> A end, all),
|
||||
fun acl:access_rules_validator/1, all),
|
||||
AccessAdmin = gen_mod:get_opt(access_admin, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
none),
|
||||
AccessPersistent = gen_mod:get_opt(access_persistent, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
all),
|
||||
HistorySize = gen_mod:get_opt(history_size, Opts,
|
||||
fun(I) when is_integer(I), I>=0 -> I end,
|
||||
@@ -230,6 +225,7 @@ init([Host, Opts]) ->
|
||||
public -> Bool;
|
||||
public_list -> Bool;
|
||||
mam -> Bool;
|
||||
allow_subscription -> Bool;
|
||||
password -> fun iolist_to_binary/1;
|
||||
title -> fun iolist_to_binary/1;
|
||||
allow_private_messages_from_visitors ->
|
||||
@@ -426,6 +422,18 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
iq_get_vcard(Lang)}]},
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = get, xmlns = ?NS_MUCSUB,
|
||||
sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ ->
|
||||
RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
|
||||
Subs = lists:map(
|
||||
fun(J) ->
|
||||
#xmlel{name = <<"subscription">>,
|
||||
attrs = [{<<"jid">>,
|
||||
jid:to_string(J)}]}
|
||||
end, RoomJIDs),
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [SubEl#xmlel{children = Subs}]},
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
|
||||
#iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
@@ -597,6 +605,8 @@ iq_disco_info(ServerHost, Lang) ->
|
||||
attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_RSM}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_MUCSUB}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++
|
||||
case gen_mod:is_loaded(ServerHost, mod_mam) of
|
||||
@@ -693,6 +703,19 @@ get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
|
||||
index = NewIndex}}
|
||||
end.
|
||||
|
||||
get_subscribed_rooms(ServerHost, Host, From) ->
|
||||
Rooms = get_rooms(ServerHost, Host),
|
||||
lists:flatmap(
|
||||
fun(#muc_room{name_host = {Name, _}, opts = Opts}) ->
|
||||
Subscribers = proplists:get_value(subscribers, Opts, []),
|
||||
case lists:keymember(From, 1, Subscribers) of
|
||||
true -> [jid:make(Name, Host, <<>>)];
|
||||
false -> []
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, Rooms).
|
||||
|
||||
%% @doc Return the position of desired room in the list of rooms.
|
||||
%% The room must exist in the list. The count starts in 0.
|
||||
%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
|
||||
@@ -925,13 +948,13 @@ import(LServer, DBType, Data) ->
|
||||
Mod:import(LServer, Data).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_admin) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_create) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_persistent) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(default_room_options) ->
|
||||
fun (L) when is_list(L) -> L end;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, muc_online_rooms/1,
|
||||
-export([start/2, stop/1, depends/2, muc_online_rooms/1,
|
||||
muc_unregister_nick/1, create_room/3, destroy_room/2,
|
||||
create_rooms_file/1, destroy_rooms_file/1,
|
||||
rooms_unused_list/2, rooms_unused_destroy/2,
|
||||
@@ -49,6 +49,9 @@ stop(Host) ->
|
||||
ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
|
||||
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_muc, hard}].
|
||||
|
||||
%%%
|
||||
%%% Register commands
|
||||
%%%
|
||||
|
||||
+6
-3
@@ -41,7 +41,7 @@
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3,
|
||||
mod_opt_type/1, opt_type/1]).
|
||||
mod_opt_type/1, opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -81,7 +81,7 @@ start_link(Host, Opts) ->
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
temporary, 1000, worker, [?MODULE]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
@@ -109,6 +109,9 @@ transform_module_options(Opts) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_muc, hard}].
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
@@ -141,7 +144,7 @@ init([Host, Opts]) ->
|
||||
fun iolist_to_binary/1,
|
||||
false),
|
||||
AccessLog = gen_mod:get_opt(access_log, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
muc_admin),
|
||||
Timezone = gen_mod:get_opt(timezone, Opts,
|
||||
fun(local) -> local;
|
||||
|
||||
+822
-413
File diff suppressed because it is too large
Load Diff
+58
-81
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_muc_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_muc).
|
||||
|
||||
%% API
|
||||
@@ -18,6 +20,7 @@
|
||||
-include("jlib.hrl").
|
||||
-include("mod_muc.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -26,61 +29,54 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
store_room(LServer, Host, Name, Opts) ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
SOpts = ejabberd_sql:encode_term(Opts),
|
||||
SOpts = jlib:term_to_expr(Opts),
|
||||
F = fun () ->
|
||||
sql_queries:update_t(<<"muc_room">>,
|
||||
[<<"name">>, <<"host">>, <<"opts">>],
|
||||
[SName, SHost, SOpts],
|
||||
[<<"name='">>, SName, <<"' and host='">>,
|
||||
SHost, <<"'">>])
|
||||
?SQL_UPSERT_T(
|
||||
"muc_room",
|
||||
["!name=%(Name)s",
|
||||
"!host=%(Host)s",
|
||||
"opts=%(SOpts)s"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
restore_room(LServer, Host, Name) ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
case catch ejabberd_sql:sql_query(LServer,
|
||||
[<<"select opts from muc_room where name='">>,
|
||||
SName, <<"' and host='">>, SHost,
|
||||
<<"';">>]) of
|
||||
{selected, [<<"opts">>], [[Opts]]} ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(opts)s from muc_room where name=%(Name)s"
|
||||
" and host=%(Host)s")) of
|
||||
{selected, [{Opts}]} ->
|
||||
mod_muc:opts_to_binary(ejabberd_sql:decode_term(Opts));
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
forget_room(LServer, Host, Name) ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
F = fun () ->
|
||||
ejabberd_sql:sql_query_t([<<"delete from muc_room where name='">>,
|
||||
SName, <<"' and host='">>, SHost,
|
||||
<<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from muc_room where name=%(Name)s"
|
||||
" and host=%(Host)s"))
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
can_use_nick(LServer, Host, JID, Nick) ->
|
||||
SJID = jid:to_string(jid:tolower(jid:remove_resource(JID))),
|
||||
SNick = ejabberd_sql:escape(Nick),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
case catch ejabberd_sql:sql_query(LServer,
|
||||
[<<"select jid from muc_registered ">>,
|
||||
<<"where nick='">>, SNick,
|
||||
<<"' and host='">>, SHost, <<"';">>]) of
|
||||
{selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(jid)s from muc_registered "
|
||||
"where nick=%(Nick)s"
|
||||
" and host=%(Host)s")) of
|
||||
{selected, [{SJID1}]} -> SJID == SJID1;
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
get_rooms(LServer, Host) ->
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
case catch ejabberd_sql:sql_query(LServer,
|
||||
[<<"select name, opts from muc_room ">>,
|
||||
<<"where host='">>, SHost, <<"';">>]) of
|
||||
{selected, [<<"name">>, <<"opts">>], RoomOpts} ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(name)s, @(opts)s from muc_room"
|
||||
" where host=%(Host)s")) of
|
||||
{selected, RoomOpts} ->
|
||||
lists:map(
|
||||
fun([Room, Opts]) ->
|
||||
fun({Room, Opts}) ->
|
||||
#muc_room{name_host = {Room, Host},
|
||||
opts = mod_muc:opts_to_binary(
|
||||
ejabberd_sql:decode_term(Opts))}
|
||||
@@ -91,49 +87,38 @@ get_rooms(LServer, Host) ->
|
||||
end.
|
||||
|
||||
get_nick(LServer, Host, From) ->
|
||||
SJID = ejabberd_sql:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
case catch ejabberd_sql:sql_query(LServer,
|
||||
[<<"select nick from muc_registered where "
|
||||
"jid='">>,
|
||||
SJID, <<"' and host='">>, SHost,
|
||||
<<"';">>]) of
|
||||
{selected, [<<"nick">>], [[Nick]]} -> Nick;
|
||||
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(nick)s from muc_registered where"
|
||||
" jid=%(SJID)s and host=%(Host)s")) of
|
||||
{selected, [{Nick}]} -> Nick;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
set_nick(LServer, Host, From, Nick) ->
|
||||
JID = jid:to_string(jid:tolower(jid:remove_resource(From))),
|
||||
SJID = ejabberd_sql:escape(JID),
|
||||
SNick = ejabberd_sql:escape(Nick),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
F = fun () ->
|
||||
case Nick of
|
||||
<<"">> ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
[<<"delete from muc_registered where ">>,
|
||||
<<"jid='">>, SJID,
|
||||
<<"' and host='">>, Host,
|
||||
<<"';">>]),
|
||||
?SQL("delete from muc_registered where"
|
||||
" jid=%(JID)s and host=%(Host)s")),
|
||||
ok;
|
||||
_ ->
|
||||
Allow = case ejabberd_sql:sql_query_t(
|
||||
[<<"select jid from muc_registered ">>,
|
||||
<<"where nick='">>,
|
||||
SNick,
|
||||
<<"' and host='">>,
|
||||
SHost, <<"';">>]) of
|
||||
{selected, [<<"jid">>], [[J]]} -> J == JID;
|
||||
?SQL("select @(jid)s from muc_registered"
|
||||
" where nick=%(Nick)s"
|
||||
" and host=%(Host)s")) of
|
||||
{selected, [{J}]} -> J == JID;
|
||||
_ -> true
|
||||
end,
|
||||
if Allow ->
|
||||
sql_queries:update_t(<<"muc_registered">>,
|
||||
[<<"jid">>, <<"host">>,
|
||||
<<"nick">>],
|
||||
[SJID, SHost, SNick],
|
||||
[<<"jid='">>, SJID,
|
||||
<<"' and host='">>, SHost,
|
||||
<<"'">>]),
|
||||
?SQL_UPSERT_T(
|
||||
"muc_registered",
|
||||
["!jid=%(JID)s",
|
||||
"!host=%(Host)s",
|
||||
"nick=%(Nick)s"]),
|
||||
ok;
|
||||
true ->
|
||||
false
|
||||
@@ -147,15 +132,12 @@ export(_Server) ->
|
||||
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
|
||||
case str:suffix(Host, RoomHost) of
|
||||
true ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
SRoomHost = ejabberd_sql:escape(RoomHost),
|
||||
SOpts = ejabberd_sql:encode_term(Opts),
|
||||
[[<<"delete from muc_room where name='">>, SName,
|
||||
<<"' and host='">>, SRoomHost, <<"';">>],
|
||||
[<<"insert into muc_room(name, host, opts) ",
|
||||
"values (">>,
|
||||
<<"'">>, SName, <<"', '">>, SRoomHost,
|
||||
<<"', '">>, SOpts, <<"');">>]];
|
||||
SOpts = jlib:term_to_expr(Opts),
|
||||
[?SQL("delete from muc_room where name=%(Name)s"
|
||||
" and host=%(RoomHost)s;"),
|
||||
?SQL("insert into muc_room(name, host, opts) "
|
||||
"values ("
|
||||
"%(Name)s, %(RoomHost)s, %(SOpts)s);")];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
@@ -165,17 +147,12 @@ export(_Server) ->
|
||||
nick = Nick}) ->
|
||||
case str:suffix(Host, RoomHost) of
|
||||
true ->
|
||||
SJID = ejabberd_sql:escape(
|
||||
jid:to_string(
|
||||
jid:make(U, S, <<"">>))),
|
||||
SNick = ejabberd_sql:escape(Nick),
|
||||
SRoomHost = ejabberd_sql:escape(RoomHost),
|
||||
[[<<"delete from muc_registered where jid='">>,
|
||||
SJID, <<"' and host='">>, SRoomHost, <<"';">>],
|
||||
[<<"insert into muc_registered(jid, host, "
|
||||
"nick) values ('">>,
|
||||
SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
|
||||
<<"');">>]];
|
||||
SJID = jid:to_string(jid:make(U, S, <<"">>)),
|
||||
[?SQL("delete from muc_registered where"
|
||||
" jid=%(SJID)s and host=%(RoomHost)s;"),
|
||||
?SQL("insert into muc_registered(jid, host, "
|
||||
"nick) values ("
|
||||
"%(SJID)s, %(RoomHost)s, %(Nick)s);")];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
-export([init/1, handle_info/2, handle_call/3,
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
|
||||
-export([purge_loop/1, mod_opt_type/1]).
|
||||
-export([purge_loop/1, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -140,7 +140,7 @@ init([LServerS, Opts]) ->
|
||||
LServiceS = gen_mod:get_opt_host(LServerS, Opts,
|
||||
<<"multicast.@HOST@">>),
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun (A) when is_atom(A) -> A end, all),
|
||||
fun acl:access_rules_validator/1, all),
|
||||
SLimits =
|
||||
build_service_limit_record(gen_mod:get_opt(limits, Opts,
|
||||
fun (A) when is_list(A) ->
|
||||
@@ -1219,8 +1219,11 @@ stj(String) -> jid:from_string(String).
|
||||
|
||||
jts(String) -> jid:to_string(String).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(limits) ->
|
||||
fun (A) when is_list(A) -> A end;
|
||||
|
||||
+5
-3
@@ -66,7 +66,7 @@
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3,
|
||||
mod_opt_type/1]).
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-deprecated({get_queue_length,2}).
|
||||
|
||||
@@ -125,6 +125,8 @@ stop(Host) ->
|
||||
supervisor:delete_child(ejabberd_sup, Proc),
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
@@ -162,7 +164,7 @@ init([Host, Opts]) ->
|
||||
?MODULE, handle_offline_query, IQDisc),
|
||||
AccessMaxOfflineMsgs =
|
||||
gen_mod:get_opt(access_max_user_messages, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:shaper_rules_validator/1,
|
||||
max_user_offline_messages),
|
||||
{ok,
|
||||
#state{host = Host,
|
||||
@@ -866,7 +868,7 @@ import(LServer, DBType, Data) ->
|
||||
Mod:import(LServer, Data).
|
||||
|
||||
mod_opt_type(access_max_user_messages) ->
|
||||
fun (A) -> A end;
|
||||
fun acl:shaper_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(store_empty_body) ->
|
||||
fun (V) when is_boolean(V) -> V;
|
||||
|
||||
+20
-28
@@ -38,8 +38,7 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
|
||||
true ->
|
||||
Query = lists:map(
|
||||
fun(M) ->
|
||||
Username =
|
||||
ejabberd_sql:escape((M#offline_msg.to)#jid.luser),
|
||||
LUser = (M#offline_msg.to)#jid.luser,
|
||||
From = M#offline_msg.from,
|
||||
To = M#offline_msg.to,
|
||||
Packet =
|
||||
@@ -49,9 +48,8 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
|
||||
jlib:add_delay_info(Packet, Host,
|
||||
M#offline_msg.timestamp,
|
||||
<<"Offline Storage">>),
|
||||
XML =
|
||||
ejabberd_sql:escape(fxml:element_to_binary(NewPacket)),
|
||||
sql_queries:add_spool_sql(Username, XML)
|
||||
XML = fxml:element_to_binary(NewPacket),
|
||||
sql_queries:add_spool_sql(LUser, XML)
|
||||
end,
|
||||
Msgs),
|
||||
sql_queries:add_spool(Host, Query)
|
||||
@@ -82,8 +80,8 @@ remove_old_messages(Days, LServer) ->
|
||||
LServer,
|
||||
[<<"DELETE FROM spool"
|
||||
" WHERE created_at < "
|
||||
"DATE_SUB(CURDATE(), INTERVAL ">>,
|
||||
integer_to_list(Days), <<" DAY);">>]) of
|
||||
"NOW() - INTERVAL '">>,
|
||||
integer_to_list(Days), <<"';">>]) of
|
||||
{updated, N} ->
|
||||
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
|
||||
_Error ->
|
||||
@@ -95,19 +93,18 @@ remove_user(LUser, LServer) ->
|
||||
sql_queries:del_spool_msg(LServer, LUser).
|
||||
|
||||
read_message_headers(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer, [<<"select xml, seq from spool where username ='">>,
|
||||
Username, <<"' order by seq;">>]) of
|
||||
{selected, [<<"xml">>, <<"seq">>], Rows} ->
|
||||
LServer,
|
||||
?SQL("select @(xml)s, @(seq)d from spool"
|
||||
" where username=%(LUser)s order by seq")) of
|
||||
{selected, Rows} ->
|
||||
lists:flatmap(
|
||||
fun([XML, Seq]) ->
|
||||
fun({XML, Seq}) ->
|
||||
case xml_to_offline_msg(XML) of
|
||||
{ok, #offline_msg{from = From,
|
||||
to = To,
|
||||
packet = El}} ->
|
||||
Seq0 = binary_to_integer(Seq),
|
||||
[{Seq0, From, To, El}];
|
||||
[{Seq, From, To, El}];
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
@@ -117,13 +114,11 @@ read_message_headers(LUser, LServer) ->
|
||||
end.
|
||||
|
||||
read_message(LUser, LServer, Seq) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select xml from spool where username='">>, Username,
|
||||
<<"' and seq='">>, SSeq, <<"';">>]) of
|
||||
{selected, [<<"xml">>], [[RawXML]|_]} ->
|
||||
?SQL("select @(xml)s from spool where username=%(LUser)s"
|
||||
" and seq=%(Seq)d")) of
|
||||
{selected, [{RawXML}|_]} ->
|
||||
case xml_to_offline_msg(RawXML) of
|
||||
{ok, Msg} ->
|
||||
{ok, Msg};
|
||||
@@ -135,12 +130,10 @@ read_message(LUser, LServer, Seq) ->
|
||||
end.
|
||||
|
||||
remove_message(LUser, LServer, Seq) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"delete from spool where username='">>, Username,
|
||||
<<"' and seq='">>, SSeq, <<"';">>]),
|
||||
?SQL("delete from spool where username=%(LUser)s"
|
||||
" and seq=%(Seq)d")),
|
||||
ok.
|
||||
|
||||
read_all_messages(LUser, LServer) ->
|
||||
@@ -180,14 +173,13 @@ export(_Server) ->
|
||||
timestamp = TimeStamp, from = From, to = To,
|
||||
packet = Packet})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Packet1 = jlib:replace_from_to(From, To, Packet),
|
||||
Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
|
||||
<<"Offline Storage">>),
|
||||
XML = ejabberd_sql:escape(fxml:element_to_binary(Packet2)),
|
||||
[[<<"delete from spool where username='">>, Username, <<"';">>],
|
||||
[<<"insert into spool(username, xml) values ('">>,
|
||||
Username, <<"', '">>, XML, <<"');">>]];
|
||||
XML = fxml:element_to_binary(Packet2),
|
||||
[?SQL("delete from spool where username=%(LUser)s;"),
|
||||
?SQL("insert into spool(username, xml) values ("
|
||||
"%(LUser)s, %(XML)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
+4
-1
@@ -55,7 +55,7 @@
|
||||
handle_cast/2, handle_info/2, code_change/3]).
|
||||
|
||||
-export([iq_ping/3, user_online/3, user_offline/3,
|
||||
user_send/4, mod_opt_type/1]).
|
||||
user_send/4, mod_opt_type/1, depends/2]).
|
||||
|
||||
-record(state,
|
||||
{host = <<"">>,
|
||||
@@ -253,6 +253,9 @@ cancel_timer(TRef) ->
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(ping_interval) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
-behavior(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, check_packet/6,
|
||||
mod_opt_type/1]).
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -48,6 +48,9 @@ stop(Host) ->
|
||||
?MODULE, check_packet, 25),
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
check_packet(_, _User, Server, _PrivacyList,
|
||||
{From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
|
||||
case Name of
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user