Compare commits
257 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce0d1704c6 | |||
| 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 | |||
| 639c2fb640 | |||
| c585f74730 | |||
| d18fe138aa | |||
| d6f02a51e4 | |||
| 9c369b7a8c | |||
| d8bb5d9c01 | |||
| 26d3a978cc | |||
| 0f06ed8a9b | |||
| d1db9f92c4 | |||
| 52fde758b3 | |||
| b79aef3bbc | |||
| bae24464d3 | |||
| ef90a389c1 | |||
| 36164d9446 | |||
| 45321fa2e2 | |||
| 65ad70d7dc | |||
| a37cf33358 | |||
| 58478e52bf | |||
| cebdfb6523 | |||
| b79f09d0eb | |||
| beeb1c82d9 | |||
| 2660dcabba | |||
| f0e6def3ed | |||
| 8487b51ecd | |||
| f7d4aae64d | |||
| 97e3a33077 | |||
| 1aae8a9fda | |||
| fafeeb80c2 | |||
| 655c22021b | |||
| 382c6ce1fb | |||
| 0bdcafdb02 | |||
| e5e4f39c01 | |||
| 222572bd56 | |||
| 64fdbe7866 | |||
| fb0ecf3361 | |||
| 52571e8624 | |||
| 901d2e0aed | |||
| 860db2ddca | |||
| c8c4a41b66 | |||
| 79d64e0d71 | |||
| f8e3560ad2 | |||
| 398f1de5ae | |||
| 0b439a7d5b | |||
| ae69f09257 | |||
| ef70ce65ab | |||
| b5d1ce795f | |||
| cd094bc903 | |||
| 2d7e03f5e1 | |||
| b2abc1edb7 | |||
| 7fd4808cde | |||
| 5eef8a8bcf | |||
| cd2e2b1a88 | |||
| 5958a91428 | |||
| 15d184a909 | |||
| 8c8a6869be | |||
| 695592a38c | |||
| bd3b7cd2df | |||
| b67dc00db2 | |||
| 127342449e | |||
| b4739396ec | |||
| 1dbdd58b1b | |||
| c5dbdfc71a | |||
| 86dfbe6ece | |||
| b83ec483e9 | |||
| afd3accf75 | |||
| 0935f493ee | |||
| 373f9fb0eb | |||
| 60c0c8e968 | |||
| 18557fa3d6 | |||
| a938af4180 | |||
| d5c29360fb | |||
| 48a1d818d6 | |||
| eb36440c2e | |||
| 47039aed15 | |||
| d45ad3e3a5 | |||
| 5ad8c790c7 | |||
| 5efcf0a175 | |||
| 0916694e0e | |||
| 2900aa208f | |||
| 27b4217a9d | |||
| a9e50468b6 | |||
| abf768274a | |||
| f78b170c24 | |||
| 991529a657 | |||
| b2279d481d | |||
| 267fdb2e95 | |||
| d9f1061b8a | |||
| dd654fa794 | |||
| 1b9b5f8e6a | |||
| 67b9b82261 | |||
| 2eee2de6e2 | |||
| acc11195f8 | |||
| be7f65da05 | |||
| 232915184c | |||
| 9ac6e4edf7 | |||
| 490aa2c6a6 | |||
| ca9ac019eb | |||
| e24da5789e | |||
| 47266de6d7 | |||
| f243c30847 | |||
| 2a2a47b5c6 | |||
| b5f1479763 | |||
| a8f92ae767 | |||
| 97d345d287 | |||
| ef2e2e45b3 | |||
| 3290a5ff15 | |||
| 7988e2e350 | |||
| dc0ca51ef1 | |||
| 78c4a0d65f | |||
| 91233eafaa | |||
| da0751239c | |||
| 3dc55c6d47 | |||
| d35c5ebde5 | |||
| 3cfcdbb245 | |||
| 7c2998a55d | |||
| fced8dc3d9 | |||
| c00cfca8e7 | |||
| 3c480a5b0b | |||
| 809057678b | |||
| 36ac1cd6c7 | |||
| ead83b008c | |||
| 6f25122f8c | |||
| ae77b1300a |
+2
-1
@@ -2,7 +2,7 @@ language: erlang
|
||||
|
||||
otp_release:
|
||||
- 17.5
|
||||
- 18.2.1
|
||||
- 18.3
|
||||
|
||||
services:
|
||||
- riak
|
||||
@@ -59,6 +59,7 @@ after_script:
|
||||
- find logs -name suite.log -exec cat '{}' ';'
|
||||
|
||||
after_failure:
|
||||
- find logs -name exunit.log -exec cat '{}' ';'
|
||||
# Try checking Riak database logs
|
||||
- tail -n 100000 /var/log/riak/*.log
|
||||
- find logs -name ejabberd.log -exec cat '{}' ';'
|
||||
|
||||
+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
|
||||
|
||||
+67
-65
@@ -254,10 +254,10 @@ auth_method: internal
|
||||
## extauth_program: "/path/to/authentication/script"
|
||||
|
||||
##
|
||||
## Authentication using ODBC
|
||||
## Authentication using SQL
|
||||
## Remember to setup a database in the next section.
|
||||
##
|
||||
## auth_method: odbc
|
||||
## auth_method: sql
|
||||
|
||||
##
|
||||
## Authentication using PAM
|
||||
@@ -330,26 +330,26 @@ auth_method: internal
|
||||
##
|
||||
## MySQL server:
|
||||
##
|
||||
## odbc_type: mysql
|
||||
## odbc_server: "server"
|
||||
## odbc_database: "database"
|
||||
## odbc_username: "username"
|
||||
## odbc_password: "password"
|
||||
## sql_type: mysql
|
||||
## sql_server: "server"
|
||||
## sql_database: "database"
|
||||
## sql_username: "username"
|
||||
## sql_password: "password"
|
||||
##
|
||||
## If you want to specify the port:
|
||||
## odbc_port: 1234
|
||||
## sql_port: 1234
|
||||
|
||||
##
|
||||
## PostgreSQL server:
|
||||
##
|
||||
## odbc_type: pgsql
|
||||
## odbc_server: "server"
|
||||
## odbc_database: "database"
|
||||
## odbc_username: "username"
|
||||
## odbc_password: "password"
|
||||
## sql_type: pgsql
|
||||
## sql_server: "server"
|
||||
## sql_database: "database"
|
||||
## sql_username: "username"
|
||||
## sql_password: "password"
|
||||
##
|
||||
## If you want to specify the port:
|
||||
## odbc_port: 1234
|
||||
## sql_port: 1234
|
||||
##
|
||||
## If you use PostgreSQL, have a large database, and need a
|
||||
## faster but inexact replacement for "select count(*) from users"
|
||||
@@ -359,25 +359,25 @@ auth_method: internal
|
||||
##
|
||||
## SQLite:
|
||||
##
|
||||
## odbc_type: sqlite
|
||||
## odbc_database: "/path/to/database.db"
|
||||
## sql_type: sqlite
|
||||
## sql_database: "/path/to/database.db"
|
||||
|
||||
##
|
||||
## ODBC compatible or MSSQL server:
|
||||
##
|
||||
## odbc_type: odbc
|
||||
## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
|
||||
## sql_type: odbc
|
||||
## sql_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
|
||||
|
||||
##
|
||||
## Number of connections to open to the database for each virtual host
|
||||
##
|
||||
## odbc_pool_size: 10
|
||||
## sql_pool_size: 10
|
||||
|
||||
##
|
||||
## Interval to make a dummy SQL request to keep the connections to the
|
||||
## database alive. Specify in seconds: for example 28800 means 8 hours
|
||||
##
|
||||
## odbc_keepalive_interval: undefined
|
||||
## sql_keepalive_interval: undefined
|
||||
|
||||
###. ===============
|
||||
###' TRAFFIC SHAPERS
|
||||
@@ -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:
|
||||
|
||||
+44
-108
@@ -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}}
|
||||
|
||||
@@ -83,18 +83,9 @@ if [ "$EJABBERD_DOC_PATH" = "" ] ; then
|
||||
fi
|
||||
if [ "$ERLANG_NODE_ARG" != "" ] ; then
|
||||
ERLANG_NODE=$ERLANG_NODE_ARG
|
||||
NODE=${ERLANG_NODE%@*}
|
||||
fi
|
||||
if [ "{{release}}" != "true" ] ; then
|
||||
if [ "$EJABBERDDIR" = "" ] ; then
|
||||
EJABBERDDIR={{libdir}}/ejabberd
|
||||
fi
|
||||
if [ "$EJABBERD_PRIV_PATH" = "" ] ; then
|
||||
EJABBERD_PRIV_PATH=$EJABBERDDIR/priv
|
||||
fi
|
||||
if [ "$EJABBERD_BIN_PATH" = "" ] ; then
|
||||
EJABBERD_BIN_PATH=$EJABBERD_PRIV_PATH/bin
|
||||
fi
|
||||
if [ "{{release}}" != "true" -a "$EJABBERD_BIN_PATH" = "" ] ; then
|
||||
EJABBERD_BIN_PATH={{libdir}}/ejabberd/priv/bin
|
||||
fi
|
||||
EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
|
||||
DATETIME=`date "+%Y%m%d-%H%M%S"`
|
||||
@@ -141,8 +132,8 @@ fi
|
||||
[ -z "$date" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_date '$date'"
|
||||
[ -z "$EJABBERD_OPTS" ] || EJABBERD_OPTS="-ejabberd ${EJABBERD_OPTS}"
|
||||
|
||||
[ -d $SPOOL_DIR ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
|
||||
cd $SPOOL_DIR
|
||||
[ -d "$SPOOL_DIR" ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
|
||||
cd "$SPOOL_DIR"
|
||||
|
||||
# export global variables
|
||||
export EJABBERD_CONFIG_PATH
|
||||
@@ -199,8 +190,8 @@ start()
|
||||
debug()
|
||||
{
|
||||
debugwarning
|
||||
TTY=`tty | sed -e 's/.*\///g'`
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"debug-${TTY}-${ERLANG_NODE}\"` \
|
||||
NID=$(uid debug)
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
|
||||
-remsh $ERLANG_NODE \
|
||||
-hidden \
|
||||
$KERNEL_OPTS \
|
||||
@@ -213,15 +204,15 @@ debug()
|
||||
iexdebug()
|
||||
{
|
||||
debugwarning
|
||||
TTY=`tty | sed -e 's/.*\///g'`
|
||||
# Elixir shell is hidden as default
|
||||
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"debug-${TTY}-${ERLANG_NODE}\"` \
|
||||
NID=$(uid debug)
|
||||
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"$NID\"` \
|
||||
-remsh $ERLANG_NODE \
|
||||
--erl `shell_escape \"$KERNEL_OPTS\"` \
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start interactive server
|
||||
@@ -251,7 +242,7 @@ iexlive()
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start server in the foreground
|
||||
@@ -319,27 +310,27 @@ livewarning()
|
||||
|
||||
etop()
|
||||
{
|
||||
TTY=`tty | sed -e 's/.*\///g'`
|
||||
NID=$(uid top)
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME debug-${TTY}-${ERLANG_NODE} \
|
||||
$NAME $NID \
|
||||
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
|
||||
}
|
||||
|
||||
ping()
|
||||
{
|
||||
TTY=`tty | sed -e 's/.*\///g'`
|
||||
if [ "$1" = "${1%.*}" ] ; then
|
||||
[ -z "$1" ] && PEER=${ERLANG_NODE} || PEER=$1
|
||||
if [ "$PEER" = "${PEER%.*}" ] ; then
|
||||
PING_NAME="-sname"
|
||||
PING_NODE=$(hostname -s)
|
||||
else
|
||||
PING_NAME="-name"
|
||||
PING_NODE=$(hostname)
|
||||
fi
|
||||
NID=$(uid ping ${PING_NODE})
|
||||
$EXEC_CMD "$ERL \
|
||||
$PING_NAME ping-${TTY}@${PING_NODE} \
|
||||
-hidden \
|
||||
$KERNEL_OPTS $ERLANG_OPTS \
|
||||
-eval 'io:format(\"~p~n\",[net_adm:ping('\"'\"'$1'\"'\"')])' \
|
||||
$PING_NAME $NID \
|
||||
-hidden $KERNEL_OPTS $ERLANG_OPTS \
|
||||
-eval 'io:format(\"~p~n\",[net_adm:ping('\"'\"'$PEER'\"'\"')])' \
|
||||
-s erlang halt -output text -noinput"
|
||||
}
|
||||
|
||||
@@ -367,97 +358,42 @@ help()
|
||||
# common control function
|
||||
ctl()
|
||||
{
|
||||
# Control number of connections identifiers
|
||||
# using flock if available. Expects a linux-style
|
||||
# flock that can lock a file descriptor.
|
||||
MAXCONNID=100
|
||||
CONNLOCKDIR={{localstatedir}}/lock/ejabberdctl
|
||||
FLOCK=/usr/bin/flock
|
||||
if [ ! -x "$FLOCK" ] || [ ! -d "$CONNLOCKDIR" ] ; then
|
||||
JOT=/usr/bin/jot
|
||||
if [ ! -x "$JOT" ] ; then
|
||||
# no flock or jot, simply invoke ctlexec()
|
||||
CTL_CONN="ctl-${ERLANG_NODE}"
|
||||
ctlexec $CTL_CONN "$@"
|
||||
result=$?
|
||||
else
|
||||
# no flock, but at least there is jot
|
||||
RAND=`jot -r 1 0 $MAXCONNID`
|
||||
CTL_CONN="ctl-${RAND}-${ERLANG_NODE}"
|
||||
ctlexec $CTL_CONN "$@"
|
||||
result=$?
|
||||
fi
|
||||
else
|
||||
# we have flock so we get a lock
|
||||
# on one of a limited number of
|
||||
# conn names -- this allows
|
||||
# concurrent invocations using a bound
|
||||
# number of atoms
|
||||
for N in `seq 1 $MAXCONNID`; do
|
||||
CTL_CONN="ctl-$N-${ERLANG_NODE}"
|
||||
CTL_LOCKFILE="$CONNLOCKDIR/$CTL_CONN"
|
||||
(
|
||||
exec 8>"$CTL_LOCKFILE"
|
||||
if flock --nb 8; then
|
||||
ctlexec $CTL_CONN "$@"
|
||||
ssresult=$?
|
||||
# segregate from possible flock exit(1)
|
||||
ssresult=`expr $ssresult \* 10`
|
||||
exit $ssresult
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
result=$?
|
||||
if [ $result -eq 1 ] ; then
|
||||
# means we errored out in flock
|
||||
# rather than in the exec - stay in the loop
|
||||
# trying other conn names...
|
||||
badlock=1
|
||||
else
|
||||
badlock=""
|
||||
break;
|
||||
fi
|
||||
done
|
||||
result=`expr $result / 10`
|
||||
fi
|
||||
|
||||
if [ "$badlock" ] ;then
|
||||
echo "Ran out of connections to try. Your ejabberd processes" >&2
|
||||
echo "may be stuck or this is a very busy server. For very" >&2
|
||||
echo "busy servers, consider raising MAXCONNID in ejabberdctl">&2
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
case $result in
|
||||
0) :;;
|
||||
1) :;;
|
||||
2) help;;
|
||||
3) help;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
ctlexec()
|
||||
{
|
||||
CONN_NAME=$1; shift
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$CONN_NAME\"` \
|
||||
NID=$(uid ctl)
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
|
||||
-noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \
|
||||
-extra `shell_escape \"$ERLANG_NODE\"` $EJABBERD_NO_TIMEOUT \
|
||||
`shell_escape \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
result=$?
|
||||
case $result in
|
||||
2) help;;
|
||||
3) help;;
|
||||
*) :;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
uid()
|
||||
{
|
||||
uuid=$(uuidgen 2>/dev/null)
|
||||
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(</proc/sys/kernel/random/uuid)
|
||||
[ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
|
||||
uuid=${uuid%%-*}
|
||||
[ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
|
||||
[ $# -eq 1 ] && echo ${uuid}-${1}-${ERLANG_NODE}
|
||||
[ $# -eq 2 ] && echo ${uuid}-${1}@${2}
|
||||
}
|
||||
|
||||
# stop epmd if there is no other running node
|
||||
stop_epmd()
|
||||
{
|
||||
$EPMD -names 2>/dev/null | grep -q name || $EPMD -kill >/dev/null
|
||||
"$EPMD" -names 2>/dev/null | grep -q name || "$EPMD" -kill >/dev/null
|
||||
}
|
||||
|
||||
# make sure node not already running and node name unregistered
|
||||
check_start()
|
||||
{
|
||||
$EPMD -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
|
||||
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
|
||||
ps ux | grep -v grep | grep -q " $ERLANG_NODE " && {
|
||||
echo "ERROR: The ejabberd node '$ERLANG_NODE' is already running."
|
||||
exit 4
|
||||
@@ -468,7 +404,7 @@ check_start()
|
||||
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
|
||||
exit 5
|
||||
} || {
|
||||
$EPMD -kill >/dev/null
|
||||
"$EPMD" -kill >/dev/null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,9 +438,9 @@ case "${ARGS[0]}" in
|
||||
'live') live;;
|
||||
'iexlive') iexlive;;
|
||||
'foreground') foreground;;
|
||||
'ping'*) ping ${ARGS# ping};;
|
||||
'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
|
||||
|
||||
@@ -31,8 +31,9 @@
|
||||
tags = [] :: [atom()] | '_' | '$2',
|
||||
desc = "" :: string() | '_' | '$3',
|
||||
longdesc = "" :: string() | '_',
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
version = 0 :: integer(),
|
||||
module :: atom() | '_',
|
||||
function :: atom() | '_',
|
||||
args = [] :: [aterm()] | '_' | '$1' | '$2',
|
||||
policy = restricted :: open | restricted | admin | user,
|
||||
result = {res, rescode} :: rterm() | '_' | '$2',
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
path = [] :: [binary()],
|
||||
q = [] :: [{binary() | nokey, binary()}],
|
||||
us = {<<>>, <<>>} :: {binary(), binary()},
|
||||
auth :: {binary(), binary()} |
|
||||
{auth_jid, {binary(), binary()}, jlib:jid()},
|
||||
auth :: {binary(), binary()} | {oauth, binary(), []} | undefined,
|
||||
lang = <<"">> :: binary(),
|
||||
data = <<"">> :: binary(),
|
||||
ip :: {inet:ip_address(), inet:port_number()},
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
|
||||
-record(session, {sid, usr, us, priority, info}).
|
||||
-record(session_counter, {vhost, count}).
|
||||
-type sid() :: {erlang:timestamp(), pid()}.
|
||||
-type sid() :: {erlang:timestamp(), pid()} | {erlang:timestamp(), undefined}.
|
||||
-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.
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
-define(SQL_UPSERT_MARK, sql_upsert__mark_).
|
||||
-define(SQL_UPSERT(Host, Table, Fields),
|
||||
ejabberd_odbc:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))).
|
||||
ejabberd_sql:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))).
|
||||
-define(SQL_UPSERT_T(Table, Fields),
|
||||
ejabberd_odbc:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))).
|
||||
ejabberd_sql:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))).
|
||||
|
||||
-record(sql_query, {hash, format_query, format_res, args, loc}).
|
||||
|
||||
|
||||
@@ -34,3 +34,10 @@
|
||||
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
lager:critical(Format, Args)).
|
||||
|
||||
%% Use only when trying to troubleshoot test problem with ExUnit
|
||||
-define(EXUNIT_LOG(Format, Args),
|
||||
case lists:keyfind(logger, 1, application:loaded_applications()) of
|
||||
false -> ok;
|
||||
_ -> 'Elixir.Logger':bare_log(error, io_lib:format(Format, Args), [?MODULE])
|
||||
end).
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-record(motd, {server = <<"">> :: binary(),
|
||||
packet = #xmlel{} :: xmlel()}).
|
||||
|
||||
-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
dummy = [] :: [] | '_'}).
|
||||
@@ -0,0 +1,4 @@
|
||||
-record(caps_features,
|
||||
{node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
features = [] :: [binary()] | pos_integer()
|
||||
}).
|
||||
@@ -0,0 +1,4 @@
|
||||
-type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
|
||||
-record(carboncopy, {us :: {binary(), binary()} | matchspec_atom(),
|
||||
resource :: binary() | matchspec_atom(),
|
||||
version :: binary() | matchspec_atom()}).
|
||||
@@ -0,0 +1,15 @@
|
||||
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
|
||||
{binary(), binary(), inet:port_number()} |
|
||||
{binary(), binary()} |
|
||||
{binary()}.
|
||||
|
||||
-type irc_data() :: [{username, binary()} | {connections_params, [conn_param()]}].
|
||||
|
||||
-record(irc_connection,
|
||||
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
|
||||
pid = self() :: pid()}).
|
||||
|
||||
-record(irc_custom,
|
||||
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
|
||||
binary()},
|
||||
data = [] :: irc_data()}).
|
||||
@@ -0,0 +1,3 @@
|
||||
-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
timestamp = 0 :: non_neg_integer(),
|
||||
status = <<"">> :: binary()}).
|
||||
@@ -0,0 +1,15 @@
|
||||
-record(archive_msg,
|
||||
{us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
|
||||
id = <<>> :: binary() | '_',
|
||||
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
|
||||
peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
|
||||
bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
|
||||
packet = #xmlel{} :: xmlel() | '_',
|
||||
nick = <<"">> :: binary(),
|
||||
type = chat :: chat | groupchat}).
|
||||
|
||||
-record(archive_prefs,
|
||||
{us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
default = never :: never | always | roster,
|
||||
always = [] :: [ljid()],
|
||||
never = [] :: [ljid()]}).
|
||||
@@ -0,0 +1,4 @@
|
||||
-record(private_storage,
|
||||
{usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
|
||||
'$1' | '_'},
|
||||
xml = #xmlel{} :: xmlel() | '_' | '$1'}).
|
||||
@@ -0,0 +1,5 @@
|
||||
-record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
|
||||
opts = [] :: list() | '_' | '$2'}).
|
||||
|
||||
-record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
|
||||
@@ -0,0 +1,8 @@
|
||||
-record(vcard_search,
|
||||
{us, user, luser, fn, lfn, family, lfamily, given,
|
||||
lgiven, middle, lmiddle, nickname, lnickname, bday,
|
||||
lbday, ctry, lctry, locality, llocality, email, lemail,
|
||||
orgname, lorgname, orgunit, lorgunit}).
|
||||
|
||||
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
|
||||
vcard = #xmlel{} :: xmlel()}).
|
||||
@@ -0,0 +1,2 @@
|
||||
-record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
|
||||
hash = <<>> :: binary()}).
|
||||
+6
-1
@@ -65,7 +65,7 @@
|
||||
%% note: pos_integer() should always be used, but we allow anything else coded
|
||||
%% as binary, so one can have a custom implementation of nodetree with custom
|
||||
%% indexing (see nodetree_virtual). this also allows to use any kind of key for
|
||||
%% indexing nodes, as this can be usefull with external backends such as odbc.
|
||||
%% indexing nodes, as this can be usefull with external backends such as sql.
|
||||
|
||||
-type(itemId() :: binary()).
|
||||
%% @type itemId() = string().
|
||||
@@ -93,7 +93,12 @@
|
||||
|
||||
-type(subOptions() :: [mod_pubsub:subOption(),...]).
|
||||
|
||||
-type(pubOption() ::
|
||||
{Option::binary(),
|
||||
Values::[binary()]
|
||||
}).
|
||||
|
||||
-type(pubOptions() :: [mod_pubsub:pubOption()]).
|
||||
|
||||
-type(affiliation() :: 'none'
|
||||
| 'owner'
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
defmodule ExUnit.CTFormatter do
|
||||
@moduledoc false
|
||||
|
||||
use GenEvent
|
||||
|
||||
import ExUnit.Formatter, only: [format_time: 2, format_filters: 2, format_test_failure: 5,
|
||||
format_test_case_failure: 5]
|
||||
|
||||
def init(opts) do
|
||||
file = File.open! "exunit.log", [:append]
|
||||
# We do not print filter in log file as exclusion of test with tag
|
||||
# pending: true is always done
|
||||
config = %{
|
||||
file: file,
|
||||
seed: opts[:seed],
|
||||
trace: opts[:trace],
|
||||
colors: Keyword.put_new(opts[:colors], :enabled, false),
|
||||
width: 80,
|
||||
tests_counter: 0,
|
||||
failures_counter: 0,
|
||||
skipped_counter: 0,
|
||||
invalids_counter: 0
|
||||
}
|
||||
{:ok, config}
|
||||
end
|
||||
|
||||
def handle_event({:suite_started, _opts}, config) do
|
||||
{:ok, config}
|
||||
end
|
||||
|
||||
def handle_event({:suite_finished, run_us, load_us}, config) do
|
||||
print_suite(config, run_us, load_us)
|
||||
File.close config[:file]
|
||||
:remove_handler
|
||||
end
|
||||
|
||||
def handle_event({:test_started, %ExUnit.Test{} = test}, config) do
|
||||
if config.tests_counter == 0, do: IO.binwrite config[:file], "== Running #{test.case} ==\n\n"
|
||||
{:ok, config}
|
||||
end
|
||||
|
||||
def handle_event({:test_finished, %ExUnit.Test{state: nil} = _test}, config) do
|
||||
IO.binwrite config[:file], "."
|
||||
{:ok, %{config | tests_counter: config.tests_counter + 1}}
|
||||
end
|
||||
|
||||
def handle_event({:test_finished, %ExUnit.Test{state: {:skip, _}} = _test}, config) do
|
||||
{:ok, %{config | tests_counter: config.tests_counter + 1,
|
||||
skipped_counter: config.skipped_counter + 1}}
|
||||
end
|
||||
|
||||
def handle_event({:test_finished, %ExUnit.Test{state: {:invalid, _}} = _test}, config) do
|
||||
IO.binwrite config[:file], "?"
|
||||
{:ok, %{config | tests_counter: config.tests_counter + 1,
|
||||
invalids_counter: config.invalids_counter + 1}}
|
||||
end
|
||||
|
||||
def handle_event({:test_finished, %ExUnit.Test{state: {:failed, failures}} = test}, config) do
|
||||
formatted = format_test_failure(test, failures, config.failures_counter + 1,
|
||||
config.width, &formatter(&1, &2, config))
|
||||
print_failure(formatted, config)
|
||||
print_logs(test.logs)
|
||||
|
||||
{:ok, %{config | tests_counter: config.tests_counter + 1,
|
||||
failures_counter: config.failures_counter + 1}}
|
||||
end
|
||||
|
||||
def handle_event({:case_started, %ExUnit.TestCase{}}, config) do
|
||||
{:ok, config}
|
||||
end
|
||||
|
||||
def handle_event({:case_finished, %ExUnit.TestCase{state: nil}}, config) do
|
||||
{:ok, config}
|
||||
end
|
||||
|
||||
def handle_event({:case_finished, %ExUnit.TestCase{state: {:failed, failures}} = test_case}, config) do
|
||||
formatted = format_test_case_failure(test_case, failures, config.failures_counter + 1,
|
||||
config.width, &formatter(&1, &2, config))
|
||||
print_failure(formatted, config)
|
||||
{:ok, %{config | failures_counter: config.failures_counter + 1}}
|
||||
end
|
||||
|
||||
## Printing
|
||||
|
||||
defp print_suite(config, run_us, load_us) do
|
||||
IO.binwrite config[:file], "\n\n"
|
||||
IO.binwrite config[:file], format_time(run_us, load_us)
|
||||
IO.binwrite config[:file], "\n\n"
|
||||
|
||||
# singular/plural
|
||||
test_pl = pluralize(config.tests_counter, "test", "tests")
|
||||
failure_pl = pluralize(config.failures_counter, "failure", "failures")
|
||||
|
||||
message =
|
||||
"#{config.tests_counter} #{test_pl}, #{config.failures_counter} #{failure_pl}"
|
||||
|> if_true(config.skipped_counter > 0, & &1 <> ", #{config.skipped_counter} skipped")
|
||||
|> if_true(config.invalids_counter > 0, & &1 <> ", #{config.invalids_counter} invalid")
|
||||
|
||||
cond do
|
||||
config.failures_counter > 0 -> IO.binwrite config[:file], message
|
||||
config.invalids_counter > 0 -> IO.binwrite config[:file], message
|
||||
true -> IO.binwrite config[:file], message
|
||||
end
|
||||
|
||||
IO.binwrite config[:file], "\nRandomized with seed #{config.seed}\n\n\n\n"
|
||||
end
|
||||
|
||||
defp if_true(value, false, _fun), do: value
|
||||
defp if_true(value, true, fun), do: fun.(value)
|
||||
|
||||
defp print_failure(formatted, config) do
|
||||
IO.binwrite config[:file], "\n"
|
||||
IO.binwrite config[:file], formatted
|
||||
IO.binwrite config[:file], "\n"
|
||||
end
|
||||
|
||||
defp formatter(_, msg, _config),
|
||||
do: msg
|
||||
|
||||
defp pluralize(1, singular, _plural), do: singular
|
||||
defp pluralize(_, _singular, plural), do: plural
|
||||
|
||||
defp print_logs(""), do: nil
|
||||
|
||||
defp print_logs(output) do
|
||||
indent = "\n "
|
||||
output = String.replace(output, "\n", indent)
|
||||
IO.puts([" The following output was logged:", indent | output])
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "16.02.0",
|
||||
version: "16.06.0",
|
||||
description: description,
|
||||
elixir: "~> 1.2",
|
||||
elixirc_paths: ["lib"],
|
||||
@@ -38,7 +38,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,7 +51,7 @@ 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"},
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
%{"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.2"},
|
||||
"exrm": {:hex, :exrm, "1.0.3"},
|
||||
"ezlib": {:hex, :ezlib, "1.0.1"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.1"},
|
||||
"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.18.0"},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.3"},
|
||||
"stun": {:hex, :stun, "1.0.1"}}
|
||||
%{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.2", "c12099fff8b0f7011e7656844f5f67b958b7033a521c69595dbf2a5d7c8bb34f", [:rebar3], [{:p1_utils, "1.0.3", [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.4", "47426c264f6ea5a2a74b53ecf825593b689b47ed3eab873ff8a595ea35aa8507", [:rebar3], [{:stun, "1.0.3", [hex: :stun, optional: false]}, {:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}, {:fast_tls, "1.0.3", [hex: :fast_tls, optional: false]}]},
|
||||
"exrm": {:hex, :exrm, "1.0.5", "53ecb20da2f4e5b4c82ea6776824fbc677c8d287bf20efc9fc29cacc2cca124f", [:mix], [{:relx, "~> 3.5", [hex: :relx, optional: false]}]},
|
||||
"ezlib": {:hex, :ezlib, "1.0.1", "add8b2770a1a70c174aaea082b4a8668c0c7fdb03ee6cc81c6c68d3a6c3d767d", [:rebar3], []},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.3", "7ccd02ffcba24f8792dd88bd71fa543ea438c58bd0f132576031533083ede578", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.11", "1d9aedbe367a3d42ba2c6d9d4ee5808c0846e06a28e366e262e41d3ce462cbdf", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.3", "862e3f89d52aa6a72eef3121edf303aac2f3c559cbaba2d2a1fd0c09d5f15f9a", [:rebar3], [{:p1_utils, "1.0.3", [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.0", "5ff1c54e5b3b9a8235de872632e9612c7952acdf89bc21db2f2efae0e72647be", [:rebar3], []},
|
||||
"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]}]},
|
||||
"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.3", "8d469a34e8fe3898dda9dfda545fdb69cabfee144ebe31732d2c7905420603ec", [: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], [{:providers, "1.6.0", [hex: :providers, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:erlware_commons, "0.19.0", [hex: :erlware_commons, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}]},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
|
||||
"stringprep": {:hex, :stringprep, "1.0.3", "0fc697484a83c868817c5c6d74c310a1f821b3cf8b6c0eb105335421d0b94d99", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}]},
|
||||
"stun": {:hex, :stun, "1.0.3", "d8dcf8beb38939f3fcded73e89e5753cb5a2fed2e74fa5b658124849a9e97fe9", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}, {:fast_tls, "1.0.3", [hex: :fast_tls, optional: false]}]}}
|
||||
|
||||
+19
-12
@@ -8,13 +8,13 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.0.2"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.3"}}},
|
||||
{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.1"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.2"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.3"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.1"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.2"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.4"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.3"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.12"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.4"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.5"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.3"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.7"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
|
||||
@@ -30,10 +30,14 @@
|
||||
{tag, "1.0.0"}}}},
|
||||
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
|
||||
{tag, "1.0.1"}}}},
|
||||
{if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang",
|
||||
"908a24fda4a46776a5135db60ca071e3d783f9f6"}}}, % for riak_pb-2.1.0.7
|
||||
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
|
||||
"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
|
||||
{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"}}}},
|
||||
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
|
||||
@@ -41,8 +45,10 @@
|
||||
{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"}}}},
|
||||
{if_var_true, tools, {meck, "0.8.2", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.2"}}}},
|
||||
{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",
|
||||
{tag, "1.0.5b"}}}},
|
||||
{if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis",
|
||||
{tag, "v1.0.8"}}}}]}.
|
||||
|
||||
@@ -62,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},
|
||||
@@ -107,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", []},
|
||||
|
||||
+16
-2
@@ -30,6 +30,20 @@ Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config"))
|
||||
|
||||
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, [Value | Acc]);
|
||||
true ->
|
||||
F(F, Tail, Acc)
|
||||
end;
|
||||
(F, [{Type, Var, Value} | Tail], Acc) when
|
||||
Type == if_var_true orelse
|
||||
Type == if_var_false ->
|
||||
@@ -122,8 +136,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"},
|
||||
|
||||
+2
-2
@@ -362,8 +362,8 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
|
||||
CREATE TABLE [dbo].[users] (
|
||||
[username] [varchar] (250) NOT NULL,
|
||||
[password] [text] NOT NULL,
|
||||
[serverkey] [text] NOT NULL,
|
||||
[salt] [text] NOT NULL,
|
||||
[serverkey] [text] NOT NULL DEFAULT '',
|
||||
[salt] [text] NOT NULL DEFAULT '',
|
||||
[iterationcount] [smallint] NOT NULL DEFAULT 0,
|
||||
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
|
||||
CONSTRAINT [users_PRIMARY] PRIMARY KEY CLUSTERED
|
||||
|
||||
+5
-5
@@ -19,15 +19,15 @@
|
||||
CREATE TABLE users (
|
||||
username varchar(191) PRIMARY KEY,
|
||||
password text NOT NULL,
|
||||
serverkey text NOT NULL DEFAULT '',
|
||||
salt text NOT NULL DEFAULT '',
|
||||
serverkey varchar(64) NOT NULL DEFAULT '',
|
||||
salt varchar(64) NOT NULL DEFAULT '',
|
||||
iterationcount integer NOT NULL DEFAULT 0,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
||||
-- ALTER TABLE users ADD COLUMN serverkey text NOT NULL DEFAULT '';
|
||||
-- ALTER TABLE users ADD COLUMN salt text NOT NULL DEFAULT '';
|
||||
-- ALTER TABLE users ADD COLUMN serverkey varchar(64) NOT NULL DEFAULT '';
|
||||
-- ALTER TABLE users ADD COLUMN salt varchar(64) NOT NULL DEFAULT '';
|
||||
-- ALTER TABLE users ADD COLUMN iterationcount integer NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE TABLE last (
|
||||
@@ -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;
|
||||
|
||||
|
||||
+321
-109
@@ -29,10 +29,13 @@
|
||||
|
||||
-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_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,
|
||||
transform_options/1, opt_type/1, acl_rule_matches/3,
|
||||
acl_rule_verify/1, access_matches/3,
|
||||
transform_access_rules_config/1,
|
||||
access_rules_validator/1, shaper_rules_validator/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -43,6 +46,7 @@
|
||||
rules = [] :: [access_rule()]}).
|
||||
|
||||
-type regexp() :: binary().
|
||||
-type iprange() :: {inet:ip_address(), integer()} | binary().
|
||||
-type glob() :: binary().
|
||||
-type access_name() :: atom().
|
||||
-type access_rule() :: {atom(), any()}.
|
||||
@@ -61,7 +65,7 @@
|
||||
{user_glob, {glob(), host()} | glob()} |
|
||||
{server_glob, glob()} |
|
||||
{resource_glob, glob()} |
|
||||
{ip, {inet:ip_address(), integer()}} |
|
||||
{ip, iprange()} |
|
||||
{node_glob, {glob(), glob()}}.
|
||||
|
||||
-type acl() :: #acl{aclname :: aclname(),
|
||||
@@ -89,12 +93,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) ->
|
||||
@@ -185,6 +183,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(
|
||||
@@ -198,12 +200,29 @@ 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
|
||||
clear() ->
|
||||
mnesia:clear_table(acl),
|
||||
mnesia:clear_table(access),
|
||||
ok.
|
||||
|
||||
b(S) ->
|
||||
iolist_to_binary(S).
|
||||
|
||||
@@ -216,19 +235,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)};
|
||||
@@ -249,104 +277,203 @@ normalize_spec(Spec) ->
|
||||
-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}).
|
||||
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
|
||||
|
||||
is_regexp_match(String, RegExp) ->
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
@@ -418,6 +545,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(
|
||||
@@ -432,6 +616,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
|
||||
@@ -445,7 +641,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
|
||||
@@ -476,5 +686,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].
|
||||
|
||||
+3
-1
@@ -68,7 +68,9 @@ parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}
|
||||
xdata = XData,
|
||||
others = Others
|
||||
};
|
||||
parse_request(_) -> {error, ?ERR_BAD_REQUEST}.
|
||||
parse_request(#iq{lang = Lang}) ->
|
||||
Text = <<"Failed to parse ad-hoc command request">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Text)}.
|
||||
|
||||
%% Borrowed from mod_vcard.erl
|
||||
find_xdata_el(#xmlel{children = SubEls}) ->
|
||||
|
||||
@@ -45,9 +45,8 @@ mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
|
||||
mech_step(#state{server = Server} = S, ClientIn) ->
|
||||
User = iolist_to_binary([randoms:get_string(),
|
||||
randoms:get_string(),
|
||||
randoms:get_string()]),
|
||||
jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]),
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true -> mech_step(S, ClientIn);
|
||||
false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
|
||||
false -> {ok, [{username, User}, {authzid, User}, {auth_module, ejabberd_auth_anonymous}]}
|
||||
end.
|
||||
|
||||
@@ -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, _, _, _, _}} =
|
||||
|
||||
+8
-12
@@ -192,20 +192,16 @@ get_commands_spec() ->
|
||||
module = ejabberd_piefxis, function = export_host,
|
||||
args = [{dir, string}, {host, string}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = export_odbc, tags = [mnesia, odbc],
|
||||
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
|
||||
desc = "Export all tables as SQL queries to a file",
|
||||
module = ejd2odbc, function = export,
|
||||
args = [{host, string}, {file, string}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, odbc],
|
||||
desc = "Export all tables as SQL queries to a file",
|
||||
module = ejd2odbc, function = delete,
|
||||
module = ejd2sql, function = delete,
|
||||
args = [{host, string}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = convert_to_scram, tags = [odbc],
|
||||
#ejabberd_commands{name = convert_to_scram, tags = [sql],
|
||||
desc = "Convert the passwords in 'users' ODBC table to SCRAM",
|
||||
module = ejabberd_auth_odbc, function = convert_to_scram,
|
||||
module = ejabberd_auth_sql, function = convert_to_scram,
|
||||
args = [{host, binary}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = import_prosody, tags = [mnesia, odbc, riak],
|
||||
#ejabberd_commands{name = import_prosody, tags = [mnesia, sql, riak],
|
||||
desc = "Import data from Prosody",
|
||||
module = prosody2ejabberd, function = from_dir,
|
||||
args = [{dir, string}], result = {res, rescode}},
|
||||
@@ -225,10 +221,10 @@ get_commands_spec() ->
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = export2odbc, tags = [mnesia],
|
||||
#ejabberd_commands{name = export2sql, tags = [mnesia],
|
||||
desc = "Export virtual host information from Mnesia tables to SQL files",
|
||||
module = ejd2odbc, function = export,
|
||||
args = [{host, string}, {directory, string}],
|
||||
module = ejd2sql, function = export,
|
||||
args = [{host, string}, {file, string}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_master, tags = [mnesia],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
|
||||
@@ -63,6 +63,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 +84,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.
|
||||
|
||||
|
||||
+9
-18
@@ -136,8 +136,8 @@ check_password(User, AuthzId, Server, Password, Digest,
|
||||
%% {true, AuthModule} | false
|
||||
%% where
|
||||
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
|
||||
%% | ejabberd_auth_internal | ejabberd_auth_ldap
|
||||
%% | ejabberd_auth_odbc | ejabberd_auth_pam | ejabberd_auth_riak
|
||||
%% | ejabberd_auth_mnesia | ejabberd_auth_ldap
|
||||
%% | ejabberd_auth_sql | ejabberd_auth_pam | ejabberd_auth_riak
|
||||
-spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false |
|
||||
{true, atom()}.
|
||||
|
||||
@@ -428,30 +428,21 @@ auth_modules() ->
|
||||
%% Return the list of authenticated modules for a given host
|
||||
auth_modules(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Default = case gen_mod:default_db(LServer) of
|
||||
mnesia -> internal;
|
||||
DBType -> DBType
|
||||
end,
|
||||
Default = ejabberd_config:default_db(LServer, ?MODULE),
|
||||
Methods = ejabberd_config:get_option(
|
||||
{auth_method, LServer},
|
||||
fun(V) when is_list(V) ->
|
||||
true = lists:all(fun is_atom/1, V),
|
||||
V;
|
||||
(V) when is_atom(V) ->
|
||||
[V]
|
||||
end, [Default]),
|
||||
{auth_method, LServer}, opt_type(auth_method), [Default]),
|
||||
[jlib:binary_to_atom(<<"ejabberd_auth_",
|
||||
(jlib:atom_to_binary(M))/binary>>)
|
||||
|| M <- Methods].
|
||||
|
||||
export(Server) ->
|
||||
ejabberd_auth_internal:export(Server).
|
||||
ejabberd_auth_mnesia:export(Server).
|
||||
|
||||
import(Server) ->
|
||||
ejabberd_auth_internal:import(Server).
|
||||
ejabberd_auth_mnesia:import(Server).
|
||||
|
||||
import(Server, mnesia, Passwd) ->
|
||||
ejabberd_auth_internal:import(Server, mnesia, Passwd);
|
||||
ejabberd_auth_mnesia:import(Server, mnesia, Passwd);
|
||||
import(Server, riak, Passwd) ->
|
||||
ejabberd_auth_riak:import(Server, riak, Passwd);
|
||||
import(_, _, _) ->
|
||||
@@ -459,7 +450,7 @@ import(_, _, _) ->
|
||||
|
||||
opt_type(auth_method) ->
|
||||
fun (V) when is_list(V) ->
|
||||
true = lists:all(fun is_atom/1, V), V;
|
||||
(V) when is_atom(V) -> [V]
|
||||
lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V);
|
||||
(V) -> [ejabberd_config:v_db(?MODULE, V)]
|
||||
end;
|
||||
opt_type(_) -> [auth_method].
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
%% Create the anonymous table if at least one virtual host has anonymous features enabled
|
||||
%% Register to login / logout events
|
||||
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
sid = {p1_time_compat:timestamp(), self()} :: ejabberd_sm:sid()}).
|
||||
sid = ejabberd_sm:make_sid() :: ejabberd_sm:sid()}).
|
||||
|
||||
start(Host) ->
|
||||
%% TODO: Check cluster mode
|
||||
|
||||
@@ -56,7 +56,7 @@ start(Host) ->
|
||||
"extauth"),
|
||||
extauth:start(Host, Cmd),
|
||||
check_cache_last_options(Host),
|
||||
ejabberd_auth_internal:start(Host).
|
||||
ejabberd_auth_mnesia:start(Host).
|
||||
|
||||
check_cache_last_options(Server) ->
|
||||
case get_cache_option(Server) of
|
||||
@@ -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,
|
||||
@@ -94,7 +96,7 @@ check_password(User, AuthzId, Server, Password, _Digest,
|
||||
set_password(User, Server, Password) ->
|
||||
case extauth:set_password(User, Server, Password) of
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), ok;
|
||||
set_password_mnesia(User, Server, Password), ok;
|
||||
_ -> {error, unknown_problem}
|
||||
end.
|
||||
|
||||
@@ -106,20 +108,20 @@ try_register(User, Server, Password) ->
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
ejabberd_auth_internal:dirty_get_registered_users().
|
||||
ejabberd_auth_mnesia:dirty_get_registered_users().
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server).
|
||||
ejabberd_auth_mnesia:get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server,
|
||||
ejabberd_auth_mnesia:get_vh_registered_users(Server,
|
||||
Data).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server).
|
||||
ejabberd_auth_mnesia:get_vh_registered_users_number(Server).
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server,
|
||||
ejabberd_auth_mnesia:get_vh_registered_users_number(Server,
|
||||
Data).
|
||||
|
||||
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
|
||||
@@ -151,7 +153,7 @@ remove_user(User, Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server)
|
||||
ejabberd_auth_mnesia:remove_user(User, Server)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -162,7 +164,7 @@ remove_user(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server,
|
||||
ejabberd_auth_mnesia:remove_user(User, Server,
|
||||
Password)
|
||||
end
|
||||
end.
|
||||
@@ -197,7 +199,7 @@ check_password_cache(User, AuthzId, Server, Password,
|
||||
CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
check_password_internal(User, AuthzId, Server, Password);
|
||||
check_password_mnesia(User, AuthzId, Server, Password);
|
||||
never ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password);
|
||||
mod_last_required ->
|
||||
@@ -210,7 +212,7 @@ check_password_cache(User, AuthzId, Server, Password,
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
%% If no need to refresh, check password against Mnesia
|
||||
true ->
|
||||
case check_password_internal(User, AuthzId, Server, Password) of
|
||||
case check_password_mnesia(User, AuthzId, Server, Password) of
|
||||
%% If password valid in Mnesia, accept it
|
||||
true -> true;
|
||||
%% Else (password nonvalid in Mnesia), check in extauth and cache result
|
||||
@@ -223,13 +225,13 @@ check_password_cache(User, AuthzId, Server, Password,
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_internal(User, Server) ->
|
||||
ejabberd_auth_internal:get_password(User, Server).
|
||||
get_password_mnesia(User, Server) ->
|
||||
ejabberd_auth_mnesia:get_password(User, Server).
|
||||
|
||||
-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
|
||||
get_password_cache(User, Server, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online -> get_password_internal(User, Server);
|
||||
online -> get_password_mnesia(User, Server);
|
||||
never -> false;
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled "
|
||||
@@ -239,7 +241,7 @@ get_password_cache(User, Server, CacheTime) ->
|
||||
false;
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true -> get_password_internal(User, Server);
|
||||
true -> get_password_mnesia(User, Server);
|
||||
false -> false
|
||||
end
|
||||
end.
|
||||
@@ -248,7 +250,7 @@ get_password_cache(User, Server, CacheTime) ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password) ->
|
||||
case check_password_extauth(User, AuthzId, Server, Password) of
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), true;
|
||||
set_password_mnesia(User, Server, Password), true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
@@ -256,21 +258,21 @@ check_password_external_cache(User, AuthzId, Server, Password) ->
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_internal(User, Server, Password), R;
|
||||
set_password_mnesia(User, Server, Password), R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password) -> true | false
|
||||
check_password_internal(User, AuthzId, Server, Password) ->
|
||||
ejabberd_auth_internal:check_password(User, AuthzId, Server,
|
||||
check_password_mnesia(User, AuthzId, Server, Password) ->
|
||||
ejabberd_auth_mnesia:check_password(User, AuthzId, Server,
|
||||
Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_internal(User, Server, Password) ->
|
||||
set_password_mnesia(User, Server, Password) ->
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
%% CacheTime = integer() | false
|
||||
ejabberd_auth_internal:set_password(User, Server,
|
||||
ejabberd_auth_mnesia:set_password(User, Server,
|
||||
Password).
|
||||
|
||||
is_fresh_enough(TimeStampLast, CacheTime) ->
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_internal.erl
|
||||
%%% File : ejabberd_auth_mnesia.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Authentification via mnesia
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
@@ -23,7 +23,9 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_internal).
|
||||
-module(ejabberd_auth_mnesia).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
@@ -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_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc: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_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc: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}].
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_odbc.erl
|
||||
%%% File : ejabberd_auth_sql.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Authentification via ODBC
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
@@ -23,7 +23,9 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_odbc).
|
||||
-module(ejabberd_auth_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
@@ -43,6 +45,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
@@ -77,7 +80,7 @@ check_password(User, AuthzId, Server, Password) ->
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
try odbc_queries:get_password_scram(LServer, LUser) of
|
||||
try sql_queries:get_password_scram(LServer, LUser) of
|
||||
{selected,
|
||||
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
||||
Scram =
|
||||
@@ -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} ->
|
||||
@@ -95,7 +98,7 @@ check_password(User, AuthzId, Server, Password) ->
|
||||
false %% Typical error is database not accessible
|
||||
end;
|
||||
false ->
|
||||
try odbc_queries:get_password(LServer, LUser) of
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{Password}]} ->
|
||||
Password /= <<"">>;
|
||||
{selected, [{_Password2}]} ->
|
||||
@@ -127,7 +130,7 @@ check_password(User, AuthzId, Server, Password, Digest,
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
false ->
|
||||
try odbc_queries:get_password(LServer, LUser) of
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
%% Account exists, check if password is valid
|
||||
{selected, [{Passwd}]} ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
@@ -164,7 +167,7 @@ set_password(User, Server, Password) ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
Scram = password_to_scram(Password),
|
||||
case catch odbc_queries:set_password_scram_t(
|
||||
case catch sql_queries:set_password_scram_t(
|
||||
LServer,
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
@@ -177,7 +180,7 @@ set_password(User, Server, Password) ->
|
||||
Other -> {error, Other}
|
||||
end;
|
||||
false ->
|
||||
case catch odbc_queries:set_password_t(LServer,
|
||||
case catch sql_queries:set_password_t(LServer,
|
||||
LUser, Password)
|
||||
of
|
||||
{atomic, ok} -> ok;
|
||||
@@ -198,7 +201,7 @@ try_register(User, Server, Password) ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
Scram = password_to_scram(Password),
|
||||
case catch odbc_queries:add_user_scram(
|
||||
case catch sql_queries:add_user_scram(
|
||||
LServer,
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
@@ -210,7 +213,7 @@ try_register(User, Server, Password) ->
|
||||
_ -> {atomic, exists}
|
||||
end;
|
||||
false ->
|
||||
case catch odbc_queries:add_user(LServer, LUser,
|
||||
case catch sql_queries:add_user(LServer, LUser,
|
||||
Password) of
|
||||
{updated, 1} -> {atomic, ok};
|
||||
_ -> {atomic, exists}
|
||||
@@ -219,7 +222,7 @@ try_register(User, Server, Password) ->
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(odbc),
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(sql),
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end,
|
||||
@@ -230,7 +233,7 @@ get_vh_registered_users(Server) ->
|
||||
error -> [];
|
||||
<<>> -> [];
|
||||
LServer ->
|
||||
case catch odbc_queries:list_users(LServer) of
|
||||
case catch sql_queries:list_users(LServer) of
|
||||
{selected, Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ -> []
|
||||
@@ -242,7 +245,7 @@ get_vh_registered_users(Server, Opts) ->
|
||||
error -> [];
|
||||
<<>> -> [];
|
||||
LServer ->
|
||||
case catch odbc_queries:list_users(LServer, Opts) of
|
||||
case catch sql_queries:list_users(LServer, Opts) of
|
||||
{selected, Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ -> []
|
||||
@@ -254,7 +257,7 @@ get_vh_registered_users_number(Server) ->
|
||||
error -> 0;
|
||||
<<>> -> 0;
|
||||
LServer ->
|
||||
case catch odbc_queries:users_number(LServer) of
|
||||
case catch sql_queries:users_number(LServer) of
|
||||
{selected, [{Res}]} ->
|
||||
Res;
|
||||
_ -> 0
|
||||
@@ -266,7 +269,7 @@ get_vh_registered_users_number(Server, Opts) ->
|
||||
error -> 0;
|
||||
<<>> -> 0;
|
||||
LServer ->
|
||||
case catch odbc_queries:users_number(LServer, Opts) of
|
||||
case catch sql_queries:users_number(LServer, Opts) of
|
||||
{selected, [{Res}]} ->
|
||||
Res;
|
||||
_Other -> 0
|
||||
@@ -283,7 +286,7 @@ get_password(User, Server) ->
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
case catch odbc_queries:get_password_scram(
|
||||
case catch sql_queries:get_password_scram(
|
||||
LServer, LUser) of
|
||||
{selected,
|
||||
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
||||
@@ -294,7 +297,7 @@ get_password(User, Server) ->
|
||||
_ -> false
|
||||
end;
|
||||
false ->
|
||||
case catch odbc_queries:get_password(LServer, LUser)
|
||||
case catch sql_queries:get_password(LServer, LUser)
|
||||
of
|
||||
{selected, [{Password}]} -> Password;
|
||||
_ -> false
|
||||
@@ -312,7 +315,7 @@ get_password_s(User, Server) ->
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
false ->
|
||||
case catch odbc_queries:get_password(LServer, LUser) of
|
||||
case catch sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{Password}]} -> Password;
|
||||
_ -> <<"">>
|
||||
end;
|
||||
@@ -329,7 +332,7 @@ is_user_exists(User, Server) ->
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
try odbc_queries:get_password(LServer, LUser) of
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{_Password}]} ->
|
||||
true; %% Account exists
|
||||
{selected, []} ->
|
||||
@@ -351,7 +354,7 @@ remove_user(User, Server) ->
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
error;
|
||||
true ->
|
||||
catch odbc_queries:del_user(LServer, LUser),
|
||||
catch sql_queries:del_user(LServer, LUser),
|
||||
ok
|
||||
end.
|
||||
|
||||
@@ -375,7 +378,7 @@ remove_user(User, Server, Password) ->
|
||||
end;
|
||||
false ->
|
||||
F = fun () ->
|
||||
Result = odbc_queries:del_user_return_password(
|
||||
Result = sql_queries:del_user_return_password(
|
||||
LServer, LUser, Password),
|
||||
case Result of
|
||||
{selected, [{Password}]} -> ok;
|
||||
@@ -383,7 +386,7 @@ remove_user(User, Server, Password) ->
|
||||
_ -> not_allowed
|
||||
end
|
||||
end,
|
||||
{atomic, Result} = odbc_queries:sql_transaction(
|
||||
{atomic, Result} = sql_queries:sql_transaction(
|
||||
LServer, F),
|
||||
Result
|
||||
end
|
||||
@@ -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) ->
|
||||
odbc_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,31 +455,31 @@ convert_to_scram(Server) ->
|
||||
{error, {incorrect_server_name, Server}};
|
||||
true ->
|
||||
F = fun () ->
|
||||
case ejabberd_odbc:sql_query_t(
|
||||
[<<"select username, password from users where "
|
||||
"iterationcount=0 limit ">>,
|
||||
integer_to_binary(?BATCH_SIZE),
|
||||
<<";">>]) of
|
||||
{selected, [<<"username">>, <<"password">>], []} ->
|
||||
BatchSize = ?BATCH_SIZE,
|
||||
case ejabberd_sql:sql_query_t(
|
||||
?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_odbc:escape(LUser),
|
||||
fun({LUser, Password}) ->
|
||||
Scram = password_to_scram(Password),
|
||||
set_password_scram_t(
|
||||
Username,
|
||||
ejabberd_odbc:escape(Scram#scram.storedkey),
|
||||
ejabberd_odbc:escape(Scram#scram.serverkey),
|
||||
ejabberd_odbc: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;
|
||||
Err -> {bad_reply, Err}
|
||||
end
|
||||
end,
|
||||
case odbc_queries:sql_transaction(LServer, F) of
|
||||
case sql_queries:sql_transaction(LServer, F) of
|
||||
{atomic, ok} -> ok;
|
||||
{atomic, continue} -> convert_to_scram(Server);
|
||||
{atomic, Error} -> {error, Error};
|
||||
+169
-167
@@ -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
|
||||
@@ -327,7 +327,7 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
|
||||
tls_required = StartTLSRequired,
|
||||
tls_enabled = TLSEnabled, tls_options = TLSOpts,
|
||||
sid = {p1_time_compat:timestamp(), self()}, streamid = new_id(),
|
||||
sid = ejabberd_sm:make_sid(), streamid = new_id(),
|
||||
access = Access, shaper = Shaper, ip = IP,
|
||||
mgmt_state = StreamMgmtState,
|
||||
mgmt_max_queue = MaxAckQueue,
|
||||
@@ -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
|
||||
@@ -522,7 +528,6 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
send_element(StateData,
|
||||
?POLICY_VIOLATION_ERR(Lang,
|
||||
<<"Use of STARTTLS required">>)),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
true ->
|
||||
fsm_next_state(wait_for_auth,
|
||||
@@ -535,36 +540,30 @@ 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)),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
|
||||
send_header(StateData, ?MYNAME, StreamVersion, DefaultLang),
|
||||
send_element(StateData, ?HOST_UNKNOWN_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
|
||||
send_element(StateData, ?INVALID_NS_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
wait_for_stream(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
wait_for_stream({xmlstreamelement, _}, StateData) ->
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
wait_for_stream({xmlstreamend, _}, StateData) ->
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
wait_for_stream({xmlstreamerror, _}, StateData) ->
|
||||
send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>),
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
wait_for_stream(closed, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
@@ -619,16 +618,17 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
send_element(StateData, Res),
|
||||
fsm_next_state(wait_for_auth, StateData);
|
||||
{auth, _ID, set, {_U, _P, _D, <<"">>}} ->
|
||||
Err = jlib:make_error_reply(El,
|
||||
?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
|
||||
Lang = StateData#state.lang,
|
||||
Txt = <<"No resource provided">>,
|
||||
Err = jlib:make_error_reply(El, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)),
|
||||
send_element(StateData, Err),
|
||||
fsm_next_state(wait_for_auth, 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) ->
|
||||
@@ -685,7 +685,10 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[false, U, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
|
||||
Lang = StateData#state.lang,
|
||||
Txt = <<"Legacy authentication failed">>,
|
||||
Err = jlib:make_error_reply(
|
||||
El, ?ERRT_NOT_AUTHORIZED(Lang, Txt)),
|
||||
send_element(StateData, Err),
|
||||
fsm_next_state(wait_for_auth, StateData)
|
||||
end;
|
||||
@@ -706,7 +709,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[false, U, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
|
||||
Lang = StateData#state.lang,
|
||||
Txt = <<"Legacy authentication forbidden">>,
|
||||
Err = jlib:make_error_reply(El, ?ERRT_NOT_ALLOWED(Lang, Txt)),
|
||||
send_element(StateData, Err),
|
||||
fsm_next_state(wait_for_auth, StateData)
|
||||
end
|
||||
@@ -718,10 +723,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
wait_for_auth(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
wait_for_auth({xmlstreamend, _Name}, StateData) ->
|
||||
send_trailer(StateData), {stop, normal, StateData};
|
||||
{stop, normal, StateData};
|
||||
wait_for_auth({xmlstreamerror, _}, StateData) ->
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
wait_for_auth(closed, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
@@ -837,7 +841,6 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
send_element(StateData,
|
||||
?POLICY_VIOLATION_ERR(Lang,
|
||||
<<"Use of STARTTLS required">>)),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
true ->
|
||||
process_unauthenticated_stanza(StateData, El),
|
||||
@@ -848,11 +851,10 @@ wait_for_feature_request(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
wait_for_feature_request({xmlstreamend, _Name},
|
||||
StateData) ->
|
||||
send_trailer(StateData), {stop, normal, StateData};
|
||||
{stop, normal, StateData};
|
||||
wait_for_feature_request({xmlstreamerror, _},
|
||||
StateData) ->
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
wait_for_feature_request(closed, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
@@ -957,11 +959,10 @@ wait_for_sasl_response(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
wait_for_sasl_response({xmlstreamend, _Name},
|
||||
StateData) ->
|
||||
send_trailer(StateData), {stop, normal, StateData};
|
||||
{stop, normal, StateData};
|
||||
wait_for_sasl_response({xmlstreamerror, _},
|
||||
StateData) ->
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
wait_for_sasl_response(closed, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
@@ -1013,7 +1014,7 @@ wait_for_bind({xmlstreamelement, #xmlel{name = Name, attrs = Attrs} = El},
|
||||
end;
|
||||
wait_for_bind({xmlstreamelement, El}, StateData) ->
|
||||
case jlib:iq_query_info(El) of
|
||||
#iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} =
|
||||
#iq{type = set, lang = Lang, xmlns = ?NS_BIND, sub_el = SubEl} =
|
||||
IQ ->
|
||||
U = StateData#state.user,
|
||||
R1 = fxml:get_path_s(SubEl,
|
||||
@@ -1025,7 +1026,8 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
|
||||
end,
|
||||
case R of
|
||||
error ->
|
||||
Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
|
||||
Txt = <<"Malformed resource">>,
|
||||
Err = jlib:make_error_reply(El, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||
send_element(StateData, Err),
|
||||
fsm_next_state(wait_for_bind, StateData);
|
||||
_ ->
|
||||
@@ -1057,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);
|
||||
@@ -1085,10 +1091,9 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
|
||||
wait_for_bind(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
wait_for_bind({xmlstreamend, _Name}, StateData) ->
|
||||
send_trailer(StateData), {stop, normal, StateData};
|
||||
{stop, normal, StateData};
|
||||
wait_for_bind({xmlstreamerror, _}, StateData) ->
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
wait_for_bind(closed, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
@@ -1099,8 +1104,11 @@ open_session(StateData) ->
|
||||
U = StateData#state.user,
|
||||
R = StateData#state.resource,
|
||||
JID = StateData#state.jid,
|
||||
case acl:match_rule(StateData#state.server,
|
||||
StateData#state.access, JID) of
|
||||
Lang = StateData#state.lang,
|
||||
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)]),
|
||||
@@ -1136,7 +1144,8 @@ open_session(StateData) ->
|
||||
StateData#state.server, [JID]),
|
||||
?INFO_MSG("(~w) Forbidden session for ~s",
|
||||
[StateData#state.socket, jid:to_string(JID)]),
|
||||
{error, ?ERR_NOT_ALLOWED}
|
||||
Txt = <<"Denied by ACL">>,
|
||||
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
|
||||
end.
|
||||
|
||||
session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
|
||||
@@ -1146,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">>,
|
||||
@@ -1159,7 +1168,6 @@ session_established({xmlstreamelement, El},
|
||||
case check_from(El, FromJID) of
|
||||
'invalid-from' ->
|
||||
send_element(StateData, ?INVALID_FROM),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
_NewEl ->
|
||||
session_established2(El, StateData)
|
||||
@@ -1172,17 +1180,15 @@ session_established(timeout, StateData) ->
|
||||
[?MODULE, Options, session_established, StateData]),
|
||||
fsm_next_state(session_established, StateData);
|
||||
session_established({xmlstreamend, _Name}, StateData) ->
|
||||
send_trailer(StateData), {stop, normal, StateData};
|
||||
{stop, normal, StateData};
|
||||
session_established({xmlstreamerror,
|
||||
<<"XML stanza is too big">> = E},
|
||||
StateData) ->
|
||||
send_element(StateData,
|
||||
?POLICY_VIOLATION_ERR((StateData#state.lang), E)),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
session_established({xmlstreamerror, _}, StateData) ->
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
session_established(closed, #state{mgmt_state = active} = StateData) ->
|
||||
catch (StateData#state.sockmod):close(StateData#state.socket),
|
||||
@@ -1283,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).
|
||||
@@ -1337,7 +1343,6 @@ handle_info(kick, StateName, StateData) ->
|
||||
handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData);
|
||||
handle_info({kick, Reason, Xmlelement}, _StateName, StateData) ->
|
||||
send_element(StateData, Xmlelement),
|
||||
send_trailer(StateData),
|
||||
{stop, normal,
|
||||
StateData#state{authenticated = Reason}};
|
||||
handle_info({route, _From, _To, {broadcast, Data}},
|
||||
@@ -1350,7 +1355,6 @@ handle_info({route, _From, _To, {broadcast, Data}},
|
||||
{exit, Reason} ->
|
||||
Lang = StateData#state.lang,
|
||||
send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)),
|
||||
catch send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
{privacy_list, PrivList, PrivListName} ->
|
||||
case ejabberd_hooks:run_fold(privacy_updated_list,
|
||||
@@ -1571,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;
|
||||
@@ -1621,7 +1631,6 @@ handle_info({route, From, To,
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> -> ok;
|
||||
<<"headline">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
Err =
|
||||
jlib:make_error_reply(Packet,
|
||||
@@ -1664,11 +1673,9 @@ handle_info(system_shutdown, StateName, StateData) ->
|
||||
wait_for_stream ->
|
||||
send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>),
|
||||
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
|
||||
send_trailer(StateData),
|
||||
ok;
|
||||
_ ->
|
||||
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
|
||||
send_trailer(StateData),
|
||||
ok
|
||||
end,
|
||||
{stop, normal, StateData};
|
||||
@@ -1767,8 +1774,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,
|
||||
@@ -1793,13 +1799,26 @@ 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
|
||||
end
|
||||
end,
|
||||
catch send_trailer(StateData),
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
@@ -1808,8 +1827,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).
|
||||
|
||||
@@ -2275,30 +2295,32 @@ get_priority_from_presence(PresencePacket) ->
|
||||
end.
|
||||
|
||||
process_privacy_iq(From, To,
|
||||
#iq{type = Type, sub_el = SubEl} = IQ, StateData) ->
|
||||
{Res, NewStateData} = case Type of
|
||||
get ->
|
||||
R = ejabberd_hooks:run_fold(privacy_iq_get,
|
||||
StateData#state.server,
|
||||
{error,
|
||||
?ERR_FEATURE_NOT_IMPLEMENTED},
|
||||
[From, To, IQ,
|
||||
StateData#state.privacy_list]),
|
||||
{R, StateData};
|
||||
set ->
|
||||
case ejabberd_hooks:run_fold(privacy_iq_set,
|
||||
StateData#state.server,
|
||||
{error,
|
||||
?ERR_FEATURE_NOT_IMPLEMENTED},
|
||||
[From, To, IQ])
|
||||
of
|
||||
{result, R, NewPrivList} ->
|
||||
{{result, R},
|
||||
StateData#state{privacy_list =
|
||||
NewPrivList}};
|
||||
R -> {R, StateData}
|
||||
end
|
||||
end,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ, StateData) ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
{Res, NewStateData} =
|
||||
case Type of
|
||||
get ->
|
||||
R = ejabberd_hooks:run_fold(
|
||||
privacy_iq_get,
|
||||
StateData#state.server,
|
||||
{error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
|
||||
[From, To, IQ,
|
||||
StateData#state.privacy_list]),
|
||||
{R, StateData};
|
||||
set ->
|
||||
case ejabberd_hooks:run_fold(
|
||||
privacy_iq_set,
|
||||
StateData#state.server,
|
||||
{error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
|
||||
[From, To, IQ])
|
||||
of
|
||||
{result, R, NewPrivList} ->
|
||||
{{result, R},
|
||||
StateData#state{privacy_list =
|
||||
NewPrivList}};
|
||||
R -> {R, StateData}
|
||||
end
|
||||
end,
|
||||
IQRes = case Res of
|
||||
{result, Result} ->
|
||||
IQ#iq{type = result, sub_el = Result};
|
||||
@@ -2365,15 +2387,16 @@ process_unauthenticated_stanza(StateData, El) ->
|
||||
_ -> El
|
||||
end,
|
||||
case jlib:iq_query_info(NewEl) of
|
||||
#iq{} = IQ ->
|
||||
#iq{lang = L} = IQ ->
|
||||
Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
|
||||
StateData#state.server, empty,
|
||||
[StateData#state.server, IQ,
|
||||
StateData#state.ip]),
|
||||
case Res of
|
||||
empty ->
|
||||
Txt = <<"Authentication required">>,
|
||||
ResIQ = IQ#iq{type = error,
|
||||
sub_el = [?ERR_SERVICE_UNAVAILABLE]},
|
||||
sub_el = [?ERRT_SERVICE_UNAVAILABLE(L, Txt)]},
|
||||
Res1 = jlib:replace_from_to(jid:make(<<"">>,
|
||||
StateData#state.server,
|
||||
<<"">>),
|
||||
@@ -2419,7 +2442,6 @@ fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} =
|
||||
Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang,
|
||||
<<"Too many unacked stanzas">>),
|
||||
send_element(StateData, Err),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData#state{mgmt_resend = false}};
|
||||
fsm_next_state(session_established, #state{mgmt_state = pending} = StateData) ->
|
||||
fsm_next_state(wait_for_resume, StateData);
|
||||
@@ -2592,9 +2614,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).
|
||||
@@ -2725,6 +2747,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;
|
||||
@@ -2761,7 +2785,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)]),
|
||||
@@ -2783,7 +2807,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;
|
||||
@@ -2838,9 +2864,10 @@ 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 ->
|
||||
@@ -2860,9 +2887,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) ->
|
||||
@@ -2879,6 +2907,7 @@ handle_unacked_stanzas(StateData)
|
||||
false
|
||||
end
|
||||
end,
|
||||
Lang = StateData#state.lang,
|
||||
ReRoute = case ResendOnTimeout of
|
||||
true ->
|
||||
fun(From, To, El, Time) ->
|
||||
@@ -2887,9 +2916,11 @@ handle_unacked_stanzas(StateData)
|
||||
end;
|
||||
false ->
|
||||
fun(From, To, El, _Time) ->
|
||||
Txt = <<"User session terminated">>,
|
||||
Err =
|
||||
jlib:make_error_reply(El,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
jlib:make_error_reply(
|
||||
El,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end,
|
||||
@@ -2897,7 +2928,9 @@ handle_unacked_stanzas(StateData)
|
||||
?DEBUG("Dropping presence stanza from ~s",
|
||||
[jid:to_string(From)]);
|
||||
(From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
|
||||
Err = jlib:make_error_reply(El, ?ERR_SERVICE_UNAVAILABLE),
|
||||
Txt = <<"User session terminated">>,
|
||||
Err = jlib:make_error_reply(
|
||||
El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
(From, To, El, Time) ->
|
||||
%% We'll drop the stanza if it was <forwarded/> by some
|
||||
@@ -2959,7 +2992,17 @@ 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
|
||||
@@ -2988,7 +3031,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,
|
||||
@@ -3021,65 +3063,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
|
||||
|
||||
+255
-121
@@ -90,7 +90,8 @@
|
||||
%%% PowFloat = math:pow(Base, Exponent),
|
||||
%%% round(PowFloat).</pre>
|
||||
%%%
|
||||
%%% Since this function will be called by ejabberd_commands, it must be exported.
|
||||
%%% Since this function will be called by ejabberd_commands, it must
|
||||
%%% be exported.
|
||||
%%% Add to your module:
|
||||
%%% <pre>-export([calc_power/2]).</pre>
|
||||
%%%
|
||||
@@ -201,24 +202,35 @@
|
||||
%%% TODO: consider this feature:
|
||||
%%% All commands are catched. If an error happens, return the restuple:
|
||||
%%% {error, flattened error string}
|
||||
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
|
||||
%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
|
||||
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc)
|
||||
%%% need to allows this. And ejabberd_xmlrpc must be prepared to
|
||||
%%% handle such an unexpected response.
|
||||
|
||||
|
||||
-module(ejabberd_commands).
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-define(DEFAULT_VERSION, 1000000).
|
||||
|
||||
-export([init/0,
|
||||
list_commands/0,
|
||||
list_commands/1,
|
||||
get_command_format/1,
|
||||
get_command_format/2,
|
||||
get_command_format/2,
|
||||
get_command_format/3,
|
||||
get_command_policy/1,
|
||||
get_command_definition/1,
|
||||
get_command_definition/2,
|
||||
get_tags_commands/0,
|
||||
get_tags_commands/1,
|
||||
get_commands/0,
|
||||
register_commands/1,
|
||||
unregister_commands/1,
|
||||
execute_command/2,
|
||||
execute_command/4,
|
||||
execute_command/3,
|
||||
execute_command/4,
|
||||
execute_command/5,
|
||||
execute_command/6,
|
||||
opt_type/1,
|
||||
get_commands_spec/0
|
||||
]).
|
||||
@@ -226,6 +238,7 @@
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-define(POLICY_ACCESS, '$policy').
|
||||
|
||||
@@ -260,23 +273,26 @@ get_commands_spec() ->
|
||||
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
|
||||
result_example = ok}].
|
||||
init() ->
|
||||
ets:new(ejabberd_commands, [named_table, set, public,
|
||||
{keypos, #ejabberd_commands.name}]),
|
||||
mnesia:delete_table(ejabberd_commands),
|
||||
mnesia:create_table(ejabberd_commands,
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, ejabberd_commands)},
|
||||
{type, bag}]),
|
||||
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
|
||||
register_commands(get_commands_spec()).
|
||||
|
||||
-spec register_commands([ejabberd_commands()]) -> ok.
|
||||
|
||||
%% @doc Register ejabberd commands.
|
||||
%% If a command is already registered, a warning is printed and the old command is preserved.
|
||||
%% If a command is already registered, a warning is printed and the
|
||||
%% old command is preserved.
|
||||
register_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
case ets:insert_new(ejabberd_commands, Command) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
?DEBUG("This command is already defined:~n~p", [Command])
|
||||
end
|
||||
% XXX check if command exists
|
||||
mnesia:dirty_write(Command)
|
||||
% ?DEBUG("This command is already defined:~n~p", [Command])
|
||||
end,
|
||||
Commands).
|
||||
|
||||
@@ -286,7 +302,7 @@ register_commands(Commands) ->
|
||||
unregister_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
ets:delete_object(ejabberd_commands, Command)
|
||||
mnesia:dirty_delete_object(Command)
|
||||
end,
|
||||
Commands).
|
||||
|
||||
@@ -294,94 +310,197 @@ unregister_commands(Commands) ->
|
||||
|
||||
%% @doc Get a list of all the available commands, arguments and description.
|
||||
list_commands() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = '$1',
|
||||
args = '$2',
|
||||
desc = '$3',
|
||||
_ = '_'}),
|
||||
[{A, B, C} || [A, B, C] <- Commands].
|
||||
list_commands(?DEFAULT_VERSION).
|
||||
|
||||
-spec list_commands_policy() -> [{atom(), [aterm()], string(), atom()}].
|
||||
-spec list_commands(integer()) -> [{atom(), [aterm()], string()}].
|
||||
|
||||
%% @doc Get a list of all the available commands, arguments, description, and
|
||||
%% policy.
|
||||
list_commands_policy() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = '$1',
|
||||
args = '$2',
|
||||
desc = '$3',
|
||||
policy = '$4',
|
||||
_ = '_'}),
|
||||
[{A, B, C, D} || [A, B, C, D] <- Commands].
|
||||
%% @doc Get a list of all the available commands, arguments and
|
||||
%% description in a given API verion.
|
||||
list_commands(Version) ->
|
||||
Commands = get_commands_definition(Version),
|
||||
[{Name, Args, Desc} || #ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc} <- Commands].
|
||||
|
||||
-spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
|
||||
|
||||
-spec list_commands_policy(integer()) ->
|
||||
[{atom(), [aterm()], string(), atom()}].
|
||||
|
||||
%% @doc Get a list of all the available commands, arguments,
|
||||
%% description, and policy in a given API version.
|
||||
list_commands_policy(Version) ->
|
||||
Commands = get_commands_definition(Version),
|
||||
[{Name, Args, Desc, Policy} ||
|
||||
#ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc,
|
||||
policy = Policy} <- Commands].
|
||||
|
||||
-spec get_command_format(atom()) -> {[aterm()], rterm()}.
|
||||
|
||||
%% @doc Get the format of arguments and result of a command.
|
||||
get_command_format(Name) ->
|
||||
get_command_format(Name, noauth).
|
||||
get_command_format(Name, noauth, ?DEFAULT_VERSION).
|
||||
get_command_format(Name, Version) when is_integer(Version) ->
|
||||
get_command_format(Name, noauth, Version);
|
||||
get_command_format(Name, Auth) ->
|
||||
get_command_format(Name, Auth, ?DEFAULT_VERSION).
|
||||
|
||||
get_command_format(Name, Auth) ->
|
||||
Admin = is_admin(Name, Auth),
|
||||
Matched = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = Name,
|
||||
args = '$1',
|
||||
result = '$2',
|
||||
policy = '$3',
|
||||
_ = '_'}),
|
||||
case Matched of
|
||||
[] ->
|
||||
{error, command_unknown};
|
||||
[[Args, Result, user]] when Admin;
|
||||
Auth == noauth ->
|
||||
-spec get_command_format(atom(),
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
integer()) ->
|
||||
{[aterm()], rterm()}.
|
||||
|
||||
get_command_format(Name, Auth, Version) ->
|
||||
Admin = is_admin(Name, Auth, #{}),
|
||||
#ejabberd_commands{args = Args,
|
||||
result = Result,
|
||||
policy = Policy} =
|
||||
get_command_definition(Name, Version),
|
||||
case Policy of
|
||||
user when Admin;
|
||||
Auth == noauth ->
|
||||
{[{user, binary}, {server, binary} | Args], Result};
|
||||
[[Args, Result, _]] ->
|
||||
_ ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
-spec get_command_definition(atom()) -> ejabberd_commands() | command_not_found.
|
||||
-spec get_command_policy(atom()) -> {ok, open|user|admin|restricted} | {error, command_not_found}.
|
||||
|
||||
%% @doc return command policy.
|
||||
get_command_policy(Name) ->
|
||||
case get_command_definition(Name) of
|
||||
#ejabberd_commands{policy = Policy} ->
|
||||
{ok, Policy};
|
||||
command_not_found ->
|
||||
{error, command_not_found}
|
||||
end.
|
||||
|
||||
-spec get_command_definition(atom()) -> ejabberd_commands().
|
||||
|
||||
%% @doc Get the definition record of a command.
|
||||
get_command_definition(Name) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[E] -> E;
|
||||
[] -> command_not_found
|
||||
get_command_definition(Name, ?DEFAULT_VERSION).
|
||||
|
||||
-spec get_command_definition(atom(), integer()) -> ejabberd_commands().
|
||||
|
||||
%% @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)
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
|
||||
%% @doc Execute a command.
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command([], noauth, Name, Arguments).
|
||||
-spec get_commands_definition(integer()) -> [ejabberd_commands()].
|
||||
|
||||
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
atom(),
|
||||
[any()]
|
||||
% @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)))),
|
||||
F = fun({_Name, _V, Command}, []) ->
|
||||
[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
|
||||
%% where
|
||||
%% Arguments = [any()]
|
||||
%% @doc Execute a command.
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command(Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command(atom(),
|
||||
[any()],
|
||||
integer() |
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin
|
||||
) -> any().
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
|
||||
%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
|
||||
%% where
|
||||
%% Auth = {User::string(), Server::string(), Password::string(),
|
||||
%% Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided
|
||||
execute_command(Name, Arguments, Version) when is_integer(Version) ->
|
||||
execute_command([], noauth, Name, Arguments, Version);
|
||||
execute_command(Name, Arguments, Auth) ->
|
||||
execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
|
||||
%% ResultTerm | {error, Error}
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Method = atom()
|
||||
%% Arguments = [any()]
|
||||
%% Error = command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments) ->
|
||||
Auth = case is_admin(Name, Auth1) of
|
||||
%%
|
||||
%% @doc Execute a command
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands, Auth, Name, Arguments) ->
|
||||
execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
atom(),
|
||||
[any()],
|
||||
integer()
|
||||
) -> any().
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
|
||||
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,
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[Command] ->
|
||||
AccessCommands = get_access_commands(AccessCommands1),
|
||||
try check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
|
||||
ok -> execute_command2(Auth, Command, Arguments)
|
||||
catch
|
||||
{error, Error} -> {error, Error}
|
||||
end;
|
||||
[] -> {error, command_unknown}
|
||||
Command = get_command_definition(Name, Version),
|
||||
AccessCommands = get_access_commands(AccessCommands1, Version),
|
||||
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
|
||||
ok -> execute_command2(Auth, Command, Arguments)
|
||||
end.
|
||||
|
||||
execute_command2(
|
||||
@@ -407,26 +526,25 @@ execute_command2(Command, Arguments) ->
|
||||
Module = Command#ejabberd_commands.module,
|
||||
Function = Command#ejabberd_commands.function,
|
||||
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
|
||||
try apply(Module, Function, Arguments) of
|
||||
Response ->
|
||||
Response
|
||||
catch
|
||||
Problem ->
|
||||
{error, Problem}
|
||||
end.
|
||||
apply(Module, Function, Arguments).
|
||||
|
||||
-spec get_tags_commands() -> [{string(), [string()]}].
|
||||
|
||||
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands.
|
||||
get_tags_commands() ->
|
||||
CommandTags = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{
|
||||
name = '$1',
|
||||
tags = '$2',
|
||||
_ = '_'}),
|
||||
get_tags_commands(?DEFAULT_VERSION).
|
||||
|
||||
-spec get_tags_commands(integer()) -> [{string(), [string()]}].
|
||||
|
||||
%% @spec (integer) -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands in a given API version
|
||||
get_tags_commands(Version) ->
|
||||
CommandTags = [{Name, Tags} ||
|
||||
#ejabberd_commands{name = Name, tags = Tags}
|
||||
<- get_commands_definition(Version)],
|
||||
Dict = lists:foldl(
|
||||
fun([CommandNameAtom, CTags], D) ->
|
||||
fun({CommandNameAtom, CTags}, D) ->
|
||||
CommandName = atom_to_list(CommandNameAtom),
|
||||
case CTags of
|
||||
[] ->
|
||||
@@ -445,7 +563,6 @@ get_tags_commands() ->
|
||||
CommandTags),
|
||||
orddict:to_list(Dict).
|
||||
|
||||
|
||||
%% -----------------------------
|
||||
%% Access verification
|
||||
%% -----------------------------
|
||||
@@ -460,9 +577,9 @@ get_tags_commands() ->
|
||||
%% 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, {_, _, _, _}} ->
|
||||
@@ -477,18 +594,20 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
|
||||
AccessCommandsAllowed =
|
||||
lists:filter(
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Command, Access, Auth) of
|
||||
case check_access(Command, Access, Auth, CallerInfo) of
|
||||
true ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
({Access, Commands}) ->
|
||||
ArgumentRestrictions = [],
|
||||
case check_access(Command, Access, Auth) of
|
||||
case check_access(Command, Access, Auth, CallerInfo) of
|
||||
true ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
@@ -522,38 +641,47 @@ 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.
|
||||
|
||||
check_access_command(Commands, Command, ArgumentRestrictions, Method, Arguments) ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
Method, Arguments) ->
|
||||
case Commands==all orelse lists:member(Method, Commands) of
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions, Arguments);
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions,
|
||||
Arguments);
|
||||
false -> false
|
||||
end.
|
||||
|
||||
@@ -577,18 +705,21 @@ tag_arguments(ArgsDefs, Args) ->
|
||||
Args).
|
||||
|
||||
|
||||
get_access_commands(undefined) ->
|
||||
Cmds = get_commands(),
|
||||
get_access_commands(undefined, Version) ->
|
||||
Cmds = get_commands(Version),
|
||||
[{?POLICY_ACCESS, Cmds, []}];
|
||||
get_access_commands(AccessCommands) ->
|
||||
get_access_commands(AccessCommands, _Version) ->
|
||||
AccessCommands.
|
||||
|
||||
get_commands() ->
|
||||
Opts = ejabberd_config:get_option(
|
||||
get_commands(?DEFAULT_VERSION).
|
||||
get_commands(Version) ->
|
||||
Opts0 = ejabberd_config:get_option(
|
||||
commands,
|
||||
fun(V) when is_list(V) -> V end,
|
||||
[]),
|
||||
CommandsList = list_commands_policy(),
|
||||
Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
|
||||
CommandsList = list_commands_policy(Version),
|
||||
OpenCmds = [N || {N, _, _, open} <- CommandsList],
|
||||
RestrictedCmds = [N || {N, _, _, restricted} <- CommandsList],
|
||||
AdminCmds = [N || {N, _, _, admin} <- CommandsList],
|
||||
@@ -617,29 +748,32 @@ get_commands() ->
|
||||
end, AdminCmds ++ UserCmds, Opts),
|
||||
Cmds.
|
||||
|
||||
is_admin(_Name, noauth) ->
|
||||
false;
|
||||
is_admin(_Name, admin) ->
|
||||
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].
|
||||
|
||||
+189
-55
@@ -30,12 +30,12 @@
|
||||
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,
|
||||
transform_options/1, collect_options/1,
|
||||
convert_to_yaml/1, convert_to_yaml/2,
|
||||
transform_options/1, collect_options/1, default_db/2,
|
||||
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
|
||||
env_binary_to_list/2, opt_type/1, may_hide_data/1]).
|
||||
|
||||
-export([start/2]).
|
||||
@@ -93,7 +93,7 @@ hosts_to_start(State) ->
|
||||
-spec(start/2 :: (Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok).
|
||||
start(Hosts, Opts) ->
|
||||
mnesia_init(),
|
||||
set_opts(#state{hosts = Hosts, opts = Opts}).
|
||||
set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})).
|
||||
|
||||
mnesia_init() ->
|
||||
case catch mnesia:table_info(local_config, storage_type) of
|
||||
@@ -227,6 +227,7 @@ 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 +247,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 +275,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 +498,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 +513,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),
|
||||
@@ -651,14 +676,42 @@ process_host_term(Term, Host, State, Action) ->
|
||||
{hosts, _} ->
|
||||
State;
|
||||
{Opt, Val} when Action == set ->
|
||||
set_option({Opt, Host}, Val, State);
|
||||
set_option({rename_option(Opt), Host}, change_val(Opt, Val), State);
|
||||
{Opt, Val} when Action == append ->
|
||||
append_option({Opt, Host}, Val, State);
|
||||
append_option({rename_option(Opt), Host}, change_val(Opt, Val), State);
|
||||
Opt ->
|
||||
?WARNING_MSG("Ignore invalid (outdated?) option ~p", [Opt]),
|
||||
State
|
||||
end.
|
||||
|
||||
rename_option(Option) when is_atom(Option) ->
|
||||
case atom_to_list(Option) of
|
||||
"odbc_" ++ T ->
|
||||
NewOption = list_to_atom("sql_" ++ T),
|
||||
?WARNING_MSG("Option '~s' is obsoleted, use '~s' instead",
|
||||
[Option, NewOption]),
|
||||
NewOption;
|
||||
_ ->
|
||||
Option
|
||||
end;
|
||||
rename_option(Option) ->
|
||||
Option.
|
||||
|
||||
change_val(auth_method, Val) ->
|
||||
prepare_opt_val(auth_method, Val,
|
||||
fun(V) ->
|
||||
L = if is_list(V) -> V;
|
||||
true -> [V]
|
||||
end,
|
||||
lists:map(
|
||||
fun(odbc) -> sql;
|
||||
(internal) -> mnesia;
|
||||
(A) when is_atom(A) -> A
|
||||
end, L)
|
||||
end, [mnesia]);
|
||||
change_val(_Opt, Val) ->
|
||||
Val.
|
||||
|
||||
set_option(Opt, Val, State) ->
|
||||
State#state{opts = [#local_config{key = Opt, value = Val} |
|
||||
State#state.opts]}.
|
||||
@@ -717,24 +770,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', _} ->
|
||||
?INFO_MSG("Configuration problem:~n"
|
||||
"** Option: ~s~n"
|
||||
"** Invalid value: ~s~n"
|
||||
"** Using as fallback: ~s",
|
||||
[format_term(Opt),
|
||||
format_term(Val),
|
||||
format_term(Default)]),
|
||||
Default;
|
||||
_ ->
|
||||
Res
|
||||
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
|
||||
end.
|
||||
|
||||
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
|
||||
@@ -787,9 +848,61 @@ 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
|
||||
ets:insert(module_db, {mod_pubsub, mnesia}),
|
||||
ets:insert(module_db, {mod_pubsub, sql}),
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
case re:split(atom_to_list(M), "_", [{return, list}]) of
|
||||
[_] ->
|
||||
ok;
|
||||
Parts ->
|
||||
[Suffix|T] = lists:reverse(Parts),
|
||||
BareMod = string:join(lists:reverse(T), "_"),
|
||||
ets:insert(module_db, {list_to_atom(BareMod),
|
||||
list_to_atom(Suffix)})
|
||||
end
|
||||
end, Modules).
|
||||
|
||||
-spec v_db(module(), atom()) -> atom().
|
||||
|
||||
v_db(Mod, internal) -> v_db(Mod, mnesia);
|
||||
v_db(Mod, odbc) -> v_db(Mod, sql);
|
||||
v_db(Mod, Type) ->
|
||||
case ets:match_object(module_db, {Mod, Type}) of
|
||||
[_|_] -> Type;
|
||||
[] -> erlang:error(badarg)
|
||||
end.
|
||||
|
||||
-spec default_db(binary(), module()) -> atom().
|
||||
|
||||
default_db(Host, Module) ->
|
||||
case ejabberd_config:get_option(
|
||||
{default_db, Host}, fun(T) when is_atom(T) -> T end) of
|
||||
undefined ->
|
||||
mnesia;
|
||||
DBType ->
|
||||
try
|
||||
v_db(Module, DBType)
|
||||
catch error:badarg ->
|
||||
?WARNING_MSG("Module '~s' doesn't support database '~s' "
|
||||
"defined in option 'default_db', using "
|
||||
"'mnesia' as fallback", [Module, DBType]),
|
||||
mnesia
|
||||
end
|
||||
end.
|
||||
|
||||
get_modules_with_options() ->
|
||||
{ok, Mods} = application:get_key(ejabberd, modules),
|
||||
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
|
||||
AllMods = [?MODULE|ExtMods++Mods],
|
||||
init_module_db_table(AllMods),
|
||||
lists:foldl(
|
||||
fun(Mod, D) ->
|
||||
case catch Mod:opt_type('') of
|
||||
@@ -801,23 +914,30 @@ get_modules_with_options() ->
|
||||
{'EXIT', {undef, _}} ->
|
||||
D
|
||||
end
|
||||
end, dict:new(), [?MODULE|ExtMods++Mods]).
|
||||
end, dict:new(), AllMods).
|
||||
|
||||
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"
|
||||
@@ -829,11 +949,25 @@ validate_opts(#state{opts = Opts} = State) ->
|
||||
|
||||
-spec get_vh_by_auth_method(atom()) -> [binary()].
|
||||
|
||||
%% Return the list of hosts handled by a given module
|
||||
%% Return the list of hosts with a given auth method
|
||||
get_vh_by_auth_method(AuthMethod) ->
|
||||
mnesia:dirty_select(local_config,
|
||||
[{#local_config{key = {auth_method, '$1'},
|
||||
value=AuthMethod},[],['$1']}]).
|
||||
Cfgs = mnesia:dirty_match_object(local_config,
|
||||
#local_config{key = {auth_method, '_'},
|
||||
_ = '_'}),
|
||||
lists:flatmap(
|
||||
fun(#local_config{key = {auth_method, Host}, value = M}) ->
|
||||
Methods = if not is_list(M) -> [M];
|
||||
true -> M
|
||||
end,
|
||||
case lists:member(AuthMethod, Methods) of
|
||||
true when Host == global ->
|
||||
get_myhosts();
|
||||
true ->
|
||||
[Host];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
end, Cfgs).
|
||||
|
||||
%% @spec (Path::string()) -> true | false
|
||||
is_file_readable(Path) ->
|
||||
@@ -867,20 +1001,20 @@ get_mylang() ->
|
||||
fun iolist_to_binary/1,
|
||||
<<"en">>).
|
||||
|
||||
replace_module(mod_announce_odbc) -> {mod_announce, odbc};
|
||||
replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
|
||||
replace_module(mod_caps_odbc) -> {mod_caps, odbc};
|
||||
replace_module(mod_irc_odbc) -> {mod_irc, odbc};
|
||||
replace_module(mod_last_odbc) -> {mod_last, odbc};
|
||||
replace_module(mod_muc_odbc) -> {mod_muc, odbc};
|
||||
replace_module(mod_offline_odbc) -> {mod_offline, odbc};
|
||||
replace_module(mod_privacy_odbc) -> {mod_privacy, odbc};
|
||||
replace_module(mod_private_odbc) -> {mod_private, odbc};
|
||||
replace_module(mod_roster_odbc) -> {mod_roster, odbc};
|
||||
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
|
||||
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
|
||||
replace_module(mod_pubsub_odbc) -> {mod_pubsub, odbc};
|
||||
replace_module(mod_announce_odbc) -> {mod_announce, sql};
|
||||
replace_module(mod_blocking_odbc) -> {mod_blocking, sql};
|
||||
replace_module(mod_caps_odbc) -> {mod_caps, sql};
|
||||
replace_module(mod_irc_odbc) -> {mod_irc, sql};
|
||||
replace_module(mod_last_odbc) -> {mod_last, sql};
|
||||
replace_module(mod_muc_odbc) -> {mod_muc, sql};
|
||||
replace_module(mod_offline_odbc) -> {mod_offline, sql};
|
||||
replace_module(mod_privacy_odbc) -> {mod_privacy, sql};
|
||||
replace_module(mod_private_odbc) -> {mod_private, sql};
|
||||
replace_module(mod_roster_odbc) -> {mod_roster, sql};
|
||||
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
|
||||
replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
|
||||
replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
|
||||
replace_module(Module) ->
|
||||
case is_elixir_module(Module) of
|
||||
true -> expand_elixir_module(Module);
|
||||
@@ -985,7 +1119,7 @@ transform_terms(Terms) ->
|
||||
mod_last,
|
||||
ejabberd_s2s,
|
||||
ejabberd_listener,
|
||||
ejabberd_odbc_sup,
|
||||
ejabberd_sql_sup,
|
||||
shaper,
|
||||
ejabberd_s2s_out,
|
||||
acl,
|
||||
|
||||
+86
-56
@@ -57,6 +57,8 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(DEFAULT_VERSION, 1000000).
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Module
|
||||
@@ -69,7 +71,7 @@ start() ->
|
||||
[SNode3 | Args3] ->
|
||||
[SNode3, 60000, Args3];
|
||||
_ ->
|
||||
print_usage(),
|
||||
print_usage(?DEFAULT_VERSION),
|
||||
halt(?STATUS_USAGE)
|
||||
end,
|
||||
SNode1 = case string:tokens(SNode, "@") of
|
||||
@@ -93,6 +95,9 @@ start() ->
|
||||
[Node, Reason]),
|
||||
%% TODO: show minimal start help
|
||||
?STATUS_BADRPC;
|
||||
{invalid_version, V} ->
|
||||
print("Invalid API version number: ~p~n", [V]),
|
||||
?STATUS_ERROR;
|
||||
S ->
|
||||
S
|
||||
end,
|
||||
@@ -126,11 +131,17 @@ unregister_commands(CmdDescs, Module, Function) ->
|
||||
%% Process
|
||||
%%-----------------------------
|
||||
|
||||
|
||||
-spec process([string()]) -> non_neg_integer().
|
||||
process(Args) ->
|
||||
process(Args, ?DEFAULT_VERSION).
|
||||
|
||||
|
||||
-spec process([string()], non_neg_integer()) -> non_neg_integer().
|
||||
|
||||
%% The commands status, stop and restart are defined here to ensure
|
||||
%% they are usable even if ejabberd is completely stopped.
|
||||
process(["status"]) ->
|
||||
process(["status"], _Version) ->
|
||||
{InternalStatus, ProvidedStatus} = init:get_status(),
|
||||
print("The node ~p is ~p with status: ~p~n",
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
@@ -146,24 +157,24 @@ process(["status"]) ->
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
process(["stop"]) ->
|
||||
process(["stop"], _Version) ->
|
||||
%%ejabberd_cover:stop(),
|
||||
init:stop(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["restart"]) ->
|
||||
process(["restart"], _Version) ->
|
||||
init:restart(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia"]) ->
|
||||
process(["mnesia"], _Version) ->
|
||||
print("~p~n", [mnesia:system_info(all)]),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia", "info"]) ->
|
||||
process(["mnesia", "info"], _Version) ->
|
||||
mnesia:info(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia", Arg]) ->
|
||||
process(["mnesia", Arg], _Version) ->
|
||||
case catch mnesia:system_info(list_to_atom(Arg)) of
|
||||
{'EXIT', Error} -> print("Error: ~p~n", [Error]);
|
||||
Return -> print("~p~n", [Return])
|
||||
@@ -172,23 +183,23 @@ process(["mnesia", Arg]) ->
|
||||
|
||||
%% The arguments --long and --dual are not documented because they are
|
||||
%% automatically selected depending in the number of columns of the shell
|
||||
process(["help" | Mode]) ->
|
||||
process(["help" | Mode], Version) ->
|
||||
{MaxC, ShCode} = get_shell_info(),
|
||||
case Mode of
|
||||
[] ->
|
||||
print_usage(dual, MaxC, ShCode),
|
||||
print_usage(dual, MaxC, ShCode, Version),
|
||||
?STATUS_USAGE;
|
||||
["--dual"] ->
|
||||
print_usage(dual, MaxC, ShCode),
|
||||
print_usage(dual, MaxC, ShCode, Version),
|
||||
?STATUS_USAGE;
|
||||
["--long"] ->
|
||||
print_usage(long, MaxC, ShCode),
|
||||
print_usage(long, MaxC, ShCode, Version),
|
||||
?STATUS_USAGE;
|
||||
["--tags"] ->
|
||||
print_usage_tags(MaxC, ShCode),
|
||||
print_usage_tags(MaxC, ShCode, Version),
|
||||
?STATUS_SUCCESS;
|
||||
["--tags", Tag] ->
|
||||
print_usage_tags(Tag, MaxC, ShCode),
|
||||
print_usage_tags(Tag, MaxC, ShCode, Version),
|
||||
?STATUS_SUCCESS;
|
||||
["help"] ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
@@ -196,13 +207,22 @@ process(["help" | Mode]) ->
|
||||
[CmdString | _] ->
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
|
||||
print_usage_commands2(binary_to_list(CmdStringU), MaxC, ShCode, Version),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
process(Args) ->
|
||||
process(["--version", Arg | Args], _) ->
|
||||
Version =
|
||||
try
|
||||
list_to_integer(Arg)
|
||||
catch _:_ ->
|
||||
throw({invalid_version, Arg})
|
||||
end,
|
||||
process(Args, Version);
|
||||
|
||||
process(Args, Version) ->
|
||||
AccessCommands = get_accesscommands(),
|
||||
{String, Code} = process2(Args, AccessCommands),
|
||||
{String, Code} = process2(Args, AccessCommands, Version),
|
||||
case String of
|
||||
[] -> ok;
|
||||
_ ->
|
||||
@@ -211,18 +231,25 @@ process(Args) ->
|
||||
Code.
|
||||
|
||||
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
|
||||
process2(["--auth", User, Server, Pass | Args], AccessCommands) ->
|
||||
process2(Args, {list_to_binary(User), list_to_binary(Server), list_to_binary(Pass), true}, AccessCommands);
|
||||
process2(Args, AccessCommands) ->
|
||||
process2(Args, noauth, AccessCommands).
|
||||
process2(Args, AccessCommands, ?DEFAULT_VERSION).
|
||||
|
||||
process2(Args, Auth, AccessCommands) ->
|
||||
case try_run_ctp(Args, Auth, AccessCommands) of
|
||||
%% @spec (Args::[string()], AccessCommands, Version) -> {String::string(), Code::integer()}
|
||||
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, noauth, Version).
|
||||
|
||||
|
||||
|
||||
process2(Args, AccessCommands, Auth, Version) ->
|
||||
case try_run_ctp(Args, Auth, AccessCommands, Version) of
|
||||
{String, wrong_command_arguments}
|
||||
when is_list(String) ->
|
||||
io:format(lists:flatten(["\n" | String]++["\n"])),
|
||||
[CommandString | _] = Args,
|
||||
process(["help" | [CommandString]]),
|
||||
process(["help" | [CommandString]], Version),
|
||||
{lists:flatten(String), ?STATUS_ERROR};
|
||||
{String, Code}
|
||||
when is_list(String) and is_integer(Code) ->
|
||||
@@ -246,29 +273,29 @@ get_accesscommands() ->
|
||||
%%-----------------------------
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
|
||||
try_run_ctp(Args, Auth, AccessCommands) ->
|
||||
try_run_ctp(Args, Auth, AccessCommands, Version) ->
|
||||
try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
|
||||
false when Args /= [] ->
|
||||
try_call_command(Args, Auth, AccessCommands);
|
||||
try_call_command(Args, Auth, AccessCommands, Version);
|
||||
false ->
|
||||
print_usage(),
|
||||
print_usage(Version),
|
||||
{"", ?STATUS_USAGE};
|
||||
Status ->
|
||||
{"", Status}
|
||||
catch
|
||||
exit:Why ->
|
||||
print_usage(),
|
||||
print_usage(Version),
|
||||
{io_lib:format("Error in ejabberd ctl process: ~p", [Why]), ?STATUS_USAGE};
|
||||
Error:Why ->
|
||||
%% In this case probably ejabberd is not started, so let's show Status
|
||||
process(["status"]),
|
||||
process(["status"], Version),
|
||||
print("~n", []),
|
||||
{io_lib:format("Error in ejabberd ctl process: '~p' ~p", [Error, Why]), ?STATUS_USAGE}
|
||||
end.
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
|
||||
try_call_command(Args, Auth, AccessCommands) ->
|
||||
try call_command(Args, Auth, AccessCommands) of
|
||||
try_call_command(Args, Auth, AccessCommands, Version) ->
|
||||
try call_command(Args, Auth, AccessCommands, Version) of
|
||||
{error, command_unknown} ->
|
||||
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
|
||||
{error, wrong_command_arguments} ->
|
||||
@@ -276,24 +303,28 @@ try_call_command(Args, Auth, AccessCommands) ->
|
||||
Res ->
|
||||
Res
|
||||
catch
|
||||
throw:Error ->
|
||||
{io_lib:format("~p", [Error]), ?STATUS_ERROR};
|
||||
A:Why ->
|
||||
Stack = erlang:get_stacktrace(),
|
||||
{io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR}
|
||||
end.
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
|
||||
call_command([CmdString | Args], Auth, AccessCommands) ->
|
||||
call_command([CmdString | Args], Auth, AccessCommands, Version) ->
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
Command = list_to_atom(binary_to_list(CmdStringU)),
|
||||
case ejabberd_commands:get_command_format(Command, Auth) of
|
||||
case ejabberd_commands:get_command_format(Command, Auth, Version) of
|
||||
{error, command_unknown} ->
|
||||
{error, command_unknown};
|
||||
{ArgsFormat, ResultFormat} ->
|
||||
case (catch format_args(Args, ArgsFormat)) of
|
||||
ArgsFormatted when is_list(ArgsFormatted) ->
|
||||
Result = ejabberd_commands:execute_command(AccessCommands, Auth, Command,
|
||||
ArgsFormatted),
|
||||
Result = ejabberd_commands:execute_command(AccessCommands,
|
||||
Auth, Command,
|
||||
ArgsFormatted,
|
||||
Version),
|
||||
format_result(Result, ResultFormat);
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
@@ -404,8 +435,8 @@ make_status(ok) -> ?STATUS_SUCCESS;
|
||||
make_status(true) -> ?STATUS_SUCCESS;
|
||||
make_status(_Error) -> ?STATUS_ERROR.
|
||||
|
||||
get_list_commands() ->
|
||||
try ejabberd_commands:list_commands() of
|
||||
get_list_commands(Version) ->
|
||||
try ejabberd_commands:list_commands(Version) of
|
||||
Commands ->
|
||||
[tuple_command_help(Command)
|
||||
|| {N,_,_}=Command <- Commands,
|
||||
@@ -458,10 +489,10 @@ get_list_ctls() ->
|
||||
-define(U2, "\e[24m").
|
||||
-define(U(S), case ShCode of true -> [?U1, S, ?U2]; false -> S end).
|
||||
|
||||
print_usage() ->
|
||||
print_usage(Version) ->
|
||||
{MaxC, ShCode} = get_shell_info(),
|
||||
print_usage(dual, MaxC, ShCode).
|
||||
print_usage(HelpMode, MaxC, ShCode) ->
|
||||
print_usage(dual, MaxC, ShCode, Version).
|
||||
print_usage(HelpMode, MaxC, ShCode, Version) ->
|
||||
AllCommands =
|
||||
[
|
||||
{"status", [], "Get ejabberd status"},
|
||||
@@ -469,12 +500,11 @@ print_usage(HelpMode, MaxC, ShCode) ->
|
||||
{"restart", [], "Restart ejabberd"},
|
||||
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},
|
||||
{"mnesia", ["[info]"], "show information of Mnesia system"}] ++
|
||||
get_list_commands() ++
|
||||
get_list_commands(Version) ++
|
||||
get_list_ctls(),
|
||||
|
||||
print(
|
||||
["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--auth ",
|
||||
?U("user"), " ", ?U("host"), " ", ?U("password"), "] ",
|
||||
["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--version ", ?U("api_version"), "] ",
|
||||
?U("command"), " [", ?U("options"), "]\n"
|
||||
"\n"
|
||||
"Available commands in this ejabberd node:\n"], []),
|
||||
@@ -598,9 +628,9 @@ format_command_lines(CALD, _MaxCmdLen, MaxC, ShCode, long) ->
|
||||
%% Print Tags
|
||||
%%-----------------------------
|
||||
|
||||
print_usage_tags(MaxC, ShCode) ->
|
||||
print_usage_tags(MaxC, ShCode, Version) ->
|
||||
print("Available tags and commands:", []),
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(),
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(Version),
|
||||
lists:foreach(
|
||||
fun({Tag, Commands} = _TagCommands) ->
|
||||
print(["\n\n ", ?B(Tag), "\n "], []),
|
||||
@@ -611,10 +641,10 @@ print_usage_tags(MaxC, ShCode) ->
|
||||
TagsCommands),
|
||||
print("\n\n", []).
|
||||
|
||||
print_usage_tags(Tag, MaxC, ShCode) ->
|
||||
print_usage_tags(Tag, MaxC, ShCode, Version) ->
|
||||
print(["Available commands with tag ", ?B(Tag), ":", "\n"], []),
|
||||
HelpMode = long,
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(),
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(Version),
|
||||
CommandsNames = case lists:keysearch(Tag, 1, TagsCommands) of
|
||||
{value, {Tag, CNs}} -> CNs;
|
||||
false -> []
|
||||
@@ -622,7 +652,7 @@ print_usage_tags(Tag, MaxC, ShCode) ->
|
||||
CommandsList = lists:map(
|
||||
fun(NameString) ->
|
||||
C = ejabberd_commands:get_command_definition(
|
||||
list_to_atom(NameString)),
|
||||
list_to_atom(NameString), Version),
|
||||
#ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc} = C,
|
||||
@@ -673,20 +703,20 @@ print_usage_help(MaxC, ShCode) ->
|
||||
%%-----------------------------
|
||||
|
||||
%% @spec (CmdSubString::string(), MaxC::integer(), ShCode::boolean()) -> ok
|
||||
print_usage_commands(CmdSubString, MaxC, ShCode) ->
|
||||
print_usage_commands2(CmdSubString, MaxC, ShCode, Version) ->
|
||||
%% Get which command names match this substring
|
||||
AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands()],
|
||||
AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands(Version)],
|
||||
Cmds = filter_commands(AllCommandsNames, CmdSubString),
|
||||
case Cmds of
|
||||
[] -> io:format("Error: not command found that match: ~p~n", [CmdSubString]);
|
||||
_ -> print_usage_commands2(lists:sort(Cmds), MaxC, ShCode)
|
||||
[] -> io:format("Error: no command found that match: ~p~n", [CmdSubString]);
|
||||
_ -> print_usage_commands3(lists:sort(Cmds), MaxC, ShCode, Version)
|
||||
end.
|
||||
|
||||
print_usage_commands2(Cmds, MaxC, ShCode) ->
|
||||
print_usage_commands3(Cmds, MaxC, ShCode, Version) ->
|
||||
%% Then for each one print it
|
||||
lists:mapfoldl(
|
||||
fun(Cmd, Remaining) ->
|
||||
print_usage_command(Cmd, MaxC, ShCode),
|
||||
print_usage_command(Cmd, MaxC, ShCode, Version),
|
||||
case Remaining > 1 of
|
||||
true -> print([" ", lists:duplicate(MaxC, 126), " \n"], []);
|
||||
false -> ok
|
||||
@@ -716,16 +746,16 @@ filter_commands_regexp(All, Glob) ->
|
||||
All).
|
||||
|
||||
%% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean()) -> ok
|
||||
print_usage_command(Cmd, MaxC, ShCode) ->
|
||||
print_usage_command(Cmd, MaxC, ShCode, Version) ->
|
||||
Name = list_to_atom(Cmd),
|
||||
case ejabberd_commands:get_command_definition(Name) of
|
||||
case ejabberd_commands:get_command_definition(Name, Version) of
|
||||
command_not_found ->
|
||||
io:format("Error: command ~p not known.~n", [Cmd]);
|
||||
C ->
|
||||
print_usage_command(Cmd, C, MaxC, ShCode)
|
||||
print_usage_command2(Cmd, C, MaxC, ShCode)
|
||||
end.
|
||||
|
||||
print_usage_command(Cmd, C, MaxC, ShCode) ->
|
||||
print_usage_command2(Cmd, C, MaxC, ShCode) ->
|
||||
#ejabberd_commands{
|
||||
tags = TagsAtoms,
|
||||
desc = Desc,
|
||||
|
||||
@@ -753,6 +753,7 @@ code_to_phrase(503) -> <<"Service Unavailable">>;
|
||||
code_to_phrase(504) -> <<"Gateway Timeout">>;
|
||||
code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
|
||||
|
||||
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
|
||||
parse_auth(<<"Basic ", Auth64/binary>>) ->
|
||||
Auth = jlib:decode_base64(Auth64),
|
||||
%% Auth should be a string with the format: user@server:password
|
||||
|
||||
+18
-5
@@ -74,7 +74,7 @@ start_link() ->
|
||||
process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS} ->
|
||||
#iq{xmlns = XMLNS, lang = Lang} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
@@ -87,8 +87,10 @@ process_iq(From, To, Packet) ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply ->
|
||||
@@ -166,8 +168,10 @@ refresh_iq_handlers() ->
|
||||
ejabberd_local ! refresh_iq_handlers.
|
||||
|
||||
bounce_resource_packet(From, To, Packet) ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"No available resource found">>,
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_ITEM_NOT_FOUND),
|
||||
?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
stop.
|
||||
|
||||
@@ -267,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;
|
||||
|
||||
+118
-10
@@ -39,6 +39,7 @@
|
||||
authenticate_user/2,
|
||||
authenticate_client/2,
|
||||
verify_resowner_scope/3,
|
||||
verify_client_scope/3,
|
||||
associate_access_code/3,
|
||||
associate_access_token/3,
|
||||
associate_refresh_token/3,
|
||||
@@ -47,6 +48,8 @@
|
||||
process/2,
|
||||
opt_type/1]).
|
||||
|
||||
-export([oauth_issue_token/1, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -55,9 +58,16 @@
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("ejabberd_web_admin.hrl").
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
|
||||
%% 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).
|
||||
-record(oauth_token, {
|
||||
token = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
us = {<<"">>, <<"">>} :: {binary(), binary()} | server_admin,
|
||||
scope = [] :: [binary()],
|
||||
expire :: integer()
|
||||
}).
|
||||
@@ -73,8 +83,77 @@ start() ->
|
||||
ChildSpec = {?MODULE, {?MODULE, start_link, []},
|
||||
temporary, 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. Available scopes are the ones usable by ejabberd admins",
|
||||
module = ?MODULE, function = oauth_issue_token,
|
||||
args = [{scopes, string}],
|
||||
policy = restricted,
|
||||
args_example = ["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 scope, and how many seconds remain until expirity",
|
||||
module = ?MODULE, function = oauth_list_tokens,
|
||||
args = [],
|
||||
policy = restricted,
|
||||
result = {tokens, {list, {token, {tuple, [{token, 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",
|
||||
module = ?MODULE, function = oauth_list_scopes,
|
||||
args = [],
|
||||
policy = restricted,
|
||||
result = {scopes, {list, {scope, 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}, {scope, string}, {expires_in, string}]}}}},
|
||||
result_desc = "List of remaining tokens"
|
||||
}
|
||||
].
|
||||
|
||||
oauth_issue_token(ScopesString) ->
|
||||
Scopes = [list_to_binary(Scope) || Scope <- string:tokens(ScopesString, ";")],
|
||||
case oauth2:authorize_client_credentials(ejabberd_ctl, Scopes, none) of
|
||||
{ok, {_AppCtx, Authorization}} ->
|
||||
{ok, {_AppCtx2, Response}} = oauth2:issue_token(Authorization, none),
|
||||
{ok, AccessToken} = oauth2_response:access_token(Response),
|
||||
{ok, Expires} = oauth2_response:expires_in(Response),
|
||||
{ok, VerifiedScope} = oauth2_response:scope(Response),
|
||||
{AccessToken, VerifiedScope, integer_to_list(Expires) ++ " seconds"};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
oauth_list_tokens() ->
|
||||
Tokens = mnesia:dirty_match_object(#oauth_token{us = server_admin, _ = '_'}),
|
||||
{MegaSecs, Secs, _MiniSecs} = os:timestamp(),
|
||||
TS = 1000000 * MegaSecs + Secs,
|
||||
[{Token, Scope, integer_to_list(Expires - TS) ++ " seconds"} ||
|
||||
#oauth_token{token=Token, scope=Scope, expire=Expires} <- Tokens].
|
||||
|
||||
|
||||
oauth_revoke_token(Token) ->
|
||||
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
|
||||
oauth_list_tokens().
|
||||
|
||||
oauth_list_scopes() ->
|
||||
get_cmd_scopes().
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
@@ -130,7 +209,7 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
|
||||
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 ->
|
||||
@@ -164,20 +243,46 @@ verify_resowner_scope(_, _, _) ->
|
||||
{error, badscope}.
|
||||
|
||||
|
||||
get_cmd_scopes() ->
|
||||
Cmds = lists:filter(fun(Cmd) -> case ejabberd_commands:get_command_policy(Cmd) of
|
||||
{ok, Policy} when Policy =/= restricted -> true;
|
||||
_ -> false
|
||||
end end,
|
||||
ejabberd_commands:get_commands()),
|
||||
[atom_to_binary(C, utf8) || C <- Cmds].
|
||||
|
||||
%% 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 = 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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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, <<"">>),
|
||||
%% Tokens generated using the API/WEB belongs to users and always include the user, server pair.
|
||||
%% Tokens generated form command line aren't tied to an user, and instead belongs to the ejabberd sysadmin
|
||||
US = case proplists:get_value(<<"resource_owner">>, Context, <<"">>) of
|
||||
{user, User, Server} -> {jid:nodeprep(User), jid:nodeprep(Server)};
|
||||
undefined -> server_admin
|
||||
end,
|
||||
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 = US,
|
||||
scope = Scope,
|
||||
expire = Expire
|
||||
},
|
||||
@@ -207,7 +312,7 @@ check_token(User, Server, Scope, Token) ->
|
||||
|
||||
check_token(Scope, Token) ->
|
||||
case catch mnesia:dirty_read(oauth_token, Token) of
|
||||
[#oauth_token{us = {LUser, LServer},
|
||||
[#oauth_token{us = US,
|
||||
scope = TokenScope,
|
||||
expire = Expire}] ->
|
||||
{MegaSecs, Secs, _} = os:timestamp(),
|
||||
@@ -215,7 +320,10 @@ check_token(Scope, Token) ->
|
||||
case oauth2_priv_set:is_member(
|
||||
Scope, oauth2_priv_set:new(TokenScope)) andalso
|
||||
Expire > TS of
|
||||
true -> {ok, LUser, LServer};
|
||||
true -> case US of
|
||||
{LUser, LServer} -> {ok, user, {LUser, LServer}};
|
||||
server_admin -> {ok, server_admin}
|
||||
end;
|
||||
false -> false
|
||||
end;
|
||||
_ ->
|
||||
@@ -486,5 +594,5 @@ 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;
|
||||
fun acl:access_rules_validator/1;
|
||||
opt_type(_) -> [oauth_expire, oauth_access].
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
|
||||
%%% Not implemented:
|
||||
%%% - write mod_piefxis with ejabberdctl commands
|
||||
%%% - Export from mod_offline_odbc.erl
|
||||
%%% - Export from mod_private_odbc.erl
|
||||
%%% - Export from mod_offline_sql.erl
|
||||
%%% - Export from mod_private_sql.erl
|
||||
%%% - XEP-227: 6. Security Considerations
|
||||
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
|
||||
%%% - If a host has many users, split that host in XML files with 50 users each.
|
||||
|
||||
+17
-17
@@ -35,10 +35,10 @@
|
||||
-include("logger.hrl").
|
||||
|
||||
start() ->
|
||||
file:delete(ejabberd_odbc:freetds_config()),
|
||||
file:delete(ejabberd_odbc:odbc_config()),
|
||||
file:delete(ejabberd_odbc:odbcinst_config()),
|
||||
case lists:any(fun(H) -> needs_odbc(H) /= false end,
|
||||
file:delete(ejabberd_sql:freetds_config()),
|
||||
file:delete(ejabberd_sql:odbc_config()),
|
||||
file:delete(ejabberd_sql:odbcinst_config()),
|
||||
case lists:any(fun(H) -> needs_sql(H) /= false end,
|
||||
?MYHOSTS) of
|
||||
true ->
|
||||
start_hosts();
|
||||
@@ -49,34 +49,34 @@ start() ->
|
||||
%% Start relationnal DB module on the nodes where it is needed
|
||||
start_hosts() ->
|
||||
lists:foreach(fun (Host) ->
|
||||
case needs_odbc(Host) of
|
||||
{true, App} -> start_odbc(Host, App);
|
||||
case needs_sql(Host) of
|
||||
{true, App} -> start_sql(Host, App);
|
||||
false -> ok
|
||||
end
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
%% Start the ODBC module on the given host
|
||||
start_odbc(Host, App) ->
|
||||
%% Start the SQL module on the given host
|
||||
start_sql(Host, App) ->
|
||||
ejabberd:start_app(App),
|
||||
Supervisor_name = gen_mod:get_module_proc(Host,
|
||||
ejabberd_odbc_sup),
|
||||
ejabberd_sql_sup),
|
||||
ChildSpec = {Supervisor_name,
|
||||
{ejabberd_odbc_sup, start_link, [Host]}, transient,
|
||||
infinity, supervisor, [ejabberd_odbc_sup]},
|
||||
{ejabberd_sql_sup, start_link, [Host]}, transient,
|
||||
infinity, supervisor, [ejabberd_sql_sup]},
|
||||
case supervisor:start_child(ejabberd_sup, ChildSpec) of
|
||||
{ok, _PID} -> ok;
|
||||
_Error ->
|
||||
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
|
||||
"..~n",
|
||||
[Supervisor_name, _Error]),
|
||||
start_odbc(Host, App)
|
||||
start_sql(Host, App)
|
||||
end.
|
||||
|
||||
%% Returns {true, App} if we have configured odbc for the given host
|
||||
needs_odbc(Host) ->
|
||||
%% Returns {true, App} if we have configured sql for the given host
|
||||
needs_sql(Host) ->
|
||||
LHost = jid:nameprep(Host),
|
||||
case ejabberd_config:get_option({odbc_type, LHost},
|
||||
case ejabberd_config:get_option({sql_type, LHost},
|
||||
fun(mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
@@ -91,11 +91,11 @@ needs_odbc(Host) ->
|
||||
undefined -> false
|
||||
end.
|
||||
|
||||
opt_type(odbc_type) ->
|
||||
opt_type(sql_type) ->
|
||||
fun (mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end;
|
||||
opt_type(_) -> [odbc_type].
|
||||
opt_type(_) -> [sql_type].
|
||||
|
||||
@@ -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].
|
||||
@@ -70,8 +70,8 @@ is_riak_configured(Host) ->
|
||||
{modules, Host},
|
||||
fun(L) when is_list(L) -> L end, []),
|
||||
ModuleWithRiakDBConfigured = lists:any(
|
||||
fun({_Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts) == riak
|
||||
fun({Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts, Module) == riak
|
||||
end, Modules),
|
||||
ServerConfigured or PortConfigured
|
||||
or AuthConfigured or ModuleWithRiakDBConfigured.
|
||||
|
||||
@@ -312,8 +312,10 @@ do_route(From, To, Packet) ->
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"No s2s connection found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end,
|
||||
false
|
||||
@@ -537,7 +539,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
|
||||
@@ -736,5 +738,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].
|
||||
|
||||
@@ -325,7 +325,7 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
{s2s_tls_compression, StateData#state.server},
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, true) of
|
||||
end, false) of
|
||||
true -> lists:delete(compression_none, TLSOpts1);
|
||||
false -> [compression_none | TLSOpts1]
|
||||
end,
|
||||
|
||||
@@ -192,7 +192,7 @@ init([From, Server, Type]) ->
|
||||
{s2s_tls_compression, From},
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, true) of
|
||||
end, false) of
|
||||
false -> [compression_none | TLSOpts4];
|
||||
true -> TLSOpts4
|
||||
end,
|
||||
|
||||
@@ -280,7 +280,9 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
and (FromJID /= error) ->
|
||||
ejabberd_router:route(FromJID, ToJID, NewEl);
|
||||
true ->
|
||||
Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
|
||||
Txt = <<"Incorrect stanza name or from/to JID">>,
|
||||
Err = jlib:make_error_reply(NewEl, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||
send_element(StateData, Err),
|
||||
error
|
||||
end,
|
||||
@@ -360,7 +362,9 @@ handle_info({route, From, To, Packet}, StateName,
|
||||
attrs = Attrs2, children = Els}),
|
||||
send_text(StateData, Text);
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
|
||||
ejabberd_router:route_error(To, From, Err, Packet)
|
||||
end,
|
||||
{next_state, StateName, StateData};
|
||||
|
||||
+99
-54
@@ -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,
|
||||
@@ -66,7 +68,8 @@
|
||||
get_max_user_sessions/2,
|
||||
get_all_pids/0,
|
||||
is_existing_resource/3,
|
||||
get_commands_spec/0
|
||||
get_commands_spec/0,
|
||||
make_sid/0
|
||||
]).
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
@@ -159,8 +162,10 @@ check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
||||
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
|
||||
|
||||
bounce_offline_message(From, To, Packet) ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
stop.
|
||||
|
||||
@@ -175,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)].
|
||||
|
||||
@@ -193,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 ->
|
||||
@@ -208,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 ->
|
||||
@@ -258,17 +263,42 @@ 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({Time, _Pid}, User, Server, Resource, Info) ->
|
||||
SID = {Time, undefined},
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
set_session(SID, LUser, LServer, LResource, undefined, Info).
|
||||
|
||||
-spec get_offline_info(erlang:timestamp(), binary(), binary(),
|
||||
binary()) -> none | info().
|
||||
|
||||
get_offline_info(Time, User, Server, Resource) ->
|
||||
SID = {Time, undefined},
|
||||
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 = SID, info = Info}] ->
|
||||
Info;
|
||||
_ ->
|
||||
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{}].
|
||||
@@ -276,7 +306,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()).
|
||||
|
||||
@@ -285,14 +315,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().
|
||||
@@ -300,7 +330,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}.
|
||||
@@ -392,6 +422,15 @@ 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(#session{sid = {_, undefined}}) ->
|
||||
false;
|
||||
(_) ->
|
||||
true
|
||||
end, Sessions).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
do_route(From, To, {broadcast, _} = Packet) ->
|
||||
@@ -406,7 +445,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 ->
|
||||
@@ -423,6 +462,7 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
#jid{user = User, server = Server,
|
||||
luser = LUser, lserver = LServer, lresource = LResource} = To,
|
||||
#xmlel{name = Name, attrs = Attrs} = Packet,
|
||||
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
case LResource of
|
||||
<<"">> ->
|
||||
case Name of
|
||||
@@ -496,8 +536,9 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
<<"headline">> -> route_message(From, To, Packet, headline);
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
route_message(From, To, Packet, normal)
|
||||
@@ -506,28 +547,33 @@ 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);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
route_message(From, To, Packet, normal)
|
||||
end;
|
||||
<<"iq">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
_ -> ?DEBUG("packet dropped~n", [])
|
||||
@@ -574,8 +620,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 ->
|
||||
@@ -592,15 +638,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)
|
||||
@@ -639,7 +682,11 @@ check_existing_resources(LUser, LServer, LResource) ->
|
||||
if SIDs == [] -> ok;
|
||||
true ->
|
||||
MaxSID = lists:max(SIDs),
|
||||
lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
|
||||
lists:foreach(fun ({_, undefined} = S) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Mod:delete_session(LUser, LServer, LResource,
|
||||
S);
|
||||
({_, Pid} = S) when S /= MaxSID ->
|
||||
Pid ! replaced;
|
||||
(_) -> ok
|
||||
end,
|
||||
@@ -656,11 +703,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
|
||||
@@ -684,7 +731,7 @@ get_max_user_sessions(LUser, Host) ->
|
||||
process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS} ->
|
||||
#iq{xmlns = XMLNS, lang = Lang} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
@@ -697,8 +744,10 @@ process_iq(From, To, Packet) ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply -> ok;
|
||||
@@ -712,7 +761,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,
|
||||
@@ -721,12 +770,10 @@ force_update_presence({LUser, LServer}) ->
|
||||
-spec get_sm_backend(binary()) -> module().
|
||||
|
||||
get_sm_backend(Host) ->
|
||||
DBType = ejabberd_config:get_option({sm_db_type, Host},
|
||||
fun(mnesia) -> mnesia;
|
||||
(internal) -> mnesia;
|
||||
(odbc) -> odbc;
|
||||
(redis) -> redis
|
||||
end, mnesia),
|
||||
DBType = ejabberd_config:get_option(
|
||||
{sm_db_type, Host},
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end,
|
||||
mnesia),
|
||||
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
|
||||
|
||||
-spec get_sm_backends() -> [module()].
|
||||
@@ -796,10 +843,8 @@ kick_user(User, Server) ->
|
||||
end, Resources),
|
||||
length(Resources).
|
||||
|
||||
opt_type(sm_db_type) ->
|
||||
fun (mnesia) -> mnesia;
|
||||
(internal) -> mnesia;
|
||||
(odbc) -> odbc;
|
||||
(redis) -> redis
|
||||
end;
|
||||
make_sid() ->
|
||||
{p1_time_compat:unique_timestamp(), self()}.
|
||||
|
||||
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(_) -> [sm_db_type].
|
||||
|
||||
+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
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
%%% @end
|
||||
%%% Created : 9 Mar 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_sm_odbc).
|
||||
-module(ejabberd_sm_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_sm).
|
||||
|
||||
@@ -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_odbc: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_odbc:sql_query(
|
||||
Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of
|
||||
case ejabberd_sql:sql_query(
|
||||
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_odbc:escape(U),
|
||||
Resource = ejabberd_odbc:escape(R),
|
||||
InfoS = ejabberd_odbc: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_odbc:escape(jlib:atom_to_binary(node(Pid))),
|
||||
case odbc_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 ->
|
||||
@@ -70,16 +72,18 @@ set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
|
||||
delete_session(_LUser, LServer, _LResource, {Now, Pid}) ->
|
||||
TS = now_to_timestamp(Now),
|
||||
PidS = list_to_binary(erlang:pid_to_list(Pid)),
|
||||
case ejabberd_odbc:sql_query(
|
||||
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_odbc: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]),
|
||||
@@ -93,10 +97,11 @@ get_sessions() ->
|
||||
end, ejabberd_sm:get_vh_by_backend(?MODULE)).
|
||||
|
||||
get_sessions(LServer) ->
|
||||
case ejabberd_odbc:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm">>]) of
|
||||
{selected, _, Rows} ->
|
||||
case ejabberd_sql:sql_query(
|
||||
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_odbc:escape(LUser),
|
||||
case ejabberd_odbc:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm where ">>,
|
||||
<<"username='">>, Username, <<"'">>]) of
|
||||
{selected, _, Rows} ->
|
||||
case ejabberd_sql:sql_query(
|
||||
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_odbc:escape(LUser),
|
||||
Resource = ejabberd_odbc:escape(LResource),
|
||||
case ejabberd_odbc:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm where ">>,
|
||||
<<"username='">>, Username, <<"' and resource='">>,
|
||||
Resource, <<"'">>]) of
|
||||
{selected, _, Rows} ->
|
||||
case ejabberd_sql:sql_query(
|
||||
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,11 +160,11 @@ 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),
|
||||
Info = ejabberd_odbc:decode_term(InfoS),
|
||||
Info = ejabberd_sql:decode_term(InfoS),
|
||||
#session{sid = {Now, Pid}, us = {User, LServer},
|
||||
usr = {User, LServer, Resource},
|
||||
priority = Priority,
|
||||
@@ -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>
|
||||
%%%
|
||||
%%%
|
||||
@@ -23,7 +23,7 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_odbc).
|
||||
-module(ejabberd_sql).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
@@ -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,
|
||||
@@ -75,9 +78,9 @@
|
||||
max_pending_requests_len :: non_neg_integer(),
|
||||
pending_requests = {0, queue:new()} :: {non_neg_integer(), ?TQUEUE}}).
|
||||
|
||||
-define(STATE_KEY, ejabberd_odbc_state).
|
||||
-define(STATE_KEY, ejabberd_sql_state).
|
||||
|
||||
-define(NESTING_KEY, ejabberd_odbc_nesting_level).
|
||||
-define(NESTING_KEY, ejabberd_sql_nesting_level).
|
||||
|
||||
-define(TOP_LEVEL_TXN, 0).
|
||||
|
||||
@@ -95,7 +98,7 @@
|
||||
|
||||
-define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]).
|
||||
|
||||
-define(PREPARE_KEY, ejabberd_odbc_prepare).
|
||||
-define(PREPARE_KEY, ejabberd_sql_prepare).
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
@@ -113,11 +116,11 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
(?GEN_FSM):start(ejabberd_odbc, [Host],
|
||||
(?GEN_FSM):start(ejabberd_sql, [Host],
|
||||
fsm_limit_opts() ++ (?FSMOPTS)).
|
||||
|
||||
start_link(Host, StartInterval) ->
|
||||
(?GEN_FSM):start_link(ejabberd_odbc,
|
||||
(?GEN_FSM):start_link(ejabberd_sql,
|
||||
[Host, StartInterval],
|
||||
fsm_limit_opts() ++ (?FSMOPTS)).
|
||||
|
||||
@@ -157,7 +160,7 @@ sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}).
|
||||
sql_call(Host, Msg) ->
|
||||
case get(?STATE_KEY) of
|
||||
undefined ->
|
||||
case ejabberd_odbc_sup:get_random_pid(Host) of
|
||||
case ejabberd_sql_sup:get_random_pid(Host) of
|
||||
none -> {error, <<"Unknown Host">>};
|
||||
Pid ->
|
||||
(?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
|
||||
@@ -190,7 +193,7 @@ sql_query_t(Query) ->
|
||||
|
||||
%% Escape character that will confuse an SQL engine
|
||||
escape(S) ->
|
||||
<< <<(odbc_queries:escape(Char))/binary>> || <<Char>> <= S >>.
|
||||
<< <<(sql_queries:escape(Char))/binary>> || <<Char>> <= S >>.
|
||||
|
||||
%% Escape character that will confuse an SQL engine
|
||||
%% Percent and underscore only need to be escaped for pattern matching like
|
||||
@@ -199,7 +202,8 @@ escape_like(S) when is_binary(S) ->
|
||||
<< <<(escape_like(C))/binary>> || <<C>> <= S >>;
|
||||
escape_like($%) -> <<"\\%">>;
|
||||
escape_like($_) -> <<"\\_">>;
|
||||
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> odbc_queries:escape(C).
|
||||
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) ->
|
||||
<< <<(escape_like_arg(C))/binary>> || <<C>> <= 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;
|
||||
@@ -232,7 +245,7 @@ sqlite_db(Host) ->
|
||||
|
||||
-spec sqlite_file(binary()) -> string().
|
||||
sqlite_file(Host) ->
|
||||
case ejabberd_config:get_option({odbc_database, Host},
|
||||
case ejabberd_config:get_option({sql_database, Host},
|
||||
fun iolist_to_binary/1) of
|
||||
undefined ->
|
||||
{ok, Cwd} = file:get_cwd(),
|
||||
@@ -247,7 +260,7 @@ sqlite_file(Host) ->
|
||||
%%%----------------------------------------------------------------------
|
||||
init([Host, StartInterval]) ->
|
||||
case ejabberd_config:get_option(
|
||||
{odbc_keepalive_interval, Host},
|
||||
{sql_keepalive_interval, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end) of
|
||||
undefined ->
|
||||
ok;
|
||||
@@ -257,7 +270,7 @@ init([Host, StartInterval]) ->
|
||||
end,
|
||||
[DBType | _] = db_opts(Host),
|
||||
(?GEN_FSM):send_event(self(), connect),
|
||||
ejabberd_odbc_sup:add_pid(Host, self()),
|
||||
ejabberd_sql_sup:add_pid(Host, self()),
|
||||
{ok, connecting,
|
||||
#state{db_type = DBType, host = Host,
|
||||
max_pending_requests_len = max_fsm_queue(),
|
||||
@@ -371,7 +384,7 @@ handle_info(Info, StateName, State) ->
|
||||
{next_state, StateName, State}.
|
||||
|
||||
terminate(_Reason, _StateName, State) ->
|
||||
ejabberd_odbc_sup:remove_pid(State#state.host, self()),
|
||||
ejabberd_sql_sup:remove_pid(State#state.host, self()),
|
||||
case State#state.db_type of
|
||||
mysql -> catch p1_mysql_conn:stop(State#state.db_ref);
|
||||
sqlite -> catch sqlite3:close(sqlite_db(State#state.host));
|
||||
@@ -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,
|
||||
@@ -869,14 +911,14 @@ log(Level, Format, Args) ->
|
||||
end.
|
||||
|
||||
db_opts(Host) ->
|
||||
Type = ejabberd_config:get_option({odbc_type, Host},
|
||||
Type = ejabberd_config:get_option({sql_type, Host},
|
||||
fun(mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end, odbc),
|
||||
Server = ejabberd_config:get_option({odbc_server, Host},
|
||||
Server = ejabberd_config:get_option({sql_server, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"localhost">>),
|
||||
case Type of
|
||||
@@ -886,20 +928,20 @@ db_opts(Host) ->
|
||||
[sqlite, Host];
|
||||
_ ->
|
||||
Port = ejabberd_config:get_option(
|
||||
{odbc_port, Host},
|
||||
{sql_port, Host},
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
|
||||
case Type of
|
||||
mssql -> ?MSSQL_PORT;
|
||||
mysql -> ?MYSQL_PORT;
|
||||
pgsql -> ?PGSQL_PORT
|
||||
end),
|
||||
DB = ejabberd_config:get_option({odbc_database, Host},
|
||||
DB = ejabberd_config:get_option({sql_database, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>),
|
||||
User = ejabberd_config:get_option({odbc_username, Host},
|
||||
User = ejabberd_config:get_option({sql_username, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>),
|
||||
Pass = ejabberd_config:get_option({odbc_password, Host},
|
||||
Pass = ejabberd_config:get_option({sql_password, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
case Type of
|
||||
@@ -912,14 +954,14 @@ db_opts(Host) ->
|
||||
end.
|
||||
|
||||
init_mssql(Host) ->
|
||||
Server = ejabberd_config:get_option({odbc_server, Host},
|
||||
Server = ejabberd_config:get_option({sql_server, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"localhost">>),
|
||||
Port = ejabberd_config:get_option(
|
||||
{odbc_port, Host},
|
||||
{sql_port, Host},
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
|
||||
?MSSQL_PORT),
|
||||
DB = ejabberd_config:get_option({odbc_database, Host},
|
||||
DB = ejabberd_config:get_option({sql_database, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>),
|
||||
FreeTDS = io_lib:fwrite("[~s]~n"
|
||||
@@ -1004,22 +1046,22 @@ check_error(Result, _Query) ->
|
||||
|
||||
opt_type(max_fsm_queue) ->
|
||||
fun (N) when is_integer(N), N > 0 -> N end;
|
||||
opt_type(odbc_database) -> fun iolist_to_binary/1;
|
||||
opt_type(odbc_keepalive_interval) ->
|
||||
opt_type(sql_database) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_keepalive_interval) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(odbc_password) -> fun iolist_to_binary/1;
|
||||
opt_type(odbc_port) ->
|
||||
opt_type(sql_password) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_port) ->
|
||||
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
|
||||
opt_type(odbc_server) -> fun iolist_to_binary/1;
|
||||
opt_type(odbc_type) ->
|
||||
opt_type(sql_server) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_type) ->
|
||||
fun (mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end;
|
||||
opt_type(odbc_username) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_username) -> fun iolist_to_binary/1;
|
||||
opt_type(_) ->
|
||||
[max_fsm_queue, odbc_database, odbc_keepalive_interval,
|
||||
odbc_password, odbc_port, odbc_server, odbc_type,
|
||||
odbc_username].
|
||||
[max_fsm_queue, sql_database, sql_keepalive_interval,
|
||||
sql_password, sql_port, sql_server, sql_type,
|
||||
sql_username].
|
||||
+19
-13
@@ -163,7 +163,7 @@ parse1([$@, $( | S], Acc, State) ->
|
||||
EVar;
|
||||
boolean ->
|
||||
erl_syntax:application(
|
||||
erl_syntax:atom(ejabberd_odbc),
|
||||
erl_syntax:atom(ejabberd_sql),
|
||||
erl_syntax:atom(to_bool),
|
||||
[EVar])
|
||||
end,
|
||||
@@ -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) ->
|
||||
@@ -348,7 +352,7 @@ make_sql_upsert_generic(Table, ParseRes) ->
|
||||
InsertBranch =
|
||||
erl_syntax:case_expr(
|
||||
erl_syntax:application(
|
||||
erl_syntax:atom(ejabberd_odbc),
|
||||
erl_syntax:atom(ejabberd_sql),
|
||||
erl_syntax:atom(sql_query_t),
|
||||
[Insert]),
|
||||
[erl_syntax:clause(
|
||||
@@ -361,7 +365,7 @@ make_sql_upsert_generic(Table, ParseRes) ->
|
||||
[erl_syntax:variable("__UpdateRes")])]),
|
||||
erl_syntax:case_expr(
|
||||
erl_syntax:application(
|
||||
erl_syntax:atom(ejabberd_odbc),
|
||||
erl_syntax:atom(ejabberd_sql),
|
||||
erl_syntax:atom(sql_query_t),
|
||||
[Update]),
|
||||
[erl_syntax:clause(
|
||||
@@ -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'
|
||||
}]
|
||||
@@ -453,7 +459,7 @@ make_sql_upsert_pgsql901(Table, ParseRes) ->
|
||||
]),
|
||||
Upsert = make_sql_query(State),
|
||||
erl_syntax:application(
|
||||
erl_syntax:atom(ejabberd_odbc),
|
||||
erl_syntax:atom(ejabberd_sql),
|
||||
erl_syntax:atom(sql_query_t),
|
||||
[Upsert]).
|
||||
|
||||
@@ -462,7 +468,7 @@ check_upsert(ParseRes, Pos) ->
|
||||
Set =
|
||||
lists:filter(
|
||||
fun({_Field, Match, _ST}) ->
|
||||
not Match
|
||||
Match /= key
|
||||
end, ParseRes),
|
||||
case Set of
|
||||
[] ->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_odbc_sup.erl
|
||||
%%% File : ejabberd_sql_sup.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : ODBC connections supervisor
|
||||
%%% Purpose : SQL connections supervisor
|
||||
%%% Created : 22 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@@ -23,7 +23,7 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_odbc_sup).
|
||||
-module(ejabberd_sql_sup).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
-define(DEFAULT_POOL_SIZE, 10).
|
||||
|
||||
-define(DEFAULT_ODBC_START_INTERVAL, 30).
|
||||
-define(DEFAULT_SQL_START_INTERVAL, 30).
|
||||
|
||||
-define(CONNECT_TIMEOUT, 500).
|
||||
|
||||
@@ -62,14 +62,14 @@ start_link(Host) ->
|
||||
|
||||
init([Host]) ->
|
||||
PoolSize = ejabberd_config:get_option(
|
||||
{odbc_pool_size, Host},
|
||||
{sql_pool_size, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?DEFAULT_POOL_SIZE),
|
||||
StartInterval = ejabberd_config:get_option(
|
||||
{odbc_start_interval, Host},
|
||||
{sql_start_interval, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?DEFAULT_ODBC_START_INTERVAL),
|
||||
Type = ejabberd_config:get_option({odbc_type, Host},
|
||||
?DEFAULT_SQL_START_INTERVAL),
|
||||
Type = ejabberd_config:get_option({sql_type, Host},
|
||||
fun(mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
@@ -80,7 +80,7 @@ init([Host]) ->
|
||||
sqlite ->
|
||||
check_sqlite_db(Host);
|
||||
mssql ->
|
||||
ejabberd_odbc:init_mssql(Host);
|
||||
ejabberd_sql:init_mssql(Host);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
@@ -89,7 +89,7 @@ init([Host]) ->
|
||||
{{one_for_one, PoolSize * 10, 1},
|
||||
lists:map(fun (I) ->
|
||||
{I,
|
||||
{ejabberd_odbc, start_link,
|
||||
{ejabberd_sql, start_link,
|
||||
[Host, StartInterval * 1000]},
|
||||
transient, 2000, worker, [?MODULE]}
|
||||
end,
|
||||
@@ -121,12 +121,12 @@ transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
|
||||
[{odbc_type, Type},
|
||||
{odbc_server, Server},
|
||||
{odbc_port, Port},
|
||||
{odbc_database, DB},
|
||||
{odbc_username, User},
|
||||
{odbc_password, Pass}|Opts];
|
||||
[{sql_type, Type},
|
||||
{sql_server, Server},
|
||||
{sql_port, Port},
|
||||
{sql_database, DB},
|
||||
{sql_username, User},
|
||||
{sql_password, Pass}|Opts];
|
||||
transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) ->
|
||||
transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts);
|
||||
transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) ->
|
||||
@@ -137,8 +137,8 @@ transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
check_sqlite_db(Host) ->
|
||||
DB = ejabberd_odbc:sqlite_db(Host),
|
||||
File = ejabberd_odbc:sqlite_file(Host),
|
||||
DB = ejabberd_sql:sqlite_db(Host),
|
||||
File = ejabberd_sql:sqlite_file(Host),
|
||||
Ret = case filelib:ensure_dir(File) of
|
||||
ok ->
|
||||
case sqlite3:open(DB, [{file, File}]) of
|
||||
@@ -211,11 +211,11 @@ read_lines(Fd, File, Acc) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
opt_type(odbc_pool_size) ->
|
||||
opt_type(sql_pool_size) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(odbc_start_interval) ->
|
||||
opt_type(sql_start_interval) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(odbc_type) ->
|
||||
opt_type(sql_type) ->
|
||||
fun (mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
@@ -223,4 +223,4 @@ opt_type(odbc_type) ->
|
||||
(odbc) -> odbc
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[odbc_pool_size, odbc_start_interval, odbc_type].
|
||||
[sql_pool_size, sql_start_interval, sql_type].
|
||||
@@ -2414,7 +2414,7 @@ node_backup_parse_query(Node, Query) ->
|
||||
lists:keysearch(<<Action/binary,
|
||||
"host">>,
|
||||
1, Query),
|
||||
ejabberd_cluster:call(Node, ejd2odbc,
|
||||
ejabberd_cluster:call(Node, ejd2sql,
|
||||
export, [Host, Path]);
|
||||
<<"import_file">> ->
|
||||
ejabberd_cluster:call(Node, ejabberd_admin,
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejd2odbc.erl
|
||||
%%% File : ejd2sql.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Export some mnesia tables to SQL DB
|
||||
%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@process-one.net>
|
||||
@@ -23,11 +23,12 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejd2odbc).
|
||||
-module(ejd2sql).
|
||||
|
||||
-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]).
|
||||
@@ -43,7 +44,7 @@
|
||||
%%% A table can be converted from Mnesia to an ODBC database by calling
|
||||
%%% one of the API function with the following parameters:
|
||||
%%% - Server is the server domain you want to convert
|
||||
%%% - Output can be either odbc to export to the configured relational
|
||||
%%% - Output can be either sql to export to the configured relational
|
||||
%%% database or "Filename" to export to text file.
|
||||
|
||||
modules() ->
|
||||
@@ -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).
|
||||
|
||||
@@ -104,7 +110,7 @@ import_file(Server, FileName) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Mods = [{Mod, gen_mod:db_type(LServer, Mod)}
|
||||
|| Mod <- modules(), gen_mod:is_loaded(LServer, Mod)],
|
||||
AuthMods = case lists:member(ejabberd_auth_internal,
|
||||
AuthMods = case lists:member(ejabberd_auth_mnesia,
|
||||
ejabberd_auth:auth_modules(LServer)) of
|
||||
true ->
|
||||
[{ejabberd_auth, mnesia}];
|
||||
@@ -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 ->
|
||||
@@ -168,8 +175,8 @@ export(LServer, Table, IO, ConvertFun) ->
|
||||
|
||||
output(_LServer, _Table, _IO, []) ->
|
||||
ok;
|
||||
output(LServer, _Table, odbc, SQLs) ->
|
||||
ejabberd_odbc:sql_transaction(LServer, SQLs);
|
||||
output(LServer, _Table, sql, SQLs) ->
|
||||
ejabberd_sql:sql_transaction(LServer, SQLs);
|
||||
output(_LServer, Table, Fd, SQLs) ->
|
||||
file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
|
||||
"\n--\n", SQLs]).
|
||||
@@ -197,7 +204,7 @@ import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
|
||||
F = case proplists:get_bool(fast, Opts) of
|
||||
true ->
|
||||
fun() ->
|
||||
case ejabberd_odbc:sql_query_t(SelectQuery) of
|
||||
case ejabberd_sql:sql_query_t(SelectQuery) of
|
||||
{selected, _, Rows} ->
|
||||
lists:foldl(fun process_sql_row/2,
|
||||
{IO, ConvertFun, undefined}, Rows);
|
||||
@@ -207,16 +214,16 @@ import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
|
||||
end;
|
||||
false ->
|
||||
fun() ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
ejabberd_sql:sql_query_t(
|
||||
[iolist_to_binary(
|
||||
[<<"declare c cursor for ">>, SelectQuery])]),
|
||||
fetch(IO, ConvertFun, undefined)
|
||||
end
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
fetch(IO, ConvertFun, PrevRow) ->
|
||||
case ejabberd_odbc:sql_query_t([<<"fetch c;">>]) of
|
||||
case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of
|
||||
{selected, _, [Row]} ->
|
||||
process_sql_row(Row, {IO, ConvertFun, PrevRow}),
|
||||
fetch(IO, ConvertFun, Row);
|
||||
@@ -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).
|
||||
+1
-1
@@ -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}} ->
|
||||
|
||||
+54
-29
@@ -31,12 +31,12 @@
|
||||
|
||||
-export([start/0, start_module/2, start_module/3,
|
||||
stop_module/2, stop_module_keep_config/2, get_opt/3,
|
||||
get_opt/4, get_opt_host/3, db_type/1, db_type/2,
|
||||
get_opt/4, get_opt_host/3, db_type/2, db_type/3,
|
||||
get_module_opt/4, get_module_opt/5, get_module_opt_host/3,
|
||||
loaded_modules/1, loaded_modules_with_opts/1,
|
||||
get_hosts/2, get_module_proc/2, is_loaded/2,
|
||||
start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
|
||||
default_db/1, v_db/1, opt_type/1]).
|
||||
opt_type/1, db_mod/2, db_mod/3]).
|
||||
|
||||
%%-export([behaviour_info/1]).
|
||||
|
||||
@@ -48,10 +48,11 @@
|
||||
opts = [] :: opts() | '_' | '$2'}).
|
||||
|
||||
-type opts() :: [{atom(), any()}].
|
||||
-type db_type() :: odbc | mnesia | riak.
|
||||
-type db_type() :: sql | mnesia | riak.
|
||||
|
||||
-callback start(binary(), opts()) -> any().
|
||||
-callback stop(binary()) -> any().
|
||||
-callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
|
||||
-export_type([opts/0]).
|
||||
-export_type([db_type/0]).
|
||||
@@ -265,18 +266,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], <<", ">>),
|
||||
@@ -295,29 +303,46 @@ validate_opts(Module, Opts) ->
|
||||
false
|
||||
end, Opts).
|
||||
|
||||
-spec v_db(db_type() | internal) -> db_type().
|
||||
|
||||
v_db(odbc) -> odbc;
|
||||
v_db(internal) -> mnesia;
|
||||
v_db(mnesia) -> mnesia;
|
||||
v_db(riak) -> riak.
|
||||
|
||||
-spec db_type(opts()) -> db_type().
|
||||
|
||||
db_type(Opts) ->
|
||||
db_type(global, Opts).
|
||||
|
||||
-spec db_type(binary() | global, atom() | opts()) -> db_type().
|
||||
-spec db_type(binary() | global, module()) -> db_type();
|
||||
(opts(), module()) -> db_type().
|
||||
|
||||
db_type(Opts, Module) when is_list(Opts) ->
|
||||
db_type(global, Opts, Module);
|
||||
db_type(Host, Module) when is_atom(Module) ->
|
||||
get_module_opt(Host, Module, db_type, fun v_db/1, default_db(Host));
|
||||
db_type(Host, Opts) when is_list(Opts) ->
|
||||
get_opt(db_type, Opts, fun v_db/1, default_db(Host)).
|
||||
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);
|
||||
Type -> Type
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
-spec default_db(binary() | global) -> db_type().
|
||||
-spec db_type(binary(), opts(), module()) -> db_type().
|
||||
|
||||
default_db(Host) ->
|
||||
ejabberd_config:get_option({default_db, Host}, fun v_db/1, mnesia).
|
||||
db_type(Host, Opts, Module) ->
|
||||
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);
|
||||
Type -> Type
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
-spec db_mod(binary() | global | db_type(), module()) -> module().
|
||||
|
||||
db_mod(Type, Module) when is_atom(Type) ->
|
||||
list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type));
|
||||
db_mod(Host, Module) when is_binary(Host) orelse Host == global ->
|
||||
db_mod(db_type(Host, Module), Module).
|
||||
|
||||
-spec db_mod(binary() | global, opts(), module()) -> module().
|
||||
|
||||
db_mod(Host, Opts, Module) when is_list(Opts) ->
|
||||
db_mod(db_type(Host, Opts, Module), Module).
|
||||
|
||||
-spec loaded_modules(binary()) -> [atom()].
|
||||
|
||||
@@ -365,6 +390,6 @@ get_module_proc(Host, Base) ->
|
||||
is_loaded(Host, Module) ->
|
||||
ets:member(ejabberd_modules, {Module, Host}).
|
||||
|
||||
opt_type(default_db) -> fun v_db/1;
|
||||
opt_type(default_db) -> fun(T) when is_atom(T) -> T end;
|
||||
opt_type(modules) -> fun (L) when is_list(L) -> L end;
|
||||
opt_type(_) -> [default_db, modules].
|
||||
|
||||
@@ -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()}.
|
||||
|
||||
|
||||
+1
-1
@@ -87,7 +87,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
|
||||
|
||||
+66
-18
@@ -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
|
||||
@@ -528,25 +529,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">>} = El) ->
|
||||
ChatStates = [<<"active">>, <<"inactive">>, <<"gone">>, <<"composing">>,
|
||||
<<"paused">>],
|
||||
Stripped =
|
||||
lists:foldl(fun(ChatState, AccEl) ->
|
||||
fxml:remove_subtags(AccEl, ChatState,
|
||||
{<<"xmlns">>, ?NS_CHATSTATES})
|
||||
end, El, ChatStates),
|
||||
case Stripped of
|
||||
#xmlel{children = [#xmlel{name = <<"thread">>}]} ->
|
||||
true;
|
||||
#xmlel{children = []} ->
|
||||
true;
|
||||
_ ->
|
||||
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;
|
||||
is_standalone_chat_state(_El) -> false.
|
||||
end.
|
||||
|
||||
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp())
|
||||
-> xmlel().
|
||||
@@ -901,6 +941,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).
|
||||
|
||||
+6
-3
@@ -233,7 +233,7 @@ process_sm_iq(From, To, IQ) ->
|
||||
process_adhoc_request(From, To, IQ, adhoc_sm_commands).
|
||||
|
||||
process_adhoc_request(From, To,
|
||||
#iq{sub_el = SubEl} = IQ, Hook) ->
|
||||
#iq{sub_el = SubEl, lang = Lang} = IQ, Hook) ->
|
||||
?DEBUG("About to parse ~p...", [IQ]),
|
||||
case adhoc:parse_request(IQ) of
|
||||
{error, Error} ->
|
||||
@@ -245,8 +245,9 @@ process_adhoc_request(From, To,
|
||||
of
|
||||
ignore -> ignore;
|
||||
empty ->
|
||||
Txt = <<"No hook has processed this command">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
|
||||
sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]};
|
||||
Command -> IQ#iq{type = result, sub_el = [Command]}
|
||||
@@ -277,7 +278,9 @@ ping_command(_Acc, _From, _To,
|
||||
[{<<"info">>,
|
||||
translate:translate(Lang,
|
||||
<<"Pong">>)}]});
|
||||
true -> {error, ?ERR_BAD_REQUEST}
|
||||
true ->
|
||||
Txt = <<"Incorrect value of 'action' attribute">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end;
|
||||
ping_command(Acc, _From, _To, _Request) -> Acc.
|
||||
|
||||
|
||||
+65
-36
@@ -31,7 +31,7 @@
|
||||
-include("logger.hrl").
|
||||
|
||||
-export([start/2, stop/1, compile/1, get_cookie/0,
|
||||
remove_node/1, set_password/3,
|
||||
remove_node/1, set_password/3, check_password/3,
|
||||
check_password_hash/4, delete_old_users/1,
|
||||
delete_old_users_vhost/2, ban_account/3,
|
||||
num_active_users/2, num_resources/2, resource_num/3,
|
||||
@@ -162,7 +162,7 @@ get_commands_spec() ->
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
#ejabberd_commands{name = check_password, tags = [accounts],
|
||||
desc = "Check if a password is correct",
|
||||
module = ejabberd_auth, function = check_password,
|
||||
module = ?MODULE, function = check_password,
|
||||
args = [{user, binary}, {host, binary}, {password, binary}],
|
||||
args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
|
||||
args_desc = ["User name to check", "Server to check", "Password to check"],
|
||||
@@ -590,36 +590,38 @@ remove_node(Node) ->
|
||||
%%%
|
||||
|
||||
set_password(User, Host, Password) ->
|
||||
case ejabberd_auth:set_password(User, Host, Password) of
|
||||
ok ->
|
||||
ok;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
Fun = fun () -> ejabberd_auth:set_password(User, Host, Password) end,
|
||||
user_action(User, Host, Fun, ok).
|
||||
|
||||
check_password(User, Host, Password) ->
|
||||
ejabberd_auth:check_password(User, <<>>, Host, Password).
|
||||
|
||||
%% Copied some code from ejabberd_commands.erl
|
||||
check_password_hash(User, Host, PasswordHash, HashMethod) ->
|
||||
AccountPass = ejabberd_auth:get_password_s(User, Host),
|
||||
AccountPassHash = case {AccountPass, HashMethod} of
|
||||
{A, _} when is_tuple(A) -> scrammed;
|
||||
{_, "md5"} -> get_md5(AccountPass);
|
||||
{_, "sha"} -> get_sha(AccountPass);
|
||||
_ -> undefined
|
||||
{_, <<"md5">>} -> get_md5(AccountPass);
|
||||
{_, <<"sha">>} -> get_sha(AccountPass);
|
||||
{_, Method} ->
|
||||
?ERROR_MSG("check_password_hash called "
|
||||
"with hash method: ~p", [Method]),
|
||||
undefined
|
||||
end,
|
||||
case AccountPassHash of
|
||||
scrammed ->
|
||||
?ERROR_MSG("Passwords are scrammed, and check_password_hash can not work.", []),
|
||||
?ERROR_MSG("Passwords are scrammed, and check_password_hash cannot work.", []),
|
||||
throw(passwords_scrammed_command_cannot_work);
|
||||
undefined -> error;
|
||||
undefined -> throw(unkown_hash_method);
|
||||
PasswordHash -> ok;
|
||||
_ -> error
|
||||
_ -> false
|
||||
end.
|
||||
get_md5(AccountPass) ->
|
||||
lists:flatten([io_lib:format("~.16B", [X])
|
||||
|| X <- binary_to_list(erlang:md5(AccountPass))]).
|
||||
iolist_to_binary([io_lib:format("~2.16.0B", [X])
|
||||
|| X <- binary_to_list(erlang:md5(AccountPass))]).
|
||||
get_sha(AccountPass) ->
|
||||
lists:flatten([io_lib:format("~.16B", [X])
|
||||
|| X <- binary_to_list(p1_sha:sha1(AccountPass))]).
|
||||
iolist_to_binary([io_lib:format("~2.16.0B", [X])
|
||||
|| X <- binary_to_list(p1_sha:sha1(AccountPass))]).
|
||||
|
||||
num_active_users(Host, Days) ->
|
||||
list_last_activity(Host, true, Days).
|
||||
@@ -782,7 +784,8 @@ resource_num(User, Host, Num) ->
|
||||
true ->
|
||||
lists:nth(Num, Resources);
|
||||
false ->
|
||||
lists:flatten(io_lib:format("Error: Wrong resource number: ~p", [Num]))
|
||||
throw({bad_argument,
|
||||
lists:flatten(io_lib:format("Wrong resource number: ~p", [Num]))})
|
||||
end.
|
||||
|
||||
kick_session(User, Server, Resource, ReasonText) ->
|
||||
@@ -860,26 +863,36 @@ connected_users_vhost(Host) ->
|
||||
dirty_get_sessions_list2() ->
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4', _ = '_'},
|
||||
[],
|
||||
[['$1', '$2', '$3', '$4']]}]).
|
||||
[{#session{usr = '$1', sid = {'$2', '$3'}, priority = '$4', info = '$5',
|
||||
_ = '_'},
|
||||
[{is_pid, '$3'}],
|
||||
[['$1', {{'$2', '$3'}}, '$4', '$5']]}]).
|
||||
|
||||
%% 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()}),
|
||||
@@ -888,7 +901,9 @@ user_sessions_info(User, Host) ->
|
||||
{'EXIT', _Reason} ->
|
||||
[];
|
||||
Ss ->
|
||||
Ss
|
||||
lists:filter(fun(#session{sid = {_, Pid}}) ->
|
||||
is_pid(Pid)
|
||||
end, Ss)
|
||||
end,
|
||||
lists:map(
|
||||
fun(Session) ->
|
||||
@@ -1151,7 +1166,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, list_to_binary(Name2), list_to_binary(Server2),
|
||||
list_to_binary(Nick2), list_to_binary(Group2), <<"both">>, []),
|
||||
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
|
||||
|
||||
push_alltoall(S, G) ->
|
||||
@@ -1309,8 +1325,7 @@ srg_get_info(Group, Host) ->
|
||||
Os when is_list(Os) -> Os;
|
||||
error -> []
|
||||
end,
|
||||
[{jlib:atom_to_binary(Title),
|
||||
io_lib:format("~p", [btl(Value)])} || {Title, Value} <- Opts].
|
||||
[{jlib:atom_to_binary(Title), btl(Value)} || {Title, Value} <- Opts].
|
||||
|
||||
btl([]) -> [];
|
||||
btl([B|L]) -> [btl(B)|btl(L)];
|
||||
@@ -1569,6 +1584,20 @@ decide_rip_jid({UName, UServer}, Match_list) ->
|
||||
end,
|
||||
Match_list).
|
||||
|
||||
user_action(User, Server, Fun, OK) ->
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true ->
|
||||
case catch Fun() of
|
||||
OK -> ok;
|
||||
{error, Error} -> throw(Error);
|
||||
Error ->
|
||||
?ERROR_MSG("Command returned: ~p", [Error]),
|
||||
1
|
||||
end;
|
||||
false ->
|
||||
throw({not_found, "unknown_user"})
|
||||
end.
|
||||
|
||||
%% Copied from ejabberd-2.0.0/src/acl.erl
|
||||
is_regexp_match(String, RegExp) ->
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
|
||||
+105
-330
@@ -41,11 +41,16 @@
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("adhoc.hrl").
|
||||
-include("mod_announce.hrl").
|
||||
|
||||
-record(motd, {server = <<"">> :: binary(),
|
||||
packet = #xmlel{} :: xmlel()}).
|
||||
-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
dummy = [] :: [] | '_'}).
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback import(binary(), #motd{} | #motd_users{}) -> ok | pass.
|
||||
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}.
|
||||
-callback set_motd(binary(), xmlel()) -> {atomic, any()}.
|
||||
-callback delete_motd(binary()) -> {atomic, any()}.
|
||||
-callback get_motd(binary()) -> {ok, xmlel()} | error.
|
||||
-callback is_motd_user(binary(), binary()) -> boolean().
|
||||
-callback set_motd_user(binary(), binary()) -> {atomic, any()}.
|
||||
|
||||
-define(PROCNAME, ejabberd_announce).
|
||||
|
||||
@@ -55,20 +60,8 @@
|
||||
tokenize(Node) -> str:tokens(Node, <<"/#">>).
|
||||
|
||||
start(Host, Opts) ->
|
||||
case gen_mod:db_type(Host, Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(motd,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, motd)}]),
|
||||
mnesia:create_table(motd_users,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, motd_users)}]),
|
||||
update_tables();
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, Host,
|
||||
?MODULE, announce, 50),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
|
||||
@@ -211,15 +204,15 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
-define(INFO_RESULT(Allow, Feats),
|
||||
-define(INFO_RESULT(Allow, Feats, Lang),
|
||||
case Allow of
|
||||
deny ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
{error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
|
||||
allow ->
|
||||
{result, Feats}
|
||||
end).
|
||||
|
||||
disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang) ->
|
||||
disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false ->
|
||||
Acc;
|
||||
@@ -229,13 +222,14 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang)
|
||||
case {acl:match_rule(LServer, Access1, From),
|
||||
acl:match_rule(global, Access2, From)} of
|
||||
{deny, deny} ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
Txt = <<"Denied by ACL">>,
|
||||
{error, ?ERRT_FORBIDDEN(Lang, Txt)};
|
||||
_ ->
|
||||
{result, []}
|
||||
end
|
||||
end;
|
||||
|
||||
disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false ->
|
||||
Acc;
|
||||
@@ -246,25 +240,25 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
|
||||
case Node of
|
||||
?NS_ADMIN_ANNOUNCE ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_ANNOUNCE_ALL ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_SET_MOTD ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_EDIT_MOTD ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_DELETE_MOTD ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_SET_MOTD_ALLHOSTS ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
@@ -283,10 +277,10 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
}
|
||||
)).
|
||||
|
||||
-define(ITEMS_RESULT(Allow, Items),
|
||||
-define(ITEMS_RESULT(Allow, Items, Lang),
|
||||
case Allow of
|
||||
deny ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
{error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
|
||||
allow ->
|
||||
{result, Items}
|
||||
end).
|
||||
@@ -320,7 +314,7 @@ disco_items(Acc, From, #jid{lserver = LServer} = To, <<"announce">>, Lang) ->
|
||||
announce_items(Acc, From, To, Lang)
|
||||
end;
|
||||
|
||||
disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false ->
|
||||
Acc;
|
||||
@@ -331,25 +325,25 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
|
||||
case Node of
|
||||
?NS_ADMIN_ANNOUNCE ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?ITEMS_RESULT(Allow, [], Lang);
|
||||
?NS_ADMIN_ANNOUNCE_ALL ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?ITEMS_RESULT(Allow, [], Lang);
|
||||
?NS_ADMIN_SET_MOTD ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?ITEMS_RESULT(Allow, [], Lang);
|
||||
?NS_ADMIN_EDIT_MOTD ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?ITEMS_RESULT(Allow, [], Lang);
|
||||
?NS_ADMIN_DELETE_MOTD ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?ITEMS_RESULT(Allow, [], Lang);
|
||||
?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?ITEMS_RESULT(AllowGlobal, [], Lang);
|
||||
?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?ITEMS_RESULT(AllowGlobal, [], Lang);
|
||||
?NS_ADMIN_SET_MOTD_ALLHOSTS ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?ITEMS_RESULT(AllowGlobal, [], Lang);
|
||||
?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?ITEMS_RESULT(AllowGlobal, [], Lang);
|
||||
?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?ITEMS_RESULT(AllowGlobal, [], Lang);
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
@@ -396,7 +390,8 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang)
|
||||
commands_result(Allow, From, To, Request) ->
|
||||
case Allow of
|
||||
deny ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
Lang = Request#adhoc_request.lang,
|
||||
{error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
|
||||
allow ->
|
||||
announce_commands(From, To, Request)
|
||||
end.
|
||||
@@ -463,12 +458,13 @@ announce_commands(From, To,
|
||||
%% User returns form.
|
||||
case jlib:parse_xdata_submit(XData) of
|
||||
invalid ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
|
||||
Fields ->
|
||||
handle_adhoc_form(From, To, Request, Fields)
|
||||
end;
|
||||
true ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
Txt = <<"Incorrect action or data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end.
|
||||
|
||||
-define(VVALUE(Val),
|
||||
@@ -688,7 +684,9 @@ announce_all(From, To, Packet) ->
|
||||
Access = get_access(Host),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Local = jid:make(<<>>, To#jid.server, <<>>),
|
||||
@@ -703,7 +701,9 @@ announce_all_hosts_all(From, To, Packet) ->
|
||||
Access = get_access(global),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Local = jid:make(<<>>, To#jid.server, <<>>),
|
||||
@@ -719,7 +719,9 @@ announce_online(From, To, Packet) ->
|
||||
Access = get_access(Host),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_online1(ejabberd_sm:get_vh_session_list(Host),
|
||||
@@ -731,7 +733,9 @@ announce_all_hosts_online(From, To, Packet) ->
|
||||
Access = get_access(global),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
|
||||
@@ -752,7 +756,9 @@ announce_motd(From, To, Packet) ->
|
||||
Access = get_access(Host),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_motd(Host, Packet)
|
||||
@@ -762,7 +768,9 @@ announce_all_hosts_motd(From, To, Packet) ->
|
||||
Access = get_access(global),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Hosts = ?MYHOSTS,
|
||||
@@ -774,48 +782,17 @@ announce_motd(Host, Packet) ->
|
||||
announce_motd_update(LServer, Packet),
|
||||
Sessions = ejabberd_sm:get_vh_session_list(LServer),
|
||||
announce_online1(Sessions, LServer, Packet),
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, S, _R}) ->
|
||||
mnesia:write(#motd_users{us = {U, S}})
|
||||
end, Sessions)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
riak ->
|
||||
try
|
||||
lists:foreach(
|
||||
fun({U, S, _R}) ->
|
||||
ok = ejabberd_riak:put(#motd_users{us = {U, S}},
|
||||
motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, S}]}])
|
||||
end, Sessions),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end;
|
||||
odbc ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, _S, _R}) ->
|
||||
Username = ejabberd_odbc:escape(U),
|
||||
odbc_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[Username, <<"">>],
|
||||
[<<"username='">>, Username, <<"'">>])
|
||||
end, Sessions)
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F)
|
||||
end.
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:set_motd_users(LServer, Sessions).
|
||||
|
||||
announce_motd_update(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
Access = get_access(Host),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_motd_update(Host, Packet)
|
||||
@@ -825,7 +802,9 @@ announce_all_hosts_motd_update(From, To, Packet) ->
|
||||
Access = get_access(global),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Hosts = ?MYHOSTS,
|
||||
@@ -834,34 +813,17 @@ announce_all_hosts_motd_update(From, To, Packet) ->
|
||||
|
||||
announce_motd_update(LServer, Packet) ->
|
||||
announce_motd_delete(LServer),
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia ->
|
||||
F = fun() ->
|
||||
mnesia:write(#motd{server = LServer, packet = Packet})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
riak ->
|
||||
{atomic, ejabberd_riak:put(#motd{server = LServer,
|
||||
packet = Packet},
|
||||
motd_schema())};
|
||||
odbc ->
|
||||
XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet)),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[<<"">>, XML],
|
||||
[<<"username=''">>])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F)
|
||||
end.
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:set_motd(LServer, Packet).
|
||||
|
||||
announce_motd_delete(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
Access = get_access(Host),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_motd_delete(Host)
|
||||
@@ -871,7 +833,9 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
|
||||
Access = get_access(global),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Hosts = ?MYHOSTS,
|
||||
@@ -879,112 +843,30 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
|
||||
end.
|
||||
|
||||
announce_motd_delete(LServer) ->
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia ->
|
||||
F = fun() ->
|
||||
mnesia:delete({motd, LServer}),
|
||||
mnesia:write_lock_table(motd_users),
|
||||
Users = mnesia:select(
|
||||
motd_users,
|
||||
[{#motd_users{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]),
|
||||
lists:foreach(fun(US) ->
|
||||
mnesia:delete({motd_users, US})
|
||||
end, Users)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
riak ->
|
||||
try
|
||||
ok = ejabberd_riak:delete(motd, LServer),
|
||||
ok = ejabberd_riak:delete_by_index(motd_users,
|
||||
<<"server">>,
|
||||
LServer),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end;
|
||||
odbc ->
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t([<<"delete from motd;">>])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F)
|
||||
end.
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:delete_motd(LServer).
|
||||
|
||||
send_motd(JID) ->
|
||||
send_motd(JID, gen_mod:db_type(JID#jid.lserver, ?MODULE)).
|
||||
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
|
||||
case catch mnesia:dirty_read({motd, LServer}) of
|
||||
[#motd{packet = Packet}] ->
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({motd_users, US}) of
|
||||
[#motd_users{}] ->
|
||||
ok;
|
||||
_ ->
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:get_motd(LServer) of
|
||||
{ok, Packet} ->
|
||||
case Mod:is_motd_user(LUser, LServer) of
|
||||
false ->
|
||||
Local = jid:make(<<>>, LServer, <<>>),
|
||||
ejabberd_router:route(Local, JID, Packet),
|
||||
F = fun() ->
|
||||
mnesia:write(#motd_users{us = US})
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
Mod:set_motd_user(LUser, LServer);
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
error ->
|
||||
ok
|
||||
end;
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID, riak) ->
|
||||
case catch ejabberd_riak:get(motd, motd_schema(), LServer) of
|
||||
{ok, #motd{packet = Packet}} ->
|
||||
US = {LUser, LServer},
|
||||
case ejabberd_riak:get(motd_users, motd_users_schema(), US) of
|
||||
{ok, #motd_users{}} ->
|
||||
ok;
|
||||
_ ->
|
||||
Local = jid:make(<<>>, LServer, <<>>),
|
||||
ejabberd_router:route(Local, JID, Packet),
|
||||
{atomic, ejabberd_riak:put(
|
||||
#motd_users{us = US}, motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, LServer}]}])}
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, [<<"select xml from motd where username='';">>]) of
|
||||
{selected, [<<"xml">>], [[XML]]} ->
|
||||
case fxml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
ok;
|
||||
Packet ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
[<<"select username from motd "
|
||||
"where username='">>, Username, <<"';">>]) of
|
||||
{selected, [<<"username">>], []} ->
|
||||
Local = jid:make(<<"">>, LServer, <<"">>),
|
||||
ejabberd_router:route(Local, JID, Packet),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[Username, <<"">>],
|
||||
[<<"username='">>, Username, <<"'">>])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
send_motd(_, odbc) ->
|
||||
send_motd(_) ->
|
||||
ok.
|
||||
|
||||
get_stored_motd(LServer) ->
|
||||
case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:get_motd(LServer) of
|
||||
{ok, Packet} ->
|
||||
{fxml:get_subtag_cdata(Packet, <<"subject">>),
|
||||
fxml:get_subtag_cdata(Packet, <<"body">>)};
|
||||
@@ -992,34 +874,6 @@ get_stored_motd(LServer) ->
|
||||
{<<>>, <<>>}
|
||||
end.
|
||||
|
||||
get_stored_motd_packet(LServer, mnesia) ->
|
||||
case catch mnesia:dirty_read({motd, LServer}) of
|
||||
[#motd{packet = Packet}] ->
|
||||
{ok, Packet};
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
get_stored_motd_packet(LServer, riak) ->
|
||||
case ejabberd_riak:get(motd, motd_schema(), LServer) of
|
||||
{ok, #motd{packet = Packet}} ->
|
||||
{ok, Packet};
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
get_stored_motd_packet(LServer, odbc) ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, [<<"select xml from motd where username='';">>]) of
|
||||
{selected, [<<"xml">>], [[XML]]} ->
|
||||
case fxml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
error;
|
||||
Packet ->
|
||||
{ok, Packet}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
%% This function is similar to others, but doesn't perform any ACL verification
|
||||
send_announcement_to_all(Host, SubjectS, BodyS) ->
|
||||
SubjectEls = if SubjectS /= <<>> ->
|
||||
@@ -1049,102 +903,23 @@ 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).
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
update_tables() ->
|
||||
update_motd_table(),
|
||||
update_motd_users_table().
|
||||
|
||||
update_motd_table() ->
|
||||
Fields = record_info(fields, motd),
|
||||
case mnesia:table_info(motd, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
motd, Fields, set,
|
||||
fun(#motd{server = S}) -> S end,
|
||||
fun(#motd{server = S, packet = P} = R) ->
|
||||
NewS = iolist_to_binary(S),
|
||||
NewP = fxml:to_xmlel(P),
|
||||
R#motd{server = NewS, packet = NewP}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating motd table", []),
|
||||
mnesia:transform_table(motd, ignore, Fields)
|
||||
end.
|
||||
|
||||
|
||||
update_motd_users_table() ->
|
||||
Fields = record_info(fields, motd_users),
|
||||
case mnesia:table_info(motd_users, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
motd_users, Fields, set,
|
||||
fun(#motd_users{us = {U, _}}) -> U end,
|
||||
fun(#motd_users{us = {U, S}} = R) ->
|
||||
NewUS = {iolist_to_binary(U),
|
||||
iolist_to_binary(S)},
|
||||
R#motd_users{us = NewUS}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating motd_users table", []),
|
||||
mnesia:transform_table(motd_users, ignore, Fields)
|
||||
end.
|
||||
|
||||
motd_schema() ->
|
||||
{record_info(fields, motd), #motd{}}.
|
||||
|
||||
motd_users_schema() ->
|
||||
{record_info(fields, motd_users), #motd_users{}}.
|
||||
|
||||
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_odbc:escape(fxml:element_to_binary(El)),
|
||||
<<"');">>]];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
{motd_users,
|
||||
fun(Host, #motd_users{us = {LUser, LServer}})
|
||||
when LServer == Host, LUser /= <<"">> ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
[[<<"delete from motd where username='">>, Username, <<"';">>],
|
||||
[<<"insert into motd(username, xml) values ('">>,
|
||||
Username, <<"', '');">>]];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
export(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:export(LServer).
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select xml from motd where username='';">>,
|
||||
fun([XML]) ->
|
||||
El = fxml_stream:parse_element(XML),
|
||||
#motd{server = LServer, packet = El}
|
||||
end},
|
||||
{<<"select username from motd where xml='';">>,
|
||||
fun([LUser]) ->
|
||||
#motd_users{us = {LUser, LServer}}
|
||||
end}].
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:import(LServer).
|
||||
|
||||
import(_LServer, mnesia, #motd{} = Motd) ->
|
||||
mnesia:dirty_write(Motd);
|
||||
import(_LServer, mnesia, #motd_users{} = Users) ->
|
||||
mnesia:dirty_write(Users);
|
||||
import(_LServer, riak, #motd{} = Motd) ->
|
||||
ejabberd_riak:put(Motd, motd_schema());
|
||||
import(_LServer, riak, #motd_users{us = {_, S}} = Users) ->
|
||||
ejabberd_riak:put(Users, motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, S}]}]);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
import(LServer, DBType, LA) ->
|
||||
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
||||
Mod:import(LServer, LA).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
|
||||
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].
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_announce_mnesia).
|
||||
-behaviour(mod_announce).
|
||||
|
||||
%% 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/2]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_announce.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
mnesia:create_table(motd,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, motd)}]),
|
||||
mnesia:create_table(motd_users,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, motd_users)}]),
|
||||
update_tables().
|
||||
|
||||
set_motd_users(_LServer, USRs) ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, S, _R}) ->
|
||||
mnesia:write(#motd_users{us = {U, S}})
|
||||
end, USRs)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
set_motd(LServer, Packet) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#motd{server = LServer, packet = Packet})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
delete_motd(LServer) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({motd, LServer}),
|
||||
mnesia:write_lock_table(motd_users),
|
||||
Users = mnesia:select(
|
||||
motd_users,
|
||||
[{#motd_users{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]),
|
||||
lists:foreach(fun(US) ->
|
||||
mnesia:delete({motd_users, US})
|
||||
end, Users)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
get_motd(LServer) ->
|
||||
case mnesia:dirty_read({motd, LServer}) of
|
||||
[#motd{packet = Packet}] ->
|
||||
{ok, Packet};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
is_motd_user(LUser, LServer) ->
|
||||
case mnesia:dirty_read({motd_users, {LUser, LServer}}) of
|
||||
[#motd_users{}] -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
set_motd_user(LUser, LServer) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#motd_users{us = {LUser, LServer}})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
import(_LServer, #motd{} = Motd) ->
|
||||
mnesia:dirty_write(Motd);
|
||||
import(_LServer, #motd_users{} = Users) ->
|
||||
mnesia:dirty_write(Users).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
update_tables() ->
|
||||
update_motd_table(),
|
||||
update_motd_users_table().
|
||||
|
||||
update_motd_table() ->
|
||||
Fields = record_info(fields, motd),
|
||||
case mnesia:table_info(motd, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
motd, Fields, set,
|
||||
fun(#motd{server = S}) -> S end,
|
||||
fun(#motd{server = S, packet = P} = R) ->
|
||||
NewS = iolist_to_binary(S),
|
||||
NewP = fxml:to_xmlel(P),
|
||||
R#motd{server = NewS, packet = NewP}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating motd table", []),
|
||||
mnesia:transform_table(motd, ignore, Fields)
|
||||
end.
|
||||
|
||||
|
||||
update_motd_users_table() ->
|
||||
Fields = record_info(fields, motd_users),
|
||||
case mnesia:table_info(motd_users, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
motd_users, Fields, set,
|
||||
fun(#motd_users{us = {U, _}}) -> U end,
|
||||
fun(#motd_users{us = {U, S}} = R) ->
|
||||
NewUS = {iolist_to_binary(U),
|
||||
iolist_to_binary(S)},
|
||||
R#motd_users{us = NewUS}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating motd_users table", []),
|
||||
mnesia:transform_table(motd_users, ignore, Fields)
|
||||
end.
|
||||
@@ -0,0 +1,87 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_announce_riak).
|
||||
-behaviour(mod_announce).
|
||||
|
||||
%% 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/2]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_announce.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
set_motd_users(_LServer, USRs) ->
|
||||
try
|
||||
lists:foreach(
|
||||
fun({U, S, _R}) ->
|
||||
ok = ejabberd_riak:put(#motd_users{us = {U, S}},
|
||||
motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, S}]}])
|
||||
end, USRs),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end.
|
||||
|
||||
set_motd(LServer, Packet) ->
|
||||
{atomic, ejabberd_riak:put(#motd{server = LServer,
|
||||
packet = Packet},
|
||||
motd_schema())}.
|
||||
|
||||
delete_motd(LServer) ->
|
||||
try
|
||||
ok = ejabberd_riak:delete(motd, LServer),
|
||||
ok = ejabberd_riak:delete_by_index(motd_users,
|
||||
<<"server">>,
|
||||
LServer),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end.
|
||||
|
||||
get_motd(LServer) ->
|
||||
case ejabberd_riak:get(motd, motd_schema(), LServer) of
|
||||
{ok, #motd{packet = Packet}} ->
|
||||
{ok, Packet};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
is_motd_user(LUser, LServer) ->
|
||||
case ejabberd_riak:get(motd_users, motd_users_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #motd_users{}} -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
set_motd_user(LUser, LServer) ->
|
||||
{atomic, ejabberd_riak:put(
|
||||
#motd_users{us = {LUser, LServer}}, motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, LServer}]}])}.
|
||||
|
||||
import(_LServer, #motd{} = Motd) ->
|
||||
ejabberd_riak:put(Motd, motd_schema());
|
||||
import(_LServer, #motd_users{us = {_, S}} = Users) ->
|
||||
ejabberd_riak:put(Users, motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, S}]}]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
motd_schema() ->
|
||||
{record_info(fields, motd), #motd{}}.
|
||||
|
||||
motd_users_schema() ->
|
||||
{record_info(fields, motd_users), #motd_users{}}.
|
||||
@@ -0,0 +1,127 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-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,
|
||||
import/2, export/1]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_announce.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
set_motd_users(LServer, USRs) ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, _S, _R}) ->
|
||||
?SQL_UPSERT_T(
|
||||
"motd",
|
||||
["!username=%(U)s",
|
||||
"xml=''"])
|
||||
end, USRs)
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
set_motd(LServer, Packet) ->
|
||||
XML = fxml:element_to_binary(Packet),
|
||||
F = fun() ->
|
||||
?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(?SQL("delete from motd"))
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
get_motd(LServer) ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(xml)s from motd where username=''")) of
|
||||
{selected, [{XML}]} ->
|
||||
case fxml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
error;
|
||||
Packet ->
|
||||
{ok, Packet}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
is_motd_user(LUser, LServer) ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s from motd"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, [_|_]} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
set_motd_user(LUser, LServer) ->
|
||||
F = fun() ->
|
||||
?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 ->
|
||||
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 /= <<"">> ->
|
||||
[?SQL("delete from motd where username=%(LUser)s;"),
|
||||
?SQL("insert into motd(username, xml) values (%(LUser)s, '');")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select xml from motd where username='';">>,
|
||||
fun([XML]) ->
|
||||
El = fxml_stream:parse_element(XML),
|
||||
#motd{server = LServer, packet = El}
|
||||
end},
|
||||
{<<"select username from motd where xml='';">>,
|
||||
fun([LUser]) ->
|
||||
#motd_users{us = {LUser, LServer}}
|
||||
end}].
|
||||
|
||||
import(_, _) ->
|
||||
pass.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
+36
-223
@@ -39,6 +39,10 @@
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
-callback process_blocklist_block(binary(), binary(), function()) -> {atomic, any()}.
|
||||
-callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}.
|
||||
-callback process_blocklist_get(binary(), binary()) -> [listitem()] | error.
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
||||
one_queue),
|
||||
@@ -64,29 +68,33 @@ process_iq(_From, _To, IQ) ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
|
||||
process_iq_get(_, From, _To,
|
||||
#iq{xmlns = ?NS_BLOCKING,
|
||||
#iq{xmlns = ?NS_BLOCKING, lang = Lang,
|
||||
sub_el = #xmlel{name = <<"blocklist">>}},
|
||||
_) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
{stop, process_blocklist_get(LUser, LServer)};
|
||||
{stop, process_blocklist_get(LUser, LServer, Lang)};
|
||||
process_iq_get(Acc, _, _, _, _) -> Acc.
|
||||
|
||||
process_iq_set(_, From, _To,
|
||||
#iq{xmlns = ?NS_BLOCKING,
|
||||
#iq{xmlns = ?NS_BLOCKING, lang = Lang,
|
||||
sub_el =
|
||||
#xmlel{name = SubElName, children = SubEls}}) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
Res = case {SubElName, fxml:remove_cdata(SubEls)} of
|
||||
{<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
|
||||
{<<"block">>, []} ->
|
||||
Txt = <<"No items found in this query">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
{<<"block">>, Els} ->
|
||||
JIDs = parse_blocklist_items(Els, []),
|
||||
process_blocklist_block(LUser, LServer, JIDs);
|
||||
process_blocklist_block(LUser, LServer, JIDs, Lang);
|
||||
{<<"unblock">>, []} ->
|
||||
process_blocklist_unblock_all(LUser, LServer);
|
||||
process_blocklist_unblock_all(LUser, LServer, Lang);
|
||||
{<<"unblock">>, Els} ->
|
||||
JIDs = parse_blocklist_items(Els, []),
|
||||
process_blocklist_unblock(LUser, LServer, JIDs);
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
process_blocklist_unblock(LUser, LServer, JIDs, Lang);
|
||||
_ ->
|
||||
Txt = <<"Unknown blocking command">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end,
|
||||
{stop, Res};
|
||||
process_iq_set(Acc, _, _, _) -> Acc.
|
||||
@@ -125,7 +133,7 @@ parse_blocklist_items([#xmlel{name = <<"item">>,
|
||||
parse_blocklist_items([_ | Els], JIDs) ->
|
||||
parse_blocklist_items(Els, JIDs).
|
||||
|
||||
process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
process_blocklist_block(LUser, LServer, JIDs, Lang) ->
|
||||
Filter = fun (List) ->
|
||||
AlreadyBlocked = list_to_blocklist_jids(List, []),
|
||||
lists:foldr(fun (JID, List1) ->
|
||||
@@ -143,9 +151,8 @@ process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
end,
|
||||
List, JIDs)
|
||||
end,
|
||||
case process_blocklist_block(LUser, LServer, Filter,
|
||||
gen_mod:db_type(LServer, mod_privacy))
|
||||
of
|
||||
Mod = db_mod(LServer),
|
||||
case Mod:process_blocklist_block(LUser, LServer, Filter) of
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default,
|
||||
@@ -155,105 +162,17 @@ process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
{result, [], UserList};
|
||||
_Err ->
|
||||
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
|
||||
end.
|
||||
|
||||
process_blocklist_block(LUser, LServer, Filter,
|
||||
mnesia) ->
|
||||
F = fun () ->
|
||||
case mnesia:wread({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
P = #privacy{us = {LUser, LServer}},
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = [],
|
||||
List = [];
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewDefault = Default,
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists);
|
||||
false ->
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = Lists,
|
||||
List = []
|
||||
end
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewLists = [{NewDefault, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{default = NewDefault,
|
||||
lists = NewLists}),
|
||||
{ok, NewDefault, NewList}
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_block(LUser, LServer, Filter,
|
||||
riak) ->
|
||||
{atomic,
|
||||
begin
|
||||
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewDefault = Default,
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists);
|
||||
false ->
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = Lists,
|
||||
List = []
|
||||
end;
|
||||
{error, _} ->
|
||||
P = #privacy{us = {LUser, LServer}},
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = [],
|
||||
List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewLists = [{NewDefault, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{default = NewDefault,
|
||||
lists = NewLists},
|
||||
mod_privacy:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, NewDefault, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
end};
|
||||
process_blocklist_block(LUser, LServer, Filter, odbc) ->
|
||||
F = fun () ->
|
||||
Default = case
|
||||
mod_privacy:sql_get_default_privacy_list_t(LUser)
|
||||
of
|
||||
{selected, []} ->
|
||||
Name = <<"Blocked contacts">>,
|
||||
mod_privacy:sql_add_privacy_list(LUser, Name),
|
||||
mod_privacy:sql_set_default_privacy_list(LUser,
|
||||
Name),
|
||||
Name;
|
||||
{selected, [{Name}]} -> Name
|
||||
end,
|
||||
{selected, [{ID}]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected, RItems = [_ | _]} ->
|
||||
List = lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
|
||||
_ -> List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList}
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
process_blocklist_unblock_all(LUser, LServer) ->
|
||||
process_blocklist_unblock_all(LUser, LServer, Lang) ->
|
||||
Filter = fun (List) ->
|
||||
lists:filter(fun (#listitem{action = A}) -> A =/= deny
|
||||
end,
|
||||
List)
|
||||
end,
|
||||
DBType = gen_mod:db_type(LServer, mod_privacy),
|
||||
case unblock_by_filter(LUser, LServer, Filter, DBType) of
|
||||
Mod = db_mod(LServer),
|
||||
case Mod:unblock_by_filter(LUser, LServer, Filter) of
|
||||
{atomic, ok} -> {result, []};
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
@@ -263,10 +182,10 @@ process_blocklist_unblock_all(LUser, LServer) ->
|
||||
{result, [], UserList};
|
||||
_Err ->
|
||||
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
|
||||
end.
|
||||
|
||||
process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
process_blocklist_unblock(LUser, LServer, JIDs, Lang) ->
|
||||
Filter = fun (List) ->
|
||||
lists:filter(fun (#listitem{action = deny, type = jid,
|
||||
value = JID}) ->
|
||||
@@ -275,8 +194,8 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
end,
|
||||
List)
|
||||
end,
|
||||
DBType = gen_mod:db_type(LServer, mod_privacy),
|
||||
case unblock_by_filter(LUser, LServer, Filter, DBType) of
|
||||
Mod = db_mod(LServer),
|
||||
case Mod:unblock_by_filter(LUser, LServer, Filter) of
|
||||
{atomic, ok} -> {result, []};
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
@@ -287,79 +206,9 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
{result, [], UserList};
|
||||
_Err ->
|
||||
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
|
||||
end.
|
||||
|
||||
unblock_by_filter(LUser, LServer, Filter, mnesia) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
% No lists, nothing to unblock
|
||||
ok;
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists}),
|
||||
{ok, Default, NewList};
|
||||
false ->
|
||||
% No default list, nothing to unblock
|
||||
ok
|
||||
end
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
unblock_by_filter(LUser, LServer, Filter, riak) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{error, _} ->
|
||||
%% No lists, nothing to unblock
|
||||
ok;
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
mod_privacy:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, Default, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
false ->
|
||||
%% No default list, nothing to unblock
|
||||
ok
|
||||
end
|
||||
end};
|
||||
unblock_by_filter(LUser, LServer, Filter, odbc) ->
|
||||
F = fun () ->
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser)
|
||||
of
|
||||
{selected, []} -> ok;
|
||||
{selected, [{Default}]} ->
|
||||
{selected, [{ID}]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected, RItems = [_ | _]} ->
|
||||
List = lists:flatmap(fun mod_privacy:raw_to_item/1,
|
||||
RItems),
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
end
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
make_userlist(Name, List) ->
|
||||
NeedDb = mod_privacy:is_list_needdb(List),
|
||||
#userlist{name = Name, list = List, needdb = NeedDb}.
|
||||
@@ -375,11 +224,11 @@ broadcast_blocklist_event(LUser, LServer, Event) ->
|
||||
ejabberd_sm:route(JID, JID,
|
||||
{broadcast, {blocking, Event}}).
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case process_blocklist_get(LUser, LServer,
|
||||
gen_mod:db_type(LServer, mod_privacy))
|
||||
of
|
||||
error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
process_blocklist_get(LUser, LServer, Lang) ->
|
||||
Mod = db_mod(LServer),
|
||||
case Mod:process_blocklist_get(LUser, LServer) of
|
||||
error ->
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
|
||||
List ->
|
||||
JIDs = list_to_blocklist_jids(List, []),
|
||||
Items = lists:map(fun (JID) ->
|
||||
@@ -397,45 +246,9 @@ process_blocklist_get(LUser, LServer) ->
|
||||
children = Items}]}
|
||||
end.
|
||||
|
||||
process_blocklist_get(LUser, LServer, mnesia) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer})
|
||||
of
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> [];
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> []
|
||||
end
|
||||
end;
|
||||
process_blocklist_get(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> []
|
||||
end;
|
||||
{error, notfound} ->
|
||||
[];
|
||||
{error, _} ->
|
||||
error
|
||||
end;
|
||||
process_blocklist_get(LUser, LServer, odbc) ->
|
||||
case catch
|
||||
mod_privacy:sql_get_default_privacy_list(LUser, LServer)
|
||||
of
|
||||
{selected, []} -> [];
|
||||
{selected, [{Default}]} ->
|
||||
case catch mod_privacy:sql_get_privacy_list_data(LUser,
|
||||
LServer, Default)
|
||||
of
|
||||
{selected, RItems} ->
|
||||
lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
|
||||
{'EXIT', _} -> error
|
||||
end;
|
||||
{'EXIT', _} -> error
|
||||
end.
|
||||
db_mod(LServer) ->
|
||||
DBType = gen_mod:db_type(LServer, mod_privacy),
|
||||
gen_mod:db_mod(DBType, ?MODULE).
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) -> [iqdisc].
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_blocking_mnesia).
|
||||
|
||||
-behaviour(mod_blocking).
|
||||
|
||||
%% API
|
||||
-export([process_blocklist_block/3, unblock_by_filter/3,
|
||||
process_blocklist_get/2]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
process_blocklist_block(LUser, LServer, Filter) ->
|
||||
F = fun () ->
|
||||
case mnesia:wread({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
P = #privacy{us = {LUser, LServer}},
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = [],
|
||||
List = [];
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewDefault = Default,
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists);
|
||||
false ->
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = Lists,
|
||||
List = []
|
||||
end
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewLists = [{NewDefault, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{default = NewDefault,
|
||||
lists = NewLists}),
|
||||
{ok, NewDefault, NewList}
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
unblock_by_filter(LUser, LServer, Filter) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
%% No lists, nothing to unblock
|
||||
ok;
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists}),
|
||||
{ok, Default, NewList};
|
||||
false ->
|
||||
%% No default list, nothing to unblock
|
||||
ok
|
||||
end
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> [];
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> []
|
||||
end
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
@@ -0,0 +1,98 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_blocking_riak).
|
||||
|
||||
-behaviour(mod_blocking).
|
||||
|
||||
%% API
|
||||
-export([process_blocklist_block/3, unblock_by_filter/3,
|
||||
process_blocklist_get/2]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
process_blocklist_block(LUser, LServer, Filter) ->
|
||||
{atomic,
|
||||
begin
|
||||
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewDefault = Default,
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists);
|
||||
false ->
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = Lists,
|
||||
List = []
|
||||
end;
|
||||
{error, _} ->
|
||||
P = #privacy{us = {LUser, LServer}},
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = [],
|
||||
List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewLists = [{NewDefault, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{default = NewDefault,
|
||||
lists = NewLists},
|
||||
mod_privacy_riak:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, NewDefault, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
end}.
|
||||
|
||||
unblock_by_filter(LUser, LServer, Filter) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{error, _} ->
|
||||
%% No lists, nothing to unblock
|
||||
ok;
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
mod_privacy_riak:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, Default, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
false ->
|
||||
%% No default list, nothing to unblock
|
||||
ok
|
||||
end
|
||||
end}.
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> []
|
||||
end;
|
||||
{error, notfound} ->
|
||||
[];
|
||||
{error, _} ->
|
||||
error
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
@@ -0,0 +1,92 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_blocking_sql).
|
||||
|
||||
-behaviour(mod_blocking).
|
||||
|
||||
%% API
|
||||
-export([process_blocklist_block/3, unblock_by_filter/3,
|
||||
process_blocklist_get/2]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
process_blocklist_block(LUser, LServer, Filter) ->
|
||||
F = fun () ->
|
||||
Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, []} ->
|
||||
Name = <<"Blocked contacts">>,
|
||||
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
|
||||
end,
|
||||
{selected, [{ID}]} =
|
||||
mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected, RItems = [_ | _]} ->
|
||||
List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
|
||||
_ ->
|
||||
List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList}
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
unblock_by_filter(LUser, LServer, Filter) ->
|
||||
F = fun () ->
|
||||
case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, []} -> ok;
|
||||
{selected, [{Default}]} ->
|
||||
{selected, [{ID}]} =
|
||||
mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected, RItems = [_ | _]} ->
|
||||
List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1,
|
||||
RItems),
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
end
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case catch mod_privacy_sql:sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, []} -> [];
|
||||
{selected, [{Default}]} ->
|
||||
case catch mod_privacy_sql:sql_get_privacy_list_data(
|
||||
LUser, LServer, Default) of
|
||||
{selected, RItems} ->
|
||||
lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
|
||||
{'EXIT', _} -> error
|
||||
end;
|
||||
{'EXIT', _} -> error
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
+20
-126
@@ -80,6 +80,12 @@
|
||||
|
||||
-record(state, {host = <<"">> :: binary()}).
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback caps_read(binary(), {binary(), binary()}) ->
|
||||
{ok, non_neg_integer() | [binary()]} | error.
|
||||
-callback caps_write(binary(), {binary(), binary()},
|
||||
non_neg_integer() | [binary()]) -> any().
|
||||
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE,
|
||||
@@ -300,28 +306,9 @@ c2s_broadcast_recipients(InAcc, Host, C2SState,
|
||||
end;
|
||||
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
|
||||
|
||||
init_db(mnesia, _Host) ->
|
||||
case catch mnesia:table_info(caps_features, storage_type) of
|
||||
{'EXIT', _} ->
|
||||
ok;
|
||||
disc_only_copies ->
|
||||
ok;
|
||||
_ ->
|
||||
mnesia:delete_table(caps_features)
|
||||
end,
|
||||
mnesia:create_table(caps_features,
|
||||
[{disc_only_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes,
|
||||
record_info(fields, caps_features)}]),
|
||||
update_table(),
|
||||
mnesia:add_table_copy(caps_features, node(),
|
||||
disc_only_copies);
|
||||
init_db(_, _) ->
|
||||
ok.
|
||||
|
||||
init([Host, Opts]) ->
|
||||
init_db(gen_mod:db_type(Host, Opts), Host),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
MaxSize = gen_mod:get_opt(cache_size, Opts,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1000),
|
||||
@@ -450,65 +437,13 @@ feature_response(_IQResult, Host, From, Caps,
|
||||
|
||||
caps_read_fun(Host, Node) ->
|
||||
LServer = jid:nameprep(Host),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
caps_read_fun(LServer, Node, DBType).
|
||||
|
||||
caps_read_fun(_LServer, Node, mnesia) ->
|
||||
fun () ->
|
||||
case mnesia:dirty_read({caps_features, Node}) of
|
||||
[#caps_features{features = Features}] -> {ok, Features};
|
||||
_ -> error
|
||||
end
|
||||
end;
|
||||
caps_read_fun(_LServer, Node, riak) ->
|
||||
fun() ->
|
||||
case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
|
||||
{ok, #caps_features{features = Features}} -> {ok, Features};
|
||||
_ -> error
|
||||
end
|
||||
end;
|
||||
caps_read_fun(LServer, {Node, SubNode}, odbc) ->
|
||||
fun() ->
|
||||
SNode = ejabberd_odbc:escape(Node),
|
||||
SSubNode = ejabberd_odbc:escape(SubNode),
|
||||
case ejabberd_odbc: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
|
||||
end
|
||||
end.
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
fun() -> Mod:caps_read(LServer, Node) end.
|
||||
|
||||
caps_write_fun(Host, Node, Features) ->
|
||||
LServer = jid:nameprep(Host),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
caps_write_fun(LServer, Node, Features, DBType).
|
||||
|
||||
caps_write_fun(_LServer, Node, Features, mnesia) ->
|
||||
fun () ->
|
||||
mnesia:dirty_write(#caps_features{node_pair = Node,
|
||||
features = Features})
|
||||
end;
|
||||
caps_write_fun(_LServer, Node, Features, riak) ->
|
||||
fun () ->
|
||||
ejabberd_riak:put(#caps_features{node_pair = Node,
|
||||
features = Features},
|
||||
caps_features_schema())
|
||||
end;
|
||||
caps_write_fun(LServer, NodePair, Features, odbc) ->
|
||||
fun () ->
|
||||
ejabberd_odbc:sql_transaction(
|
||||
LServer,
|
||||
sql_write_features_t(NodePair, Features))
|
||||
end.
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
fun() -> Mod:caps_write(LServer, Node, Features) end.
|
||||
|
||||
make_my_disco_hash(Host) ->
|
||||
JID = jid:make(<<"">>, Host, <<"">>),
|
||||
@@ -658,64 +593,23 @@ is_valid_node(Node) ->
|
||||
false
|
||||
end.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, caps_features),
|
||||
case mnesia:table_info(caps_features, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
caps_features, Fields, set,
|
||||
fun(#caps_features{node_pair = {N, _}}) -> N end,
|
||||
fun(#caps_features{node_pair = {N, P},
|
||||
features = Fs} = R) ->
|
||||
NewFs = if is_integer(Fs) ->
|
||||
Fs;
|
||||
true ->
|
||||
[iolist_to_binary(F) || F <- Fs]
|
||||
end,
|
||||
R#caps_features{node_pair = {iolist_to_binary(N),
|
||||
iolist_to_binary(P)},
|
||||
features = NewFs}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating caps_features table", []),
|
||||
mnesia:transform_table(caps_features, ignore, Fields)
|
||||
end.
|
||||
|
||||
sql_write_features_t({Node, SubNode}, Features) ->
|
||||
SNode = ejabberd_odbc:escape(Node),
|
||||
SSubNode = ejabberd_odbc: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_odbc:escape(F), <<"');">>] || F <- NewFeatures]].
|
||||
|
||||
caps_features_schema() ->
|
||||
{record_info(fields, caps_features), #caps_features{}}.
|
||||
|
||||
export(_Server) ->
|
||||
[{caps_features,
|
||||
fun(_Host, #caps_features{node_pair = NodePair,
|
||||
features = Features}) ->
|
||||
sql_write_features_t(NodePair, Features);
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
export(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:export(LServer).
|
||||
|
||||
import_info() ->
|
||||
[{<<"caps_features">>, 4}].
|
||||
|
||||
import_start(LServer, DBType) ->
|
||||
ets:new(caps_features_tmp, [private, named_table, bag]),
|
||||
init_db(DBType, LServer),
|
||||
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
||||
Mod:init(LServer, []),
|
||||
ok.
|
||||
|
||||
import(_LServer, {odbc, _}, _DBType, <<"caps_features">>,
|
||||
import(_LServer, {sql, _}, _DBType, <<"caps_features">>,
|
||||
[Node, SubNode, Feature, _TimeStamp]) ->
|
||||
Feature1 = case catch jlib:binary_to_integer(Feature) of
|
||||
I when is_integer(I), I>0 -> I;
|
||||
@@ -748,7 +642,7 @@ import_next(LServer, DBType, NodePair) ->
|
||||
ejabberd_riak:put(
|
||||
#caps_features{node_pair = NodePair, features = Features},
|
||||
caps_features_schema());
|
||||
_ when DBType == odbc ->
|
||||
_ when DBType == sql ->
|
||||
ok
|
||||
end,
|
||||
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
|
||||
@@ -757,6 +651,6 @@ mod_opt_type(cache_life_time) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
mod_opt_type(cache_size) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) ->
|
||||
[cache_life_time, cache_size, db_type].
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_caps_mnesia).
|
||||
-behaviour(mod_caps).
|
||||
|
||||
%% API
|
||||
-export([init/2, caps_read/2, caps_write/3]).
|
||||
|
||||
-include("mod_caps.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
case catch mnesia:table_info(caps_features, storage_type) of
|
||||
{'EXIT', _} ->
|
||||
ok;
|
||||
disc_only_copies ->
|
||||
ok;
|
||||
_ ->
|
||||
mnesia:delete_table(caps_features)
|
||||
end,
|
||||
mnesia:create_table(caps_features,
|
||||
[{disc_only_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes,
|
||||
record_info(fields, caps_features)}]),
|
||||
update_table(),
|
||||
mnesia:add_table_copy(caps_features, node(),
|
||||
disc_only_copies).
|
||||
|
||||
caps_read(_LServer, Node) ->
|
||||
case mnesia:dirty_read({caps_features, Node}) of
|
||||
[#caps_features{features = Features}] -> {ok, Features};
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
caps_write(_LServer, Node, Features) ->
|
||||
mnesia:dirty_write(#caps_features{node_pair = Node,
|
||||
features = Features}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
update_table() ->
|
||||
Fields = record_info(fields, caps_features),
|
||||
case mnesia:table_info(caps_features, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
caps_features, Fields, set,
|
||||
fun(#caps_features{node_pair = {N, _}}) -> N end,
|
||||
fun(#caps_features{node_pair = {N, P},
|
||||
features = Fs} = R) ->
|
||||
NewFs = if is_integer(Fs) ->
|
||||
Fs;
|
||||
true ->
|
||||
[iolist_to_binary(F) || F <- Fs]
|
||||
end,
|
||||
R#caps_features{node_pair = {iolist_to_binary(N),
|
||||
iolist_to_binary(P)},
|
||||
features = NewFs}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating caps_features table", []),
|
||||
mnesia:transform_table(caps_features, ignore, Fields)
|
||||
end.
|
||||
@@ -0,0 +1,38 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_caps_riak).
|
||||
-behaviour(mod_caps).
|
||||
|
||||
%% API
|
||||
-export([init/2, caps_read/2, caps_write/3]).
|
||||
|
||||
-include("mod_caps.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
caps_read(_LServer, Node) ->
|
||||
case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
|
||||
{ok, #caps_features{features = Features}} -> {ok, Features};
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
caps_write(_LServer, Node, Features) ->
|
||||
ejabberd_riak:put(#caps_features{node_pair = Node,
|
||||
features = Features},
|
||||
caps_features_schema()).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
caps_features_schema() ->
|
||||
{record_info(fields, caps_features), #caps_features{}}.
|
||||
@@ -0,0 +1,69 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-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
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
caps_read(LServer, {Node, SubNode}) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
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) ->
|
||||
ejabberd_sql:sql_transaction(
|
||||
LServer,
|
||||
sql_write_features_t(NodePair, Features)).
|
||||
|
||||
export(_Server) ->
|
||||
[{caps_features,
|
||||
fun(_Host, #caps_features{node_pair = NodePair,
|
||||
features = Features}) ->
|
||||
sql_write_features_t(NodePair, Features);
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
sql_write_features_t({Node, SubNode}, Features) ->
|
||||
NewFeatures = if is_integer(Features) ->
|
||||
[jlib:integer_to_binary(Features)];
|
||||
true ->
|
||||
Features
|
||||
end,
|
||||
[?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]].
|
||||
|
||||
+23
-33
@@ -43,12 +43,11 @@
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-define(PROCNAME, ?MODULE).
|
||||
-define(TABLE, carboncopy).
|
||||
|
||||
-type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
|
||||
-record(carboncopy,{us :: {binary(), binary()} | matchspec_atom(),
|
||||
resource :: binary() | matchspec_atom(),
|
||||
version :: binary() | matchspec_atom()}).
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
|
||||
-callback disable(binary(), binary(), binary()) -> ok | {error, any()}.
|
||||
-callback list(binary(), binary()) -> [{binary(), binary()}].
|
||||
|
||||
is_carbon_copy(Packet) ->
|
||||
is_carbon_copy(Packet, <<"sent">>) orelse
|
||||
@@ -69,17 +68,8 @@ start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue),
|
||||
mod_disco:register_feature(Host, ?NS_CARBONS_1),
|
||||
mod_disco:register_feature(Host, ?NS_CARBONS_2),
|
||||
Fields = record_info(fields, ?TABLE),
|
||||
try mnesia:table_info(?TABLE, attributes) of
|
||||
Fields -> ok;
|
||||
_ -> mnesia:delete_table(?TABLE) %% recreate..
|
||||
catch _:_Error -> ok %%probably table don't exist
|
||||
end,
|
||||
mnesia:create_table(?TABLE,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, ?TABLE)},
|
||||
{type, bag}]),
|
||||
mnesia:add_table_copy(?TABLE, node(), ram_copies),
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
ejabberd_hooks:add(unset_presence_hook,Host, ?MODULE, remove_connection, 10),
|
||||
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
|
||||
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
|
||||
@@ -102,7 +92,9 @@ iq_handler2(From, To, IQ) ->
|
||||
iq_handler1(From, To, IQ) ->
|
||||
iq_handler(From, To, IQ, ?NS_CARBONS_1).
|
||||
|
||||
iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children = []}} = IQ, CC)->
|
||||
iq_handler(From, _To,
|
||||
#iq{type=set, lang = Lang,
|
||||
sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
|
||||
?DEBUG("carbons IQ received: ~p", [IQ]),
|
||||
{U, S, R} = jid:tolower(From),
|
||||
Result = case Operation of
|
||||
@@ -118,12 +110,14 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children
|
||||
?DEBUG("carbons IQ result: ok", []),
|
||||
IQ#iq{type=result, sub_el=[]};
|
||||
{error,_Error} ->
|
||||
?WARNING_MSG("Error enabling / disabling carbons: ~p", [Result]),
|
||||
IQ#iq{type=error,sub_el = [?ERR_BAD_REQUEST]}
|
||||
?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
|
||||
Txt = <<"Database failure">>,
|
||||
IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
|
||||
end;
|
||||
|
||||
iq_handler(_From, _To, IQ, _CC)->
|
||||
IQ#iq{type=error, sub_el = [?ERR_NOT_ALLOWED]}.
|
||||
iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)->
|
||||
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type=error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
|
||||
|
||||
user_send_packet(Packet, _C2SState, From, To) ->
|
||||
check_and_forward(From, To, Packet, sent).
|
||||
@@ -240,18 +234,13 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
|
||||
|
||||
enable(Host, U, R, CC)->
|
||||
?DEBUG("enabling for ~p", [U]),
|
||||
try mnesia:dirty_write(#carboncopy{us = {U, Host}, resource=R, version = CC}) of
|
||||
ok -> ok
|
||||
catch _:Error -> {error, Error}
|
||||
end.
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:enable(U, Host, R, CC).
|
||||
|
||||
disable(Host, U, R)->
|
||||
?DEBUG("disabling for ~p", [U]),
|
||||
ToDelete = mnesia:dirty_match_object(?TABLE, #carboncopy{us = {U, Host}, resource = R, version = '_'}),
|
||||
try lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete) of
|
||||
ok -> ok
|
||||
catch _:Error -> {error, Error}
|
||||
end.
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:disable(U, Host, R).
|
||||
|
||||
complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet, sent) ->
|
||||
%% if this is a packet sent by user on this host, then Packet doesn't
|
||||
@@ -286,8 +275,9 @@ has_non_empty_body(Packet) ->
|
||||
|
||||
%% list {resource, cc_version} with carbons enabled for given user and host
|
||||
list(User, Server) ->
|
||||
mnesia:dirty_select(?TABLE, [{#carboncopy{us = {User, Server}, resource = '$2', version = '$3'}, [], [{{'$2','$3'}}]}]).
|
||||
|
||||
Mod = gen_mod:db_mod(Server, ?MODULE),
|
||||
Mod:list(User, Server).
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) -> [iqdisc].
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) -> [db_type, iqdisc].
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_carboncopy_mnesia).
|
||||
|
||||
-behaviour(mod_carboncopy).
|
||||
|
||||
%% API
|
||||
-export([init/2, enable/4, disable/3, list/2]).
|
||||
|
||||
-include("mod_carboncopy.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
Fields = record_info(fields, carboncopy),
|
||||
try mnesia:table_info(carboncopy, attributes) of
|
||||
Fields ->
|
||||
ok;
|
||||
_ ->
|
||||
%% recreate..
|
||||
mnesia:delete_table(carboncopy)
|
||||
catch _:_Error ->
|
||||
%% probably table don't exist
|
||||
ok
|
||||
end,
|
||||
mnesia:create_table(carboncopy,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, carboncopy)},
|
||||
{type, bag}]),
|
||||
mnesia:add_table_copy(carboncopy, node(), ram_copies).
|
||||
|
||||
enable(LUser, LServer, LResource, NS) ->
|
||||
try mnesia:dirty_write(
|
||||
#carboncopy{us = {LUser, LServer},
|
||||
resource = LResource,
|
||||
version = NS}) of
|
||||
ok -> ok
|
||||
catch _:Error ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
disable(LUser, LServer, LResource) ->
|
||||
ToDelete = mnesia:dirty_match_object(
|
||||
#carboncopy{us = {LUser, LServer},
|
||||
resource = LResource,
|
||||
version = '_'}),
|
||||
try lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete) of
|
||||
ok -> ok
|
||||
catch _:Error ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
list(LUser, LServer) ->
|
||||
mnesia:dirty_select(
|
||||
carboncopy,
|
||||
[{#carboncopy{us = {LUser, LServer}, resource = '$2', version = '$3'},
|
||||
[], [{{'$2','$3'}}]}]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
+243
-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]).
|
||||
|
||||
%% 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,
|
||||
false),
|
||||
if QueuePresence; QueueChatStates; QueuePEP ->
|
||||
ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE,
|
||||
add_stream_feature, 50),
|
||||
if QueuePresence ->
|
||||
@@ -55,23 +75,145 @@ 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,
|
||||
false),
|
||||
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].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% 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 +221,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.
|
||||
|
||||
+233
-158
@@ -198,81 +198,81 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
|
||||
|
||||
%%%-----------------------------------------------------------------------
|
||||
|
||||
-define(INFO_RESULT(Allow, Feats),
|
||||
-define(INFO_RESULT(Allow, Feats, Lang),
|
||||
case Allow of
|
||||
deny -> {error, ?ERR_FORBIDDEN};
|
||||
deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
|
||||
allow -> {result, Feats}
|
||||
end).
|
||||
|
||||
get_sm_features(Acc, From,
|
||||
#jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
#jid{lserver = LServer} = _To, Node, Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false -> Acc;
|
||||
_ ->
|
||||
Allow = acl:match_rule(LServer, configure, From),
|
||||
case Node of
|
||||
<<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
<<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
_ -> Acc
|
||||
end
|
||||
end.
|
||||
|
||||
get_local_features(Acc, From,
|
||||
#jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
#jid{lserver = LServer} = _To, Node, Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false -> Acc;
|
||||
_ ->
|
||||
LNode = tokenize(Node),
|
||||
Allow = acl:match_rule(LServer, configure, From),
|
||||
case LNode of
|
||||
[<<"config">>] -> ?INFO_RESULT(Allow, []);
|
||||
[<<"user">>] -> ?INFO_RESULT(Allow, []);
|
||||
[<<"online users">>] -> ?INFO_RESULT(Allow, []);
|
||||
[<<"all users">>] -> ?INFO_RESULT(Allow, []);
|
||||
[<<"config">>] -> ?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"user">>] -> ?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"online users">>] -> ?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"all users">>] -> ?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"all users">>, <<$@, _/binary>>] ->
|
||||
?INFO_RESULT(Allow, []);
|
||||
[<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, []);
|
||||
[<<"running nodes">>] -> ?INFO_RESULT(Allow, []);
|
||||
[<<"stopped nodes">>] -> ?INFO_RESULT(Allow, []);
|
||||
?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"running nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"stopped nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"running nodes">>, _ENode] ->
|
||||
?INFO_RESULT(Allow, [?NS_STATS]);
|
||||
?INFO_RESULT(Allow, [?NS_STATS], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"DB">>] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"modules">>] ->
|
||||
?INFO_RESULT(Allow, []);
|
||||
?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"backup">>] ->
|
||||
?INFO_RESULT(Allow, []);
|
||||
?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"import">>] ->
|
||||
?INFO_RESULT(Allow, []);
|
||||
?INFO_RESULT(Allow, [], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"import">>, _] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"restart">>] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
[<<"config">>, _] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"add-user">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"delete-user">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"end-user-session">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"get-user-password">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"change-user-password">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"get-user-lastlogin">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"user-stats">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"get-registered-users-num">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
?NS_ADMINL(<<"get-online-users-num">>) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
|
||||
_ -> Acc
|
||||
end
|
||||
end.
|
||||
@@ -318,7 +318,8 @@ get_sm_items(Acc, From,
|
||||
{result,
|
||||
Items ++ Nodes ++ get_user_resources(User, Server)};
|
||||
{allow, <<"config">>} -> {result, []};
|
||||
{_, <<"config">>} -> {error, ?ERR_FORBIDDEN};
|
||||
{_, <<"config">>} ->
|
||||
{error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
|
||||
_ -> Acc
|
||||
end
|
||||
end.
|
||||
@@ -448,63 +449,64 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
|
||||
_ ->
|
||||
LNode = tokenize(Node),
|
||||
Allow = acl:match_rule(LServer, configure, From),
|
||||
Err = ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>),
|
||||
case LNode of
|
||||
[<<"config">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"user">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"online users">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"all users">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"all users">>, <<$@, _/binary>>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"outgoing s2s">> | _] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"stopped nodes">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"DB">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"modules">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"backup">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"import">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"import">>, _] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"restart">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
[<<"config">>, _] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"add-user">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"delete-user">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"end-user-session">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"get-user-password">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"change-user-password">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"get-user-lastlogin">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"user-stats">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"get-registered-users-num">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
?NS_ADMINL(<<"get-online-users-num">>) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
_ -> Acc
|
||||
end
|
||||
end.
|
||||
@@ -562,33 +564,29 @@ get_local_items({_, Host}, [<<"all users">>], _Server,
|
||||
get_local_items({_, Host},
|
||||
[<<"all users">>, <<$@, Diap/binary>>], _Server,
|
||||
_Lang) ->
|
||||
case catch ejabberd_auth:get_vh_registered_users(Host)
|
||||
of
|
||||
{'EXIT', _Reason} -> ?ERR_INTERNAL_SERVER_ERROR;
|
||||
Users ->
|
||||
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
|
||||
case catch begin
|
||||
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
|
||||
N1 = jlib:binary_to_integer(S1),
|
||||
N2 = jlib:binary_to_integer(S2),
|
||||
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
|
||||
lists:map(fun ({S, U}) ->
|
||||
#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>,
|
||||
<<U/binary, "@",
|
||||
S/binary>>},
|
||||
{<<"name">>,
|
||||
<<U/binary, "@",
|
||||
S/binary>>}],
|
||||
children = []}
|
||||
end,
|
||||
Sub)
|
||||
end
|
||||
of
|
||||
{'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE;
|
||||
Res -> {result, Res}
|
||||
end
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
|
||||
case catch begin
|
||||
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
|
||||
N1 = jlib:binary_to_integer(S1),
|
||||
N2 = jlib:binary_to_integer(S2),
|
||||
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
|
||||
lists:map(fun ({S, U}) ->
|
||||
#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>,
|
||||
<<U/binary, "@",
|
||||
S/binary>>},
|
||||
{<<"name">>,
|
||||
<<U/binary, "@",
|
||||
S/binary>>}],
|
||||
children = []}
|
||||
end,
|
||||
Sub)
|
||||
end
|
||||
of
|
||||
{'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE;
|
||||
Res -> {result, Res}
|
||||
end;
|
||||
get_local_items({_, Host}, [<<"outgoing s2s">>],
|
||||
_Server, Lang) ->
|
||||
@@ -826,33 +824,33 @@ get_stopped_nodes(_Lang) ->
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
|
||||
Request),
|
||||
Request, Lang),
|
||||
case acl:match_rule(LServerOrGlobal, configure, From) of
|
||||
deny -> {error, ?ERR_FORBIDDEN};
|
||||
deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
|
||||
allow -> adhoc_local_commands(From, To, Request)
|
||||
end).
|
||||
|
||||
adhoc_local_commands(Acc, From,
|
||||
#jid{lserver = LServer} = To,
|
||||
#adhoc_request{node = Node} = Request) ->
|
||||
#adhoc_request{node = Node, lang = Lang} = Request) ->
|
||||
LNode = tokenize(Node),
|
||||
case LNode of
|
||||
[<<"running nodes">>, _ENode, <<"DB">>] ->
|
||||
?COMMANDS_RESULT(global, From, To, Request);
|
||||
?COMMANDS_RESULT(global, From, To, Request, Lang);
|
||||
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
|
||||
?COMMANDS_RESULT(LServer, From, To, Request);
|
||||
?COMMANDS_RESULT(LServer, From, To, Request, Lang);
|
||||
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
|
||||
?COMMANDS_RESULT(global, From, To, Request);
|
||||
?COMMANDS_RESULT(global, From, To, Request, Lang);
|
||||
[<<"running nodes">>, _ENode, <<"import">>, _] ->
|
||||
?COMMANDS_RESULT(global, From, To, Request);
|
||||
?COMMANDS_RESULT(global, From, To, Request, Lang);
|
||||
[<<"running nodes">>, _ENode, <<"restart">>] ->
|
||||
?COMMANDS_RESULT(global, From, To, Request);
|
||||
?COMMANDS_RESULT(global, From, To, Request, Lang);
|
||||
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
|
||||
?COMMANDS_RESULT(global, From, To, Request);
|
||||
?COMMANDS_RESULT(global, From, To, Request, Lang);
|
||||
[<<"config">>, _] ->
|
||||
?COMMANDS_RESULT(LServer, From, To, Request);
|
||||
?COMMANDS_RESULT(LServer, From, To, Request, Lang);
|
||||
?NS_ADMINL(_) ->
|
||||
?COMMANDS_RESULT(LServer, From, To, Request);
|
||||
?COMMANDS_RESULT(LServer, From, To, Request, Lang);
|
||||
_ -> Acc
|
||||
end.
|
||||
|
||||
@@ -882,7 +880,8 @@ adhoc_local_commands(From,
|
||||
end;
|
||||
XData /= false, ActionIsExecute ->
|
||||
case jlib:parse_xdata_submit(XData) of
|
||||
invalid -> {error, ?ERR_BAD_REQUEST};
|
||||
invalid ->
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
|
||||
Fields ->
|
||||
case catch set_form(From, LServer, LNode, Lang, Fields)
|
||||
of
|
||||
@@ -898,7 +897,8 @@ adhoc_local_commands(From,
|
||||
{error, Error} -> {error, Error}
|
||||
end
|
||||
end;
|
||||
true -> {error, ?ERR_BAD_REQUEST}
|
||||
true ->
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect action or data form">>)}
|
||||
end.
|
||||
|
||||
-define(TVFIELD(Type, Var, Val),
|
||||
@@ -980,10 +980,14 @@ adhoc_local_commands(From,
|
||||
get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
|
||||
Lang) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
|
||||
{badrpc, _Reason} ->
|
||||
{badrpc, Reason} ->
|
||||
?ERROR_MSG("RPC call mnesia:system_info(tables) on node "
|
||||
"~s failed: ~p", [Node, Reason]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
Tables ->
|
||||
STables = lists:sort(Tables),
|
||||
@@ -1023,10 +1027,14 @@ get_form(Host,
|
||||
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
|
||||
Lang) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
case ejabberd_cluster:call(Node, gen_mod, loaded_modules, [Host]) of
|
||||
{badrpc, _Reason} ->
|
||||
{badrpc, Reason} ->
|
||||
?ERROR_MSG("RPC call gen_mod:loaded_modules(~s) on node "
|
||||
"~s failed: ~p", [Host, Node, Reason]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
Modules ->
|
||||
SModules = lists:sort(Modules),
|
||||
@@ -1562,9 +1570,11 @@ get_form(_Host, _, _Lang) ->
|
||||
{error, ?ERR_SERVICE_UNAVAILABLE}.
|
||||
|
||||
set_form(_From, _Host,
|
||||
[<<"running nodes">>, ENode, <<"DB">>], _Lang, XData) ->
|
||||
[<<"running nodes">>, ENode, <<"DB">>], Lang, XData) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
lists:foreach(fun ({SVar, SVals}) ->
|
||||
Table = jlib:binary_to_atom(SVar),
|
||||
@@ -1596,9 +1606,11 @@ set_form(_From, _Host,
|
||||
end;
|
||||
set_form(_From, Host,
|
||||
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
|
||||
_Lang, XData) ->
|
||||
Lang, XData) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
lists:foreach(fun ({Var, Vals}) ->
|
||||
case Vals of
|
||||
@@ -1615,12 +1627,16 @@ set_form(_From, Host,
|
||||
set_form(_From, Host,
|
||||
[<<"running nodes">>, ENode, <<"modules">>,
|
||||
<<"start">>],
|
||||
_Lang, XData) ->
|
||||
Lang, XData) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
case lists:keysearch(<<"modules">>, 1, XData) of
|
||||
false -> {error, ?ERR_BAD_REQUEST};
|
||||
false ->
|
||||
Txt = <<"No 'modules' found in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
{value, {_, Strings}} ->
|
||||
String = lists:foldl(fun (S, Res) ->
|
||||
<<Res/binary, S/binary, "\n">>
|
||||
@@ -1637,98 +1653,143 @@ set_form(_From, Host,
|
||||
end,
|
||||
Modules),
|
||||
{result, []};
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
|
||||
end
|
||||
end
|
||||
end;
|
||||
set_form(_From, _Host,
|
||||
[<<"running nodes">>, ENode, <<"backup">>,
|
||||
<<"backup">>],
|
||||
_Lang, XData) ->
|
||||
Lang, XData) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
case lists:keysearch(<<"path">>, 1, XData) of
|
||||
false -> {error, ?ERR_BAD_REQUEST};
|
||||
false ->
|
||||
Txt = <<"No 'path' found in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
{value, {_, [String]}} ->
|
||||
case ejabberd_cluster:call(Node, mnesia, backup, [String]) of
|
||||
{badrpc, _Reason} ->
|
||||
{badrpc, Reason} ->
|
||||
?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
|
||||
"failed: ~p", [String, Node, Reason]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
|
||||
"failed: ~p", [String, Node, Reason]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
_ -> {result, []}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ ->
|
||||
Txt = <<"Incorrect value of 'path' in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end
|
||||
end;
|
||||
set_form(_From, _Host,
|
||||
[<<"running nodes">>, ENode, <<"backup">>,
|
||||
<<"restore">>],
|
||||
_Lang, XData) ->
|
||||
Lang, XData) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
case lists:keysearch(<<"path">>, 1, XData) of
|
||||
false -> {error, ?ERR_BAD_REQUEST};
|
||||
false ->
|
||||
Txt = <<"No 'path' found in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
{value, {_, [String]}} ->
|
||||
case ejabberd_cluster:call(Node, ejabberd_admin, restore, [String])
|
||||
of
|
||||
{badrpc, _Reason} ->
|
||||
{badrpc, Reason} ->
|
||||
?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
|
||||
"~s failed: ~p", [String, Node, Reason]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
|
||||
"~s failed: ~p", [String, Node, Reason]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
_ -> {result, []}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ ->
|
||||
Txt = <<"Incorrect value of 'path' in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end
|
||||
end;
|
||||
set_form(_From, _Host,
|
||||
[<<"running nodes">>, ENode, <<"backup">>,
|
||||
<<"textfile">>],
|
||||
_Lang, XData) ->
|
||||
Lang, XData) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
case lists:keysearch(<<"path">>, 1, XData) of
|
||||
false -> {error, ?ERR_BAD_REQUEST};
|
||||
false ->
|
||||
Txt = <<"No 'path' found in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
{value, {_, [String]}} ->
|
||||
case ejabberd_cluster:call(Node, ejabberd_admin, dump_to_textfile,
|
||||
[String])
|
||||
of
|
||||
{badrpc, _Reason} ->
|
||||
{badrpc, Reason} ->
|
||||
?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
|
||||
"to node ~s failed: ~p", [String, Node, Reason]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
|
||||
"to node ~s failed: ~p", [String, Node, Reason]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
_ -> {result, []}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ ->
|
||||
Txt = <<"Incorrect value of 'path' in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end
|
||||
end;
|
||||
set_form(_From, _Host,
|
||||
[<<"running nodes">>, ENode, <<"import">>, <<"file">>],
|
||||
_Lang, XData) ->
|
||||
Lang, XData) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
case lists:keysearch(<<"path">>, 1, XData) of
|
||||
false -> {error, ?ERR_BAD_REQUEST};
|
||||
false ->
|
||||
Txt = <<"No 'path' found in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
{value, {_, [String]}} ->
|
||||
ejabberd_cluster:call(Node, jd2ejd, import_file, [String]),
|
||||
{result, []};
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ ->
|
||||
Txt = <<"Incorrect value of 'path' in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end
|
||||
end;
|
||||
set_form(_From, _Host,
|
||||
[<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
|
||||
_Lang, XData) ->
|
||||
Lang, XData) ->
|
||||
case search_running_node(ENode) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
false ->
|
||||
Txt = <<"No running node found">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
|
||||
Node ->
|
||||
case lists:keysearch(<<"path">>, 1, XData) of
|
||||
false -> {error, ?ERR_BAD_REQUEST};
|
||||
false ->
|
||||
Txt = <<"No 'path' found in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
{value, {_, [String]}} ->
|
||||
ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]),
|
||||
{result, []};
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ ->
|
||||
Txt = <<"Incorrect value of 'path' in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end
|
||||
end;
|
||||
set_form(From, Host,
|
||||
@@ -1739,7 +1800,7 @@ set_form(From, Host,
|
||||
[<<"running nodes">>, ENode, <<"shutdown">>], _Lang,
|
||||
XData) ->
|
||||
stop_node(From, Host, ENode, stop, XData);
|
||||
set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
|
||||
set_form(_From, Host, [<<"config">>, <<"acls">>], Lang,
|
||||
XData) ->
|
||||
case lists:keysearch(<<"acls">>, 1, XData) of
|
||||
{value, {_, Strings}} ->
|
||||
@@ -1753,14 +1814,16 @@ set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
|
||||
{ok, ACLs} ->
|
||||
acl:add_list(Host, ACLs, true),
|
||||
{result, []};
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ ->
|
||||
Txt = <<"No 'acls' found in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end;
|
||||
set_form(_From, Host, [<<"config">>, <<"access">>],
|
||||
_Lang, XData) ->
|
||||
Lang, XData) ->
|
||||
SetAccess = fun (Rs) ->
|
||||
mnesia:transaction(fun () ->
|
||||
Os = mnesia:select(local_config,
|
||||
@@ -1803,11 +1866,13 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
|
||||
{atomic, _} -> {result, []};
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
_ ->
|
||||
Txt = <<"No 'access' found in data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end;
|
||||
set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
|
||||
XData) ->
|
||||
@@ -1852,17 +1917,19 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
|
||||
case JID#jid.lresource of
|
||||
<<>> ->
|
||||
SIDs = mnesia:dirty_select(session,
|
||||
[{#session{sid = '$1',
|
||||
[{#session{sid = {'$1', '$2'},
|
||||
usr = {LUser, LServer, '_'},
|
||||
_ = '_'},
|
||||
[], ['$1']}]),
|
||||
[{is_pid, '$2'}],
|
||||
[{{'$1', '$2'}}]}]),
|
||||
[Pid ! {kick, kicked_by_admin, Xmlelement} || {_, Pid} <- SIDs];
|
||||
R ->
|
||||
[{_, Pid}] = mnesia:dirty_select(session,
|
||||
[{#session{sid = '$1',
|
||||
[{#session{sid = {'$1', '$2'},
|
||||
usr = {LUser, LServer, R},
|
||||
_ = '_'},
|
||||
[], ['$1']}]),
|
||||
[{is_pid, '$2'}],
|
||||
[{{'$1', '$2'}}]}]),
|
||||
Pid ! {kick, kicked_by_admin, Xmlelement}
|
||||
end,
|
||||
{result, []};
|
||||
@@ -2052,7 +2119,7 @@ adhoc_sm_commands(_Acc, From,
|
||||
action = Action, xdata = XData} =
|
||||
Request) ->
|
||||
case acl:match_rule(LServer, configure, From) of
|
||||
deny -> {error, ?ERR_FORBIDDEN};
|
||||
deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
|
||||
allow ->
|
||||
ActionIsExecute = lists:member(Action,
|
||||
[<<"">>, <<"execute">>,
|
||||
@@ -2071,11 +2138,15 @@ adhoc_sm_commands(_Acc, From,
|
||||
end;
|
||||
XData /= false, ActionIsExecute ->
|
||||
case jlib:parse_xdata_submit(XData) of
|
||||
invalid -> {error, ?ERR_BAD_REQUEST};
|
||||
invalid ->
|
||||
Txt = <<"Incorrect data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
Fields ->
|
||||
set_sm_form(User, Server, <<"config">>, Request, Fields)
|
||||
end;
|
||||
true -> {error, ?ERR_BAD_REQUEST}
|
||||
true ->
|
||||
Txt = <<"Incorrect action or data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
|
||||
end
|
||||
end;
|
||||
adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc.
|
||||
@@ -2135,12 +2206,16 @@ set_sm_form(User, Server, <<"config">>,
|
||||
{value, {_, [Password]}} ->
|
||||
ejabberd_auth:set_password(User, Server, Password),
|
||||
adhoc:produce_response(Response);
|
||||
_ -> {error, ?ERR_NOT_ACCEPTABLE}
|
||||
_ ->
|
||||
Txt = <<"No 'password' found in data form">>,
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
|
||||
end;
|
||||
{value, {_, [<<"remove">>]}} ->
|
||||
catch ejabberd_auth:remove_user(User, Server),
|
||||
adhoc:produce_response(Response);
|
||||
_ -> {error, ?ERR_NOT_ACCEPTABLE}
|
||||
_ ->
|
||||
Txt = <<"Incorrect value of 'action' in data form">>,
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
|
||||
end;
|
||||
set_sm_form(_User, _Server, _Node, _Request, _Fields) ->
|
||||
{error, ?ERR_SERVICE_UNAVAILABLE}.
|
||||
|
||||
+15
-12
@@ -56,15 +56,17 @@ stop(Host) ->
|
||||
?NS_ECONFIGURE).
|
||||
|
||||
process_local_iq(From, To,
|
||||
#iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case acl:match_rule(To#jid.lserver, configure, From) of
|
||||
deny ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
Txt = <<"Denied by ACL">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
allow ->
|
||||
case Type of
|
||||
set ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
|
||||
sub_el = [SubEl, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)]};
|
||||
%%case fxml:get_tag_attr_s("type", SubEl) of
|
||||
%% "cancel" ->
|
||||
%% IQ#iq{type = result,
|
||||
@@ -98,7 +100,7 @@ process_local_iq(From, To,
|
||||
%% sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
|
||||
%%end;
|
||||
get ->
|
||||
case process_get(SubEl) of
|
||||
case process_get(SubEl, Lang) of
|
||||
{result, Res} -> IQ#iq{type = result, sub_el = [Res]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
@@ -106,7 +108,7 @@ process_local_iq(From, To,
|
||||
end
|
||||
end.
|
||||
|
||||
process_get(#xmlel{name = <<"info">>}) ->
|
||||
process_get(#xmlel{name = <<"info">>}, _Lang) ->
|
||||
S2SConns = ejabberd_s2s:dirty_get_connections(),
|
||||
TConns = lists:usort([element(2, C) || C <- S2SConns]),
|
||||
Attrs = [{<<"registered-users">>,
|
||||
@@ -130,7 +132,7 @@ process_get(#xmlel{name = <<"info">>}) ->
|
||||
attrs = [{<<"xmlns">>, ?NS_ECONFIGURE} | Attrs],
|
||||
children = []}};
|
||||
process_get(#xmlel{name = <<"welcome-message">>,
|
||||
attrs = Attrs}) ->
|
||||
attrs = Attrs}, _Lang) ->
|
||||
{Subj, Body} = ejabberd_config:get_option(
|
||||
welcome_message,
|
||||
fun({Subj, Body}) ->
|
||||
@@ -146,7 +148,7 @@ process_get(#xmlel{name = <<"welcome-message">>,
|
||||
#xmlel{name = <<"body">>, attrs = [],
|
||||
children = [{xmlcdata, Body}]}]}};
|
||||
process_get(#xmlel{name = <<"registration-watchers">>,
|
||||
attrs = Attrs}) ->
|
||||
attrs = Attrs}, _Lang) ->
|
||||
SubEls = ejabberd_config:get_option(
|
||||
registration_watchers,
|
||||
fun(JIDs) when is_list(JIDs) ->
|
||||
@@ -160,14 +162,14 @@ process_get(#xmlel{name = <<"registration-watchers">>,
|
||||
{result,
|
||||
#xmlel{name = <<"registration_watchers">>,
|
||||
attrs = Attrs, children = SubEls}};
|
||||
process_get(#xmlel{name = <<"acls">>, attrs = Attrs}) ->
|
||||
process_get(#xmlel{name = <<"acls">>, attrs = Attrs}, _Lang) ->
|
||||
Str = iolist_to_binary(io_lib:format("~p.",
|
||||
[ets:tab2list(acl)])),
|
||||
{result,
|
||||
#xmlel{name = <<"acls">>, attrs = Attrs,
|
||||
children = [{xmlcdata, Str}]}};
|
||||
process_get(#xmlel{name = <<"access">>,
|
||||
attrs = Attrs}) ->
|
||||
attrs = Attrs}, _Lang) ->
|
||||
Str = iolist_to_binary(io_lib:format("~p.",
|
||||
[ets:select(local_config,
|
||||
[{{local_config, {access, '$1'},
|
||||
@@ -178,13 +180,14 @@ process_get(#xmlel{name = <<"access">>,
|
||||
{result,
|
||||
#xmlel{name = <<"access">>, attrs = Attrs,
|
||||
children = [{xmlcdata, Str}]}};
|
||||
process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
|
||||
process_get(#xmlel{name = <<"last">>, attrs = Attrs}, Lang) ->
|
||||
case catch mnesia:dirty_select(last_activity,
|
||||
[{{last_activity, '_', '$1', '_'}, [],
|
||||
['$1']}])
|
||||
of
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
Txt = <<"Database failure">>,
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)};
|
||||
Vals ->
|
||||
TimeStamp = p1_time_compat:system_time(seconds),
|
||||
Str = list_to_binary(
|
||||
@@ -196,7 +199,7 @@ process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
|
||||
end;
|
||||
%%process_get({xmlelement, Name, Attrs, SubEls}) ->
|
||||
%% {result, };
|
||||
process_get(_) -> {error, ?ERR_BAD_REQUEST}.
|
||||
process_get(_, _) -> {error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) -> [iqdisc].
|
||||
|
||||
+26
-14
@@ -150,7 +150,8 @@ process_local_iq_items(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
Host = To#jid.lserver,
|
||||
@@ -177,7 +178,8 @@ process_local_iq_info(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
Host = To#jid.lserver,
|
||||
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
@@ -229,10 +231,12 @@ get_local_features(Acc, _From, To, <<>>, _Lang) ->
|
||||
ets:select(disco_features,
|
||||
[{{{'_', Host}}, [], ['$_']}])
|
||||
++ Feats};
|
||||
get_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
get_local_features(Acc, _From, _To, _Node, Lang) ->
|
||||
case Acc of
|
||||
{result, _Features} -> Acc;
|
||||
empty -> {error, ?ERR_ITEM_NOT_FOUND}
|
||||
empty ->
|
||||
Txt = <<"No features available">>,
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}
|
||||
end.
|
||||
|
||||
features_to_xml(FeatureList) ->
|
||||
@@ -271,8 +275,8 @@ get_local_services(Acc, _From, To, <<>>, _Lang) ->
|
||||
get_local_services({result, _} = Acc, _From, _To, _Node,
|
||||
_Lang) ->
|
||||
Acc;
|
||||
get_local_services(empty, _From, _To, _Node, _Lang) ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}.
|
||||
get_local_services(empty, _From, _To, _Node, Lang) ->
|
||||
{error, ?ERRT_ITEM_NOT_FOUND(Lang, <<"No services available">>)}.
|
||||
|
||||
get_vh_services(Host) ->
|
||||
Hosts = lists:sort(fun (H1, H2) ->
|
||||
@@ -300,7 +304,8 @@ process_sm_iq_items(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
@@ -325,8 +330,9 @@ process_sm_iq_items(From, To,
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
|
||||
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -347,12 +353,14 @@ get_sm_items(Acc, From,
|
||||
get_sm_items({result, _} = Acc, _From, _To, _Node,
|
||||
_Lang) ->
|
||||
Acc;
|
||||
get_sm_items(empty, From, To, _Node, _Lang) ->
|
||||
get_sm_items(empty, From, To, _Node, Lang) ->
|
||||
#jid{luser = LFrom, lserver = LSFrom} = From,
|
||||
#jid{luser = LTo, lserver = LSTo} = To,
|
||||
case {LFrom, LSFrom} of
|
||||
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
_ -> {error, ?ERR_NOT_ALLOWED}
|
||||
_ ->
|
||||
Txt = <<"Query to another users is forbidden">>,
|
||||
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
|
||||
end.
|
||||
|
||||
is_presence_subscribed(#jid{luser = User, lserver = Server},
|
||||
@@ -373,7 +381,8 @@ process_sm_iq_info(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
|
||||
get ->
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
@@ -405,8 +414,9 @@ process_sm_iq_info(From, To,
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
|
||||
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -423,12 +433,14 @@ get_sm_identity(Acc, _From,
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
get_sm_features(empty, From, To, _Node, _Lang) ->
|
||||
get_sm_features(empty, From, To, _Node, Lang) ->
|
||||
#jid{luser = LFrom, lserver = LSFrom} = From,
|
||||
#jid{luser = LTo, lserver = LSTo} = To,
|
||||
case {LFrom, LSFrom} of
|
||||
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
_ -> {error, ?ERR_NOT_ALLOWED}
|
||||
_ ->
|
||||
Txt = <<"Query to another users is forbidden">>,
|
||||
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
|
||||
end;
|
||||
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
|
||||
|
||||
|
||||
+4
-1
@@ -118,7 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}.
|
||||
handle_info({route, From, To, Packet}, State) ->
|
||||
Packet2 = case From#jid.user of
|
||||
<<"">> ->
|
||||
jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"User part of JID in 'from' is empty">>,
|
||||
jlib:make_error_reply(
|
||||
Packet, ?ERRT_BAD_REQUEST(Lang, Txt));
|
||||
_ -> Packet
|
||||
end,
|
||||
do_client_version(disabled, To, From),
|
||||
|
||||
@@ -167,7 +167,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 +187,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) ->
|
||||
|
||||
+145
-78
@@ -29,6 +29,11 @@
|
||||
%% request_handlers:
|
||||
%% "/api": mod_http_api
|
||||
%%
|
||||
%% To use a specific API version N, add a vN element in the URL path:
|
||||
%% in ejabberd_http listener
|
||||
%% request_handlers:
|
||||
%% "/api/v2": mod_http_api
|
||||
%%
|
||||
%% Access rights are defined with:
|
||||
%% commands_admin_access: configure
|
||||
%% commands:
|
||||
@@ -76,6 +81,8 @@
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
-define(DEFAULT_API_VERSION, 0).
|
||||
|
||||
-define(CT_PLAIN,
|
||||
{<<"Content-Type">>, <<"text/plain">>}).
|
||||
|
||||
@@ -116,7 +123,6 @@ start(_Host, _Opts) ->
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
|
||||
%% ----------
|
||||
%% basic auth
|
||||
%% ----------
|
||||
@@ -124,12 +130,13 @@ stop(_Host) ->
|
||||
check_permissions(Request, Command) ->
|
||||
case catch binary_to_existing_atom(Command, utf8) of
|
||||
Call when is_atom(Call) ->
|
||||
check_permissions2(Request, Call);
|
||||
{ok, CommandPolicy} = ejabberd_commands:get_command_policy(Call),
|
||||
check_permissions2(Request, Call, CommandPolicy);
|
||||
_ ->
|
||||
unauthorized_response()
|
||||
end.
|
||||
|
||||
check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call)
|
||||
check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
|
||||
when HTTPAuth /= undefined ->
|
||||
Admin =
|
||||
case lists:keysearch(<<"X-Admin">>, 1, Headers) of
|
||||
@@ -150,8 +157,10 @@ check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call)
|
||||
end;
|
||||
{oauth, Token, _} ->
|
||||
case oauth_check_token(Call, Token) of
|
||||
{ok, User, Server} ->
|
||||
{ok, user, {User, Server}} ->
|
||||
{ok, {User, Server, {oauth, Token}, Admin}};
|
||||
{ok, server_admin} -> %% token whas generated using issue_token command line
|
||||
{ok, admin};
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
@@ -162,9 +171,11 @@ check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call)
|
||||
{ok, A} -> {allowed, Call, A};
|
||||
_ -> unauthorized_response()
|
||||
end;
|
||||
check_permissions2(#request{ip={IP, _Port}}, Call) ->
|
||||
check_permissions2(_Request, Call, open) ->
|
||||
{allowed, Call, noauth};
|
||||
check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
|
||||
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
|
||||
@@ -179,9 +190,11 @@ check_permissions2(#request{ip={IP, _Port}}, Call) ->
|
||||
true -> {allowed, Call, admin};
|
||||
_ -> unauthorized_response()
|
||||
end;
|
||||
_ ->
|
||||
unauthorized_response()
|
||||
end.
|
||||
_E ->
|
||||
{allowed, Call, noauth}
|
||||
end;
|
||||
check_permissions2(_Request, _Call, _Policy) ->
|
||||
unauthorized_response().
|
||||
|
||||
oauth_check_token(Scope, Token) when is_atom(Scope) ->
|
||||
oauth_check_token(atom_to_binary(Scope, utf8), Token);
|
||||
@@ -192,44 +205,53 @@ oauth_check_token(Scope, Token) ->
|
||||
%% command processing
|
||||
%% ------------------
|
||||
|
||||
%process(Call, Request) ->
|
||||
% ?DEBUG("~p~n~p", [Call, Request]), ok;
|
||||
process(_, #request{method = 'POST', data = <<>>}) ->
|
||||
?DEBUG("Bad Request: no data", []),
|
||||
badrequest_response();
|
||||
process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
|
||||
badrequest_response(<<"Missing POST data">>);
|
||||
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),
|
||||
log(Call, Args, IPPort),
|
||||
case check_permissions(Req, Call) of
|
||||
{allowed, Cmd, Auth} ->
|
||||
{Code, Result} = handle(Cmd, Auth, Args),
|
||||
json_response(Code, jiffy:encode(Result));
|
||||
ErrorResponse -> %% Should we reply 403 ?
|
||||
ErrorResponse
|
||||
end
|
||||
catch _:Error ->
|
||||
?DEBUG("Bad Request: ~p", [Error]),
|
||||
badrequest_response()
|
||||
end;
|
||||
process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
|
||||
try
|
||||
Args = case Data of
|
||||
[{nokey, <<>>}] -> [];
|
||||
_ -> Data
|
||||
end,
|
||||
log(Call, Args, IP),
|
||||
case check_permissions(Req, Call) of
|
||||
{allowed, Cmd, Auth} ->
|
||||
{Code, Result} = handle(Cmd, Auth, Args),
|
||||
{Code, Result} = handle(Cmd, Auth, Args, Version, IP),
|
||||
json_response(Code, jiffy:encode(Result));
|
||||
%% Warning: check_permission direcly formats 401 reply if not authorized
|
||||
ErrorResponse ->
|
||||
ErrorResponse
|
||||
end
|
||||
catch _:Error ->
|
||||
?DEBUG("Bad Request: ~p", [Error]),
|
||||
catch _:{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;
|
||||
process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
|
||||
Version = get_api_version(Req),
|
||||
try
|
||||
Args = case Data of
|
||||
[{nokey, <<>>}] -> [];
|
||||
_ -> Data
|
||||
end,
|
||||
log(Call, Args, IP),
|
||||
case check_permissions(Req, Call) of
|
||||
{allowed, Cmd, Auth} ->
|
||||
{Code, Result} = handle(Cmd, Auth, Args, Version, IP),
|
||||
json_response(Code, jiffy:encode(Result));
|
||||
%% Warning: check_permission direcly formats 401 reply if not authorized
|
||||
ErrorResponse ->
|
||||
ErrorResponse
|
||||
end
|
||||
catch _:_Error ->
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
badrequest_response()
|
||||
end;
|
||||
process([], #request{method = 'OPTIONS', data = <<>>}) ->
|
||||
@@ -238,13 +260,28 @@ process(_Path, Request) ->
|
||||
?DEBUG("Bad Request: no handler ~p", [Request]),
|
||||
badrequest_response().
|
||||
|
||||
% 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)
|
||||
end;
|
||||
get_api_version([_Head | Tail]) ->
|
||||
get_api_version(Tail);
|
||||
get_api_version([]) ->
|
||||
?DEFAULT_API_VERSION.
|
||||
|
||||
%% ----------------
|
||||
%% command handlers
|
||||
%% ----------------
|
||||
|
||||
% generic ejabberd command handler
|
||||
handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
|
||||
case ejabberd_commands:get_command_format(Call, Auth) of
|
||||
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],
|
||||
Spec = lists:foldr(
|
||||
@@ -259,23 +296,48 @@ handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
|
||||
({Key, atom}, Acc) ->
|
||||
[{Key, undefined}|Acc]
|
||||
end, [], ArgsSpec),
|
||||
handle2(Call, Auth, match(Args2, Spec));
|
||||
try
|
||||
handle2(Call, Auth, match(Args2, Spec), Version, IP)
|
||||
catch throw:not_found ->
|
||||
{404, <<"not_found">>};
|
||||
throw:{not_found, Why} when is_atom(Why) ->
|
||||
{404, jlib:atom_to_binary(Why)};
|
||||
throw:{not_found, Msg} ->
|
||||
{404, iolist_to_binary(Msg)};
|
||||
throw:not_allowed ->
|
||||
{401, <<"not_allowed">>};
|
||||
throw:{not_allowed, Why} when is_atom(Why) ->
|
||||
{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:{invalid_parameter, Msg} ->
|
||||
{400, iolist_to_binary(Msg)};
|
||||
throw:{error, Why} when is_atom(Why) ->
|
||||
{400, jlib:atom_to_binary(Why)};
|
||||
throw:{error, Msg} ->
|
||||
{400, iolist_to_binary(Msg)};
|
||||
throw:Error when is_atom(Error) ->
|
||||
{400, jlib:atom_to_binary(Error)};
|
||||
throw:Msg when is_list(Msg); is_binary(Msg) ->
|
||||
{400, iolist_to_binary(Msg)};
|
||||
_Error ->
|
||||
?ERROR_MSG("REST API Error: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
{500, <<"internal_error">>}
|
||||
end;
|
||||
{error, Msg} ->
|
||||
?ERROR_MSG("REST API Error: ~p", [Msg]),
|
||||
{400, Msg};
|
||||
_Error ->
|
||||
?ERROR_MSG("REST API Error: ~p", [_Error]),
|
||||
{400, <<"Error">>}
|
||||
end.
|
||||
|
||||
handle2(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
|
||||
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth),
|
||||
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),
|
||||
case ejabberd_command(Auth, Call, ArgsFormatted, 400) of
|
||||
0 -> {200, <<"OK">>};
|
||||
1 -> {500, <<"500 Internal server error">>};
|
||||
400 -> {400, <<"400 Bad Request">>};
|
||||
404 -> {404, <<"404 Not found">>};
|
||||
Res -> format_command_result(Call, Auth, Res)
|
||||
end.
|
||||
ejabberd_command(Auth, Call, ArgsFormatted, Version, IP).
|
||||
|
||||
get_elem_delete(A, L) ->
|
||||
case proplists:get_all_values(A, L) of
|
||||
@@ -339,7 +401,9 @@ 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.
|
||||
throw({invalid_parameter,
|
||||
io_lib:format("Arg ~p is not in format ~p",
|
||||
[Arg, Format])}).
|
||||
|
||||
process_unicode_codepoints(Str) ->
|
||||
iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
|
||||
@@ -353,36 +417,37 @@ process_unicode_codepoints(Str) ->
|
||||
match(Args, Spec) ->
|
||||
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
|
||||
|
||||
ejabberd_command(Auth, Cmd, Args, Default) ->
|
||||
ejabberd_command(Auth, Cmd, Args, Version, IP) ->
|
||||
Access = case Auth of
|
||||
admin -> [];
|
||||
_ -> undefined
|
||||
end,
|
||||
case catch ejabberd_commands:execute_command(Access, Auth, Cmd, Args) of
|
||||
{'EXIT', _} -> Default;
|
||||
{error, _} -> Default;
|
||||
Result -> Result
|
||||
case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version, #{ip => IP}) of
|
||||
{error, Error} ->
|
||||
throw(Error);
|
||||
Res ->
|
||||
format_command_result(Cmd, Auth, Res, Version)
|
||||
end.
|
||||
|
||||
format_command_result(Cmd, Auth, Result) ->
|
||||
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth),
|
||||
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, <<"">>};
|
||||
{{_, rescode}, _} ->
|
||||
{500, <<"">>};
|
||||
{{_, 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};
|
||||
{{_, 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)]}}
|
||||
end.
|
||||
|
||||
format_result(Atom, {Name, atom}) ->
|
||||
@@ -421,21 +486,23 @@ format_result(404, {_Name, _}) ->
|
||||
"not_found".
|
||||
|
||||
unauthorized_response() ->
|
||||
{401, ?HEADER(?CT_XML),
|
||||
#xmlel{name = <<"h1">>, attrs = [],
|
||||
children = [{xmlcdata, <<"401 Unauthorized">>}]}}.
|
||||
unauthorized_response(<<"401 Unauthorized">>).
|
||||
unauthorized_response(Body) ->
|
||||
json_response(401, jiffy:encode(Body)).
|
||||
|
||||
badrequest_response() ->
|
||||
{400, ?HEADER(?CT_XML),
|
||||
#xmlel{name = <<"h1">>, attrs = [],
|
||||
children = [{xmlcdata, <<"400 Bad Request">>}]}}.
|
||||
badrequest_response(<<"400 Bad Request">>).
|
||||
badrequest_response(Body) ->
|
||||
json_response(400, jiffy:encode(Body)).
|
||||
|
||||
json_response(Code, Body) when is_integer(Code) ->
|
||||
{Code, ?HEADER(?CT_JSON), Body}.
|
||||
|
||||
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]).
|
||||
?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].
|
||||
|
||||
+28
-24
@@ -178,7 +178,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
|
||||
@@ -235,7 +235,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 +321,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};
|
||||
@@ -375,7 +377,7 @@ handle_info(Info, State) ->
|
||||
?ERROR_MSG("Got unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
|
||||
|
||||
terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
|
||||
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
|
||||
@@ -406,9 +408,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 +423,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",
|
||||
@@ -569,7 +570,8 @@ process_iq(From,
|
||||
deny ->
|
||||
?DEBUG("Denying HTTP upload slot request from ~s",
|
||||
[jid:to_string(From)]),
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
Txt = <<"Denied by ACL">>,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
|
||||
end;
|
||||
process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
@@ -601,7 +603,8 @@ parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) ->
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Text)}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
Text = <<"No or invalid XML namespace">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Text)}
|
||||
end;
|
||||
parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
@@ -639,7 +642,7 @@ create_slot(#state{service_url = undefined,
|
||||
end;
|
||||
create_slot(#state{service_url = ServiceURL},
|
||||
#jid{luser = U, lserver = S} = JID, File, Size, ContentType,
|
||||
_Lang) ->
|
||||
Lang) ->
|
||||
Options = [{body_format, binary}, {full_result, false}],
|
||||
HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}],
|
||||
SizeStr = jlib:integer_to_binary(Size),
|
||||
@@ -659,7 +662,8 @@ create_slot(#state{service_url = ServiceURL},
|
||||
Lines ->
|
||||
?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p",
|
||||
[jid:to_string(JID), ServiceURL, Lines]),
|
||||
{error, ?ERR_SERVICE_UNAVAILABLE}
|
||||
Txt = <<"Failed to parse HTTP response">>,
|
||||
{error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
|
||||
end;
|
||||
{ok, {402, _Body}} ->
|
||||
?INFO_MSG("Got status code 402 for ~s from <~s>",
|
||||
|
||||
@@ -99,9 +99,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
|
||||
@@ -118,10 +118,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;
|
||||
@@ -239,7 +239,7 @@ handle_info(Info, State) ->
|
||||
?ERROR_MSG("Got unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
|
||||
|
||||
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
|
||||
?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
|
||||
|
||||
+65
-172
@@ -33,7 +33,8 @@
|
||||
|
||||
%% API
|
||||
-export([start_link/2, start/2, stop/1, export/1, import/1,
|
||||
import/3, closed_connection/3, get_connection_params/3]).
|
||||
import/3, closed_connection/3, get_connection_params/3,
|
||||
data_to_binary/2]).
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3,
|
||||
@@ -46,6 +47,8 @@
|
||||
|
||||
-include("adhoc.hrl").
|
||||
|
||||
-include("mod_irc.hrl").
|
||||
|
||||
-define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>).
|
||||
|
||||
-define(DEFAULT_IRC_PORT, 6667).
|
||||
@@ -58,27 +61,19 @@
|
||||
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
|
||||
<<"utf-8">>, <<"utf-8+latin-1">>]).
|
||||
|
||||
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
|
||||
{binary(), binary(), inet:port_number()} |
|
||||
{binary(), binary()} |
|
||||
{binary()}.
|
||||
|
||||
-record(irc_connection,
|
||||
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
|
||||
pid = self() :: pid()}).
|
||||
|
||||
-record(irc_custom,
|
||||
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
|
||||
binary()},
|
||||
data = [] :: [{username, binary()} |
|
||||
{connections_params, [conn_param()]}]}).
|
||||
|
||||
-record(state, {host = <<"">> :: binary(),
|
||||
server_host = <<"">> :: binary(),
|
||||
access = all :: atom()}).
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_irc).
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback import(binary(), #irc_custom{}) -> ok | pass.
|
||||
-callback get_data(binary(), binary(), {binary(), binary()}) ->
|
||||
error | empty | irc_data().
|
||||
-callback set_data(binary(), binary(), {binary(), binary()}, irc_data()) ->
|
||||
{atomic, any()}.
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -119,16 +114,10 @@ init([Host, Opts]) ->
|
||||
ejabberd:start_app(iconv),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"irc.@HOST@">>),
|
||||
case gen_mod:db_type(Host, Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(irc_custom,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, irc_custom)}]),
|
||||
update_table();
|
||||
_ -> ok
|
||||
end,
|
||||
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,
|
||||
@@ -304,8 +293,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
Lang)}]},
|
||||
Res = jlib:iq_to_xml(ResIQ);
|
||||
_ ->
|
||||
Res = jlib:make_error_reply(Packet,
|
||||
?ERR_ITEM_NOT_FOUND)
|
||||
Txt = <<"Node not found">>,
|
||||
Res = jlib:make_error_reply(
|
||||
Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt))
|
||||
end,
|
||||
ejabberd_router:route(To, From, Res);
|
||||
#iq{xmlns = ?NS_REGISTER} = IQ ->
|
||||
@@ -319,7 +309,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
attrs = [{<<"xmlns">>, XMLNS}],
|
||||
children = iq_get_vcard(Lang)}]},
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
|
||||
#iq{type = set, xmlns = ?NS_COMMANDS, lang = _Lang,
|
||||
#iq{type = set, xmlns = ?NS_COMMANDS, lang = Lang,
|
||||
sub_el = SubEl} =
|
||||
IQ ->
|
||||
Request = adhoc:parse_request(IQ),
|
||||
@@ -348,8 +338,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
true -> ok
|
||||
end;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_ITEM_NOT_FOUND),
|
||||
Txt = <<"Node not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
#iq{} = _IQ ->
|
||||
@@ -407,12 +398,14 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
case str:tokens(ChanServ, <<"!">>) of
|
||||
[<<_, _/binary>> = Nick, <<_, _/binary>> = Server] ->
|
||||
case ets:lookup(irc_connection, {From, Server, Host}) of
|
||||
[] ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
Txt = <<"IRC connection not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
[R] ->
|
||||
Pid = R#irc_connection.pid,
|
||||
@@ -421,7 +414,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
Txt = <<"Failed to parse chanserv">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end
|
||||
@@ -532,8 +527,9 @@ process_irc_register(ServerHost, Host, From, _To,
|
||||
XDataEl = find_xdata_el(SubEl),
|
||||
case XDataEl of
|
||||
false ->
|
||||
Txt1 = <<"No data form found">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]};
|
||||
sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, Txt1)]};
|
||||
#xmlel{attrs = Attrs} ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"cancel">> ->
|
||||
@@ -546,8 +542,9 @@ process_irc_register(ServerHost, Host, From, _To,
|
||||
XData = jlib:parse_xdata_submit(XDataEl),
|
||||
case XData of
|
||||
invalid ->
|
||||
Txt2 = <<"Incorrect data form">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_BAD_REQUEST]};
|
||||
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt2)]};
|
||||
_ ->
|
||||
Node = str:tokens(fxml:get_tag_attr_s(<<"node">>,
|
||||
SubEl),
|
||||
@@ -567,7 +564,9 @@ process_irc_register(ServerHost, Host, From, _To,
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
||||
Txt3 = <<"Incorrect value of 'type' attribute">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt3)]}
|
||||
end
|
||||
end;
|
||||
get ->
|
||||
@@ -587,49 +586,16 @@ process_irc_register(ServerHost, Host, From, _To,
|
||||
|
||||
get_data(ServerHost, Host, From) ->
|
||||
LServer = jid:nameprep(ServerHost),
|
||||
get_data(LServer, Host, From,
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_data(_LServer, Host, From, mnesia) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({irc_custom, {US, Host}})
|
||||
of
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> empty;
|
||||
[#irc_custom{data = Data}] -> Data
|
||||
end;
|
||||
get_data(LServer, Host, From, riak) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
US = {LUser, LServer},
|
||||
case ejabberd_riak:get(irc_custom, irc_custom_schema(), {US, Host}) of
|
||||
{ok, #irc_custom{data = Data}} ->
|
||||
Data;
|
||||
{error, notfound} ->
|
||||
empty;
|
||||
_Err ->
|
||||
error
|
||||
end;
|
||||
get_data(LServer, Host, From, odbc) ->
|
||||
SJID =
|
||||
ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
case catch ejabberd_odbc:sql_query(LServer,
|
||||
[<<"select data from irc_custom where jid='">>,
|
||||
SJID, <<"' and host='">>, SHost,
|
||||
<<"';">>])
|
||||
of
|
||||
{selected, [<<"data">>], [[SData]]} ->
|
||||
data_to_binary(From, ejabberd_odbc:decode_term(SData));
|
||||
{'EXIT', _} -> error;
|
||||
{selected, _, _} -> empty
|
||||
end.
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_data(LServer, Host, From).
|
||||
|
||||
get_form(ServerHost, Host, From, [], Lang) ->
|
||||
#jid{user = User, server = Server} = From,
|
||||
DefaultEncoding = get_default_encoding(Host),
|
||||
Customs = case get_data(ServerHost, Host, From) of
|
||||
error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
error ->
|
||||
Txt1 = <<"Database failure">>,
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt1)};
|
||||
empty -> {User, []};
|
||||
Data -> get_username_and_connection_params(Data)
|
||||
end,
|
||||
@@ -731,39 +697,10 @@ get_form(_ServerHost, _Host, _, _, _Lang) ->
|
||||
|
||||
set_data(ServerHost, Host, From, Data) ->
|
||||
LServer = jid:nameprep(ServerHost),
|
||||
set_data(LServer, Host, From, data_to_binary(From, Data),
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:set_data(LServer, Host, From, data_to_binary(From, Data)).
|
||||
|
||||
set_data(_LServer, Host, From, Data, mnesia) ->
|
||||
{LUser, LServer, _} = jid:tolower(From),
|
||||
US = {LUser, LServer},
|
||||
F = fun () ->
|
||||
mnesia:write(#irc_custom{us_host = {US, Host},
|
||||
data = Data})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
set_data(LServer, Host, From, Data, riak) ->
|
||||
{LUser, LServer, _} = jid:tolower(From),
|
||||
US = {LUser, LServer},
|
||||
{atomic, ejabberd_riak:put(#irc_custom{us_host = {US, Host},
|
||||
data = Data},
|
||||
irc_custom_schema())};
|
||||
set_data(LServer, Host, From, Data, odbc) ->
|
||||
SJID =
|
||||
ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
SData = ejabberd_odbc:encode_term(Data),
|
||||
F = fun () ->
|
||||
odbc_queries:update_t(<<"irc_custom">>,
|
||||
[<<"jid">>, <<"host">>, <<"data">>],
|
||||
[SJID, SHost, SData],
|
||||
[<<"jid='">>, SJID, <<"' and host='">>,
|
||||
SHost, <<"'">>]),
|
||||
ok
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
set_form(ServerHost, Host, From, [], _Lang, XData) ->
|
||||
set_form(ServerHost, Host, From, [], Lang, XData) ->
|
||||
case {lists:keysearch(<<"username">>, 1, XData),
|
||||
lists:keysearch(<<"connections_params">>, 1, XData)}
|
||||
of
|
||||
@@ -781,11 +718,11 @@ set_form(ServerHost, Host, From, [], _Lang, XData) ->
|
||||
{connections_params, ConnectionsParams}])
|
||||
of
|
||||
{atomic, _} -> {result, []};
|
||||
_ -> {error, ?ERR_NOT_ACCEPTABLE}
|
||||
_ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Database failure">>)}
|
||||
end;
|
||||
_ -> {error, ?ERR_NOT_ACCEPTABLE}
|
||||
_ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Parse error">>)}
|
||||
end;
|
||||
_ -> {error, ?ERR_NOT_ACCEPTABLE}
|
||||
_ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Scan error">>)}
|
||||
end;
|
||||
_ -> {error, ?ERR_NOT_ACCEPTABLE}
|
||||
end;
|
||||
@@ -909,7 +846,9 @@ adhoc_join(From, To,
|
||||
elements = [Form]});
|
||||
true ->
|
||||
case jlib:parse_xdata_submit(XData) of
|
||||
invalid -> {error, ?ERR_BAD_REQUEST};
|
||||
invalid ->
|
||||
Txt1 = <<"Incorrect data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt1)};
|
||||
Fields ->
|
||||
Channel = case lists:keysearch(<<"channel">>, 1, Fields)
|
||||
of
|
||||
@@ -998,7 +937,8 @@ adhoc_register(ServerHost, From, To,
|
||||
true ->
|
||||
case jlib:parse_xdata_submit(XData) of
|
||||
invalid ->
|
||||
Error = {error, ?ERR_BAD_REQUEST},
|
||||
Txt1 = <<"Incorrect data form">>,
|
||||
Error = {error, ?ERRT_BAD_REQUEST(Lang, Txt1)},
|
||||
Username = false,
|
||||
ConnectionsParams = false;
|
||||
Fields ->
|
||||
@@ -1021,7 +961,9 @@ adhoc_register(ServerHost, From, To,
|
||||
{atomic, _} ->
|
||||
adhoc:produce_response(Request,
|
||||
#adhoc_response{status = completed});
|
||||
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
_ ->
|
||||
Txt2 = <<"Database failure">>,
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt2)}
|
||||
end;
|
||||
true ->
|
||||
Form = generate_adhoc_register_form(Lang, Username,
|
||||
@@ -1297,70 +1239,21 @@ conn_params_to_list(Params) ->
|
||||
Port, binary_to_list(P)}
|
||||
end, Params).
|
||||
|
||||
irc_custom_schema() ->
|
||||
{record_info(fields, irc_custom), #irc_custom{}}.
|
||||
export(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:export(LServer).
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, irc_custom),
|
||||
case mnesia:table_info(irc_custom, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
irc_custom, Fields, set,
|
||||
fun(#irc_custom{us_host = {_, H}}) -> H end,
|
||||
fun(#irc_custom{us_host = {{U, S}, H},
|
||||
data = Data} = R) ->
|
||||
JID = jid:make(U, S, <<"">>),
|
||||
R#irc_custom{us_host = {{iolist_to_binary(U),
|
||||
iolist_to_binary(S)},
|
||||
iolist_to_binary(H)},
|
||||
data = data_to_binary(JID, Data)}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating irc_custom table", []),
|
||||
mnesia:transform_table(irc_custom, ignore, Fields)
|
||||
end.
|
||||
import(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:import(LServer).
|
||||
|
||||
export(_Server) ->
|
||||
[{irc_custom,
|
||||
fun(Host, #irc_custom{us_host = {{U, S}, IRCHost},
|
||||
data = Data}) ->
|
||||
case str:suffix(Host, IRCHost) of
|
||||
true ->
|
||||
SJID = ejabberd_odbc:escape(
|
||||
jid:to_string(
|
||||
jid:make(U, S, <<"">>))),
|
||||
SIRCHost = ejabberd_odbc:escape(IRCHost),
|
||||
SData = ejabberd_odbc:encode_term(Data),
|
||||
[[<<"delete from irc_custom where jid='">>, SJID,
|
||||
<<"' and host='">>, SIRCHost, <<"';">>],
|
||||
[<<"insert into irc_custom(jid, host, "
|
||||
"data) values ('">>,
|
||||
SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
|
||||
<<"');">>]];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
end}].
|
||||
|
||||
import(_LServer) ->
|
||||
[{<<"select jid, host, data from irc_custom;">>,
|
||||
fun([SJID, IRCHost, SData]) ->
|
||||
#jid{luser = U, lserver = S} = jid:from_string(SJID),
|
||||
Data = ejabberd_odbc:decode_term(SData),
|
||||
#irc_custom{us_host = {{U, S}, IRCHost},
|
||||
data = Data}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #irc_custom{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_LServer, riak, #irc_custom{} = R) ->
|
||||
ejabberd_riak:put(R, irc_custom_schema());
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
import(LServer, DBType, Data) ->
|
||||
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
||||
Mod:import(LServer, Data).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
|
||||
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;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_irc_mnesia).
|
||||
|
||||
-behaviour(mod_irc).
|
||||
|
||||
%% API
|
||||
-export([init/2, get_data/3, set_data/4, import/2]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_irc.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
mnesia:create_table(irc_custom,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, irc_custom)}]),
|
||||
update_table().
|
||||
|
||||
get_data(_LServer, Host, From) ->
|
||||
{U, S, _} = jid:tolower(From),
|
||||
case catch mnesia:dirty_read({irc_custom, {{U, S}, Host}}) of
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> empty;
|
||||
[#irc_custom{data = Data}] -> Data
|
||||
end.
|
||||
|
||||
set_data(_LServer, Host, From, Data) ->
|
||||
{U, S, _} = jid:tolower(From),
|
||||
F = fun () ->
|
||||
mnesia:write(#irc_custom{us_host = {{U, S}, Host},
|
||||
data = Data})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
import(_LServer, #irc_custom{} = R) ->
|
||||
mnesia:dirty_write(R).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
update_table() ->
|
||||
Fields = record_info(fields, irc_custom),
|
||||
case mnesia:table_info(irc_custom, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
irc_custom, Fields, set,
|
||||
fun(#irc_custom{us_host = {_, H}}) -> H end,
|
||||
fun(#irc_custom{us_host = {{U, S}, H},
|
||||
data = Data} = R) ->
|
||||
JID = jid:make(U, S, <<"">>),
|
||||
R#irc_custom{us_host = {{iolist_to_binary(U),
|
||||
iolist_to_binary(S)},
|
||||
iolist_to_binary(H)},
|
||||
data = mod_irc:data_to_binary(JID, Data)}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating irc_custom table", []),
|
||||
mnesia:transform_table(irc_custom, ignore, Fields)
|
||||
end.
|
||||
@@ -0,0 +1,49 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_irc_riak).
|
||||
|
||||
-behaviour(mod_irc).
|
||||
|
||||
%% API
|
||||
-export([init/2, get_data/3, set_data/4, import/2]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_irc.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
get_data(_LServer, Host, From) ->
|
||||
{U, S, _} = jid:tolower(From),
|
||||
case ejabberd_riak:get(irc_custom, irc_custom_schema(), {{U, S}, Host}) of
|
||||
{ok, #irc_custom{data = Data}} ->
|
||||
Data;
|
||||
{error, notfound} ->
|
||||
empty;
|
||||
_Err ->
|
||||
error
|
||||
end.
|
||||
|
||||
set_data(_LServer, Host, From, Data) ->
|
||||
{U, S, _} = jid:tolower(From),
|
||||
{atomic, ejabberd_riak:put(#irc_custom{us_host = {{U, S}, Host},
|
||||
data = Data},
|
||||
irc_custom_schema())}.
|
||||
|
||||
import(_LServer, #irc_custom{} = R) ->
|
||||
ejabberd_riak:put(R, irc_custom_schema()).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
irc_custom_schema() ->
|
||||
{record_info(fields, irc_custom), #irc_custom{}}.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user