Compare commits
137 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 | |||
| 36164d9446 | |||
| 45321fa2e2 | |||
| cebdfb6523 |
+2
-1
@@ -184,7 +184,8 @@ install: all copy-files
|
||||
-e "s*{{sysconfdir}}*@sysconfdir@*" \
|
||||
-e "s*{{localstatedir}}*@localstatedir@*" \
|
||||
-e "s*{{docdir}}*@docdir@*" \
|
||||
-e "s*{{erl}}*@ERL@*" ejabberdctl.template \
|
||||
-e "s*{{erl}}*@ERL@*" \
|
||||
-e "s*{{epmd}}*@EPMD@*" ejabberdctl.template \
|
||||
> ejabberdctl.example
|
||||
[ -f $(ETCDIR)/ejabberdctl.cfg ] \
|
||||
&& $(INSTALL) -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new \
|
||||
|
||||
@@ -30,6 +30,7 @@ fi
|
||||
|
||||
AC_PATH_TOOL(ERL, erl, , [${extra_erl_path}$PATH])
|
||||
AC_PATH_TOOL(ERLC, erlc, , [${extra_erl_path}$PATH])
|
||||
AC_PATH_TOOL(EPMD, epmd, , [${extra_erl_path}$PATH])
|
||||
|
||||
AC_ERLANG_NEED_ERL
|
||||
AC_ERLANG_NEED_ERLC
|
||||
|
||||
@@ -242,9 +242,7 @@ print_usage() ->
|
||||
print_po_header(File) ->
|
||||
MsgProps = get_msg_header_props(File),
|
||||
{Language, [LastT | AddT]} = prepare_props(MsgProps),
|
||||
application:load(ejabberd),
|
||||
{ok, Version} = application:get_key(ejabberd, vsn),
|
||||
print_po_header(Version, Language, LastT, AddT).
|
||||
print_po_header(Language, LastT, AddT).
|
||||
|
||||
get_msg_header_props(File) ->
|
||||
{ok, F} = file:open(File, [read]),
|
||||
@@ -274,12 +272,11 @@ prepare_props(MsgProps) ->
|
||||
Authors = proplists:get_all_values("Author:", MsgProps),
|
||||
{Language, Authors}.
|
||||
|
||||
print_po_header(Version, Language, LastTranslator, AdditionalTranslatorsList) ->
|
||||
print_po_header(Language, LastTranslator, AdditionalTranslatorsList) ->
|
||||
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
|
||||
HeaderString =
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
"\"Project-Id-Version: " ++ Version ++ "\\n\"\n"
|
||||
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
|
||||
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
|
||||
++ AdditionalTranslatorsString ++
|
||||
|
||||
@@ -157,7 +157,7 @@ extract_lang_srcmsg2po ()
|
||||
echo $MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
|
||||
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
|
||||
|
||||
@@ -176,7 +176,7 @@ extract_lang_src2pot ()
|
||||
echo "" >>$MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
|
||||
|
||||
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
|
||||
|
||||
+47
-45
@@ -408,14 +408,14 @@ acl:
|
||||
##
|
||||
## admin:
|
||||
## user:
|
||||
## - "aleksey": "localhost"
|
||||
## - "ermine": "example.org"
|
||||
## - "aleksey@localhost"
|
||||
## - "ermine@example.org"
|
||||
##
|
||||
## Blocked users
|
||||
##
|
||||
## blocked:
|
||||
## user:
|
||||
## - "baduser": "example.org"
|
||||
## - "baduser@example.org"
|
||||
## - "test"
|
||||
|
||||
## Local users: don't modify this.
|
||||
@@ -431,7 +431,7 @@ acl:
|
||||
## - "jabber.org"
|
||||
## aleksey:
|
||||
## user:
|
||||
## - "aleksey": "jabber.ru"
|
||||
## - "aleksey@jabber.ru"
|
||||
## test:
|
||||
## user_regexp: "^test"
|
||||
## user_glob: "test*"
|
||||
@@ -459,61 +459,61 @@ acl:
|
||||
## acl:
|
||||
## admin:
|
||||
## user:
|
||||
## - "bob-local": "localhost"
|
||||
## - "bob-local@localhost"
|
||||
|
||||
###. ============
|
||||
###' SHAPER RULES
|
||||
|
||||
shaper_rules:
|
||||
## Maximum number of simultaneous sessions allowed for a single user:
|
||||
max_user_sessions: 10
|
||||
## Maximum number of offline messages that users can have:
|
||||
max_user_offline_messages:
|
||||
- 5000: admin
|
||||
- 100
|
||||
## For C2S connections, all users except admins use the "normal" shaper
|
||||
c2s_shaper:
|
||||
- none: admin
|
||||
- normal
|
||||
## All S2S connections use the "fast" shaper
|
||||
s2s_shaper: fast
|
||||
|
||||
###. ============
|
||||
###' ACCESS RULES
|
||||
access:
|
||||
## Maximum number of simultaneous sessions allowed for a single user:
|
||||
max_user_sessions:
|
||||
all: 10
|
||||
## Maximum number of offline messages that users can have:
|
||||
max_user_offline_messages:
|
||||
admin: 5000
|
||||
all: 100
|
||||
access_rules:
|
||||
## This rule allows access only for local users:
|
||||
local:
|
||||
local: allow
|
||||
local:
|
||||
- allow: local
|
||||
## Only non-blocked users can use c2s connections:
|
||||
c2s:
|
||||
blocked: deny
|
||||
all: allow
|
||||
## For C2S connections, all users except admins use the "normal" shaper
|
||||
c2s_shaper:
|
||||
admin: none
|
||||
all: normal
|
||||
## All S2S connections use the "fast" shaper
|
||||
s2s_shaper:
|
||||
all: fast
|
||||
c2s:
|
||||
- deny: blocked
|
||||
- allow
|
||||
## Only admins can send announcement messages:
|
||||
announce:
|
||||
admin: allow
|
||||
announce:
|
||||
- allow: admin
|
||||
## Only admins can use the configuration interface:
|
||||
configure:
|
||||
admin: allow
|
||||
## Admins of this server are also admins of the MUC service:
|
||||
muc_admin:
|
||||
admin: allow
|
||||
- allow: admin
|
||||
## Only accounts of the local ejabberd server can create rooms:
|
||||
muc_create:
|
||||
local: allow
|
||||
## All users are allowed to use the MUC service:
|
||||
muc:
|
||||
all: allow
|
||||
- allow: local
|
||||
## Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
pubsub_createnode:
|
||||
local: allow
|
||||
- allow: local
|
||||
## In-band registration allows registration of any possible username.
|
||||
## To disable in-band registration, replace 'allow' with 'deny'.
|
||||
register:
|
||||
all: allow
|
||||
- allow
|
||||
## Only allow to register from localhost
|
||||
trusted_network:
|
||||
loopback: allow
|
||||
- allow: loopback
|
||||
## Do not establish S2S connections with bad servers
|
||||
## s2s:
|
||||
## bad_servers: deny
|
||||
## all: allow
|
||||
## - deny:
|
||||
## - ip: "XXX.XXX.XXX.XXX/32"
|
||||
## - deny:
|
||||
## - ip: "XXX.XXX.XXX.XXX/32"
|
||||
## - allow
|
||||
|
||||
## By default the frequency of account registrations from the same IP
|
||||
## is limited to 1 account every 10 minutes. To disable, specify: infinity
|
||||
@@ -526,10 +526,10 @@ access:
|
||||
## "localhost":
|
||||
## access:
|
||||
## c2s:
|
||||
## admin: allow
|
||||
## all: deny
|
||||
## - allow: admin
|
||||
## - deny
|
||||
## register:
|
||||
## all: deny
|
||||
## - deny
|
||||
|
||||
###. ================
|
||||
###' DEFAULT LANGUAGE
|
||||
@@ -590,10 +590,12 @@ modules:
|
||||
mod_last: {}
|
||||
mod_muc:
|
||||
## host: "conference.@HOST@"
|
||||
access: muc
|
||||
access:
|
||||
- allow
|
||||
access_admin:
|
||||
- allow: admin
|
||||
access_create: muc_create
|
||||
access_persistent: muc_create
|
||||
access_admin: muc_admin
|
||||
## mod_muc_log: {}
|
||||
## mod_multicast: {}
|
||||
mod_offline:
|
||||
|
||||
@@ -13,7 +13,7 @@ ERLANG_NODE=ejabberd@localhost
|
||||
SCRIPT_DIR=`cd ${0%/*} && pwd`
|
||||
ERL={{erl}}
|
||||
IEX={{bindir}}/iex
|
||||
EPMD={{bindir}}/epmd
|
||||
EPMD={{epmd}}
|
||||
INSTALLUSER={{installuser}}
|
||||
ERL_LIBS={{libdir}}
|
||||
|
||||
@@ -212,7 +212,7 @@ iexdebug()
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "ERL_PATH=$\"$ERL\" $CMD"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start interactive server
|
||||
@@ -441,6 +441,6 @@ case "${ARGS[0]}" in
|
||||
'ping'*) ping ${ARGS[1]};;
|
||||
'etop') etop;;
|
||||
'started') wait_for_status 0 30 2;; # wait 30x2s before timeout
|
||||
'stopped') wait_for_status 3 15 2 && stop_epmd;; # wait 15x2s before timeout
|
||||
'stopped') wait_for_status 3 30 2 && stop_epmd;; # wait 30x2s before timeout
|
||||
*) ctl "${ARGS[@]}";;
|
||||
esac
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -93,7 +93,12 @@
|
||||
|
||||
-type(subOptions() :: [mod_pubsub:subOption(),...]).
|
||||
|
||||
-type(pubOption() ::
|
||||
{Option::binary(),
|
||||
Values::[binary()]
|
||||
}).
|
||||
|
||||
-type(pubOptions() :: [mod_pubsub:pubOption()]).
|
||||
|
||||
-type(affiliation() :: 'none'
|
||||
| 'owner'
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "16.04.0",
|
||||
version: "16.06.0",
|
||||
description: description,
|
||||
elixir: "~> 1.2",
|
||||
elixirc_paths: ["lib"],
|
||||
@@ -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.4"},
|
||||
"exrm": {:hex, :exrm, "1.0.3"},
|
||||
"ezlib": {:hex, :ezlib, "1.0.1"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.3"},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.11"},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.3"},
|
||||
"getopt": {:hex, :getopt, "0.8.2"},
|
||||
"goldrush": {:hex, :goldrush, "0.1.7"},
|
||||
"iconv": {:hex, :iconv, "1.0.0"},
|
||||
"jiffy": {:hex, :jiffy, "0.14.7"},
|
||||
"lager": {:hex, :lager, "3.0.2"},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.1"},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.0"},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.3"},
|
||||
"p1_xmlrpc": {:hex, :p1_xmlrpc, "1.15.1"},
|
||||
"providers": {:hex, :providers, "1.6.0"},
|
||||
"relx": {:hex, :relx, "3.19.0"},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.3"},
|
||||
"stun": {:hex, :stun, "1.0.3"}}
|
||||
%{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.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]}]}}
|
||||
|
||||
+7
-6
@@ -10,11 +10,11 @@
|
||||
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.0.2"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.4"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.2"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.3"}}},
|
||||
{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.3"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.3"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.4"}}},
|
||||
{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"}}},
|
||||
@@ -68,7 +68,7 @@
|
||||
ezlib,
|
||||
iconv]}}.
|
||||
|
||||
{erl_first_files, ["src/ejabberd_config.erl"]}.
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl"]}.
|
||||
|
||||
{erl_opts, [nowarn_deprecated_function,
|
||||
{if_var_false, debug, no_debug_info},
|
||||
@@ -113,11 +113,12 @@
|
||||
{if_var_false, iconv, "(\"iconv\":_/_)"},
|
||||
{if_var_false, odbc, "(\"odbc\":_/_)"},
|
||||
{if_var_false, sqlite, "(\"sqlite3\":_/_)"},
|
||||
{if_var_false, elixir, "(\"Elixir.Logger.*\":_/_)"},
|
||||
{if_var_false, redis, "(\"eredis\":_/_)"}]}.
|
||||
|
||||
{eunit_compile_opts, [{i, "tools"}]}.
|
||||
|
||||
{cover_enabled, true}.
|
||||
{if_version_above, "17", {cover_enabled, true}}.
|
||||
{cover_export_enabled, true}.
|
||||
|
||||
{post_hook_configure, [{"fast_tls", []},
|
||||
|
||||
+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"},
|
||||
|
||||
+1
-1
@@ -275,7 +275,7 @@ CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt(subid(3
|
||||
CREATE TABLE muc_room (
|
||||
name text NOT NULL,
|
||||
host text NOT NULL,
|
||||
opts text NOT NULL,
|
||||
opts mediumtext NOT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
+312
-132
@@ -29,12 +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_access/4, match_acl/3, transform_options/1,
|
||||
opt_type/1]).
|
||||
|
||||
-export([add_access/3, clear/0]).
|
||||
-export([start/0, add/3, add_list/3, add_local/3, add_list_local/3,
|
||||
load_from_config/0, match_rule/3,
|
||||
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").
|
||||
@@ -92,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) ->
|
||||
@@ -188,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(
|
||||
@@ -201,10 +200,21 @@ load_from_config() ->
|
||||
add(Host, ACLName, {ACLType, ACLSpecs})
|
||||
end, lists:flatten(SpecList))
|
||||
end, ACLs),
|
||||
lists:foreach(
|
||||
fun({Access, Rules}) ->
|
||||
NRules = lists:map(fun({ACL, Type}) ->
|
||||
{Type, [{acl, ACL}]}
|
||||
end, Rules),
|
||||
add_access(Host, Access, NRules ++ [{deny, [all]}])
|
||||
end, AccessRules),
|
||||
lists:foreach(
|
||||
fun({Access, Rules}) ->
|
||||
add_access(Host, Access, Rules)
|
||||
end, AccessRules)
|
||||
end, AccessRulesNew),
|
||||
lists:foreach(
|
||||
fun({Access, Rules}) ->
|
||||
add_access(Host, Access, Rules)
|
||||
end, ShaperRules)
|
||||
end, Hosts).
|
||||
|
||||
%% Delete all previous set ACLs and Access rules
|
||||
@@ -225,19 +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)};
|
||||
@@ -255,130 +274,206 @@ normalize_spec(Spec) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec match_access(global | binary(), access_name(),
|
||||
jid() | ljid() | inet:ip_address(),
|
||||
atom()) -> any().
|
||||
|
||||
match_access(_Host, all, _JID, _Default) ->
|
||||
allow;
|
||||
match_access(_Host, none, _JID, _Default) ->
|
||||
deny;
|
||||
match_access(_Host, {user, UserPattern}, JID, Default) ->
|
||||
match_user_spec({user, UserPattern}, JID, Default);
|
||||
match_access(Host, AccessRule, JID, _Default) ->
|
||||
match_rule(Host, AccessRule, JID).
|
||||
|
||||
-spec match_rule(global | binary(), access_name(),
|
||||
jid() | ljid() | inet:ip_address()) -> any().
|
||||
|
||||
match_rule(_Host, all, _JID) ->
|
||||
allow;
|
||||
match_rule(_Host, none, _JID) ->
|
||||
deny;
|
||||
match_rule(Host, Access, IP) when tuple_size(IP) == 4;
|
||||
tuple_size(IP) == 8 ->
|
||||
access_matches(Access, #{ip => IP}, Host);
|
||||
match_rule(Host, Access, JID) ->
|
||||
GAccess = ets:lookup(access, {Access, global}),
|
||||
LAccess = if Host /= global ->
|
||||
ets:lookup(access, {Access, Host});
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
case GAccess ++ LAccess of
|
||||
[] ->
|
||||
deny;
|
||||
AccessList ->
|
||||
Rules = lists:flatmap(
|
||||
fun(#access{rules = Rs}) ->
|
||||
Rs
|
||||
end, AccessList),
|
||||
match_acls(Rules, JID, Host)
|
||||
end.
|
||||
access_matches(Access, #{usr => jid:tolower(JID)}, Host).
|
||||
|
||||
match_acls([], _, _Host) -> deny;
|
||||
match_acls([{ACL, Access} | ACLs], JID, Host) ->
|
||||
case match_acl(ACL, JID, Host) of
|
||||
true -> Access;
|
||||
_ -> match_acls(ACLs, JID, Host)
|
||||
end.
|
||||
-spec acl_rule_verify(aclspec()) -> boolean().
|
||||
|
||||
-spec match_acl(atom(),
|
||||
jid() | ljid() | inet:ip_address(),
|
||||
binary()) -> boolean().
|
||||
|
||||
match_acl(all, _JID, _Host) ->
|
||||
acl_rule_verify(all) ->
|
||||
true;
|
||||
match_acl(none, _JID, _Host) ->
|
||||
acl_rule_verify(none) ->
|
||||
true;
|
||||
acl_rule_verify({ip, {{A,B,C,D}, Mask}})
|
||||
when is_integer(A), is_integer(B), is_integer(C), is_integer(D),
|
||||
A >= 0, A =< 255, B >= 0, B =< 255, C >= 0, C =< 255, D >= 0, D =< 255,
|
||||
is_integer(Mask), Mask >= 0, Mask =< 32 ->
|
||||
true;
|
||||
acl_rule_verify({ip, {{A,B,C,D,E,F,G,H}, Mask}}) when
|
||||
is_integer(A), is_integer(B), is_integer(C), is_integer(D),
|
||||
is_integer(E), is_integer(F), is_integer(G), is_integer(H),
|
||||
A >= 0, A =< 65535, B >= 0, B =< 65535, C >= 0, C =< 65535, D >= 0, D =< 65535,
|
||||
E >= 0, E =< 65535, F >= 0, F =< 65535, G >= 0, G =< 65535, H >= 0, H =< 65535,
|
||||
is_integer(Mask), Mask >= 0, Mask =< 64 ->
|
||||
true;
|
||||
acl_rule_verify({user, {U, S}}) when is_binary(U), is_binary(S) ->
|
||||
true;
|
||||
acl_rule_verify({user, U}) when is_binary(U) ->
|
||||
true;
|
||||
acl_rule_verify({server, S}) when is_binary(S) ->
|
||||
true;
|
||||
acl_rule_verify({resource, R}) when is_binary(R) ->
|
||||
true;
|
||||
acl_rule_verify({shared_group, {G, H}}) when is_binary(G), is_binary(H) ->
|
||||
true;
|
||||
acl_rule_verify({shared_group, G}) when is_binary(G) ->
|
||||
true;
|
||||
acl_rule_verify({user_regexp, {UR, S}}) when is_binary(UR), is_binary(S) ->
|
||||
true;
|
||||
acl_rule_verify({user_regexp, UR}) when is_binary(UR) ->
|
||||
true;
|
||||
acl_rule_verify({server_regexp, SR}) when is_binary(SR) ->
|
||||
true;
|
||||
acl_rule_verify({resource_regexp, RR}) when is_binary(RR) ->
|
||||
true;
|
||||
acl_rule_verify({node_regexp, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
|
||||
true;
|
||||
acl_rule_verify({user_glob, {UR, S}}) when is_binary(UR), is_binary(S) ->
|
||||
true;
|
||||
acl_rule_verify({user_glob, UR}) when is_binary(UR) ->
|
||||
true;
|
||||
acl_rule_verify({server_glob, SR}) when is_binary(SR) ->
|
||||
true;
|
||||
acl_rule_verify({resource_glob, RR}) when is_binary(RR) ->
|
||||
true;
|
||||
acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
|
||||
true;
|
||||
acl_rule_verify(_Spec) ->
|
||||
false.
|
||||
invalid_syntax(Msg, Data) ->
|
||||
throw({invalid_syntax, iolist_to_binary(io_lib:format(Msg, Data))}).
|
||||
|
||||
acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) ->
|
||||
acl_rules_verify(Rest, true);
|
||||
acl_rules_verify([{acl, Name} = Rule | _Rest], false) when is_atom(Name) ->
|
||||
invalid_syntax(<<"Using acl: rules not allowed: ~p">>, [Rule]);
|
||||
acl_rules_verify([Rule | Rest], AllowAcl) ->
|
||||
case acl_rule_verify(Rule) of
|
||||
false ->
|
||||
invalid_syntax(<<"Invalid rule: ~p">>, [Rule]);
|
||||
true ->
|
||||
acl_rules_verify(Rest, AllowAcl)
|
||||
end;
|
||||
acl_rules_verify([], _AllowAcl) ->
|
||||
true;
|
||||
acl_rules_verify(Rules, _AllowAcl) ->
|
||||
invalid_syntax(<<"Not a acl rules list: ~p">>, [Rules]).
|
||||
|
||||
|
||||
|
||||
all_acl_rules_matches([], _Data, _Host) ->
|
||||
false;
|
||||
match_acl(ACL, IP, Host) when tuple_size(IP) == 4;
|
||||
tuple_size(IP) == 8 ->
|
||||
lists:any(
|
||||
fun(#acl{aclspec = {ip, {Net, Mask}}}) ->
|
||||
is_ip_match(IP, Net, Mask);
|
||||
(_) ->
|
||||
false
|
||||
end, get_aclspecs(ACL, Host));
|
||||
match_acl(ACL, JID, Host) ->
|
||||
{User, Server, Resource} = jid:tolower(JID),
|
||||
lists:any(
|
||||
fun(#acl{aclspec = Spec}) ->
|
||||
case Spec of
|
||||
all -> true;
|
||||
{user, {U, S}} -> U == User andalso S == Server;
|
||||
{user, U} ->
|
||||
U == User andalso
|
||||
lists:member(Server, ?MYHOSTS);
|
||||
{server, S} -> S == Server;
|
||||
{resource, R} -> R == Resource;
|
||||
{shared_group, {G, H}} ->
|
||||
Mod = loaded_shared_roster_module(H),
|
||||
Mod:is_user_in_group({User, Server}, G, H);
|
||||
{shared_group, G} ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({User, Server}, G, Host);
|
||||
{user_regexp, {UR, S}} ->
|
||||
S == Server andalso is_regexp_match(User, UR);
|
||||
{user_regexp, UR} ->
|
||||
lists:member(Server, ?MYHOSTS)
|
||||
andalso is_regexp_match(User, UR);
|
||||
{server_regexp, SR} ->
|
||||
is_regexp_match(Server, SR);
|
||||
{resource_regexp, RR} ->
|
||||
is_regexp_match(Resource, RR);
|
||||
{node_regexp, {UR, SR}} ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_glob, {UR, S}} ->
|
||||
S == Server andalso is_glob_match(User, UR);
|
||||
{user_glob, UR} ->
|
||||
lists:member(Server, ?MYHOSTS)
|
||||
andalso is_glob_match(User, UR);
|
||||
{server_glob, SR} -> is_glob_match(Server, SR);
|
||||
{resource_glob, RR} ->
|
||||
is_glob_match(Resource, RR);
|
||||
{node_glob, {UR, SR}} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
WrongSpec ->
|
||||
?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
|
||||
"config file and reload it with the override_a"
|
||||
"cls option enabled",
|
||||
[WrongSpec]),
|
||||
false
|
||||
end
|
||||
end,
|
||||
get_aclspecs(ACL, Host)).
|
||||
all_acl_rules_matches(Rules, Data, Host) ->
|
||||
all_acl_rules_matches2(Rules, Data, Host).
|
||||
|
||||
all_acl_rules_matches2([Rule | Tail], Data, Host) ->
|
||||
case acl_rule_matches(Rule, Data, Host) of
|
||||
true ->
|
||||
all_acl_rules_matches2(Tail, Data, Host);
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
all_acl_rules_matches2([], _Data, _Host) ->
|
||||
true.
|
||||
|
||||
any_acl_rules_matches([], _Data, _Host) ->
|
||||
false;
|
||||
any_acl_rules_matches([Rule|Tail], Data, Host) ->
|
||||
case acl_rule_matches(Rule, Data, Host) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
any_acl_rules_matches(Tail, Data, Host)
|
||||
end.
|
||||
|
||||
-spec acl_rule_matches(aclspec(), any(), global|binary()) -> boolean().
|
||||
|
||||
acl_rule_matches(all, _Data, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({acl, all}, _Data, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({acl, Name}, Data, Host) ->
|
||||
ACLs = get_aclspecs(Name, Host),
|
||||
RawACLs = lists:map(fun(#acl{aclspec = R}) -> R end, ACLs),
|
||||
any_acl_rules_matches(RawACLs, Data, Host);
|
||||
acl_rule_matches({ip, {Net, Mask}}, #{ip := {IP, _Port}}, _Host) ->
|
||||
is_ip_match(IP, Net, Mask);
|
||||
acl_rule_matches({ip, {Net, Mask}}, #{ip := IP}, _Host) ->
|
||||
is_ip_match(IP, Net, Mask);
|
||||
acl_rule_matches({user, {U, S}}, #{usr := {U, S, _}}, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({user, U}, #{usr := {U, S, _}}, _Host) ->
|
||||
lists:member(S, ?MYHOSTS);
|
||||
acl_rule_matches({server, S}, #{usr := {_, S, _}}, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({resource, R}, #{usr := {_, _, R}}, _Host) ->
|
||||
true;
|
||||
acl_rule_matches({shared_group, {G, H}}, #{usr := {U, S, _}}, _Host) ->
|
||||
Mod = loaded_shared_roster_module(H),
|
||||
Mod:is_user_in_group({U, S}, G, H);
|
||||
acl_rule_matches({shared_group, G}, #{usr := {U, S, _}}, Host) ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({U, S}, G, Host);
|
||||
acl_rule_matches({user_regexp, {UR, S}}, #{usr := {U, S, _}}, _Host) ->
|
||||
is_regexp_match(U, UR);
|
||||
acl_rule_matches({user_regexp, UR}, #{usr := {U, S, _}}, _Host) ->
|
||||
lists:member(S, ?MYHOSTS) andalso is_regexp_match(U, UR);
|
||||
acl_rule_matches({server_regexp, SR}, #{usr := {_, S, _}}, _Host) ->
|
||||
is_regexp_match(S, SR);
|
||||
acl_rule_matches({resource_regexp, RR}, #{usr := {_, _, R}}, _Host) ->
|
||||
is_regexp_match(R, RR);
|
||||
acl_rule_matches({node_regexp, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
|
||||
is_regexp_match(U, UR) andalso is_regexp_match(S, SR);
|
||||
acl_rule_matches({user_glob, {UR, S}}, #{usr := {U, S, _}}, _Host) ->
|
||||
is_glob_match(U, UR);
|
||||
acl_rule_matches({user_glob, UR}, #{usr := {U, S, _}}, _Host) ->
|
||||
lists:member(S, ?MYHOSTS) andalso is_glob_match(U, UR);
|
||||
acl_rule_matches({server_glob, SR}, #{usr := {_, S, _}}, _Host) ->
|
||||
is_glob_match(S, SR);
|
||||
acl_rule_matches({resource_glob, RR}, #{usr := {_, _, R}}, _Host) ->
|
||||
is_glob_match(R, RR);
|
||||
acl_rule_matches({node_glob, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
|
||||
is_glob_match(U, UR) andalso is_glob_match(S, SR);
|
||||
acl_rule_matches(_ACL, _Data, _Host) ->
|
||||
false.
|
||||
|
||||
-spec access_matches(atom()|list(), any(), global|binary()) -> any().
|
||||
access_matches(all, _Data, _Host) ->
|
||||
allow;
|
||||
access_matches(none, _Data, _Host) ->
|
||||
deny;
|
||||
access_matches(Name, Data, Host) when is_atom(Name) ->
|
||||
GAccess = ets:lookup(access, {Name, global}),
|
||||
LAccess =
|
||||
if Host /= global -> ets:lookup(access, {Name, Host});
|
||||
true -> []
|
||||
end,
|
||||
case GAccess ++ LAccess of
|
||||
[] ->
|
||||
deny;
|
||||
AccessList ->
|
||||
Rules = lists:flatmap(
|
||||
fun(#access{rules = Rs}) ->
|
||||
Rs
|
||||
end, AccessList),
|
||||
access_rules_matches(Rules, Data, Host)
|
||||
end;
|
||||
access_matches(Rules, Data, Host) when is_list(Rules) ->
|
||||
access_rules_matches(Rules, Data, Host).
|
||||
|
||||
|
||||
-spec access_rules_matches(list(), any(), global|binary()) -> any().
|
||||
|
||||
access_rules_matches(AR, Data, Host) ->
|
||||
access_rules_matches(AR, Data, Host, deny).
|
||||
|
||||
access_rules_matches([{Type, Acls} | Rest], Data, Host, Default) ->
|
||||
case all_acl_rules_matches(Acls, Data, Host) of
|
||||
false ->
|
||||
access_rules_matches(Rest, Data, Host, Default);
|
||||
true ->
|
||||
Type
|
||||
end;
|
||||
access_rules_matches([], _Data, _Host, Default) ->
|
||||
Default.
|
||||
|
||||
get_aclspecs(ACL, Host) ->
|
||||
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
|
||||
|
||||
|
||||
match_user_spec(Spec, JID, Default) ->
|
||||
case do_match_user_spec(Spec, jid:tolower(JID)) of
|
||||
true -> Default;
|
||||
false -> deny
|
||||
end.
|
||||
|
||||
do_match_user_spec({user, {U, S}}, {User, Server, _Resource}) ->
|
||||
U == User andalso S == Server.
|
||||
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
|
||||
|
||||
is_regexp_match(String, RegExp) ->
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
@@ -450,6 +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(
|
||||
@@ -464,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
|
||||
@@ -477,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
|
||||
@@ -508,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].
|
||||
|
||||
@@ -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, _, _, _, _}} =
|
||||
|
||||
@@ -192,10 +192,6 @@ get_commands_spec() ->
|
||||
module = ejabberd_piefxis, function = export_host,
|
||||
args = [{dir, string}, {host, string}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = export_sql, tags = [mnesia, sql],
|
||||
desc = "Export all tables as SQL queries to a file",
|
||||
module = ejd2sql, function = export,
|
||||
args = [{host, string}, {file, string}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
|
||||
desc = "Export all tables as SQL queries to a file",
|
||||
module = ejd2sql, function = delete,
|
||||
@@ -228,7 +224,7 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = export2sql, tags = [mnesia],
|
||||
desc = "Export virtual host information from Mnesia tables to SQL files",
|
||||
module = ejd2sql, function = export,
|
||||
args = [{host, string}, {directory, string}],
|
||||
args = [{host, string}, {file, string}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_master, tags = [mnesia],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -78,13 +78,15 @@ store_type() -> external.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> check_password_extauth(User, AuthzId, Server, Password);
|
||||
{true, CacheTime} ->
|
||||
check_password_cache(User, AuthzId, Server, Password, CacheTime)
|
||||
end
|
||||
false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false ->
|
||||
check_password_extauth(User, AuthzId, Server, Password);
|
||||
{true, CacheTime} ->
|
||||
check_password_cache(User, AuthzId, Server, Password,
|
||||
CacheTime)
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
|
||||
@@ -118,16 +118,15 @@ store_type() -> external.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
if Password == <<"">> -> false;
|
||||
false;
|
||||
true ->
|
||||
case catch check_password_ldap(User, Server, Password)
|
||||
of
|
||||
{'EXIT', _} -> false;
|
||||
Result -> Result
|
||||
end
|
||||
end
|
||||
if Password == <<"">> -> false;
|
||||
true ->
|
||||
case catch check_password_ldap(User, Server, Password) of
|
||||
{'EXIT', _} -> false;
|
||||
Result -> Result
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
-module(ejabberd_auth_mnesia).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
@@ -43,6 +45,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
@@ -88,51 +91,48 @@ store_type() ->
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ -> false
|
||||
end
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}] when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
Passwd = jlib:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
Passwd = jlib:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
@@ -473,12 +473,22 @@ is_password_scram_valid(Password, Scram) ->
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
when LServer == Host,
|
||||
is_binary(Password) ->
|
||||
[?SQL("delete from users where username=%(LUser)s;"),
|
||||
?SQL("insert into users(username, password) "
|
||||
"values (%(LUser)s, %(Password)s);")];
|
||||
(Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Pass = ejabberd_sql:escape(Password),
|
||||
[[<<"delete from users where username='">>, Username, <<"';">>],
|
||||
[<<"insert into users(username, password) "
|
||||
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
|
||||
StoredKey = Scram#scram.storedkey,
|
||||
ServerKey = Scram#scram.serverkey,
|
||||
Salt = Scram#scram.salt,
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
[?SQL("delete from users where username=%(LUser)s;"),
|
||||
?SQL("insert into users(username, password, serverkey, salt, "
|
||||
"iterationcount) "
|
||||
"values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
|
||||
" %(Salt)s, %(IterationCount)d);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
-module(ejabberd_auth_riak).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
@@ -42,6 +44,7 @@
|
||||
-export([passwd_schema/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
@@ -290,12 +293,10 @@ is_password_scram_valid(Password, Scram) ->
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Pass = ejabberd_sql:escape(Password),
|
||||
[[<<"delete from users where username='">>, Username, <<"';">>],
|
||||
[<<"insert into users(username, password) "
|
||||
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
|
||||
when LServer == Host ->
|
||||
[?SQL("delete from users where username=%(LUser)s;"),
|
||||
?SQL("insert into users(username, password) "
|
||||
"values (%(LUser)s, %(Password)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
+34
-26
@@ -25,6 +25,8 @@
|
||||
|
||||
-module(ejabberd_auth_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
@@ -43,6 +45,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
@@ -85,7 +88,7 @@ check_password(User, AuthzId, Server, Password) ->
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
iterationcount = IterationCount},
|
||||
is_password_scram_valid(Password, Scram);
|
||||
is_password_scram_valid_stored(Password, Scram, LUser, LServer);
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
@@ -414,6 +417,15 @@ password_to_scram(Password, IterationCount) ->
|
||||
salt = jlib:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) ->
|
||||
?INFO_MSG("Apparently, SQL auth method and scram password formatting are "
|
||||
"enabled, but the password of user '~s' in the 'users' table is not "
|
||||
"scrammed. You may want to execute this command: "
|
||||
"ejabberdctl convert_to_scram ~s", [LUser, LServer]),
|
||||
false;
|
||||
is_password_scram_valid_stored(Password, Scram, _, _) ->
|
||||
is_password_scram_valid(Password, Scram).
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = jlib:decode_base64(Scram#scram.salt),
|
||||
@@ -425,19 +437,15 @@ is_password_scram_valid(Password, Scram) ->
|
||||
|
||||
-define(BATCH_SIZE, 1000).
|
||||
|
||||
set_password_scram_t(Username,
|
||||
set_password_scram_t(LUser,
|
||||
StoredKey, ServerKey, Salt, IterationCount) ->
|
||||
sql_queries:update_t(<<"users">>,
|
||||
[<<"username">>,
|
||||
<<"password">>,
|
||||
<<"serverkey">>,
|
||||
<<"salt">>,
|
||||
<<"iterationcount">>],
|
||||
[Username, StoredKey,
|
||||
ServerKey, Salt,
|
||||
IterationCount],
|
||||
[<<"username='">>, Username,
|
||||
<<"'">>]).
|
||||
?SQL_UPSERT_T(
|
||||
"users",
|
||||
["!username=%(LUser)s",
|
||||
"password=%(StoredKey)s",
|
||||
"serverkey=%(ServerKey)s",
|
||||
"salt=%(Salt)s",
|
||||
"iterationcount=%(IterationCount)d"]).
|
||||
|
||||
convert_to_scram(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
@@ -447,24 +455,24 @@ convert_to_scram(Server) ->
|
||||
{error, {incorrect_server_name, Server}};
|
||||
true ->
|
||||
F = fun () ->
|
||||
BatchSize = ?BATCH_SIZE,
|
||||
case ejabberd_sql:sql_query_t(
|
||||
[<<"select username, password from users where "
|
||||
"iterationcount=0 limit ">>,
|
||||
integer_to_binary(?BATCH_SIZE),
|
||||
<<";">>]) of
|
||||
{selected, [<<"username">>, <<"password">>], []} ->
|
||||
?SQL("select @(username)s, @(password)s"
|
||||
" from users"
|
||||
" where iterationcount=0"
|
||||
" limit %(BatchSize)d")) of
|
||||
{selected, []} ->
|
||||
ok;
|
||||
{selected, [<<"username">>, <<"password">>], Rs} ->
|
||||
{selected, Rs} ->
|
||||
lists:foreach(
|
||||
fun([LUser, Password]) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
fun({LUser, Password}) ->
|
||||
Scram = password_to_scram(Password),
|
||||
set_password_scram_t(
|
||||
Username,
|
||||
ejabberd_sql:escape(Scram#scram.storedkey),
|
||||
ejabberd_sql:escape(Scram#scram.serverkey),
|
||||
ejabberd_sql:escape(Scram#scram.salt),
|
||||
integer_to_binary(Scram#scram.iterationcount)
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
Scram#scram.serverkey,
|
||||
Scram#scram.salt,
|
||||
Scram#scram.iterationcount
|
||||
)
|
||||
end, Rs),
|
||||
continue;
|
||||
|
||||
+111
-106
@@ -104,7 +104,6 @@
|
||||
ip,
|
||||
aux_fields = [],
|
||||
csi_state = active,
|
||||
csi_queue = [],
|
||||
mgmt_state,
|
||||
mgmt_xmlns,
|
||||
mgmt_queue,
|
||||
@@ -167,27 +166,32 @@
|
||||
(Xmlns == ?NS_STREAM_MGMT_2) or
|
||||
(Xmlns == ?NS_STREAM_MGMT_3)).
|
||||
|
||||
-define(MGMT_FAILED(Condition, Xmlns),
|
||||
-define(MGMT_FAILED(Condition, Attrs),
|
||||
#xmlel{name = <<"failed">>,
|
||||
attrs = [{<<"xmlns">>, Xmlns}],
|
||||
attrs = Attrs,
|
||||
children = [#xmlel{name = Condition,
|
||||
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
|
||||
children = []}]}).
|
||||
|
||||
-define(MGMT_BAD_REQUEST(Xmlns),
|
||||
?MGMT_FAILED(<<"bad-request">>, Xmlns)).
|
||||
|
||||
-define(MGMT_ITEM_NOT_FOUND(Xmlns),
|
||||
?MGMT_FAILED(<<"item-not-found">>, Xmlns)).
|
||||
?MGMT_FAILED(<<"bad-request">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_SERVICE_UNAVAILABLE(Xmlns),
|
||||
?MGMT_FAILED(<<"service-unavailable">>, Xmlns)).
|
||||
?MGMT_FAILED(<<"service-unavailable">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_UNEXPECTED_REQUEST(Xmlns),
|
||||
?MGMT_FAILED(<<"unexpected-request">>, Xmlns)).
|
||||
?MGMT_FAILED(<<"unexpected-request">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_UNSUPPORTED_VERSION(Xmlns),
|
||||
?MGMT_FAILED(<<"unsupported-version">>, Xmlns)).
|
||||
?MGMT_FAILED(<<"unsupported-version">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_ITEM_NOT_FOUND(Xmlns),
|
||||
?MGMT_FAILED(<<"item-not-found">>, [{<<"xmlns">>, Xmlns}])).
|
||||
|
||||
-define(MGMT_ITEM_NOT_FOUND_H(Xmlns, NumStanzasIn),
|
||||
?MGMT_FAILED(<<"item-not-found">>,
|
||||
[{<<"xmlns">>, Xmlns},
|
||||
{<<"h">>, jlib:integer_to_binary(NumStanzasIn)}])).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@@ -255,14 +259,10 @@ close(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed).
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
init([{SockMod, Socket}, Opts]) ->
|
||||
Access = case lists:keysearch(access, 1, Opts) of
|
||||
{value, {_, A}} -> A;
|
||||
_ -> all
|
||||
end,
|
||||
Shaper = case lists:keysearch(shaper, 1, Opts) of
|
||||
{value, {_, S}} -> S;
|
||||
_ -> none
|
||||
end,
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun acl:access_rules_validator/1, all),
|
||||
Shaper = gen_mod:get_opt(shaper, Opts,
|
||||
fun acl:shaper_rules_validator/1, none),
|
||||
XMLSocket = case lists:keysearch(xml_socket, 1, Opts) of
|
||||
{value, {_, XS}} -> XS;
|
||||
_ -> false
|
||||
@@ -364,11 +364,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
%% avoid possible DoS/flood attacks
|
||||
<<"">>
|
||||
end,
|
||||
StreamVersion = case fxml:get_attr_s(<<"version">>, Attrs) of
|
||||
<<"1.0">> ->
|
||||
<<"1.0">>;
|
||||
_ ->
|
||||
<<"">>
|
||||
end,
|
||||
IsBlacklistedIP = is_ip_blacklisted(StateData#state.ip, Lang),
|
||||
case lists:member(Server, ?MYHOSTS) of
|
||||
true when IsBlacklistedIP == false ->
|
||||
change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)),
|
||||
case fxml:get_attr_s(<<"version">>, Attrs) of
|
||||
case StreamVersion of
|
||||
<<"1.0">> ->
|
||||
send_header(StateData, Server, <<"1.0">>, DefaultLang),
|
||||
case StateData#state.authenticated of
|
||||
@@ -534,11 +540,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
{true, LogReason, ReasonT} = IsBlacklistedIP,
|
||||
?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
|
||||
[jlib:ip_to_list(IP), LogReason]),
|
||||
send_header(StateData, Server, <<"">>, DefaultLang),
|
||||
send_header(StateData, Server, StreamVersion, DefaultLang),
|
||||
send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
|
||||
send_header(StateData, ?MYNAME, StreamVersion, DefaultLang),
|
||||
send_element(StateData, ?HOST_UNKNOWN_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
@@ -620,9 +626,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
{auth, _ID, set, {U, P, D, R}} ->
|
||||
JID = jid:make(U, StateData#state.server, R),
|
||||
case JID /= error andalso
|
||||
acl:match_rule(StateData#state.server,
|
||||
StateData#state.access, JID)
|
||||
== allow
|
||||
acl:access_matches(StateData#state.access,
|
||||
#{usr => jid:split(JID), ip => StateData#state.ip},
|
||||
StateData#state.server) == allow
|
||||
of
|
||||
true ->
|
||||
DGen = fun (PW) ->
|
||||
@@ -1053,7 +1059,11 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
|
||||
children =
|
||||
[{xmlcdata,
|
||||
jid:to_string(JID)}]}]}]},
|
||||
send_element(StateData3, jlib:iq_to_xml(Res)),
|
||||
try
|
||||
send_element(StateData3, jlib:iq_to_xml(Res))
|
||||
catch exit:normal ->
|
||||
close(self())
|
||||
end,
|
||||
fsm_next_state_pack(
|
||||
session_established,
|
||||
StateData3);
|
||||
@@ -1095,8 +1105,10 @@ open_session(StateData) ->
|
||||
R = StateData#state.resource,
|
||||
JID = StateData#state.jid,
|
||||
Lang = StateData#state.lang,
|
||||
case acl:match_rule(StateData#state.server,
|
||||
StateData#state.access, JID) of
|
||||
IP = StateData#state.ip,
|
||||
case acl:access_matches(StateData#state.access,
|
||||
#{usr => jid:split(JID), ip => IP},
|
||||
StateData#state.server) of
|
||||
allow ->
|
||||
?INFO_MSG("(~w) Opened session for ~s",
|
||||
[StateData#state.socket, jid:to_string(JID)]),
|
||||
@@ -1143,7 +1155,7 @@ session_established({xmlstreamelement,
|
||||
#xmlel{name = <<"active">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
|
||||
StateData) ->
|
||||
NewStateData = csi_queue_flush(StateData),
|
||||
NewStateData = csi_flush_queue(StateData),
|
||||
fsm_next_state(session_established, NewStateData#state{csi_state = active});
|
||||
session_established({xmlstreamelement,
|
||||
#xmlel{name = <<"inactive">>,
|
||||
@@ -1277,7 +1289,7 @@ wait_for_resume({xmlstreamelement, _El} = Event, StateData) ->
|
||||
wait_for_resume(timeout, StateData) ->
|
||||
?DEBUG("Timed out waiting for resumption of stream for ~s",
|
||||
[jid:to_string(StateData#state.jid)]),
|
||||
{stop, normal, StateData};
|
||||
{stop, normal, StateData#state{mgmt_state = timeout}};
|
||||
wait_for_resume(Event, StateData) ->
|
||||
?DEBUG("Ignoring event while waiting for resumption: ~p", [Event]),
|
||||
fsm_next_state(wait_for_resume, StateData).
|
||||
@@ -1563,6 +1575,12 @@ handle_info({route, From, To,
|
||||
{true, Attrs,
|
||||
StateData};
|
||||
deny ->
|
||||
Err =
|
||||
jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
Err),
|
||||
{false, Attrs,
|
||||
StateData}
|
||||
end;
|
||||
@@ -1756,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,
|
||||
@@ -1782,8 +1799,20 @@ terminate(_Reason, StateName, StateData) ->
|
||||
presence_broadcast(StateData, From,
|
||||
StateData#state.pres_a, Packet)
|
||||
end,
|
||||
handle_unacked_stanzas(StateData)
|
||||
case StateData#state.mgmt_state of
|
||||
timeout ->
|
||||
Info = [{num_stanzas_in,
|
||||
StateData#state.mgmt_stanzas_in}],
|
||||
ejabberd_sm:set_offline_info(StateData#state.sid,
|
||||
StateData#state.user,
|
||||
StateData#state.server,
|
||||
StateData#state.resource,
|
||||
Info);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
handle_unacked_stanzas(StateData),
|
||||
bounce_messages();
|
||||
true ->
|
||||
ok
|
||||
@@ -1798,8 +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).
|
||||
|
||||
@@ -2584,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).
|
||||
@@ -2717,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;
|
||||
@@ -2753,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)]),
|
||||
@@ -2775,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;
|
||||
@@ -2830,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 ->
|
||||
@@ -2852,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) ->
|
||||
@@ -2880,7 +2916,7 @@ handle_unacked_stanzas(StateData)
|
||||
end;
|
||||
false ->
|
||||
fun(From, To, El, _Time) ->
|
||||
Txt = <<"User session not found">>,
|
||||
Txt = <<"User session terminated">>,
|
||||
Err =
|
||||
jlib:make_error_reply(
|
||||
El,
|
||||
@@ -2892,7 +2928,7 @@ handle_unacked_stanzas(StateData)
|
||||
?DEBUG("Dropping presence stanza from ~s",
|
||||
[jid:to_string(From)]);
|
||||
(From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
|
||||
Txt = <<"User session not found">>,
|
||||
Txt = <<"User session terminated">>,
|
||||
Err = jlib:make_error_reply(
|
||||
El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
@@ -2956,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
|
||||
@@ -2985,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,
|
||||
@@ -3018,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
|
||||
|
||||
+40
-26
@@ -230,6 +230,7 @@
|
||||
execute_command/3,
|
||||
execute_command/4,
|
||||
execute_command/5,
|
||||
execute_command/6,
|
||||
opt_type/1,
|
||||
get_commands_spec/0
|
||||
]).
|
||||
@@ -352,7 +353,7 @@ get_command_format(Name, Auth) ->
|
||||
{[aterm()], rterm()}.
|
||||
|
||||
get_command_format(Name, Auth, Version) ->
|
||||
Admin = is_admin(Name, Auth),
|
||||
Admin = is_admin(Name, Auth, #{}),
|
||||
#ejabberd_commands{args = Args,
|
||||
result = Result,
|
||||
policy = Policy} =
|
||||
@@ -489,13 +490,16 @@ execute_command(AccessCommands, Auth, Name, Arguments) ->
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
|
||||
Auth = case is_admin(Name, Auth1) of
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
|
||||
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
|
||||
Auth = case is_admin(Name, Auth1, CallerInfo) of
|
||||
true -> admin;
|
||||
false -> Auth1
|
||||
end,
|
||||
Command = get_command_definition(Name, Version),
|
||||
AccessCommands = get_access_commands(AccessCommands1, Version),
|
||||
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
|
||||
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
|
||||
ok -> execute_command2(Auth, Command, Arguments)
|
||||
end.
|
||||
|
||||
@@ -573,9 +577,9 @@ get_tags_commands(Version) ->
|
||||
%% At least one AccessCommand must be satisfied.
|
||||
%% It may throw {error, Error} where:
|
||||
%% Error = account_unprivileged | invalid_account_data
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments, _CallerInfo) ->
|
||||
ok;
|
||||
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
|
||||
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerInfo) ->
|
||||
Command =
|
||||
case {Command1#ejabberd_commands.policy, Auth} of
|
||||
{user, {_, _, _, _}} ->
|
||||
@@ -590,7 +594,7 @@ 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,
|
||||
@@ -600,7 +604,7 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
|
||||
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,
|
||||
@@ -637,31 +641,38 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
|
||||
_ -> throw({error, invalid_account_data})
|
||||
end.
|
||||
|
||||
check_access(Command, ?POLICY_ACCESS, _)
|
||||
check_access(Command, ?POLICY_ACCESS, _, _)
|
||||
when Command#ejabberd_commands.policy == open ->
|
||||
true;
|
||||
check_access(_Command, _Access, admin) ->
|
||||
check_access(_Command, _Access, admin, _) ->
|
||||
true;
|
||||
check_access(_Command, _Access, {_User, _Server, _, true}) ->
|
||||
check_access(_Command, _Access, {_User, _Server, _, true}, _) ->
|
||||
false;
|
||||
check_access(Command, Access, Auth)
|
||||
check_access(Command, Access, Auth, CallerInfo)
|
||||
when Access =/= ?POLICY_ACCESS;
|
||||
Command#ejabberd_commands.policy == open;
|
||||
Command#ejabberd_commands.policy == user ->
|
||||
case check_auth(Command, Auth) of
|
||||
{ok, User, Server} ->
|
||||
check_access2(Access, User, Server);
|
||||
check_access2(Access, CallerInfo#{usr => jid:split(jid:make(User, Server, <<>>))}, Server);
|
||||
no_auth_provided ->
|
||||
case Command#ejabberd_commands.policy of
|
||||
user ->
|
||||
false;
|
||||
_ ->
|
||||
check_access2(Access, CallerInfo, global)
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
check_access(_Command, _Access, _Auth) ->
|
||||
check_access(_Command, _Access, _Auth, _CallerInfo) ->
|
||||
false.
|
||||
|
||||
check_access2(?POLICY_ACCESS, _User, _Server) ->
|
||||
check_access2(?POLICY_ACCESS, _CallerInfo, _Server) ->
|
||||
true;
|
||||
check_access2(Access, User, Server) ->
|
||||
check_access2(Access, AccessInfo, Server) ->
|
||||
%% Check this user has access permission
|
||||
case acl:match_rule(Server, Access, jid:make(User, Server, <<"">>)) of
|
||||
case acl:access_matches(Access, AccessInfo, Server) of
|
||||
allow -> true;
|
||||
deny -> false
|
||||
end.
|
||||
@@ -737,29 +748,32 @@ get_commands(Version) ->
|
||||
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].
|
||||
|
||||
+69
-23
@@ -30,7 +30,7 @@
|
||||
add_global_option/2, add_local_option/2,
|
||||
get_global_option/2, get_local_option/2,
|
||||
get_global_option/3, get_local_option/3,
|
||||
get_option/2, get_option/3, add_option/2,
|
||||
get_option/2, get_option/3, add_option/2, has_option/1,
|
||||
get_vh_by_auth_method/1, is_file_readable/1,
|
||||
get_version/0, get_myhosts/0, get_mylang/0,
|
||||
prepare_opt_val/4, convert_table_to_binary/5,
|
||||
@@ -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),
|
||||
@@ -745,22 +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', _} ->
|
||||
Call = case F of
|
||||
{Mod, Fun} ->
|
||||
fun() -> Mod:Fun(Val) end;
|
||||
_ ->
|
||||
fun() -> F(Val) end
|
||||
end,
|
||||
try Call() of
|
||||
Res ->
|
||||
Res
|
||||
catch {replace_with, NewRes} ->
|
||||
NewRes;
|
||||
{invalid_syntax, Error} ->
|
||||
?WARNING_MSG("incorrect value '~s' of option '~s', "
|
||||
"using '~s' as fallback: ~s",
|
||||
[format_term(Val),
|
||||
format_term(Opt),
|
||||
format_term(Default),
|
||||
Error]),
|
||||
Default;
|
||||
_:_ ->
|
||||
?WARNING_MSG("incorrect value '~s' of option '~s', "
|
||||
"using '~s' as fallback",
|
||||
[format_term(Val),
|
||||
format_term(Opt),
|
||||
format_term(Default)]),
|
||||
Default;
|
||||
_ ->
|
||||
Res
|
||||
Default
|
||||
end.
|
||||
|
||||
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
|
||||
@@ -813,6 +848,10 @@ get_option(Opt, F, Default) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec has_option(atom() | {atom(), global | binary()}) -> any().
|
||||
has_option(Opt) ->
|
||||
get_option(Opt, fun(_) -> true end, false).
|
||||
|
||||
init_module_db_table(Modules) ->
|
||||
catch ets:new(module_db, [named_table, public, bag]),
|
||||
%% Dirty hack for mod_pubsub
|
||||
@@ -879,19 +918,26 @@ get_modules_with_options() ->
|
||||
|
||||
validate_opts(#state{opts = Opts} = State) ->
|
||||
ModOpts = get_modules_with_options(),
|
||||
NewOpts = lists:filter(
|
||||
fun(#local_config{key = {Opt, _Host}, value = Val}) ->
|
||||
NewOpts = lists:filtermap(
|
||||
fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
|
||||
case dict:find(Opt, ModOpts) of
|
||||
{ok, [Mod|_]} ->
|
||||
VFun = Mod:opt_type(Opt),
|
||||
case catch VFun(Val) of
|
||||
{'EXIT', _} ->
|
||||
try VFun(Val) of
|
||||
_ ->
|
||||
true
|
||||
catch {replace_with, NewVal} ->
|
||||
{true, In#local_config{value = NewVal}};
|
||||
{invalid_syntax, Error} ->
|
||||
?ERROR_MSG("ignoring option '~s' with "
|
||||
"invalid value: ~p: ~s",
|
||||
[Opt, Val, Error]),
|
||||
false;
|
||||
_:_ ->
|
||||
?ERROR_MSG("ignoring option '~s' with "
|
||||
"invalid value: ~p",
|
||||
[Opt, Val]),
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
?ERROR_MSG("unknown option '~s' will be likely"
|
||||
|
||||
@@ -239,7 +239,7 @@ process2(["--auth", User, Server, Pass | Args], AccessCommands, Version) ->
|
||||
process2(Args, AccessCommands, {list_to_binary(User), list_to_binary(Server),
|
||||
list_to_binary(Pass), true}, Version);
|
||||
process2(Args, AccessCommands, Version) ->
|
||||
process2(Args, AccessCommands, admin, Version).
|
||||
process2(Args, AccessCommands, noauth, Version).
|
||||
|
||||
|
||||
|
||||
|
||||
+10
-1
@@ -271,7 +271,16 @@ do_route(From, To, Packet) ->
|
||||
#xmlel{name = Name} = Packet,
|
||||
case Name of
|
||||
<<"iq">> -> process_iq(From, To, Packet);
|
||||
<<"message">> -> ok;
|
||||
<<"message">> ->
|
||||
#xmlel{attrs = Attrs} = Packet,
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
<<"presence">> -> ok;
|
||||
_ -> ok
|
||||
end;
|
||||
|
||||
+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].
|
||||
|
||||
@@ -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].
|
||||
@@ -539,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
|
||||
@@ -738,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].
|
||||
|
||||
+68
-31
@@ -47,6 +47,8 @@
|
||||
set_presence/7,
|
||||
unset_presence/6,
|
||||
close_session_unset_presence/5,
|
||||
set_offline_info/5,
|
||||
get_offline_info/4,
|
||||
dirty_get_sessions_list/0,
|
||||
dirty_get_my_sessions_list/0,
|
||||
get_vh_session_list/1,
|
||||
@@ -178,14 +180,14 @@ get_user_resources(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
|
||||
|
||||
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
|
||||
|
||||
get_user_present_resources(LUser, LServer) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
[{S#session.priority, element(3, S#session.usr)}
|
||||
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
|
||||
|
||||
@@ -196,7 +198,7 @@ get_user_ip(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
undefined;
|
||||
Ss ->
|
||||
@@ -211,7 +213,7 @@ get_user_info(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
offline;
|
||||
Ss ->
|
||||
@@ -261,17 +263,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{}].
|
||||
@@ -279,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()).
|
||||
|
||||
@@ -288,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().
|
||||
@@ -303,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}.
|
||||
@@ -395,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) ->
|
||||
@@ -409,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 ->
|
||||
@@ -511,23 +547,23 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
case Name of
|
||||
<<"message">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"chat">> -> route_message(From, To, Packet, chat);
|
||||
<<"normal">> -> route_message(From, To, Packet, normal);
|
||||
<<"">> -> route_message(From, To, Packet, normal);
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> ok;
|
||||
_ ->
|
||||
<<"groupchat">> ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
route_message(From, To, Packet, normal)
|
||||
end;
|
||||
<<"iq">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
@@ -584,8 +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 ->
|
||||
@@ -602,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)
|
||||
@@ -649,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,
|
||||
@@ -666,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
|
||||
@@ -724,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,
|
||||
|
||||
+11
-47
@@ -21,48 +21,12 @@
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(PROCNAME, 'ejabberd_redis_client').
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec init() -> ok | {error, any()}.
|
||||
init() ->
|
||||
Server = ejabberd_config:get_option(redis_server,
|
||||
fun iolist_to_list/1,
|
||||
"localhost"),
|
||||
Port = ejabberd_config:get_option(redis_port,
|
||||
fun(P) when is_integer(P),
|
||||
P>0, P<65536 ->
|
||||
P
|
||||
end, 6379),
|
||||
DB = ejabberd_config:get_option(redis_db,
|
||||
fun(I) when is_integer(I), I >= 0 ->
|
||||
I
|
||||
end, 0),
|
||||
Pass = ejabberd_config:get_option(redis_password,
|
||||
fun iolist_to_list/1,
|
||||
""),
|
||||
ReconnTimeout = timer:seconds(
|
||||
ejabberd_config:get_option(
|
||||
redis_reconnect_timeout,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1)),
|
||||
ConnTimeout = timer:seconds(
|
||||
ejabberd_config:get_option(
|
||||
redis_connect_timeout,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1)),
|
||||
case eredis:start_link(Server, Port, DB, Pass,
|
||||
ReconnTimeout, ConnTimeout) of
|
||||
{ok, Client} ->
|
||||
register(?PROCNAME, Client),
|
||||
clean_table(),
|
||||
ok;
|
||||
{error, _} = Err ->
|
||||
?ERROR_MSG("failed to start redis client: ~p", [Err]),
|
||||
Err
|
||||
end.
|
||||
clean_table().
|
||||
|
||||
-spec set_session(#session{}) -> ok.
|
||||
set_session(Session) ->
|
||||
@@ -71,8 +35,8 @@ set_session(Session) ->
|
||||
SIDKey = sid_to_key(Session#session.sid),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, Session#session.sid),
|
||||
case eredis:qp(?PROCNAME, [["HSET", USKey, SIDKey, T],
|
||||
["HSET", ServKey, USSIDKey, T]]) of
|
||||
case ejabberd_redis:qp([["HSET", USKey, SIDKey, T],
|
||||
["HSET", ServKey, USSIDKey, T]]) of
|
||||
[{ok, _}, {ok, _}] ->
|
||||
ok;
|
||||
Err ->
|
||||
@@ -83,7 +47,7 @@ set_session(Session) ->
|
||||
{ok, #session{}} | {error, notfound}.
|
||||
delete_session(LUser, LServer, _LResource, SID) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
|
||||
case ejabberd_redis:q(["HGETALL", USKey]) of
|
||||
{ok, Vals} ->
|
||||
Ss = decode_session_list(Vals),
|
||||
case lists:keyfind(SID, #session.sid, Ss) of
|
||||
@@ -93,8 +57,8 @@ delete_session(LUser, LServer, _LResource, SID) ->
|
||||
SIDKey = sid_to_key(SID),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, SID),
|
||||
eredis:qp(?PROCNAME, [["HDEL", USKey, SIDKey],
|
||||
["HDEL", ServKey, USSIDKey]]),
|
||||
ejabberd_redis:qp([["HDEL", USKey, SIDKey],
|
||||
["HDEL", ServKey, USSIDKey]]),
|
||||
{ok, Session}
|
||||
end;
|
||||
Err ->
|
||||
@@ -112,7 +76,7 @@ get_sessions() ->
|
||||
-spec get_sessions(binary()) -> [#session{}].
|
||||
get_sessions(LServer) ->
|
||||
ServKey = server_to_key(LServer),
|
||||
case eredis:q(?PROCNAME, ["HGETALL", ServKey]) of
|
||||
case ejabberd_redis:q(["HGETALL", ServKey]) of
|
||||
{ok, Vals} ->
|
||||
decode_session_list(Vals);
|
||||
Err ->
|
||||
@@ -123,7 +87,7 @@ get_sessions(LServer) ->
|
||||
-spec get_sessions(binary(), binary()) -> [#session{}].
|
||||
get_sessions(LUser, LServer) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
|
||||
case ejabberd_redis:q(["HGETALL", USKey]) of
|
||||
{ok, Vals} when is_list(Vals) ->
|
||||
decode_session_list(Vals);
|
||||
Err ->
|
||||
@@ -135,7 +99,7 @@ get_sessions(LUser, LServer) ->
|
||||
[#session{}].
|
||||
get_sessions(LUser, LServer, LResource) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
|
||||
case ejabberd_redis:q(["HGETALL", USKey]) of
|
||||
{ok, Vals} when is_list(Vals) ->
|
||||
[S || S <- decode_session_list(Vals),
|
||||
element(3, S#session.usr) == LResource];
|
||||
@@ -172,7 +136,7 @@ clean_table() ->
|
||||
lists:foreach(
|
||||
fun(LServer) ->
|
||||
ServKey = server_to_key(LServer),
|
||||
case eredis:q(?PROCNAME, ["HKEYS", ServKey]) of
|
||||
case ejabberd_redis:q(["HKEYS", ServKey]) of
|
||||
{ok, []} ->
|
||||
ok;
|
||||
{ok, Vals} ->
|
||||
@@ -189,7 +153,7 @@ clean_table() ->
|
||||
SIDKey = sid_to_key(SID),
|
||||
["HDEL", USKey, SIDKey]
|
||||
end, Vals1),
|
||||
Res = eredis:qp(?PROCNAME, [Q1|Q2]),
|
||||
Res = ejabberd_redis:qp([Q1|Q2]),
|
||||
case lists:filter(
|
||||
fun({ok, _}) -> false;
|
||||
(_) -> true
|
||||
|
||||
+41
-39
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_sm_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_sm).
|
||||
|
||||
%% API
|
||||
@@ -23,18 +25,19 @@
|
||||
-include("ejabberd_sm.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec init() -> ok | {error, any()}.
|
||||
init() ->
|
||||
Node = ejabberd_sql:escape(jlib:atom_to_binary(node())),
|
||||
Node = jlib:atom_to_binary(node()),
|
||||
?INFO_MSG("Cleaning SQL SM table...", []),
|
||||
lists:foldl(
|
||||
fun(Host, ok) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of
|
||||
Host, ?SQL("delete from sm where node=%(Node)s")) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
@@ -47,20 +50,19 @@ init() ->
|
||||
|
||||
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
|
||||
priority = Priority, info = Info}) ->
|
||||
Username = ejabberd_sql:escape(U),
|
||||
Resource = ejabberd_sql:escape(R),
|
||||
InfoS = ejabberd_sql:encode_term(Info),
|
||||
InfoS = jlib:term_to_expr(Info),
|
||||
PrioS = enc_priority(Priority),
|
||||
TS = now_to_timestamp(Now),
|
||||
PidS = list_to_binary(erlang:pid_to_list(Pid)),
|
||||
Node = ejabberd_sql:escape(jlib:atom_to_binary(node(Pid))),
|
||||
case sql_queries:update(
|
||||
LServer,
|
||||
<<"sm">>,
|
||||
[<<"usec">>, <<"pid">>, <<"node">>, <<"username">>,
|
||||
<<"resource">>, <<"priority">>, <<"info">>],
|
||||
[TS, PidS, Node, Username, Resource, PrioS, InfoS],
|
||||
[<<"usec='">>, TS, <<"' and pid='">>, PidS, <<"'">>]) of
|
||||
Node = jlib:atom_to_binary(node(Pid)),
|
||||
case ?SQL_UPSERT(LServer, "sm",
|
||||
["!usec=%(TS)d",
|
||||
"!pid=%(PidS)s",
|
||||
"node=%(Node)s",
|
||||
"username=%(U)s",
|
||||
"resource=%(R)s",
|
||||
"priority=%(PrioS)s",
|
||||
"info=%(InfoS)s"]) of
|
||||
ok ->
|
||||
ok;
|
||||
Err ->
|
||||
@@ -72,14 +74,16 @@ delete_session(_LUser, LServer, _LResource, {Now, Pid}) ->
|
||||
PidS = list_to_binary(erlang:pid_to_list(Pid)),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select usec, pid, username, resource, priority, info ">>,
|
||||
<<"from sm where usec='">>, TS, <<"' and pid='">>,PidS, <<"'">>]) of
|
||||
{selected, _, [Row]} ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer, [<<"delete from sm where usec='">>,
|
||||
TS, <<"' and pid='">>, PidS, <<"'">>]),
|
||||
?SQL("select @(usec)d, @(pid)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s "
|
||||
"from sm where usec=%(TS)d and pid=%(PidS)s")) of
|
||||
{selected, [Row]} ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from sm"
|
||||
" where usec=%(TS)d and pid=%(PidS)s")),
|
||||
{ok, row_to_session(LServer, Row)};
|
||||
{selected, _, []} ->
|
||||
{selected, []} ->
|
||||
{error, notfound};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to delete from 'sm' table: ~p", [Err]),
|
||||
@@ -94,9 +98,10 @@ get_sessions() ->
|
||||
|
||||
get_sessions(LServer) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm">>]) of
|
||||
{selected, _, Rows} ->
|
||||
LServer,
|
||||
?SQL("select @(usec)d, @(pid)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s from sm")) of
|
||||
{selected, Rows} ->
|
||||
[row_to_session(LServer, Row) || Row <- Rows];
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
|
||||
@@ -104,12 +109,12 @@ get_sessions(LServer) ->
|
||||
end.
|
||||
|
||||
get_sessions(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm where ">>,
|
||||
<<"username='">>, Username, <<"'">>]) of
|
||||
{selected, _, Rows} ->
|
||||
LServer,
|
||||
?SQL("select @(usec)d, @(pid)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s from sm"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, Rows} ->
|
||||
[row_to_session(LServer, Row) || Row <- Rows];
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
|
||||
@@ -117,14 +122,12 @@ get_sessions(LUser, LServer) ->
|
||||
end.
|
||||
|
||||
get_sessions(LUser, LServer, LResource) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Resource = ejabberd_sql:escape(LResource),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer, [<<"select usec, pid, username, ">>,
|
||||
<<"resource, priority, info from sm where ">>,
|
||||
<<"username='">>, Username, <<"' and resource='">>,
|
||||
Resource, <<"'">>]) of
|
||||
{selected, _, Rows} ->
|
||||
LServer,
|
||||
?SQL("select @(usec)d, @(pid)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s from sm"
|
||||
" where username=%(LUser)s and resource=%(LResource)s")) of
|
||||
{selected, Rows} ->
|
||||
[row_to_session(LServer, Row) || Row <- Rows];
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
|
||||
@@ -135,10 +138,9 @@ get_sessions(LUser, LServer, LResource) ->
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
now_to_timestamp({MSec, Sec, USec}) ->
|
||||
jlib:integer_to_binary((MSec * 1000000 + Sec) * 1000000 + USec).
|
||||
(MSec * 1000000 + Sec) * 1000000 + USec.
|
||||
|
||||
timestamp_to_now(TS) ->
|
||||
I = jlib:binary_to_integer(TS),
|
||||
timestamp_to_now(I) ->
|
||||
Head = I div 1000000,
|
||||
USec = I rem 1000000,
|
||||
MSec = Head div 1000000,
|
||||
@@ -158,7 +160,7 @@ enc_priority(undefined) ->
|
||||
enc_priority(Int) when is_integer(Int) ->
|
||||
jlib:integer_to_binary(Int).
|
||||
|
||||
row_to_session(LServer, [USec, PidS, User, Resource, PrioS, InfoS]) ->
|
||||
row_to_session(LServer, {USec, PidS, User, Resource, PrioS, InfoS}) ->
|
||||
Now = timestamp_to_now(USec),
|
||||
Pid = erlang:list_to_pid(binary_to_list(PidS)),
|
||||
Priority = dec_priority(PrioS),
|
||||
|
||||
+46
-4
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_odbc.erl
|
||||
%%% File : ejabberd_sql.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Serve ODBC connection
|
||||
%%% Purpose : Serve SQL connection
|
||||
%%% Created : 8 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@@ -39,9 +39,12 @@
|
||||
sql_query_t/1,
|
||||
sql_transaction/2,
|
||||
sql_bloc/2,
|
||||
sql_query_to_iolist/1,
|
||||
escape/1,
|
||||
standard_escape/1,
|
||||
escape_like/1,
|
||||
escape_like_arg/1,
|
||||
escape_like_arg_circumflex/1,
|
||||
to_bool/1,
|
||||
sqlite_db/1,
|
||||
sqlite_file/1,
|
||||
@@ -199,6 +202,7 @@ escape_like(S) when is_binary(S) ->
|
||||
<< <<(escape_like(C))/binary>> || <<C>> <= S >>;
|
||||
escape_like($%) -> <<"\\%">>;
|
||||
escape_like($_) -> <<"\\_">>;
|
||||
escape_like($\\) -> <<"\\\\\\\\">>;
|
||||
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C).
|
||||
|
||||
escape_like_arg(S) when is_binary(S) ->
|
||||
@@ -208,6 +212,15 @@ escape_like_arg($_) -> <<"\\_">>;
|
||||
escape_like_arg($\\) -> <<"\\\\">>;
|
||||
escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
|
||||
|
||||
escape_like_arg_circumflex(S) when is_binary(S) ->
|
||||
<< <<(escape_like_arg_circumflex(C))/binary>> || <<C>> <= S >>;
|
||||
escape_like_arg_circumflex($%) -> <<"^%">>;
|
||||
escape_like_arg_circumflex($_) -> <<"^_">>;
|
||||
escape_like_arg_circumflex($^) -> <<"^^">>;
|
||||
escape_like_arg_circumflex($[) -> <<"^[">>; % For MSSQL
|
||||
escape_like_arg_circumflex($]) -> <<"^]">>;
|
||||
escape_like_arg_circumflex(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
|
||||
|
||||
to_bool(<<"t">>) -> true;
|
||||
to_bool(<<"true">>) -> true;
|
||||
to_bool(<<"1">>) -> true;
|
||||
@@ -507,7 +520,7 @@ sql_query_internal(#sql_query{} = Query) ->
|
||||
odbc ->
|
||||
generic_sql_query(Query);
|
||||
mssql ->
|
||||
generic_sql_query(Query);
|
||||
mssql_sql_query(Query);
|
||||
pgsql ->
|
||||
Key = {?PREPARE_KEY, Query#sql_query.hash},
|
||||
case get(Key) of
|
||||
@@ -533,7 +546,7 @@ sql_query_internal(#sql_query{} = Query) ->
|
||||
mysql ->
|
||||
generic_sql_query(Query);
|
||||
sqlite ->
|
||||
generic_sql_query(Query)
|
||||
sqlite_sql_query(Query)
|
||||
end
|
||||
catch
|
||||
Class:Reason ->
|
||||
@@ -622,6 +635,32 @@ generic_escape() ->
|
||||
end
|
||||
}.
|
||||
|
||||
sqlite_sql_query(SQLQuery) ->
|
||||
sql_query_format_res(
|
||||
sql_query_internal(sqlite_sql_query_format(SQLQuery)),
|
||||
SQLQuery).
|
||||
|
||||
sqlite_sql_query_format(SQLQuery) ->
|
||||
Args = (SQLQuery#sql_query.args)(sqlite_escape()),
|
||||
(SQLQuery#sql_query.format_query)(Args).
|
||||
|
||||
sqlite_escape() ->
|
||||
#sql_escape{string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end,
|
||||
integer = fun(X) -> integer_to_binary(X) end,
|
||||
boolean = fun(true) -> <<"1">>;
|
||||
(false) -> <<"0">>
|
||||
end
|
||||
}.
|
||||
|
||||
standard_escape(S) ->
|
||||
<< <<(case Char of
|
||||
$' -> << "''" >>;
|
||||
_ -> << Char >>
|
||||
end)/binary>> || <<Char>> <= S >>.
|
||||
|
||||
mssql_sql_query(SQLQuery) ->
|
||||
sqlite_sql_query(SQLQuery).
|
||||
|
||||
pgsql_prepare(SQLQuery, State) ->
|
||||
Escape = #sql_escape{_ = fun(X) -> X end},
|
||||
N = length((SQLQuery#sql_query.args)(Escape)),
|
||||
@@ -668,6 +707,9 @@ sql_query_format_res({selected, _, Rows}, SQLQuery) ->
|
||||
sql_query_format_res(Res, _SQLQuery) ->
|
||||
Res.
|
||||
|
||||
sql_query_to_iolist(SQLQuery) ->
|
||||
generic_sql_query_format(SQLQuery).
|
||||
|
||||
%% Generate the OTP callback return tuple depending on the driver result.
|
||||
abort_on_driver_error({error, <<"query timed out">>} =
|
||||
Reply,
|
||||
|
||||
+15
-9
@@ -305,20 +305,24 @@ parse_upsert(Fields) ->
|
||||
"a constant string"})
|
||||
end
|
||||
end, {[], 0}, Fields),
|
||||
%io:format("asd ~p~n", [{Fields, Fs}]),
|
||||
%io:format("upsert ~p~n", [{Fields, Fs}]),
|
||||
Fs.
|
||||
|
||||
%% key | {Update}
|
||||
parse_upsert_field([$! | S], ParamPos, Loc) ->
|
||||
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
|
||||
{Name, true, ParseState};
|
||||
{Name, key, ParseState};
|
||||
parse_upsert_field([$- | S], ParamPos, Loc) ->
|
||||
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
|
||||
{Name, {false}, ParseState};
|
||||
parse_upsert_field(S, ParamPos, Loc) ->
|
||||
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
|
||||
{Name, false, ParseState}.
|
||||
{Name, {true}, ParseState}.
|
||||
|
||||
parse_upsert_field1([], _Acc, _ParamPos, Loc) ->
|
||||
throw({error, Loc,
|
||||
"?SQL_UPSERT fields must have the "
|
||||
"following form: \"[!]name=value\""});
|
||||
"following form: \"[!-]name=value\""});
|
||||
parse_upsert_field1([$= | S], Acc, ParamPos, Loc) ->
|
||||
{lists:reverse(Acc), parse(S, ParamPos, Loc)};
|
||||
parse_upsert_field1([C | S], Acc, ParamPos, Loc) ->
|
||||
@@ -376,9 +380,9 @@ make_sql_upsert_generic(Table, ParseRes) ->
|
||||
make_sql_upsert_update(Table, ParseRes) ->
|
||||
WPairs =
|
||||
lists:flatmap(
|
||||
fun({_Field, false, _ST}) ->
|
||||
fun({_Field, {_}, _ST}) ->
|
||||
[];
|
||||
({Field, true, ST}) ->
|
||||
({Field, key, ST}) ->
|
||||
[ST#state{
|
||||
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
|
||||
}]
|
||||
@@ -386,9 +390,11 @@ make_sql_upsert_update(Table, ParseRes) ->
|
||||
Where = join_states(WPairs, " AND "),
|
||||
SPairs =
|
||||
lists:flatmap(
|
||||
fun({_Field, true, _ST}) ->
|
||||
fun({_Field, key, _ST}) ->
|
||||
[];
|
||||
({Field, false, ST}) ->
|
||||
({_Field, {false}, _ST}) ->
|
||||
[];
|
||||
({Field, {true}, ST}) ->
|
||||
[ST#state{
|
||||
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
|
||||
}]
|
||||
@@ -462,7 +468,7 @@ check_upsert(ParseRes, Pos) ->
|
||||
Set =
|
||||
lists:filter(
|
||||
fun({_Field, Match, _ST}) ->
|
||||
not Match
|
||||
Match /= key
|
||||
end, ParseRes),
|
||||
case Set of
|
||||
[] ->
|
||||
|
||||
@@ -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]);
|
||||
|
||||
+17
-2
@@ -28,6 +28,7 @@
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-export([export/2, export/3, import_file/2, import/2,
|
||||
import/3, delete/1]).
|
||||
@@ -76,7 +77,12 @@ export(Server, Output, Module) ->
|
||||
IO = prepare_output(Output),
|
||||
lists:foreach(
|
||||
fun({Table, ConvertFun}) ->
|
||||
export(LServer, Table, IO, ConvertFun)
|
||||
case export(LServer, Table, IO, ConvertFun) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Failed export for module ~p: ~p",
|
||||
[Module, Reason])
|
||||
end
|
||||
end, Module:export(Server)),
|
||||
close_output(Output, IO).
|
||||
|
||||
@@ -150,7 +156,8 @@ export(LServer, Table, IO, ConvertFun) ->
|
||||
case ConvertFun(LServer, R) of
|
||||
[] ->
|
||||
Acc;
|
||||
SQL ->
|
||||
SQL1 ->
|
||||
SQL = format_queries(SQL1),
|
||||
if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 ->
|
||||
{N + 1, [SQL | SQLs]};
|
||||
true ->
|
||||
@@ -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}} ->
|
||||
|
||||
+16
-8
@@ -52,6 +52,7 @@
|
||||
|
||||
-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], <<", ">>),
|
||||
@@ -301,7 +309,7 @@ validate_opts(Module, Opts) ->
|
||||
db_type(Opts, Module) when is_list(Opts) ->
|
||||
db_type(global, Opts, Module);
|
||||
db_type(Host, Module) when is_atom(Module) ->
|
||||
case Module:mod_opt_type(db_type) of
|
||||
case catch Module:mod_opt_type(db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_module_opt(Host, Module, db_type, F) of
|
||||
undefined -> ejabberd_config:default_db(Host, Module);
|
||||
@@ -314,7 +322,7 @@ db_type(Host, Module) when is_atom(Module) ->
|
||||
-spec db_type(binary(), opts(), module()) -> db_type().
|
||||
|
||||
db_type(Host, Opts, Module) ->
|
||||
case Module:mod_opt_type(db_type) of
|
||||
case catch Module:mod_opt_type(db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_opt(db_type, Opts, F) of
|
||||
undefined -> ejabberd_config:default_db(Host, Module);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
-type(pubsubState() :: mod_pubsub:pubsubState()).
|
||||
-type(pubsubItem() :: mod_pubsub:pubsubItem()).
|
||||
-type(subOptions() :: mod_pubsub:subOptions()).
|
||||
-type(pubOptions() :: mod_pubsub:pubOptions()).
|
||||
-type(affiliation() :: mod_pubsub:affiliation()).
|
||||
-type(subscription() :: mod_pubsub:subscription()).
|
||||
-type(subId() :: mod_pubsub:subId()).
|
||||
@@ -109,7 +110,8 @@
|
||||
PublishModel :: publishModel(),
|
||||
Max_Items :: non_neg_integer(),
|
||||
ItemId :: <<>> | itemId(),
|
||||
Payload :: payload()) ->
|
||||
Payload :: payload(),
|
||||
Options :: pubOptions()) ->
|
||||
{result, {default, broadcast, [itemId()]}} |
|
||||
{error, xmlel()}.
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+67
-8
@@ -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,14 +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">>, children = Els}) ->
|
||||
Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els,
|
||||
fxml:get_attr_s(<<"xmlns">>, Attrs) /= ?NS_CHATSTATES,
|
||||
Name /= <<"thread">>],
|
||||
Stripped == [];
|
||||
is_standalone_chat_state(_El) -> false.
|
||||
is_standalone_chat_state(Stanza) ->
|
||||
case unwrap_carbon(Stanza) of
|
||||
#xmlel{name = <<"message">>, children = Els} ->
|
||||
IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY],
|
||||
Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els,
|
||||
not lists:member(fxml:get_attr_s(<<"xmlns">>,
|
||||
Attrs),
|
||||
IgnoreNS),
|
||||
Name /= <<"thread">>],
|
||||
Stripped == [];
|
||||
#xmlel{} ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp())
|
||||
-> xmlel().
|
||||
@@ -890,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).
|
||||
|
||||
+28
-15
@@ -863,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()}),
|
||||
@@ -891,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) ->
|
||||
@@ -1154,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) ->
|
||||
|
||||
@@ -903,7 +903,7 @@ send_announcement_to_all(Host, SubjectS, BodyS) ->
|
||||
|
||||
get_access(Host) ->
|
||||
gen_mod:get_module_opt(Host, ?MODULE, access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
none).
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
@@ -920,6 +920,6 @@ import(LServer, DBType, LA) ->
|
||||
Mod:import(LServer, LA).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) -> [access, db_type].
|
||||
|
||||
+30
-35
@@ -9,6 +9,8 @@
|
||||
-module(mod_announce_sql).
|
||||
-behaviour(mod_announce).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
%% API
|
||||
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
|
||||
get_motd/1, is_motd_user/2, set_motd_user/2, import/1,
|
||||
@@ -16,6 +18,7 @@
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_announce.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -27,37 +30,35 @@ set_motd_users(LServer, USRs) ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, _S, _R}) ->
|
||||
Username = ejabberd_sql:escape(U),
|
||||
sql_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[Username, <<"">>],
|
||||
[<<"username='">>, Username, <<"'">>])
|
||||
?SQL_UPSERT_T(
|
||||
"motd",
|
||||
["!username=%(U)s",
|
||||
"xml=''"])
|
||||
end, USRs)
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
set_motd(LServer, Packet) ->
|
||||
XML = ejabberd_sql:escape(fxml:element_to_binary(Packet)),
|
||||
XML = fxml:element_to_binary(Packet),
|
||||
F = fun() ->
|
||||
sql_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[<<"">>, XML],
|
||||
[<<"username=''">>])
|
||||
?SQL_UPSERT_T(
|
||||
"motd",
|
||||
["!username=''",
|
||||
"xml=%(XML)s"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
delete_motd(LServer) ->
|
||||
F = fun() ->
|
||||
ejabberd_sql:sql_query_t([<<"delete from motd;">>])
|
||||
ejabberd_sql:sql_query_t(?SQL("delete from motd"))
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
get_motd(LServer) ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer, [<<"select xml from motd where username='';">>]) of
|
||||
{selected, [<<"xml">>], [[XML]]} ->
|
||||
LServer,
|
||||
?SQL("select @(xml)s from motd where username=''")) of
|
||||
{selected, [{XML}]} ->
|
||||
case fxml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
error;
|
||||
@@ -69,46 +70,40 @@ get_motd(LServer) ->
|
||||
end.
|
||||
|
||||
is_motd_user(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select username from motd "
|
||||
"where username='">>, Username, <<"';">>]) of
|
||||
{selected, [<<"username">>], [_|_]} ->
|
||||
LServer,
|
||||
?SQL("select @(username)s from motd"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, [_|_]} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
set_motd_user(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
F = fun() ->
|
||||
sql_queries:update_t(
|
||||
<<"motd">>,
|
||||
[<<"username">>, <<"xml">>],
|
||||
[Username, <<"">>],
|
||||
[<<"username='">>, Username, <<"'">>])
|
||||
end,
|
||||
?SQL_UPSERT_T(
|
||||
"motd",
|
||||
["!username=%(LUser)s",
|
||||
"xml=''"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
export(_Server) ->
|
||||
[{motd,
|
||||
fun(Host, #motd{server = LServer, packet = El})
|
||||
when LServer == Host ->
|
||||
[[<<"delete from motd where username='';">>],
|
||||
[<<"insert into motd(username, xml) values ('', '">>,
|
||||
ejabberd_sql:escape(fxml:element_to_binary(El)),
|
||||
<<"');">>]];
|
||||
XML = fxml:element_to_binary(El),
|
||||
[?SQL("delete from motd where username='';"),
|
||||
?SQL("insert into motd(username, xml) values ('', %(XML)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
{motd_users,
|
||||
fun(Host, #motd_users{us = {LUser, LServer}})
|
||||
when LServer == Host, LUser /= <<"">> ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
[[<<"delete from motd where username='">>, Username, <<"';">>],
|
||||
[<<"insert into motd(username, xml) values ('">>,
|
||||
Username, <<"', '');">>]];
|
||||
[?SQL("delete from motd where username=%(LUser)s;"),
|
||||
?SQL("insert into motd(username, xml) values (%(LUser)s, '');")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
@@ -25,7 +25,12 @@ process_blocklist_block(LUser, LServer, Filter) ->
|
||||
Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, []} ->
|
||||
Name = <<"Blocked contacts">>,
|
||||
mod_privacy_sql:sql_add_privacy_list(LUser, Name),
|
||||
case mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Name) of
|
||||
{selected, []} ->
|
||||
mod_privacy_sql:sql_add_privacy_list(LUser, Name);
|
||||
{selected, [{_ID}]} ->
|
||||
ok
|
||||
end,
|
||||
mod_privacy_sql:sql_set_default_privacy_list(LUser, Name),
|
||||
Name;
|
||||
{selected, [{Name}]} -> Name
|
||||
|
||||
+19
-21
@@ -9,10 +9,13 @@
|
||||
-module(mod_caps_sql).
|
||||
-behaviour(mod_caps).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
%% API
|
||||
-export([init/2, caps_read/2, caps_write/3, export/1]).
|
||||
|
||||
-include("mod_caps.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -21,21 +24,19 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
caps_read(LServer, {Node, SubNode}) ->
|
||||
SNode = ejabberd_sql:escape(Node),
|
||||
SSubNode = ejabberd_sql:escape(SubNode),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer, [<<"select feature from caps_features where ">>,
|
||||
<<"node='">>, SNode, <<"' and subnode='">>,
|
||||
SSubNode, <<"';">>]) of
|
||||
{selected, [<<"feature">>], [[H]|_] = Fs} ->
|
||||
case catch jlib:binary_to_integer(H) of
|
||||
Int when is_integer(Int), Int>=0 ->
|
||||
{ok, Int};
|
||||
_ ->
|
||||
{ok, lists:flatten(Fs)}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
LServer,
|
||||
?SQL("select @(feature)s from caps_features where"
|
||||
" node=%(Node)s and subnode=%(SubNode)s")) of
|
||||
{selected, [{H}|_] = Fs} ->
|
||||
case catch jlib:binary_to_integer(H) of
|
||||
Int when is_integer(Int), Int>=0 ->
|
||||
{ok, Int};
|
||||
_ ->
|
||||
{ok, [F || {F} <- Fs]}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
caps_write(LServer, NodePair, Features) ->
|
||||
@@ -56,16 +57,13 @@ export(_Server) ->
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
sql_write_features_t({Node, SubNode}, Features) ->
|
||||
SNode = ejabberd_sql:escape(Node),
|
||||
SSubNode = ejabberd_sql:escape(SubNode),
|
||||
NewFeatures = if is_integer(Features) ->
|
||||
[jlib:integer_to_binary(Features)];
|
||||
true ->
|
||||
Features
|
||||
end,
|
||||
[[<<"delete from caps_features where node='">>,
|
||||
SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
|
||||
[[<<"insert into caps_features(node, subnode, feature) ">>,
|
||||
<<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
|
||||
ejabberd_sql:escape(F), <<"');">>] || F <- NewFeatures]].
|
||||
[?SQL("delete from caps_features where node=%(Node)s"
|
||||
" and subnode=%(SubNode)s;") |
|
||||
[?SQL("insert into caps_features(node, subnode, feature)"
|
||||
" values (%(Node)s, %(SubNode)s, %(F)s);") || F <- NewFeatures]].
|
||||
|
||||
|
||||
+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.
|
||||
|
||||
@@ -1917,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, []};
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
+17
-17
@@ -157,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;
|
||||
@@ -173,7 +175,7 @@ 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
|
||||
@@ -188,9 +190,8 @@ check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
|
||||
true -> {allowed, Call, admin};
|
||||
_ -> unauthorized_response()
|
||||
end;
|
||||
E ->
|
||||
?DEBUG("Unauthorized: ~p", [E]),
|
||||
unauthorized_response()
|
||||
_E ->
|
||||
{allowed, Call, noauth}
|
||||
end;
|
||||
check_permissions2(_Request, _Call, _Policy) ->
|
||||
unauthorized_response().
|
||||
@@ -209,7 +210,7 @@ oauth_check_token(Scope, Token) ->
|
||||
process(_, #request{method = 'POST', data = <<>>}) ->
|
||||
?DEBUG("Bad Request: no data", []),
|
||||
badrequest_response(<<"Missing POST data">>);
|
||||
process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
|
||||
process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} = Req) ->
|
||||
Version = get_api_version(Req),
|
||||
try
|
||||
Args = case jiffy:decode(Data) of
|
||||
@@ -217,10 +218,10 @@ process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
|
||||
{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, Version),
|
||||
{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 ->
|
||||
@@ -243,7 +244,7 @@ process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
|
||||
log(Call, Args, IP),
|
||||
case check_permissions(Req, Call) of
|
||||
{allowed, Cmd, Auth} ->
|
||||
{Code, Result} = handle(Cmd, Auth, Args, Version),
|
||||
{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 ->
|
||||
@@ -279,7 +280,7 @@ get_api_version([]) ->
|
||||
%% ----------------
|
||||
|
||||
% generic ejabberd command handler
|
||||
handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
|
||||
case ejabberd_commands:get_command_format(Call, Auth, Version) of
|
||||
{ArgsSpec, _} when is_list(ArgsSpec) ->
|
||||
Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
|
||||
@@ -296,7 +297,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
[{Key, undefined}|Acc]
|
||||
end, [], ArgsSpec),
|
||||
try
|
||||
handle2(Call, Auth, match(Args2, Spec), Version)
|
||||
handle2(Call, Auth, match(Args2, Spec), Version, IP)
|
||||
catch throw:not_found ->
|
||||
{404, <<"not_found">>};
|
||||
throw:{not_found, Why} when is_atom(Why) ->
|
||||
@@ -333,10 +334,10 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
{400, <<"Error">>}
|
||||
end.
|
||||
|
||||
handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
handle2(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
|
||||
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
|
||||
ArgsFormatted = format_args(Args, ArgsF),
|
||||
ejabberd_command(Auth, Call, ArgsFormatted, Version).
|
||||
ejabberd_command(Auth, Call, ArgsFormatted, Version, IP).
|
||||
|
||||
get_elem_delete(A, L) ->
|
||||
case proplists:get_all_values(A, L) of
|
||||
@@ -416,12 +417,12 @@ process_unicode_codepoints(Str) ->
|
||||
match(Args, Spec) ->
|
||||
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
|
||||
|
||||
ejabberd_command(Auth, Cmd, Args, Version) ->
|
||||
ejabberd_command(Auth, Cmd, Args, Version, IP) ->
|
||||
Access = case Auth of
|
||||
admin -> [];
|
||||
_ -> undefined
|
||||
end,
|
||||
case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version) of
|
||||
case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version, #{ip => IP}) of
|
||||
{error, Error} ->
|
||||
throw(Error);
|
||||
Res ->
|
||||
@@ -503,6 +504,5 @@ log(Call, Args, {Addr, 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].
|
||||
|
||||
+20
-19
@@ -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};
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
+2
-2
@@ -117,7 +117,7 @@ init([Host, Opts]) ->
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
all),
|
||||
catch ets:new(irc_connection,
|
||||
[named_table, public,
|
||||
@@ -1252,7 +1252,7 @@ import(LServer, DBType, Data) ->
|
||||
Mod:import(LServer, Data).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(default_encoding) ->
|
||||
fun iolist_to_binary/1;
|
||||
|
||||
+24
-31
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_irc_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_irc).
|
||||
|
||||
%% API
|
||||
@@ -15,6 +17,7 @@
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_irc.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -23,31 +26,26 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
get_data(LServer, Host, From) ->
|
||||
LJID = jid:tolower(jid:remove_resource(From)),
|
||||
SJID = ejabberd_sql:escape(jid:to_string(LJID)),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select data from irc_custom where jid='">>,
|
||||
SJID, <<"' and host='">>, SHost,
|
||||
<<"';">>]) of
|
||||
{selected, [<<"data">>], [[SData]]} ->
|
||||
mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData));
|
||||
{'EXIT', _} -> error;
|
||||
{selected, _, _} -> empty
|
||||
LServer,
|
||||
?SQL("select @(data)s from irc_custom"
|
||||
" where jid=%(SJID)s and host=%(Host)s")) of
|
||||
{selected, [{SData}]} ->
|
||||
mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData));
|
||||
{'EXIT', _} -> error;
|
||||
{selected, _} -> empty
|
||||
end.
|
||||
|
||||
set_data(LServer, Host, From, Data) ->
|
||||
LJID = jid:tolower(jid:remove_resource(From)),
|
||||
SJID = ejabberd_sql:escape(jid:to_string(LJID)),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
SData = ejabberd_sql:encode_term(Data),
|
||||
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
|
||||
SData = jlib:term_to_expr(Data),
|
||||
F = fun () ->
|
||||
sql_queries:update_t(<<"irc_custom">>,
|
||||
[<<"jid">>, <<"host">>, <<"data">>],
|
||||
[SJID, SHost, SData],
|
||||
[<<"jid='">>, SJID, <<"' and host='">>,
|
||||
SHost, <<"'">>]),
|
||||
?SQL_UPSERT_T(
|
||||
"irc_custom",
|
||||
["!jid=%(SJID)s",
|
||||
"!host=%(Host)s",
|
||||
"data=%(SData)s"]),
|
||||
ok
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
@@ -58,17 +56,12 @@ export(_Server) ->
|
||||
data = Data}) ->
|
||||
case str:suffix(Host, IRCHost) of
|
||||
true ->
|
||||
SJID = ejabberd_sql:escape(
|
||||
jid:to_string(
|
||||
jid:make(U, S, <<"">>))),
|
||||
SIRCHost = ejabberd_sql:escape(IRCHost),
|
||||
SData = ejabberd_sql:encode_term(Data),
|
||||
[[<<"delete from irc_custom where jid='">>, SJID,
|
||||
<<"' and host='">>, SIRCHost, <<"';">>],
|
||||
[<<"insert into irc_custom(jid, host, "
|
||||
"data) values ('">>,
|
||||
SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
|
||||
<<"');">>]];
|
||||
SJID = jid:to_string(jid:make(U, S, <<"">>)),
|
||||
SData = jlib:term_to_expr(Data),
|
||||
[?SQL("delete from irc_custom"
|
||||
" where jid=%(SJID)s and host=%(IRCHost)s;"),
|
||||
?SQL("insert into irc_custom(jid, host, data)"
|
||||
" values (%(SJID)s, %(IRCHost)s, %(SData)s);")];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
|
||||
+12
-1
@@ -37,7 +37,7 @@
|
||||
process_sm_iq/3, on_presence_update/4, import/1,
|
||||
import/3, store_last_info/4, get_last_info/2,
|
||||
remove_user/2, transform_options/1, mod_opt_type/1,
|
||||
opt_type/1]).
|
||||
opt_type/1, register_user/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -64,12 +64,16 @@ start(Host, Opts) ->
|
||||
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_LAST, ?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(register_user, Host, ?MODULE,
|
||||
register_user, 50),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
|
||||
on_presence_update, 50).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(register_user, Host, ?MODULE,
|
||||
register_user, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:delete(unset_presence_hook, Host,
|
||||
@@ -198,6 +202,13 @@ get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) ->
|
||||
children = []}]}
|
||||
end.
|
||||
|
||||
register_user(User, Server) ->
|
||||
on_presence_update(
|
||||
User,
|
||||
Server,
|
||||
<<"RegisterResource">>,
|
||||
<<"Registered but didn't login">>).
|
||||
|
||||
on_presence_update(User, Server, _Resource, Status) ->
|
||||
TimeStamp = p1_time_compat:system_time(seconds),
|
||||
store_last_info(User, Server, TimeStamp, Status).
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
-module(mod_last_sql).
|
||||
-behaviour(mod_last).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
%% API
|
||||
-export([init/2, get_last/2, store_last_info/4, remove_user/2,
|
||||
import/1, import/2, export/1]).
|
||||
|
||||
-include("mod_last.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -48,15 +51,9 @@ export(_Server) ->
|
||||
fun(Host, #last_activity{us = {LUser, LServer},
|
||||
timestamp = TimeStamp, status = Status})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Seconds =
|
||||
ejabberd_sql:escape(jlib:integer_to_binary(TimeStamp)),
|
||||
State = ejabberd_sql:escape(Status),
|
||||
[[<<"delete from last where username='">>, Username, <<"';">>],
|
||||
[<<"insert into last(username, seconds, "
|
||||
"state) values ('">>,
|
||||
Username, <<"', '">>, Seconds, <<"', '">>, State,
|
||||
<<"');">>]];
|
||||
[?SQL("delete from last where username=%(LUser)s;"),
|
||||
?SQL("insert into last(username, seconds, state)"
|
||||
" values (%(LUser)s, %(TimeStamp)d, %(Status)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
+39
-12
@@ -25,7 +25,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_mam).
|
||||
|
||||
-protocol({xep, 313, '0.4'}).
|
||||
-protocol({xep, 313, '0.5.1'}).
|
||||
-protocol({xep, 334, '0.2'}).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
@@ -33,7 +33,7 @@
|
||||
%% API
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
-export([user_send_packet/4, user_receive_packet/5,
|
||||
-export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
|
||||
process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
|
||||
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
|
||||
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
|
||||
@@ -89,9 +89,11 @@ start(Host, Opts) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
|
||||
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
|
||||
user_receive_packet, 500),
|
||||
user_receive_packet, 88),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 500),
|
||||
user_send_packet, 88),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet_strip_tag, 500),
|
||||
ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
|
||||
muc_filter_message, 50),
|
||||
ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
|
||||
@@ -128,9 +130,11 @@ init_cache(Opts) ->
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 500),
|
||||
user_send_packet, 88),
|
||||
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
|
||||
user_receive_packet, 500),
|
||||
user_receive_packet, 88),
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet_strip_tag, 500),
|
||||
ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE,
|
||||
muc_filter_message, 50),
|
||||
ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
|
||||
@@ -205,13 +209,30 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
|
||||
case should_archive(Pkt, LServer) of
|
||||
true ->
|
||||
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
||||
store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
|
||||
LUser, LServer, Peer, send),
|
||||
NewPkt;
|
||||
case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
|
||||
LUser, LServer, Peer, send) of
|
||||
{ok, ID} ->
|
||||
Archived = #xmlel{name = <<"archived">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_MAM_TMP},
|
||||
{<<"id">>, ID}]},
|
||||
StanzaID = #xmlel{name = <<"stanza-id">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_SID_0},
|
||||
{<<"id">>, ID}]},
|
||||
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
|
||||
NewPkt#xmlel{children = NewEls};
|
||||
_ ->
|
||||
NewPkt
|
||||
end;
|
||||
false ->
|
||||
Pkt
|
||||
end.
|
||||
|
||||
user_send_packet_strip_tag(Pkt, _C2SState, JID, _Peer) ->
|
||||
LServer = JID#jid.lserver,
|
||||
strip_my_archived_tag(Pkt, LServer).
|
||||
|
||||
muc_filter_message(Pkt, #state{config = Config} = MUCState,
|
||||
RoomJID, From, FromNick) ->
|
||||
if Config#config.mam ->
|
||||
@@ -316,7 +337,12 @@ message_is_archived(false, C2SState, Peer,
|
||||
(never) -> never
|
||||
end, never) of
|
||||
if_enabled ->
|
||||
get_prefs(LUser, LServer);
|
||||
case get_prefs(LUser, LServer) of
|
||||
#archive_prefs{} = P ->
|
||||
{ok, P};
|
||||
error ->
|
||||
error
|
||||
end;
|
||||
on_request ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
cache_tab:lookup(archive_prefs, {LUser, LServer},
|
||||
@@ -812,9 +838,10 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
|
||||
_ ->
|
||||
{Msgs, true, L}
|
||||
end;
|
||||
select(LServer, From, From, Start, End, With, RSM, MsgType) ->
|
||||
select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:select(LServer, From, From, Start, End, With, RSM, MsgType).
|
||||
Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM,
|
||||
MsgType).
|
||||
|
||||
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
|
||||
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
|
||||
|
||||
+63
-26
@@ -16,6 +16,7 @@
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_mam.hrl").
|
||||
|
||||
-define(BIN_GREATER_THAN(A, B),
|
||||
@@ -25,6 +26,8 @@
|
||||
((A < B andalso byte_size(A) == byte_size(B))
|
||||
orelse byte_size(A) < byte_size(B))).
|
||||
|
||||
-define(TABLE_SIZE_LIMIT, 2000000000). % A bit less than 2 GiB.
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
@@ -49,37 +52,71 @@ remove_room(_LServer, LName, LHost) ->
|
||||
remove_user(LName, LHost).
|
||||
|
||||
delete_old_messages(global, TimeStamp, Type) ->
|
||||
MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
|
||||
type = MsgType} = Msg)
|
||||
when MsgTS < TimeStamp,
|
||||
MsgType == Type orelse Type == all ->
|
||||
Msg
|
||||
end),
|
||||
OldMsgs = mnesia:dirty_select(archive_msg, MS),
|
||||
lists:foreach(fun(Rec) ->
|
||||
ok = mnesia:dirty_delete_object(Rec)
|
||||
end, OldMsgs).
|
||||
mnesia:change_table_copy_type(archive_msg, node(), disc_copies),
|
||||
Result = delete_old_user_messages(mnesia:dirty_first(archive_msg), TimeStamp, Type),
|
||||
mnesia:change_table_copy_type(archive_msg, node(), disc_only_copies),
|
||||
Result.
|
||||
|
||||
delete_old_user_messages('$end_of_table', _TimeStamp, _Type) ->
|
||||
ok;
|
||||
delete_old_user_messages(User, TimeStamp, Type) ->
|
||||
F = fun() ->
|
||||
Msgs = mnesia:read(archive_msg, User),
|
||||
Keep = lists:filter(
|
||||
fun(#archive_msg{timestamp = MsgTS,
|
||||
type = MsgType}) ->
|
||||
MsgTS >= TimeStamp orelse (Type /= all andalso
|
||||
Type /= MsgType)
|
||||
end, Msgs),
|
||||
if length(Keep) < length(Msgs) ->
|
||||
mnesia:delete({archive_msg, User}),
|
||||
lists:foreach(fun(Msg) -> mnesia:write(Msg) end, Keep);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
delete_old_user_messages(mnesia:dirty_next(archive_msg, User),
|
||||
TimeStamp, Type);
|
||||
{aborted, Err} ->
|
||||
?ERROR_MSG("Cannot delete old MAM messages: ~s", [Err]),
|
||||
Err
|
||||
end.
|
||||
|
||||
extended_fields() ->
|
||||
[].
|
||||
|
||||
store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) ->
|
||||
LPeer = {PUser, PServer, _} = jid:tolower(Peer),
|
||||
TS = p1_time_compat:timestamp(),
|
||||
ID = jlib:integer_to_binary(now_to_usec(TS)),
|
||||
case mnesia:dirty_write(
|
||||
#archive_msg{us = {LUser, LServer},
|
||||
id = ID,
|
||||
timestamp = TS,
|
||||
peer = LPeer,
|
||||
bare_peer = {PUser, PServer, <<>>},
|
||||
type = Type,
|
||||
nick = Nick,
|
||||
packet = Pkt}) of
|
||||
ok ->
|
||||
{ok, ID};
|
||||
Err ->
|
||||
Err
|
||||
case {mnesia:table_info(archive_msg, disc_only_copies),
|
||||
mnesia:table_info(archive_msg, memory)} of
|
||||
{[_|_], TableSize} when TableSize > ?TABLE_SIZE_LIMIT ->
|
||||
?ERROR_MSG("MAM archives too large, won't store message for ~s@~s",
|
||||
[LUser, LServer]),
|
||||
{error, overflow};
|
||||
_ ->
|
||||
LPeer = {PUser, PServer, _} = jid:tolower(Peer),
|
||||
TS = p1_time_compat:timestamp(),
|
||||
ID = jlib:integer_to_binary(now_to_usec(TS)),
|
||||
F = fun() ->
|
||||
mnesia:write(
|
||||
#archive_msg{us = {LUser, LServer},
|
||||
id = ID,
|
||||
timestamp = TS,
|
||||
peer = LPeer,
|
||||
bare_peer = {PUser, PServer, <<>>},
|
||||
type = Type,
|
||||
nick = Nick,
|
||||
packet = Pkt})
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
{ok, ID};
|
||||
{aborted, Err} ->
|
||||
?ERROR_MSG("Cannot add message to MAM archive of ~s@~s: ~s",
|
||||
[LUser, LServer, Err]),
|
||||
Err
|
||||
end
|
||||
end.
|
||||
|
||||
write_prefs(_LUser, _LServer, Prefs, _ServerHost) ->
|
||||
|
||||
+40
-30
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_mam_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_mam).
|
||||
|
||||
%% API
|
||||
@@ -18,6 +20,7 @@
|
||||
-include("jlib.hrl").
|
||||
-include("mod_mam.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -26,13 +29,12 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
SUser = ejabberd_sql:escape(LUser),
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"delete from archive where username='">>, SUser, <<"';">>]),
|
||||
?SQL("delete from archive where username=%(LUser)s")),
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"delete from archive_prefs where username='">>, SUser, <<"';">>]).
|
||||
?SQL("delete from archive_prefs where username=%(LUser)s")).
|
||||
|
||||
remove_room(LServer, LName, LHost) ->
|
||||
LUser = jid:to_string({LName, LHost, <<>>}),
|
||||
@@ -55,7 +57,7 @@ extended_fields() ->
|
||||
|
||||
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
|
||||
TSinteger = p1_time_compat:system_time(micro_seconds),
|
||||
ID = TS = jlib:integer_to_binary(TSinteger),
|
||||
ID = jlib:integer_to_binary(TSinteger),
|
||||
SUser = case Type of
|
||||
chat -> LUser;
|
||||
groupchat -> jid:to_string({LUser, LHost, <<>>})
|
||||
@@ -67,18 +69,19 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
|
||||
jid:tolower(Peer)),
|
||||
XML = fxml:element_to_binary(Pkt),
|
||||
Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
|
||||
SType = jlib:atom_to_binary(Type),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"insert into archive (username, timestamp, "
|
||||
"peer, bare_peer, xml, txt, kind, nick) values (">>,
|
||||
<<"'">>, ejabberd_sql:escape(SUser), <<"', ">>,
|
||||
<<"'">>, TS, <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(LPeer), <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(BarePeer), <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(XML), <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(Body), <<"', ">>,
|
||||
<<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
|
||||
<<"'">>, ejabberd_sql:escape(Nick), <<"');">>]) of
|
||||
LServer,
|
||||
?SQL("insert into archive (username, timestamp,"
|
||||
" peer, bare_peer, xml, txt, kind, nick) values ("
|
||||
"%(SUser)s, "
|
||||
"%(TSinteger)d, "
|
||||
"%(LPeer)s, "
|
||||
"%(BarePeer)s, "
|
||||
"%(XML)s, "
|
||||
"%(Body)s, "
|
||||
"%(SType)s, "
|
||||
"%(Nick)s)")) of
|
||||
{updated, _} ->
|
||||
{ok, ID};
|
||||
Err ->
|
||||
@@ -89,14 +92,16 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
|
||||
never = Never,
|
||||
always = Always},
|
||||
ServerHost) ->
|
||||
SUser = ejabberd_sql:escape(LUser),
|
||||
SDefault = erlang:atom_to_binary(Default, utf8),
|
||||
SAlways = ejabberd_sql:encode_term(Always),
|
||||
SNever = ejabberd_sql:encode_term(Never),
|
||||
case update(ServerHost, <<"archive_prefs">>,
|
||||
[<<"username">>, <<"def">>, <<"always">>, <<"never">>],
|
||||
[SUser, SDefault, SAlways, SNever],
|
||||
[<<"username='">>, SUser, <<"'">>]) of
|
||||
SAlways = jlib:term_to_expr(Always),
|
||||
SNever = jlib:term_to_expr(Never),
|
||||
case ?SQL_UPSERT(
|
||||
ServerHost,
|
||||
"archive_prefs",
|
||||
["!username=%(LUser)s",
|
||||
"def=%(SDefault)s",
|
||||
"always=%(SAlways)s",
|
||||
"never=%(SNever)s"]) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
@@ -106,10 +111,9 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
|
||||
get_prefs(LUser, LServer) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select def, always, never from archive_prefs ">>,
|
||||
<<"where username='">>,
|
||||
ejabberd_sql:escape(LUser), <<"';">>]) of
|
||||
{selected, _, [[SDefault, SAlways, SNever]]} ->
|
||||
?SQL("select @(def)s, @(always)s, @(never)s from archive_prefs"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, [{SDefault, SAlways, SNever}]} ->
|
||||
Default = erlang:binary_to_existing_atom(SDefault, utf8),
|
||||
Always = ejabberd_sql:decode_term(SAlways),
|
||||
Never = ejabberd_sql:decode_term(SNever),
|
||||
@@ -208,6 +212,12 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
ODBCType = ejabberd_config:get_option(
|
||||
{sql_type, LServer},
|
||||
ejabberd_sql:opt_type(sql_type)),
|
||||
Escape =
|
||||
case ODBCType of
|
||||
mssql -> fun ejabberd_sql:standard_escape/1;
|
||||
sqlite -> fun ejabberd_sql:standard_escape/1;
|
||||
_ -> fun ejabberd_sql:escape/1
|
||||
end,
|
||||
LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql ->
|
||||
[<<" limit ">>, jlib:integer_to_binary(Max+1)];
|
||||
true ->
|
||||
@@ -223,14 +233,14 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
[];
|
||||
{text, Txt} ->
|
||||
[<<" and match (txt) against ('">>,
|
||||
ejabberd_sql:escape(Txt), <<"')">>];
|
||||
Escape(Txt), <<"')">>];
|
||||
{_, _, <<>>} ->
|
||||
[<<" and bare_peer='">>,
|
||||
ejabberd_sql:escape(jid:to_string(With)),
|
||||
Escape(jid:to_string(With)),
|
||||
<<"'">>];
|
||||
{_, _, _} ->
|
||||
[<<" and peer='">>,
|
||||
ejabberd_sql:escape(jid:to_string(With)),
|
||||
Escape(jid:to_string(With)),
|
||||
<<"'">>];
|
||||
none ->
|
||||
[]
|
||||
@@ -262,7 +272,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
SUser = ejabberd_sql:escape(User),
|
||||
SUser = Escape(User),
|
||||
|
||||
Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
|
||||
" FROM archive WHERE username='">>,
|
||||
|
||||
+4
-1
@@ -39,7 +39,7 @@
|
||||
s2s_send_packet, s2s_receive_packet,
|
||||
remove_user, register_user]).
|
||||
|
||||
-export([start/2, stop/1, send_metrics/4, opt_type/1]).
|
||||
-export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1]).
|
||||
|
||||
-export([offline_message_hook/3,
|
||||
sm_register_connection_hook/3, sm_remove_connection_hook/3,
|
||||
@@ -126,3 +126,6 @@ send_metrics(Host, Probe, Peer, Port) ->
|
||||
|
||||
opt_type(_) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(_) ->
|
||||
[].
|
||||
|
||||
+8
-8
@@ -193,14 +193,14 @@ init([Host, Opts]) ->
|
||||
clean_table_from_bad_node(node(), MyHost),
|
||||
mnesia:subscribe(system),
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun(A) when is_atom(A) -> A end, all),
|
||||
fun acl:access_rules_validator/1, all),
|
||||
AccessCreate = gen_mod:get_opt(access_create, Opts,
|
||||
fun(A) when is_atom(A) -> A end, all),
|
||||
fun acl:access_rules_validator/1, all),
|
||||
AccessAdmin = gen_mod:get_opt(access_admin, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
none),
|
||||
AccessPersistent = gen_mod:get_opt(access_persistent, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
all),
|
||||
HistorySize = gen_mod:get_opt(history_size, Opts,
|
||||
fun(I) when is_integer(I), I>=0 -> I end,
|
||||
@@ -925,13 +925,13 @@ import(LServer, DBType, Data) ->
|
||||
Mod:import(LServer, Data).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_admin) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_create) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_persistent) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(default_room_options) ->
|
||||
fun (L) when is_list(L) -> L end;
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ init([Host, Opts]) ->
|
||||
fun iolist_to_binary/1,
|
||||
false),
|
||||
AccessLog = gen_mod:get_opt(access_log, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:access_rules_validator/1,
|
||||
muc_admin),
|
||||
Timezone = gen_mod:get_opt(timezone, Opts,
|
||||
fun(local) -> local;
|
||||
|
||||
+64
-10
@@ -266,6 +266,8 @@ normal_state({route, From, <<"">>,
|
||||
none ->
|
||||
NSD = set_affiliation(IJID, member,
|
||||
StateData),
|
||||
send_affiliation(IJID, member,
|
||||
StateData),
|
||||
case
|
||||
(NSD#state.config)#config.persistent
|
||||
of
|
||||
@@ -1801,8 +1803,8 @@ add_new_user(From, Nick,
|
||||
10),
|
||||
Collision = nick_collision(From, Nick, StateData),
|
||||
case {(ServiceAffiliation == owner orelse
|
||||
(Affiliation == admin orelse Affiliation == owner)
|
||||
andalso NUsers < MaxAdminUsers
|
||||
((Affiliation == admin orelse Affiliation == owner)
|
||||
andalso NUsers < MaxAdminUsers)
|
||||
orelse NUsers < MaxUsers)
|
||||
andalso NConferences < MaxConferences,
|
||||
Collision,
|
||||
@@ -2440,6 +2442,51 @@ send_nick_changing(JID, OldNick, StateData,
|
||||
end,
|
||||
(?DICT):to_list(StateData#state.users)).
|
||||
|
||||
maybe_send_affiliation(JID, Affiliation, StateData) ->
|
||||
LJID = jid:tolower(JID),
|
||||
IsOccupant = case LJID of
|
||||
{LUser, LServer, <<"">>} ->
|
||||
not (?DICT):is_empty(
|
||||
(?DICT):filter(fun({U, S, _}, _) ->
|
||||
U == LUser andalso
|
||||
S == LServer
|
||||
end, StateData#state.users));
|
||||
{_LUser, _LServer, _LResource} ->
|
||||
(?DICT):is_key(LJID, StateData#state.users)
|
||||
end,
|
||||
case IsOccupant of
|
||||
true ->
|
||||
ok; % The new affiliation is published via presence.
|
||||
false ->
|
||||
send_affiliation(LJID, Affiliation, StateData)
|
||||
end.
|
||||
|
||||
send_affiliation(LJID, Affiliation, StateData) ->
|
||||
ItemAttrs = [{<<"jid">>, jid:to_string(LJID)},
|
||||
{<<"affiliation">>, affiliation_to_list(Affiliation)},
|
||||
{<<"role">>, <<"none">>}],
|
||||
Message = #xmlel{name = <<"message">>,
|
||||
attrs = [{<<"id">>, randoms:get_string()}],
|
||||
children =
|
||||
[#xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
|
||||
children =
|
||||
[#xmlel{name = <<"item">>,
|
||||
attrs = ItemAttrs}]}]},
|
||||
Recipients = case (StateData#state.config)#config.anonymous of
|
||||
true ->
|
||||
(?DICT):filter(fun(_, #user{role = moderator}) ->
|
||||
true;
|
||||
(_, _) ->
|
||||
false
|
||||
end, StateData#state.users);
|
||||
false ->
|
||||
StateData#state.users
|
||||
end,
|
||||
send_multiple(StateData#state.jid,
|
||||
StateData#state.server_host,
|
||||
Recipients, Message).
|
||||
|
||||
status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
|
||||
Status = case IsInitialPresence of
|
||||
true ->
|
||||
@@ -2722,11 +2769,13 @@ process_item_change(E, SD, UJID) ->
|
||||
<<"321">>,
|
||||
none,
|
||||
SD),
|
||||
maybe_send_affiliation(JID, none, SD),
|
||||
SD1 = set_affiliation(JID, none, SD),
|
||||
set_role(JID, none, SD1);
|
||||
_ ->
|
||||
SD1 = set_affiliation(JID, none, SD),
|
||||
send_update_presence(JID, SD1, SD),
|
||||
maybe_send_affiliation(JID, none, SD1),
|
||||
SD1
|
||||
end;
|
||||
{JID, affiliation, outcast, Reason} ->
|
||||
@@ -2736,6 +2785,7 @@ process_item_change(E, SD, UJID) ->
|
||||
<<"301">>,
|
||||
outcast,
|
||||
SD),
|
||||
maybe_send_affiliation(JID, outcast, SD),
|
||||
set_affiliation(JID,
|
||||
outcast,
|
||||
set_role(JID, none, SD),
|
||||
@@ -2745,11 +2795,13 @@ process_item_change(E, SD, UJID) ->
|
||||
SD1 = set_affiliation(JID, A, SD, Reason),
|
||||
SD2 = set_role(JID, moderator, SD1),
|
||||
send_update_presence(JID, Reason, SD2, SD),
|
||||
maybe_send_affiliation(JID, A, SD2),
|
||||
SD2;
|
||||
{JID, affiliation, member, Reason} ->
|
||||
SD1 = set_affiliation(JID, member, SD, Reason),
|
||||
SD2 = set_role(JID, participant, SD1),
|
||||
send_update_presence(JID, Reason, SD2, SD),
|
||||
maybe_send_affiliation(JID, member, SD2),
|
||||
SD2;
|
||||
{JID, role, Role, Reason} ->
|
||||
SD1 = set_role(JID, Role, SD),
|
||||
@@ -2759,6 +2811,7 @@ process_item_change(E, SD, UJID) ->
|
||||
{JID, affiliation, A, _Reason} ->
|
||||
SD1 = set_affiliation(JID, A, SD),
|
||||
send_update_presence(JID, SD1, SD),
|
||||
maybe_send_affiliation(JID, A, SD1),
|
||||
SD1
|
||||
end
|
||||
of
|
||||
@@ -3092,14 +3145,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
|
||||
StateData#state.users),
|
||||
SAffiliation = affiliation_to_list(Affiliation),
|
||||
BannedJIDString = jid:to_string(RealJID),
|
||||
case MJID /= <<"">> of
|
||||
true ->
|
||||
{ok, #user{nick = ActorNick}} =
|
||||
(?DICT):find(jid:tolower(MJID),
|
||||
StateData#state.users);
|
||||
false ->
|
||||
ActorNick = <<"">>
|
||||
end,
|
||||
ActorNick = get_actor_nick(MJID, StateData),
|
||||
lists:foreach(fun ({_LJID, Info}) ->
|
||||
JidAttrList = case Info#user.role == moderator orelse
|
||||
(StateData#state.config)#config.anonymous
|
||||
@@ -3154,6 +3200,14 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
|
||||
end,
|
||||
(?DICT):to_list(StateData#state.users)).
|
||||
|
||||
get_actor_nick(<<"">>, _StateData) ->
|
||||
<<"">>;
|
||||
get_actor_nick(MJID, StateData) ->
|
||||
case (?DICT):find(jid:tolower(MJID), StateData#state.users) of
|
||||
{ok, #user{nick = ActorNick}} -> ActorNick;
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% Owner stuff
|
||||
|
||||
|
||||
+58
-81
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_muc_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_muc).
|
||||
|
||||
%% API
|
||||
@@ -18,6 +20,7 @@
|
||||
-include("jlib.hrl").
|
||||
-include("mod_muc.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -26,61 +29,54 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
store_room(LServer, Host, Name, Opts) ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
SOpts = ejabberd_sql:encode_term(Opts),
|
||||
SOpts = jlib:term_to_expr(Opts),
|
||||
F = fun () ->
|
||||
sql_queries:update_t(<<"muc_room">>,
|
||||
[<<"name">>, <<"host">>, <<"opts">>],
|
||||
[SName, SHost, SOpts],
|
||||
[<<"name='">>, SName, <<"' and host='">>,
|
||||
SHost, <<"'">>])
|
||||
?SQL_UPSERT_T(
|
||||
"muc_room",
|
||||
["!name=%(Name)s",
|
||||
"!host=%(Host)s",
|
||||
"opts=%(SOpts)s"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
restore_room(LServer, Host, Name) ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
case catch ejabberd_sql:sql_query(LServer,
|
||||
[<<"select opts from muc_room where name='">>,
|
||||
SName, <<"' and host='">>, SHost,
|
||||
<<"';">>]) of
|
||||
{selected, [<<"opts">>], [[Opts]]} ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(opts)s from muc_room where name=%(Name)s"
|
||||
" and host=%(Host)s")) of
|
||||
{selected, [{Opts}]} ->
|
||||
mod_muc:opts_to_binary(ejabberd_sql:decode_term(Opts));
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
forget_room(LServer, Host, Name) ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
F = fun () ->
|
||||
ejabberd_sql:sql_query_t([<<"delete from muc_room where name='">>,
|
||||
SName, <<"' and host='">>, SHost,
|
||||
<<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from muc_room where name=%(Name)s"
|
||||
" and host=%(Host)s"))
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
can_use_nick(LServer, Host, JID, Nick) ->
|
||||
SJID = jid:to_string(jid:tolower(jid:remove_resource(JID))),
|
||||
SNick = ejabberd_sql:escape(Nick),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
case catch ejabberd_sql:sql_query(LServer,
|
||||
[<<"select jid from muc_registered ">>,
|
||||
<<"where nick='">>, SNick,
|
||||
<<"' and host='">>, SHost, <<"';">>]) of
|
||||
{selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(jid)s from muc_registered "
|
||||
"where nick=%(Nick)s"
|
||||
" and host=%(Host)s")) of
|
||||
{selected, [{SJID1}]} -> SJID == SJID1;
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
get_rooms(LServer, Host) ->
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
case catch ejabberd_sql:sql_query(LServer,
|
||||
[<<"select name, opts from muc_room ">>,
|
||||
<<"where host='">>, SHost, <<"';">>]) of
|
||||
{selected, [<<"name">>, <<"opts">>], RoomOpts} ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(name)s, @(opts)s from muc_room"
|
||||
" where host=%(Host)s")) of
|
||||
{selected, RoomOpts} ->
|
||||
lists:map(
|
||||
fun([Room, Opts]) ->
|
||||
fun({Room, Opts}) ->
|
||||
#muc_room{name_host = {Room, Host},
|
||||
opts = mod_muc:opts_to_binary(
|
||||
ejabberd_sql:decode_term(Opts))}
|
||||
@@ -91,49 +87,38 @@ get_rooms(LServer, Host) ->
|
||||
end.
|
||||
|
||||
get_nick(LServer, Host, From) ->
|
||||
SJID = ejabberd_sql:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
case catch ejabberd_sql:sql_query(LServer,
|
||||
[<<"select nick from muc_registered where "
|
||||
"jid='">>,
|
||||
SJID, <<"' and host='">>, SHost,
|
||||
<<"';">>]) of
|
||||
{selected, [<<"nick">>], [[Nick]]} -> Nick;
|
||||
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(nick)s from muc_registered where"
|
||||
" jid=%(SJID)s and host=%(Host)s")) of
|
||||
{selected, [{Nick}]} -> Nick;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
set_nick(LServer, Host, From, Nick) ->
|
||||
JID = jid:to_string(jid:tolower(jid:remove_resource(From))),
|
||||
SJID = ejabberd_sql:escape(JID),
|
||||
SNick = ejabberd_sql:escape(Nick),
|
||||
SHost = ejabberd_sql:escape(Host),
|
||||
F = fun () ->
|
||||
case Nick of
|
||||
<<"">> ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
[<<"delete from muc_registered where ">>,
|
||||
<<"jid='">>, SJID,
|
||||
<<"' and host='">>, Host,
|
||||
<<"';">>]),
|
||||
?SQL("delete from muc_registered where"
|
||||
" jid=%(JID)s and host=%(Host)s")),
|
||||
ok;
|
||||
_ ->
|
||||
Allow = case ejabberd_sql:sql_query_t(
|
||||
[<<"select jid from muc_registered ">>,
|
||||
<<"where nick='">>,
|
||||
SNick,
|
||||
<<"' and host='">>,
|
||||
SHost, <<"';">>]) of
|
||||
{selected, [<<"jid">>], [[J]]} -> J == JID;
|
||||
?SQL("select @(jid)s from muc_registered"
|
||||
" where nick=%(Nick)s"
|
||||
" and host=%(Host)s")) of
|
||||
{selected, [{J}]} -> J == JID;
|
||||
_ -> true
|
||||
end,
|
||||
if Allow ->
|
||||
sql_queries:update_t(<<"muc_registered">>,
|
||||
[<<"jid">>, <<"host">>,
|
||||
<<"nick">>],
|
||||
[SJID, SHost, SNick],
|
||||
[<<"jid='">>, SJID,
|
||||
<<"' and host='">>, SHost,
|
||||
<<"'">>]),
|
||||
?SQL_UPSERT_T(
|
||||
"muc_registered",
|
||||
["!jid=%(JID)s",
|
||||
"!host=%(Host)s",
|
||||
"nick=%(Nick)s"]),
|
||||
ok;
|
||||
true ->
|
||||
false
|
||||
@@ -147,15 +132,12 @@ export(_Server) ->
|
||||
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
|
||||
case str:suffix(Host, RoomHost) of
|
||||
true ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
SRoomHost = ejabberd_sql:escape(RoomHost),
|
||||
SOpts = ejabberd_sql:encode_term(Opts),
|
||||
[[<<"delete from muc_room where name='">>, SName,
|
||||
<<"' and host='">>, SRoomHost, <<"';">>],
|
||||
[<<"insert into muc_room(name, host, opts) ",
|
||||
"values (">>,
|
||||
<<"'">>, SName, <<"', '">>, SRoomHost,
|
||||
<<"', '">>, SOpts, <<"');">>]];
|
||||
SOpts = jlib:term_to_expr(Opts),
|
||||
[?SQL("delete from muc_room where name=%(Name)s"
|
||||
" and host=%(RoomHost)s;"),
|
||||
?SQL("insert into muc_room(name, host, opts) "
|
||||
"values ("
|
||||
"%(Name)s, %(RoomHost)s, %(SOpts)s);")];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
@@ -165,17 +147,12 @@ export(_Server) ->
|
||||
nick = Nick}) ->
|
||||
case str:suffix(Host, RoomHost) of
|
||||
true ->
|
||||
SJID = ejabberd_sql:escape(
|
||||
jid:to_string(
|
||||
jid:make(U, S, <<"">>))),
|
||||
SNick = ejabberd_sql:escape(Nick),
|
||||
SRoomHost = ejabberd_sql:escape(RoomHost),
|
||||
[[<<"delete from muc_registered where jid='">>,
|
||||
SJID, <<"' and host='">>, SRoomHost, <<"';">>],
|
||||
[<<"insert into muc_registered(jid, host, "
|
||||
"nick) values ('">>,
|
||||
SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
|
||||
<<"');">>]];
|
||||
SJID = jid:to_string(jid:make(U, S, <<"">>)),
|
||||
[?SQL("delete from muc_registered where"
|
||||
" jid=%(SJID)s and host=%(RoomHost)s;"),
|
||||
?SQL("insert into muc_registered(jid, host, "
|
||||
"nick) values ("
|
||||
"%(SJID)s, %(RoomHost)s, %(Nick)s);")];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -140,7 +140,7 @@ init([LServerS, Opts]) ->
|
||||
LServiceS = gen_mod:get_opt_host(LServerS, Opts,
|
||||
<<"multicast.@HOST@">>),
|
||||
Access = gen_mod:get_opt(access, Opts,
|
||||
fun (A) when is_atom(A) -> A end, all),
|
||||
fun acl:access_rules_validator/1, all),
|
||||
SLimits =
|
||||
build_service_limit_record(gen_mod:get_opt(limits, Opts,
|
||||
fun (A) when is_list(A) ->
|
||||
@@ -1220,7 +1220,7 @@ stj(String) -> jid:from_string(String).
|
||||
jts(String) -> jid:to_string(String).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(limits) ->
|
||||
fun (A) when is_list(A) -> A end;
|
||||
|
||||
+3
-3
@@ -162,7 +162,7 @@ init([Host, Opts]) ->
|
||||
?MODULE, handle_offline_query, IQDisc),
|
||||
AccessMaxOfflineMsgs =
|
||||
gen_mod:get_opt(access_max_user_messages, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:shaper_rules_validator/1,
|
||||
max_user_offline_messages),
|
||||
{ok,
|
||||
#state{host = Host,
|
||||
@@ -791,7 +791,7 @@ get_messages_subset(User, Host, MsgsAll) ->
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
max_user_offline_messages),
|
||||
MaxOfflineMsgs = case get_max_user_messages(Access,
|
||||
User, Host)
|
||||
{User, Host}, Host)
|
||||
of
|
||||
Number when is_integer(Number) -> Number;
|
||||
_ -> 100
|
||||
@@ -866,7 +866,7 @@ import(LServer, DBType, Data) ->
|
||||
Mod:import(LServer, Data).
|
||||
|
||||
mod_opt_type(access_max_user_messages) ->
|
||||
fun (A) -> A end;
|
||||
fun acl:shaper_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(store_empty_body) ->
|
||||
fun (V) when is_boolean(V) -> V;
|
||||
|
||||
+20
-28
@@ -38,8 +38,7 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
|
||||
true ->
|
||||
Query = lists:map(
|
||||
fun(M) ->
|
||||
Username =
|
||||
ejabberd_sql:escape((M#offline_msg.to)#jid.luser),
|
||||
LUser = (M#offline_msg.to)#jid.luser,
|
||||
From = M#offline_msg.from,
|
||||
To = M#offline_msg.to,
|
||||
Packet =
|
||||
@@ -49,9 +48,8 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
|
||||
jlib:add_delay_info(Packet, Host,
|
||||
M#offline_msg.timestamp,
|
||||
<<"Offline Storage">>),
|
||||
XML =
|
||||
ejabberd_sql:escape(fxml:element_to_binary(NewPacket)),
|
||||
sql_queries:add_spool_sql(Username, XML)
|
||||
XML = fxml:element_to_binary(NewPacket),
|
||||
sql_queries:add_spool_sql(LUser, XML)
|
||||
end,
|
||||
Msgs),
|
||||
sql_queries:add_spool(Host, Query)
|
||||
@@ -82,8 +80,8 @@ remove_old_messages(Days, LServer) ->
|
||||
LServer,
|
||||
[<<"DELETE FROM spool"
|
||||
" WHERE created_at < "
|
||||
"DATE_SUB(CURDATE(), INTERVAL ">>,
|
||||
integer_to_list(Days), <<" DAY);">>]) of
|
||||
"NOW() - INTERVAL '">>,
|
||||
integer_to_list(Days), <<"';">>]) of
|
||||
{updated, N} ->
|
||||
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
|
||||
_Error ->
|
||||
@@ -95,19 +93,18 @@ remove_user(LUser, LServer) ->
|
||||
sql_queries:del_spool_msg(LServer, LUser).
|
||||
|
||||
read_message_headers(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer, [<<"select xml, seq from spool where username ='">>,
|
||||
Username, <<"' order by seq;">>]) of
|
||||
{selected, [<<"xml">>, <<"seq">>], Rows} ->
|
||||
LServer,
|
||||
?SQL("select @(xml)s, @(seq)d from spool"
|
||||
" where username=%(LUser)s order by seq")) of
|
||||
{selected, Rows} ->
|
||||
lists:flatmap(
|
||||
fun([XML, Seq]) ->
|
||||
fun({XML, Seq}) ->
|
||||
case xml_to_offline_msg(XML) of
|
||||
{ok, #offline_msg{from = From,
|
||||
to = To,
|
||||
packet = El}} ->
|
||||
Seq0 = binary_to_integer(Seq),
|
||||
[{Seq0, From, To, El}];
|
||||
[{Seq, From, To, El}];
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
@@ -117,13 +114,11 @@ read_message_headers(LUser, LServer) ->
|
||||
end.
|
||||
|
||||
read_message(LUser, LServer, Seq) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select xml from spool where username='">>, Username,
|
||||
<<"' and seq='">>, SSeq, <<"';">>]) of
|
||||
{selected, [<<"xml">>], [[RawXML]|_]} ->
|
||||
?SQL("select @(xml)s from spool where username=%(LUser)s"
|
||||
" and seq=%(Seq)d")) of
|
||||
{selected, [{RawXML}|_]} ->
|
||||
case xml_to_offline_msg(RawXML) of
|
||||
{ok, Msg} ->
|
||||
{ok, Msg};
|
||||
@@ -135,12 +130,10 @@ read_message(LUser, LServer, Seq) ->
|
||||
end.
|
||||
|
||||
remove_message(LUser, LServer, Seq) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"delete from spool where username='">>, Username,
|
||||
<<"' and seq='">>, SSeq, <<"';">>]),
|
||||
?SQL("delete from spool where username=%(LUser)s"
|
||||
" and seq=%(Seq)d")),
|
||||
ok.
|
||||
|
||||
read_all_messages(LUser, LServer) ->
|
||||
@@ -180,14 +173,13 @@ export(_Server) ->
|
||||
timestamp = TimeStamp, from = From, to = To,
|
||||
packet = Packet})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
Packet1 = jlib:replace_from_to(From, To, Packet),
|
||||
Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
|
||||
<<"Offline Storage">>),
|
||||
XML = ejabberd_sql:escape(fxml:element_to_binary(Packet2)),
|
||||
[[<<"delete from spool where username='">>, Username, <<"';">>],
|
||||
[<<"insert into spool(username, xml) values ('">>,
|
||||
Username, <<"', '">>, XML, <<"');">>]];
|
||||
XML = fxml:element_to_binary(Packet2),
|
||||
[?SQL("delete from spool where username=%(LUser)s;"),
|
||||
?SQL("insert into spool(username, xml) values ("
|
||||
"%(LUser)s, %(XML)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
+31
-32
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_privacy_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_privacy).
|
||||
|
||||
%% API
|
||||
@@ -29,6 +31,7 @@
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -208,38 +211,38 @@ export(Server) ->
|
||||
fun(Host, #privacy{us = {LUser, LServer}, lists = Lists,
|
||||
default = Default})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
if Default /= none ->
|
||||
SDefault = ejabberd_sql:escape(Default),
|
||||
[[<<"delete from privacy_default_list where ">>,
|
||||
<<"username='">>, Username, <<"';">>],
|
||||
[<<"insert into privacy_default_list(username, "
|
||||
"name) ">>,
|
||||
<<"values ('">>, Username, <<"', '">>,
|
||||
SDefault, <<"');">>]];
|
||||
[?SQL("delete from privacy_default_list where"
|
||||
" username=%(LUser)s;"),
|
||||
?SQL("insert into privacy_default_list(username, name) "
|
||||
"values (%(LUser)s, %(Default)s);")];
|
||||
true ->
|
||||
[]
|
||||
end ++
|
||||
lists:flatmap(
|
||||
fun({Name, List}) ->
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
RItems = lists:map(fun item_to_raw/1, List),
|
||||
ID = jlib:integer_to_binary(get_id()),
|
||||
[[<<"delete from privacy_list where username='">>,
|
||||
Username, <<"' and name='">>,
|
||||
SName, <<"';">>],
|
||||
[<<"insert into privacy_list(username, "
|
||||
"name, id) values ('">>,
|
||||
Username, <<"', '">>, SName,
|
||||
<<"', '">>, ID, <<"');">>],
|
||||
[<<"delete from privacy_list_data where "
|
||||
"id='">>, ID, <<"';">>]] ++
|
||||
[[<<"insert into privacy_list_data(id, t, "
|
||||
"value, action, ord, match_all, match_iq, "
|
||||
"match_message, match_presence_in, "
|
||||
"match_presence_out) values ('">>,
|
||||
ID, <<"', '">>, str:join(Items, <<"', '">>),
|
||||
<<"');">>] || Items <- RItems]
|
||||
ID = get_id(),
|
||||
[?SQL("delete from privacy_list where"
|
||||
" username=%(LUser)s and"
|
||||
" name=%(Name)s;"),
|
||||
?SQL("insert into privacy_list(username, "
|
||||
"name, id) values ("
|
||||
"%(LUser)s, %(Name)s, %(ID)d);"),
|
||||
?SQL("delete from privacy_list_data where"
|
||||
" id=%(ID)d;")] ++
|
||||
[?SQL("insert into privacy_list_data(id, t, "
|
||||
"value, action, ord, match_all, match_iq, "
|
||||
"match_message, match_presence_in, "
|
||||
"match_presence_out) "
|
||||
"values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
|
||||
" %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
|
||||
" %(MatchMessage)b, %(MatchPresenceIn)b,"
|
||||
" %(MatchPresenceOut)b)")
|
||||
|| {SType, SValue, SAction, Order,
|
||||
MatchAll, MatchIQ,
|
||||
MatchMessage, MatchPresenceIn,
|
||||
MatchPresenceOut} <- RItems]
|
||||
end,
|
||||
Lists);
|
||||
(_Host, _R) ->
|
||||
@@ -327,10 +330,8 @@ item_to_raw(#listitem{type = Type, value = Value,
|
||||
match_presence_out = MatchPresenceOut}) ->
|
||||
{SType, SValue} = case Type of
|
||||
none -> {<<"n">>, <<"">>};
|
||||
jid ->
|
||||
{<<"j">>,
|
||||
ejabberd_sql:escape(jid:to_string(Value))};
|
||||
group -> {<<"g">>, ejabberd_sql:escape(Value)};
|
||||
jid -> {<<"j">>, jid:to_string(Value)};
|
||||
group -> {<<"g">>, Value};
|
||||
subscription ->
|
||||
case Value of
|
||||
none -> {<<"s">>, <<"none">>};
|
||||
@@ -368,9 +369,7 @@ sql_get_privacy_list_data(LUser, LServer, Name) ->
|
||||
sql_queries:get_privacy_list_data(LServer, LUser, Name).
|
||||
|
||||
sql_get_privacy_list_data_t(LUser, Name) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SName = ejabberd_sql:escape(Name),
|
||||
sql_queries:get_privacy_list_data_t(Username, SName).
|
||||
sql_queries:get_privacy_list_data_t(LUser, Name).
|
||||
|
||||
sql_get_privacy_list_data_by_id(ID, LServer) ->
|
||||
sql_queries:get_privacy_list_data_by_id(LServer, ID).
|
||||
|
||||
@@ -71,12 +71,8 @@ export(_Server) ->
|
||||
fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
|
||||
xml = Data})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
LXMLNS = ejabberd_sql:escape(XMLNS),
|
||||
SData =
|
||||
ejabberd_sql:escape(fxml:element_to_binary(Data)),
|
||||
sql_queries:set_private_data_sql(Username, LXMLNS,
|
||||
SData);
|
||||
SData = fxml:element_to_binary(Data),
|
||||
sql_queries:set_private_data_sql(LUser, XMLNS, SData);
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
@@ -260,7 +260,7 @@ parse_options(ServerHost, Opts) ->
|
||||
Port = gen_mod:get_opt(port, Opts,
|
||||
fun(P) when is_integer(P), P>0, P<65536 -> P end,
|
||||
7777),
|
||||
ACL = gen_mod:get_opt(access, Opts, fun(A) when is_atom(A) -> A end,
|
||||
ACL = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1,
|
||||
all),
|
||||
Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
|
||||
<<"SOCKS5 Bytestreams">>),
|
||||
|
||||
@@ -83,7 +83,7 @@ init([Socket, Host, Opts]) ->
|
||||
(anonymous) -> anonymous
|
||||
end, anonymous),
|
||||
Shaper = gen_mod:get_opt(shaper, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun acl:shaper_rules_validator/1,
|
||||
none),
|
||||
RecvBuf = gen_mod:get_opt(recbuf, Opts,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
|
||||
+24
-13
@@ -107,6 +107,8 @@
|
||||
nodeOptions/0,
|
||||
subOption/0,
|
||||
subOptions/0,
|
||||
pubOption/0,
|
||||
pubOptions/0,
|
||||
%%
|
||||
affiliation/0,
|
||||
subscription/0,
|
||||
@@ -1289,7 +1291,16 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
|
||||
[#xmlel{name = <<"item">>, attrs = ItemAttrs,
|
||||
children = Payload}] ->
|
||||
ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
|
||||
publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access);
|
||||
PubOpts = case [C || #xmlel{name = <<"publish-options">>,
|
||||
children = [C]} <- Rest] of
|
||||
[XEl] ->
|
||||
case jlib:parse_xdata_submit(XEl) of
|
||||
invalid -> [];
|
||||
Form -> Form
|
||||
end;
|
||||
_ -> []
|
||||
end,
|
||||
publish_item(Host, ServerHost, Node, From, ItemId, Payload, PubOpts, Access);
|
||||
[] ->
|
||||
{error,
|
||||
extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
|
||||
@@ -2185,10 +2196,10 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
|
||||
| {error, xmlel()}
|
||||
).
|
||||
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, all).
|
||||
publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, Access) ->
|
||||
publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, Access);
|
||||
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
|
||||
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, [], all).
|
||||
publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, PubOpts, Access) ->
|
||||
publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, PubOpts, Access);
|
||||
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access) ->
|
||||
Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
|
||||
Features = plugin_features(Host, Type),
|
||||
PublishFeature = lists:member(<<"publish">>, Features),
|
||||
@@ -2220,7 +2231,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
|
||||
extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
|
||||
true ->
|
||||
node_call(Host, Type, publish_item,
|
||||
[Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload])
|
||||
[Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, PubOpts])
|
||||
end
|
||||
end,
|
||||
Reply = [#xmlel{name = <<"pubsub">>,
|
||||
@@ -2281,7 +2292,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
|
||||
attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
|
||||
children = [#xmlel{name = <<"create">>,
|
||||
attrs = [{<<"node">>, NewNode}]}]}]} ->
|
||||
publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload);
|
||||
publish_item(Host, ServerHost, NewNode, Publisher, ItemId,
|
||||
Payload, PubOpts, Access);
|
||||
_ ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
end;
|
||||
@@ -3172,11 +3184,9 @@ subscription_to_string(_) -> <<"none">>.
|
||||
Host :: mod_pubsub:host())
|
||||
-> jid()
|
||||
).
|
||||
service_jid(Host) ->
|
||||
case Host of
|
||||
{U, S, _} -> {jid, U, S, <<>>, U, S, <<>>};
|
||||
_ -> {jid, <<>>, Host, <<>>, <<>>, Host, <<>>}
|
||||
end.
|
||||
service_jid(#jid{} = Jid) -> Jid;
|
||||
service_jid({U, S, R}) -> jid:make(U, S, R);
|
||||
service_jid(Host) -> jid:make(<<>>, Host, <<>>).
|
||||
|
||||
%% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean()
|
||||
%% LJID = jid()
|
||||
@@ -3525,7 +3535,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
|
||||
end, SubIDsByJID).
|
||||
|
||||
broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza({LUser, LServer, LResource}, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM),
|
||||
broadcast_stanza({LUser, LServer, <<>>}, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM),
|
||||
%% Handles implicit presence subscriptions
|
||||
SenderResource = user_resource(LUser, LServer, LResource),
|
||||
case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
|
||||
@@ -3863,6 +3873,7 @@ set_configure(Host, Node, From, Els, Lang) ->
|
||||
set_node,
|
||||
[N#pubsub_node{options = NewOpts}])
|
||||
of
|
||||
{result, Nidx} -> {result, ok};
|
||||
ok -> {result, ok};
|
||||
Err -> Err
|
||||
end;
|
||||
|
||||
+23
-6
@@ -74,7 +74,7 @@ stop(Host) ->
|
||||
|
||||
stream_feature_register(Acc, Host) ->
|
||||
AF = gen_mod:get_module_opt(Host, ?MODULE, access_from,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
all),
|
||||
case (AF /= none) and lists:keymember(<<"mechanisms">>, 2, Acc) of
|
||||
true ->
|
||||
@@ -126,7 +126,7 @@ process_iq(From, To,
|
||||
RTag = fxml:get_subtag(SubEl, <<"remove">>),
|
||||
Server = To#jid.lserver,
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
all),
|
||||
AllowRemove = allow ==
|
||||
acl:match_rule(Server, Access, From),
|
||||
@@ -386,6 +386,10 @@ try_set_password(User, Server, Password, IQ, SubEl,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
||||
end;
|
||||
error_preparing_password ->
|
||||
ErrText = <<"The password contains unacceptable characters">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]};
|
||||
false ->
|
||||
ErrText = <<"The password is too weak">>,
|
||||
IQ#iq{type = error,
|
||||
@@ -398,7 +402,7 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
|
||||
_ ->
|
||||
JID = jid:make(User, Server, <<"">>),
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
all),
|
||||
IPAccess = get_ip_access(Server),
|
||||
case {acl:match_rule(Server, Access, JID),
|
||||
@@ -440,7 +444,12 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end
|
||||
end;
|
||||
error_preparing_password ->
|
||||
remove_timeout(Source),
|
||||
ErrText = <<"The password contains unacceptable characters">>,
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
|
||||
false ->
|
||||
remove_timeout(Source),
|
||||
ErrText = <<"The password is too weak">>,
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
|
||||
end;
|
||||
@@ -519,7 +528,7 @@ check_from(#jid{user = <<"">>, server = <<"">>},
|
||||
allow;
|
||||
check_from(JID, Server) ->
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access_from,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
none),
|
||||
acl:match_rule(Server, Access, JID).
|
||||
|
||||
@@ -635,6 +644,14 @@ process_xdata_submit(El) ->
|
||||
end.
|
||||
|
||||
is_strong_password(Server, Password) ->
|
||||
case jid:resourceprep(Password) of
|
||||
PP when is_binary(PP) ->
|
||||
is_strong_password2(Server, Password);
|
||||
error ->
|
||||
error_preparing_password
|
||||
end.
|
||||
|
||||
is_strong_password2(Server, Password) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
case gen_mod:get_module_opt(LServer, ?MODULE, password_strength,
|
||||
fun(N) when is_number(N), N>=0 -> N end,
|
||||
@@ -719,13 +736,13 @@ check_ip_access(IPAddress, IPAccess) ->
|
||||
acl:match_rule(global, IPAccess, IPAddress).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_from) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
mod_opt_type(captcha_protected) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(ip_access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(password_strength) ->
|
||||
fun (N) when is_number(N), N >= 0 -> N end;
|
||||
|
||||
@@ -491,7 +491,7 @@ form_del_get(Host, Lang) ->
|
||||
%% {error, invalid_jid}
|
||||
register_account(Username, Host, Password) ->
|
||||
Access = gen_mod:get_module_opt(Host, mod_register, access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
fun(A) -> A end,
|
||||
all),
|
||||
case jid:make(Username, Host, <<"">>) of
|
||||
error -> {error, invalid_jid};
|
||||
|
||||
+7
-7
@@ -351,7 +351,7 @@ get_roster_by_jid_t(LUser, LServer, LJID) ->
|
||||
|
||||
try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
|
||||
#jid{server = Server} = From,
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) when is_atom(A) -> A end, all),
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all),
|
||||
case acl:match_rule(Server, Access, From) of
|
||||
deny ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
@@ -382,14 +382,14 @@ process_item_set(From, To,
|
||||
Item = get_roster_by_jid_t(LUser, LServer, LJID),
|
||||
Item1 = process_item_attrs_managed(Item, Attrs, Managed),
|
||||
Item2 = process_item_els(Item1, Els),
|
||||
case Item2#roster.subscription of
|
||||
remove -> del_roster_t(LUser, LServer, LJID);
|
||||
_ -> update_roster_t(LUser, LServer, LJID, Item2)
|
||||
end,
|
||||
send_itemset_to_managers(From, Item2, Managed),
|
||||
Item3 = ejabberd_hooks:run_fold(roster_process_item,
|
||||
LServer, Item2,
|
||||
[LServer]),
|
||||
case Item3#roster.subscription of
|
||||
remove -> del_roster_t(LUser, LServer, LJID);
|
||||
_ -> update_roster_t(LUser, LServer, LJID, Item3)
|
||||
end,
|
||||
send_itemset_to_managers(From, Item3, Managed),
|
||||
case roster_version_on_db(LServer) of
|
||||
true -> write_roster_version_t(LUser, LServer);
|
||||
false -> ok
|
||||
@@ -1235,7 +1235,7 @@ import(LServer, DBType, R) ->
|
||||
Mod:import(LServer, R).
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(managers) ->
|
||||
|
||||
+12
-29
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_roster_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_roster).
|
||||
|
||||
%% API
|
||||
@@ -20,6 +22,7 @@
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -34,15 +37,13 @@ read_roster_version(LUser, LServer) ->
|
||||
end.
|
||||
|
||||
write_roster_version(LUser, LServer, InTransaction, Ver) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
EVer = ejabberd_sql:escape(Ver),
|
||||
if InTransaction ->
|
||||
sql_queries:set_roster_version(Username, EVer);
|
||||
sql_queries:set_roster_version(LUser, Ver);
|
||||
true ->
|
||||
sql_queries:sql_transaction(
|
||||
LServer,
|
||||
fun () ->
|
||||
sql_queries:set_roster_version(Username, EVer)
|
||||
sql_queries:set_roster_version(LUser, Ver)
|
||||
end)
|
||||
end.
|
||||
|
||||
@@ -167,26 +168,20 @@ read_subscription_and_groups(LUser, LServer, LJID) ->
|
||||
|
||||
export(_Server) ->
|
||||
[{roster,
|
||||
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
|
||||
fun(Host, #roster{usj = {_LUser, LServer, _LJID}} = R)
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SJID = ejabberd_sql:escape(jid:to_string(LJID)),
|
||||
ItemVals = record_to_string(R),
|
||||
ItemGroups = groups_to_string(R),
|
||||
sql_queries:update_roster_sql(Username, SJID,
|
||||
ItemVals, ItemGroups);
|
||||
ItemVals = record_to_row(R),
|
||||
ItemGroups = R#roster.groups,
|
||||
sql_queries:update_roster_sql(ItemVals, ItemGroups);
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
{roster_version,
|
||||
fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SVer = ejabberd_sql:escape(Ver),
|
||||
[[<<"delete from roster_version where username='">>,
|
||||
Username, <<"';">>],
|
||||
[<<"insert into roster_version(username, version) values('">>,
|
||||
Username, <<"', '">>, SVer, <<"');">>]];
|
||||
[?SQL("delete from roster_version where username=%(LUser)s;"),
|
||||
?SQL("insert into roster_version(username, version) values("
|
||||
" %(LUser)s, %(Ver)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
@@ -294,15 +289,3 @@ record_to_row(
|
||||
none -> <<"N">>
|
||||
end,
|
||||
{LUser, SJID, Name, SSubscription, SAsk, AskMessage}.
|
||||
|
||||
groups_to_string(#roster{us = {User, _Server},
|
||||
jid = JID, groups = Groups}) ->
|
||||
Username = ejabberd_sql:escape(User),
|
||||
SJID =
|
||||
ejabberd_sql:escape(jid:to_string(jid:tolower(JID))),
|
||||
lists:foldl(fun (<<"">>, Acc) -> Acc;
|
||||
(Group, Acc) ->
|
||||
G = ejabberd_sql:escape(Group),
|
||||
[[Username, SJID, G] | Acc]
|
||||
end,
|
||||
[], Groups).
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_shared_roster_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_shared_roster).
|
||||
|
||||
%% API
|
||||
@@ -21,6 +23,7 @@
|
||||
-include("jlib.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
-include("mod_shared_roster.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -30,38 +33,39 @@ init(_Host, _Opts) ->
|
||||
|
||||
list_groups(Host) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
Host, [<<"select name from sr_group;">>]) of
|
||||
{selected, [<<"name">>], Rs} -> [G || [G] <- Rs];
|
||||
Host,
|
||||
?SQL("select @(name)s from sr_group")) of
|
||||
{selected, Rs} -> [G || {G} <- Rs];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
groups_with_opts(Host) ->
|
||||
case ejabberd_sql:sql_query(Host,
|
||||
[<<"select name, opts from sr_group;">>])
|
||||
case ejabberd_sql:sql_query(
|
||||
Host,
|
||||
?SQL("select @(name)s, @(opts)s from sr_group"))
|
||||
of
|
||||
{selected, [<<"name">>, <<"opts">>], Rs} ->
|
||||
{selected, Rs} ->
|
||||
[{G, mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(Opts))}
|
||||
|| [G, Opts] <- Rs];
|
||||
|| {G, Opts} <- Rs];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
create_group(Host, Group, Opts) ->
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
SOpts = ejabberd_sql:encode_term(Opts),
|
||||
SOpts = jlib:term_to_expr(Opts),
|
||||
F = fun () ->
|
||||
sql_queries:update_t(<<"sr_group">>,
|
||||
[<<"name">>, <<"opts">>], [SGroup, SOpts],
|
||||
[<<"name='">>, SGroup, <<"'">>])
|
||||
?SQL_UPSERT_T(
|
||||
"sr_group",
|
||||
["!name=%(Group)s",
|
||||
"opts=%(SOpts)s"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(Host, F).
|
||||
|
||||
delete_group(Host, Group) ->
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
F = fun () ->
|
||||
ejabberd_sql:sql_query_t([<<"delete from sr_group where name='">>,
|
||||
SGroup, <<"';">>]),
|
||||
ejabberd_sql:sql_query_t([<<"delete from sr_user where grp='">>,
|
||||
SGroup, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from sr_group where name=%(Group)s")),
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from sr_user where grp=%(Group)s"))
|
||||
end,
|
||||
case ejabberd_sql:sql_transaction(Host, F) of
|
||||
{atomic,{updated,_}} -> {atomic, ok};
|
||||
@@ -69,23 +73,21 @@ delete_group(Host, Group) ->
|
||||
end.
|
||||
|
||||
get_group_opts(Host, Group) ->
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
Host,
|
||||
[<<"select opts from sr_group where name='">>,
|
||||
SGroup, <<"';">>]) of
|
||||
{selected, [<<"opts">>], [[SOpts]]} ->
|
||||
?SQL("select @(opts)s from sr_group where name=%(Group)s")) of
|
||||
{selected, [{SOpts}]} ->
|
||||
mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(SOpts));
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
set_group_opts(Host, Group, Opts) ->
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
SOpts = ejabberd_sql:encode_term(Opts),
|
||||
SOpts = jlib:term_to_expr(Opts),
|
||||
F = fun () ->
|
||||
sql_queries:update_t(<<"sr_group">>,
|
||||
[<<"name">>, <<"opts">>], [SGroup, SOpts],
|
||||
[<<"name='">>, SGroup, <<"'">>])
|
||||
?SQL_UPSERT_T(
|
||||
"sr_group",
|
||||
["!name=%(Group)s",
|
||||
"opts=%(SOpts)s"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(Host, F).
|
||||
|
||||
@@ -93,21 +95,18 @@ get_user_groups(US, Host) ->
|
||||
SJID = make_jid_s(US),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
Host,
|
||||
[<<"select grp from sr_user where jid='">>,
|
||||
SJID, <<"';">>]) of
|
||||
{selected, [<<"grp">>], Rs} -> [G || [G] <- Rs];
|
||||
?SQL("select @(grp)s from sr_user where jid=%(SJID)s")) of
|
||||
{selected, Rs} -> [G || {G} <- Rs];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
get_group_explicit_users(Host, Group) ->
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
Host,
|
||||
[<<"select jid from sr_user where grp='">>,
|
||||
SGroup, <<"';">>]) of
|
||||
{selected, [<<"jid">>], Rs} ->
|
||||
?SQL("select @(jid)s from sr_user where grp=%(Group)s")) of
|
||||
{selected, Rs} ->
|
||||
lists:map(
|
||||
fun([JID]) ->
|
||||
fun({JID}) ->
|
||||
{U, S, _} = jid:tolower(jid:from_string(JID)),
|
||||
{U, S}
|
||||
end, Rs);
|
||||
@@ -119,43 +118,36 @@ get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
|
||||
SJID = make_jid_s(LUser, LServer),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"select grp from sr_user where jid='">>,
|
||||
SJID, <<"';">>]) of
|
||||
{selected, [<<"grp">>], Rs} ->
|
||||
?SQL("select @(grp)s from sr_user where jid=%(SJID)s")) of
|
||||
{selected, Rs} ->
|
||||
[{Group, proplists:get_value(Group, GroupsOpts, [])}
|
||||
|| [Group] <- Rs];
|
||||
|| {Group} <- Rs];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
is_user_in_group(US, Group, Host) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
case catch ejabberd_sql:sql_query(Host,
|
||||
[<<"select * from sr_user where jid='">>,
|
||||
SJID, <<"' and grp='">>, SGroup,
|
||||
<<"';">>]) of
|
||||
{selected, _, []} -> false;
|
||||
case catch ejabberd_sql:sql_query(
|
||||
Host,
|
||||
?SQL("select @(jid)s from sr_user where jid=%(SJID)s"
|
||||
" and grp=%(Group)s")) of
|
||||
{selected, []} -> false;
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
add_user_to_group(Host, US, Group) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
F = fun () ->
|
||||
sql_queries:update_t(<<"sr_user">>,
|
||||
[<<"jid">>, <<"grp">>], [SJID, SGroup],
|
||||
[<<"jid='">>, SJID, <<"' and grp='">>,
|
||||
SGroup, <<"'">>])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(Host, F).
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
?SQL("insert into sr_user(jid, grp) values ("
|
||||
"%(SJID)s, %(Group)s)")).
|
||||
|
||||
remove_user_from_group(Host, US, Group) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
F = fun () ->
|
||||
ejabberd_sql:sql_query_t([<<"delete from sr_user where jid='">>,
|
||||
SJID, <<"' and grp='">>, SGroup,
|
||||
<<"';">>]),
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from sr_user where jid=%(SJID)s and"
|
||||
" grp=%(Group)s")),
|
||||
ok
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(Host, F).
|
||||
@@ -164,26 +156,23 @@ export(_Server) ->
|
||||
[{sr_group,
|
||||
fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
|
||||
when LServer == Host ->
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
SOpts = ejabberd_sql:encode_term(Opts),
|
||||
[[<<"delete from sr_group where name='">>, Group, <<"';">>],
|
||||
[<<"insert into sr_group(name, opts) values ('">>,
|
||||
SGroup, <<"', '">>, SOpts, <<"');">>]];
|
||||
SOpts = jlib:term_to_expr(Opts),
|
||||
[?SQL("delete from sr_group where name=%(Group)s;"),
|
||||
?SQL("insert into sr_group(name, opts) values ("
|
||||
"%(Group)s, %(SOpts)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
{sr_user,
|
||||
fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
|
||||
when LServer == Host ->
|
||||
SGroup = ejabberd_sql:escape(Group),
|
||||
SJID = ejabberd_sql:escape(
|
||||
jid:to_string(
|
||||
jid:tolower(
|
||||
jid:make(U, S, <<"">>)))),
|
||||
[[<<"delete from sr_user where jid='">>, SJID,
|
||||
<<"'and grp='">>, Group, <<"';">>],
|
||||
[<<"insert into sr_user(jid, grp) values ('">>,
|
||||
SJID, <<"', '">>, SGroup, <<"');">>]];
|
||||
SJID = jid:to_string(
|
||||
jid:tolower(
|
||||
jid:make(U, S, <<"">>))),
|
||||
[?SQL("select @(jid)s from sr_user where jid=%(SJID)s"
|
||||
" and grp=%(Group)s;"),
|
||||
?SQL("insert into sr_user(jid, grp) values ("
|
||||
"%(SJID)s, %(Group)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
@@ -207,6 +196,6 @@ import(_, _) ->
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
make_jid_s(U, S) ->
|
||||
ejabberd_sql:escape(jid:to_string(jid:tolower(jid:make(U, S, <<"">>)))).
|
||||
jid:to_string(jid:tolower(jid:make(U, S, <<"">>))).
|
||||
|
||||
make_jid_s({U, S}) -> make_jid_s(U, S).
|
||||
|
||||
+28
-54
@@ -113,12 +113,10 @@ export(_Server) ->
|
||||
[{vcard,
|
||||
fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SVCARD =
|
||||
ejabberd_sql:escape(fxml:element_to_binary(VCARD)),
|
||||
[[<<"delete from vcard where username='">>, Username, <<"';">>],
|
||||
[<<"insert into vcard(username, vcard) values ('">>,
|
||||
Username, <<"', '">>, SVCARD, <<"');">>]];
|
||||
SVCARD = fxml:element_to_binary(VCARD),
|
||||
[?SQL("delete from vcard where username=%(LUser)s;"),
|
||||
?SQL("insert into vcard(username, vcard) values ("
|
||||
"%(LUser)s, %(SVCARD)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
@@ -135,52 +133,26 @@ export(_Server) ->
|
||||
orgname = OrgName, lorgname = LOrgName,
|
||||
orgunit = OrgUnit, lorgunit = LOrgUnit})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(User),
|
||||
LUsername = ejabberd_sql:escape(LUser),
|
||||
SFN = ejabberd_sql:escape(FN),
|
||||
SLFN = ejabberd_sql:escape(LFN),
|
||||
SFamily = ejabberd_sql:escape(Family),
|
||||
SLFamily = ejabberd_sql:escape(LFamily),
|
||||
SGiven = ejabberd_sql:escape(Given),
|
||||
SLGiven = ejabberd_sql:escape(LGiven),
|
||||
SMiddle = ejabberd_sql:escape(Middle),
|
||||
SLMiddle = ejabberd_sql:escape(LMiddle),
|
||||
SNickname = ejabberd_sql:escape(Nickname),
|
||||
SLNickname = ejabberd_sql:escape(LNickname),
|
||||
SBDay = ejabberd_sql:escape(BDay),
|
||||
SLBDay = ejabberd_sql:escape(LBDay),
|
||||
SCTRY = ejabberd_sql:escape(CTRY),
|
||||
SLCTRY = ejabberd_sql:escape(LCTRY),
|
||||
SLocality = ejabberd_sql:escape(Locality),
|
||||
SLLocality = ejabberd_sql:escape(LLocality),
|
||||
SEMail = ejabberd_sql:escape(EMail),
|
||||
SLEMail = ejabberd_sql:escape(LEMail),
|
||||
SOrgName = ejabberd_sql:escape(OrgName),
|
||||
SLOrgName = ejabberd_sql:escape(LOrgName),
|
||||
SOrgUnit = ejabberd_sql:escape(OrgUnit),
|
||||
SLOrgUnit = ejabberd_sql:escape(LOrgUnit),
|
||||
[[<<"delete from vcard_search where lusername='">>,
|
||||
LUsername, <<"';">>],
|
||||
[<<"insert into vcard_search( username, "
|
||||
"lusername, fn, lfn, family, lfamily, "
|
||||
" given, lgiven, middle, lmiddle, "
|
||||
"nickname, lnickname, bday, lbday, "
|
||||
"ctry, lctry, locality, llocality, "
|
||||
" email, lemail, orgname, lorgname, "
|
||||
"orgunit, lorgunit)values (">>,
|
||||
<<" '">>, Username, <<"', '">>, LUsername,
|
||||
<<"', '">>, SFN, <<"', '">>, SLFN,
|
||||
<<"', '">>, SFamily, <<"', '">>, SLFamily,
|
||||
<<"', '">>, SGiven, <<"', '">>, SLGiven,
|
||||
<<"', '">>, SMiddle, <<"', '">>, SLMiddle,
|
||||
<<"', '">>, SNickname, <<"', '">>, SLNickname,
|
||||
<<"', '">>, SBDay, <<"', '">>, SLBDay,
|
||||
<<"', '">>, SCTRY, <<"', '">>, SLCTRY,
|
||||
<<"', '">>, SLocality, <<"', '">>, SLLocality,
|
||||
<<"', '">>, SEMail, <<"', '">>, SLEMail,
|
||||
<<"', '">>, SOrgName, <<"', '">>, SLOrgName,
|
||||
<<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit,
|
||||
<<"');">>]];
|
||||
[?SQL("delete from vcard_search where lusername=%(LUser)s;"),
|
||||
?SQL("insert into vcard_search(username,"
|
||||
" lusername, fn, lfn, family, lfamily,"
|
||||
" given, lgiven, middle, lmiddle,"
|
||||
" nickname, lnickname, bday, lbday,"
|
||||
" ctry, lctry, locality, llocality,"
|
||||
" email, lemail, orgname, lorgname,"
|
||||
" orgunit, lorgunit) values ("
|
||||
" %(LUser)s, %(User)s,"
|
||||
" %(FN)s, %(LFN)s,"
|
||||
" %(Family)s, %(LFamily)s,"
|
||||
" %(Given)s, %(LGiven)s,"
|
||||
" %(Middle)s, %(LMiddle)s,"
|
||||
" %(Nickname)s, %(LNickname)s,"
|
||||
" %(BDay)s, %(LBDay)s,"
|
||||
" %(CTRY)s, %(LCTRY)s,"
|
||||
" %(Locality)s, %(LLocality)s,"
|
||||
" %(EMail)s, %(LEMail)s,"
|
||||
" %(OrgName)s, %(LOrgName)s,"
|
||||
" %(OrgUnit)s, %(LOrgUnit)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
@@ -255,9 +227,11 @@ make_val(Match, Field, Val) ->
|
||||
Condition = case str:suffix(<<"*">>, Val) of
|
||||
true ->
|
||||
Val1 = str:substr(Val, 1, byte_size(Val) - 1),
|
||||
SVal = <<(ejabberd_sql:escape_like(Val1))/binary,
|
||||
SVal = <<(ejabberd_sql:escape(
|
||||
ejabberd_sql:escape_like_arg_circumflex(
|
||||
Val1)))/binary,
|
||||
"%">>,
|
||||
[Field, <<" LIKE '">>, SVal, <<"'">>];
|
||||
[Field, <<" LIKE '">>, SVal, <<"' ESCAPE '^'">>];
|
||||
_ ->
|
||||
SVal = ejabberd_sql:escape(Val),
|
||||
[Field, <<" = '">>, SVal, <<"'">>]
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_vcard_xupdate_riak).
|
||||
|
||||
-behaviour(mod_vcard_xupdate).
|
||||
|
||||
%% API
|
||||
-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
|
||||
|
||||
|
||||
@@ -8,11 +8,16 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_vcard_xupdate_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(mod_vcard_xupdate).
|
||||
|
||||
%% API
|
||||
-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2,
|
||||
import/1, export/1]).
|
||||
|
||||
-include("mod_vcard_xupdate.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -21,46 +26,38 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
add_xupdate(LUser, LServer, Hash) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SHash = ejabberd_sql:escape(Hash),
|
||||
F = fun () ->
|
||||
sql_queries:update_t(<<"vcard_xupdate">>,
|
||||
[<<"username">>, <<"hash">>],
|
||||
[Username, SHash],
|
||||
[<<"username='">>, Username, <<"'">>])
|
||||
?SQL_UPSERT_T(
|
||||
"vcard_xupdate",
|
||||
["!username=%(LUser)s",
|
||||
"hash=%(Hash)s"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
get_xupdate(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
case ejabberd_sql:sql_query(LServer,
|
||||
[<<"select hash from vcard_xupdate where "
|
||||
"username='">>,
|
||||
Username, <<"';">>])
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(hash)s from vcard_xupdate where"
|
||||
" username=%(LUser)s"))
|
||||
of
|
||||
{selected, [<<"hash">>], [[Hash]]} -> Hash;
|
||||
_ -> undefined
|
||||
{selected, [{Hash}]} -> Hash;
|
||||
_ -> undefined
|
||||
end.
|
||||
|
||||
remove_xupdate(LUser, LServer) ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
F = fun () ->
|
||||
ejabberd_sql:sql_query_t([<<"delete from vcard_xupdate where username='">>,
|
||||
Username, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from vcard_xupdate where username=%(LUser)s"))
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
export(_Server) ->
|
||||
[{vcard_xupdate,
|
||||
fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_sql:escape(LUser),
|
||||
SHash = ejabberd_sql:escape(Hash),
|
||||
[[<<"delete from vcard_xupdate where username='">>,
|
||||
Username, <<"';">>],
|
||||
[<<"insert into vcard_xupdate(username, "
|
||||
"hash) values ('">>,
|
||||
Username, <<"', '">>, SHash, <<"');">>]];
|
||||
when LServer == Host ->
|
||||
[?SQL("delete from vcard_xupdate where username=%(LUser)s;"),
|
||||
?SQL("insert into vcard_xupdate(username, hash) values ("
|
||||
"%(LUser)s, %(Hash)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
+4
-3
@@ -33,7 +33,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -102,8 +102,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+4
-3
@@ -33,7 +33,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+3
-3
@@ -33,7 +33,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -72,7 +72,7 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
case nodetree_dag:get_node(Nidx) of
|
||||
#pubsub_node{options = Options} ->
|
||||
case find_opt(node_type, Options) of
|
||||
@@ -82,7 +82,7 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
?ERR_EXTENDED(?ERRT_NOT_ALLOWED(?MYLANG, Txt), <<"publish">>)};
|
||||
_ ->
|
||||
node_hometree:publish_item(Nidx, Publisher, Model,
|
||||
MaxItems, ItemId, Payload)
|
||||
MaxItems, ItemId, Payload, PubOpts)
|
||||
end;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -99,13 +99,14 @@ subscribe_node(_Nidx, _Sender, _Subscriber, _AccessModel, _SendLast, _PresenceSu
|
||||
unsubscribe_node(_Nidx, _Sender, _Subscriber, _SubId) ->
|
||||
{error, ?ERR_FORBIDDEN}.
|
||||
|
||||
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
|
||||
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
|
||||
PubOpts) ->
|
||||
case nodetree_tree:get_node(Nidx) of
|
||||
#pubsub_node{nodeid = {Host, Node}} ->
|
||||
lists:foreach(fun (SubNode) ->
|
||||
node_hometree:publish_item(SubNode#pubsub_node.id,
|
||||
Publisher, PublishModel, MaxItems,
|
||||
ItemId, Payload)
|
||||
ItemId, Payload, PubOpts)
|
||||
end,
|
||||
nodetree_tree:get_subnodes(Host, Node, Publisher)),
|
||||
{result, {default, broadcast, []}};
|
||||
|
||||
+3
-2
@@ -39,7 +39,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -345,7 +345,8 @@ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
|
||||
%% to completly disable persistance.</li></ul>
|
||||
%% </p>
|
||||
%% <p>In the default plugin module, the record is unchanged.</p>
|
||||
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
|
||||
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
|
||||
_PubOpts) ->
|
||||
SubKey = jid:tolower(Publisher),
|
||||
GenKey = jid:remove_resource(SubKey),
|
||||
GenState = get_state(Nidx, GenKey),
|
||||
|
||||
+194
-171
@@ -33,13 +33,16 @@
|
||||
-behaviour(gen_pubsub_node).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -51,9 +54,11 @@
|
||||
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
|
||||
|
||||
-export([decode_jid/1, encode_jid/1,
|
||||
encode_jid_like/1,
|
||||
decode_affiliation/1, decode_subscriptions/1,
|
||||
encode_affiliation/1, encode_subscriptions/1,
|
||||
encode_host/1]).
|
||||
encode_host/1,
|
||||
encode_host_like/1]).
|
||||
|
||||
init(_Host, _ServerHost, _Opts) ->
|
||||
%%pubsub_subscription_sql:init(),
|
||||
@@ -73,19 +78,25 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
|
||||
|
||||
create_node(Nidx, Owner) ->
|
||||
{_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)),
|
||||
State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner},
|
||||
catch ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
|
||||
"values(">>, state_to_raw(Nidx, State), <<");">>]),
|
||||
J = encode_jid(OwnerKey),
|
||||
A = encode_affiliation(owner),
|
||||
S = encode_subscriptions([]),
|
||||
catch ejabberd_sql:sql_query_t(
|
||||
?SQL("insert into pubsub_state("
|
||||
"nodeid, jid, affiliation, subscriptions) "
|
||||
"values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")),
|
||||
{result, {default, broadcast}}.
|
||||
|
||||
delete_node(Nodes) ->
|
||||
Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
|
||||
Subscriptions = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select jid, subscriptions "
|
||||
"from pubsub_state where nodeid='">>, Nidx, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(jid)s, @(subscriptions)s "
|
||||
"from pubsub_state where nodeid=%(Nidx)d"))
|
||||
of
|
||||
{selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
|
||||
[{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems];
|
||||
{selected, RItems} ->
|
||||
[{decode_jid(SJID), decode_subscriptions(Subs)} ||
|
||||
{SJID, Subs} <- RItems];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
@@ -215,7 +226,8 @@ delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscripti
|
||||
_ -> update_subscription(Nidx, SubKey, NewSubs)
|
||||
end.
|
||||
|
||||
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
|
||||
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
|
||||
_PubOpts) ->
|
||||
SubKey = jid:tolower(Publisher),
|
||||
GenKey = jid:remove_resource(SubKey),
|
||||
{Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
|
||||
@@ -296,13 +308,14 @@ get_entity_affiliations(Host, Owner) ->
|
||||
H = encode_host(Host),
|
||||
J = encode_jid(GenKey),
|
||||
Reply = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select node, type, i.nodeid, affiliation "
|
||||
"from pubsub_state i, pubsub_node n where "
|
||||
"i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(node)s, @(type)s, @(i.nodeid)d, @(affiliation)s "
|
||||
"from pubsub_state i, pubsub_node n where "
|
||||
"i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s"))
|
||||
of
|
||||
{selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} ->
|
||||
[{nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
|
||||
|| [N, T, I, A] <- RItems];
|
||||
{selected, RItems} ->
|
||||
[{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), decode_affiliation(A)}
|
||||
|| {N, T, I, A} <- RItems];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
@@ -310,11 +323,12 @@ get_entity_affiliations(Host, Owner) ->
|
||||
|
||||
get_node_affiliations(Nidx) ->
|
||||
Reply = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select jid, affiliation from pubsub_state "
|
||||
"where nodeid='">>, Nidx, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(jid)s, @(affiliation)s from pubsub_state "
|
||||
"where nodeid=%(Nidx)d"))
|
||||
of
|
||||
{selected, [<<"jid">>, <<"affiliation">>], RItems} ->
|
||||
[{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems];
|
||||
{selected, RItems} ->
|
||||
[{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
@@ -325,10 +339,11 @@ get_affiliation(Nidx, Owner) ->
|
||||
GenKey = jid:remove_resource(SubKey),
|
||||
J = encode_jid(GenKey),
|
||||
Reply = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select affiliation from pubsub_state "
|
||||
"where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(affiliation)s from pubsub_state "
|
||||
"where nodeid=%(Nidx)d and jid=%(J)s"))
|
||||
of
|
||||
{selected, [<<"affiliation">>], [[A]]} ->
|
||||
{selected, [{A}]} ->
|
||||
decode_affiliation(A);
|
||||
_ ->
|
||||
none
|
||||
@@ -350,21 +365,26 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
H = encode_host(Host),
|
||||
SJ = encode_jid(SubKey),
|
||||
GJ = encode_jid(GenKey),
|
||||
Query = case SubKey of
|
||||
GenKey ->
|
||||
[<<"select node, type, i.nodeid, jid, subscriptions "
|
||||
"from pubsub_state i, pubsub_node n "
|
||||
"where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
|
||||
_ ->
|
||||
[<<"select node, type, i.nodeid, jid, subscriptions "
|
||||
"from pubsub_state i, pubsub_node n "
|
||||
"where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
|
||||
end,
|
||||
GJLike = <<(encode_jid_like(GenKey))/binary, "%">>,
|
||||
Query =
|
||||
case SubKey of
|
||||
GenKey ->
|
||||
?SQL("select @(node)s, @(type)s, @(i.nodeid)d,"
|
||||
" @(jid)s, @(subscriptions)s "
|
||||
"from pubsub_state i, pubsub_node n "
|
||||
"where i.nodeid = n.nodeid and jid like %(GJLike)s"
|
||||
" escape '^' and host=%(H)s");
|
||||
_ ->
|
||||
?SQL("select @(node)s, @(type)s, @(i.nodeid)d,"
|
||||
" @(jid)s, @(subscriptions)s "
|
||||
"from pubsub_state i, pubsub_node n "
|
||||
"where i.nodeid = n.nodeid and jid in"
|
||||
" (%(SJ)s, %(GJ)s) and host=%(H)s")
|
||||
end,
|
||||
Reply = case catch ejabberd_sql:sql_query_t(Query) of
|
||||
{selected,
|
||||
[<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
|
||||
lists:foldl(fun ([N, T, I, J, S], Acc) ->
|
||||
Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]),
|
||||
{selected, RItems} ->
|
||||
lists:foldl(fun ({N, T, I, J, S}, Acc) ->
|
||||
Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
|
||||
Jid = decode_jid(J),
|
||||
case decode_subscriptions(S) of
|
||||
[] ->
|
||||
@@ -399,23 +419,28 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
|
||||
H = encode_host(Host),
|
||||
SJ = encode_jid(SubKey),
|
||||
GJ = encode_jid(GenKey),
|
||||
Query = case SubKey of
|
||||
GenKey ->
|
||||
[<<"select node, type, i.nodeid, jid, subscriptions "
|
||||
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
|
||||
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
|
||||
"and val='on_sub_and_presence' and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
|
||||
_ ->
|
||||
[<<"select node, type, i.nodeid, jid, subscriptions "
|
||||
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
|
||||
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
|
||||
"and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
|
||||
GJLike = <<(encode_jid_like(GenKey))/binary, "%">>,
|
||||
Query =
|
||||
case SubKey of
|
||||
GenKey ->
|
||||
?SQL("select @(node)s, @(type)s, @(i.nodeid)d,"
|
||||
" @(jid)s, @(subscriptions)s "
|
||||
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
|
||||
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
|
||||
"and val='on_sub_and_presence' and jid like %(GJLike)s"
|
||||
" escape '^' and host=%(H)s");
|
||||
_ ->
|
||||
?SQL("select @(node)s, @(type)s, @(i.nodeid)d,"
|
||||
" @(jid)s, @(subscriptions)s "
|
||||
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
|
||||
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
|
||||
"and val='on_sub_and_presence' and"
|
||||
" jid in (%(SJ)s, %(GJ)s) and host=%(H)s")
|
||||
end,
|
||||
Reply = case catch ejabberd_sql:sql_query_t(Query) of
|
||||
{selected,
|
||||
[<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
|
||||
lists:foldl(fun ([N, T, I, J, S], Acc) ->
|
||||
Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]),
|
||||
{selected, RItems} ->
|
||||
lists:foldl(fun ({N, T, I, J, S}, Acc) ->
|
||||
Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
|
||||
Jid = decode_jid(J),
|
||||
case decode_subscriptions(S) of
|
||||
[] ->
|
||||
@@ -435,11 +460,12 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
|
||||
|
||||
get_node_subscriptions(Nidx) ->
|
||||
Reply = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select jid, subscriptions from pubsub_state "
|
||||
"where nodeid='">>, Nidx, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(jid)s, @(subscriptions)s from pubsub_state "
|
||||
"where nodeid=%(Nidx)d"))
|
||||
of
|
||||
{selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
|
||||
lists:foldl(fun ([J, S], Acc) ->
|
||||
{selected, RItems} ->
|
||||
lists:foldl(fun ({J, S}, Acc) ->
|
||||
Jid = decode_jid(J),
|
||||
case decode_subscriptions(S) of
|
||||
[] ->
|
||||
@@ -461,10 +487,11 @@ get_subscriptions(Nidx, Owner) ->
|
||||
SubKey = jid:tolower(Owner),
|
||||
J = encode_jid(SubKey),
|
||||
Reply = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select subscriptions from pubsub_state where "
|
||||
"nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(subscriptions)s from pubsub_state"
|
||||
" where nodeid=%(Nidx)d and jid=%(J)s"))
|
||||
of
|
||||
{selected, [<<"subscriptions">>], [[S]]} ->
|
||||
{selected, [{S}]} ->
|
||||
decode_subscriptions(S);
|
||||
_ ->
|
||||
[]
|
||||
@@ -561,15 +588,16 @@ get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}
|
||||
|
||||
get_states(Nidx) ->
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions "
|
||||
"from pubsub_state where nodeid='">>, Nidx, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
|
||||
"from pubsub_state where nodeid=%(Nidx)d"))
|
||||
of
|
||||
{selected,
|
||||
[<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} ->
|
||||
{selected, RItems} ->
|
||||
{result,
|
||||
lists:map(fun ([SJID, Aff, Subs]) ->
|
||||
#pubsub_state{stateid = {decode_jid(SJID), Nidx},
|
||||
items = itemids(Nidx, SJID),
|
||||
lists:map(fun ({SJID, Aff, Subs}) ->
|
||||
JID = decode_jid(SJID),
|
||||
#pubsub_state{stateid = {JID, Nidx},
|
||||
items = itemids(Nidx, JID),
|
||||
affiliation = decode_affiliation(Aff),
|
||||
subscriptions = decode_subscriptions(Subs)}
|
||||
end,
|
||||
@@ -591,11 +619,12 @@ get_state(Nidx, JID) ->
|
||||
get_state_without_itemids(Nidx, JID) ->
|
||||
J = encode_jid(JID),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions "
|
||||
"from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
|
||||
"from pubsub_state "
|
||||
"where nodeid=%(Nidx)d and jid=%(J)s"))
|
||||
of
|
||||
{selected,
|
||||
[<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} ->
|
||||
{selected, [{SJID, Aff, Subs}]} ->
|
||||
#pubsub_state{stateid = {decode_jid(SJID), Nidx},
|
||||
affiliation = decode_affiliation(Aff),
|
||||
subscriptions = decode_subscriptions(Subs)};
|
||||
@@ -612,24 +641,20 @@ set_state(Nidx, State) ->
|
||||
J = encode_jid(JID),
|
||||
S = encode_subscriptions(State#pubsub_state.subscriptions),
|
||||
A = encode_affiliation(State#pubsub_state.affiliation),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
|
||||
<<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
|
||||
of
|
||||
{updated, 1} ->
|
||||
ok;
|
||||
_ ->
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
|
||||
"values('">>,
|
||||
Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>])
|
||||
end,
|
||||
?SQL_UPSERT_T(
|
||||
"pubsub_state",
|
||||
["!nodeid=%(Nidx)d",
|
||||
"!jid=%(J)s",
|
||||
"affiliation=%(A)s",
|
||||
"subscriptions=%(S)s"
|
||||
]),
|
||||
ok.
|
||||
|
||||
del_state(Nidx, JID) ->
|
||||
J = encode_jid(JID),
|
||||
catch ejabberd_sql:sql_query_t([<<"delete from pubsub_state where jid='">>,
|
||||
J, <<"' and nodeid='">>, Nidx, <<"';">>]),
|
||||
catch ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from pubsub_state"
|
||||
" where jid=%(J)s and nodeid=%(Nidx)d")),
|
||||
ok.
|
||||
|
||||
%get_items(Nidx, _From) ->
|
||||
@@ -647,12 +672,12 @@ del_state(Nidx, JID) ->
|
||||
|
||||
get_items(Nidx, From, none) ->
|
||||
MaxItems = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select val from pubsub_node_option "
|
||||
"where nodeid='">>, Nidx, <<"' and name='max_items';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(val)s from pubsub_node_option "
|
||||
"where nodeid=%(Nidx)d and name='max_items'"))
|
||||
of
|
||||
{selected, [<<"val">>], [[Value]]} ->
|
||||
Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
|
||||
element(2, erl_parse:parse_term(Tokens));
|
||||
{selected, [{Value}]} ->
|
||||
jlib:expr_to_term(Value);
|
||||
_ ->
|
||||
?MAXITEMS
|
||||
end,
|
||||
@@ -661,20 +686,19 @@ get_items(Nidx, _From,
|
||||
#rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
|
||||
Max = ejabberd_sql:escape(jlib:i2l(M)),
|
||||
{Way, Order} = case Direction of
|
||||
% aft -> {<<"<">>, <<"desc">>};
|
||||
% before when I == <<>> -> {<<"is not">>, <<"asc">>};
|
||||
% before -> {<<">">>, <<"asc">>};
|
||||
% _ when IncIndex =/= undefined ->
|
||||
% {<<"<">>, <<"desc">>}; % using index
|
||||
_ ->
|
||||
{<<"is not">>, <<"desc">>}% Can be better
|
||||
aft when I == <<>> -> {<<"is not">>, <<"desc">>};
|
||||
aft -> {<<"<">>, <<"desc">>};
|
||||
before when I == <<>> -> {<<"is not">>, <<"asc">>};
|
||||
before -> {<<">">>, <<"asc">>};
|
||||
_ -> {<<"is not">>, <<"desc">>}
|
||||
end,
|
||||
SNidx = integer_to_binary(Nidx),
|
||||
[AttrName, Id] = case I of
|
||||
undefined when IncIndex =/= undefined ->
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi "
|
||||
"where exists ( select count(*) as count1 "
|
||||
"from pubsub_item where nodeid='">>, Nidx,
|
||||
"from pubsub_item where nodeid='">>, SNidx,
|
||||
<<"' and modification > pi.modification having count1 = ">>,
|
||||
ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>])
|
||||
of
|
||||
@@ -692,7 +716,7 @@ get_items(Nidx, _From,
|
||||
[A, <<"'", B/binary, "'">>]
|
||||
end,
|
||||
Count = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
|
||||
ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, SNidx, <<"';">>])
|
||||
of
|
||||
{selected, [_], [[C]]} -> C;
|
||||
_ -> <<"0">>
|
||||
@@ -701,13 +725,13 @@ get_items(Nidx, _From,
|
||||
ejabberd_sql:sql_query_t(
|
||||
[<<"select top ">>, jlib:i2l(Max),
|
||||
<<" itemid, publisher, creation, modification, payload "
|
||||
"from pubsub_item where nodeid='">>, Nidx,
|
||||
"from pubsub_item where nodeid='">>, SNidx,
|
||||
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
|
||||
AttrName, <<" ">>, Order, <<";">>]);
|
||||
(_, _) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
[<<"select itemid, publisher, creation, modification, payload "
|
||||
"from pubsub_item where nodeid='">>, Nidx,
|
||||
"from pubsub_item where nodeid='">>, SNidx,
|
||||
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
|
||||
AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
|
||||
end,
|
||||
@@ -718,7 +742,7 @@ get_items(Nidx, _From,
|
||||
[[_, _, _, F, _]|_] ->
|
||||
Index = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item "
|
||||
"where nodeid='">>, Nidx, <<"' and ">>,
|
||||
"where nodeid='">>, SNidx, <<"' and ">>,
|
||||
AttrName, <<" > '">>, F, <<"';">>])
|
||||
of
|
||||
%{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
|
||||
@@ -770,16 +794,17 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
|
||||
|
||||
get_last_items(Nidx, _From, Count) ->
|
||||
Limit = jlib:i2l(Count),
|
||||
SNidx = integer_to_binary(Nidx),
|
||||
Query = fun(mssql, _) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
[<<"select top ">>, Limit,
|
||||
<<" itemid, publisher, creation, modification, payload "
|
||||
"from pubsub_item where nodeid='">>, Nidx,
|
||||
"from pubsub_item where nodeid='">>, SNidx,
|
||||
<<"' order by modification desc ;">>]);
|
||||
(_, _) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
[<<"select itemid, publisher, creation, modification, payload "
|
||||
"from pubsub_item where nodeid='">>, Nidx,
|
||||
"from pubsub_item where nodeid='">>, SNidx,
|
||||
<<"' order by modification desc limit ">>, Limit, <<";">>])
|
||||
end,
|
||||
case catch ejabberd_sql:sql_query_t(Query) of
|
||||
@@ -791,16 +816,14 @@ get_last_items(Nidx, _From, Count) ->
|
||||
end.
|
||||
|
||||
get_item(Nidx, ItemId) ->
|
||||
I = ejabberd_sql:escape(ItemId),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, "
|
||||
"modification, payload from pubsub_item "
|
||||
"where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
|
||||
case catch ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(itemid)s, @(publisher)s, @(creation)s,"
|
||||
" @(modification)s, @(payload)s from pubsub_item"
|
||||
" where nodeid=%(Nidx)d and itemid=%(ItemId)s"))
|
||||
of
|
||||
{selected,
|
||||
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
|
||||
{selected, [RItem]} ->
|
||||
{result, raw_to_item(Nidx, RItem)};
|
||||
{selected, _, []} ->
|
||||
{selected, []} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
{'EXIT', _} ->
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
|
||||
@@ -839,37 +862,31 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub
|
||||
|
||||
set_item(Item) ->
|
||||
{ItemId, Nidx} = Item#pubsub_item.itemid,
|
||||
I = ejabberd_sql:escape(ItemId),
|
||||
{C, _} = Item#pubsub_item.creation,
|
||||
{M, JID} = Item#pubsub_item.modification,
|
||||
P = encode_jid(JID),
|
||||
Payload = Item#pubsub_item.payload,
|
||||
XML = ejabberd_sql:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)),
|
||||
XML = str:join([fxml:element_to_binary(X) || X<-Payload], <<>>),
|
||||
S = fun ({T1, T2, T3}) ->
|
||||
str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
|
||||
end,
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"update pubsub_item set publisher='">>, P,
|
||||
<<"', modification='">>, S(M),
|
||||
<<"', payload='">>, XML,
|
||||
<<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
|
||||
of
|
||||
{updated, 1} ->
|
||||
ok;
|
||||
_ ->
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
|
||||
"publisher, creation, modification, payload) "
|
||||
"values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
|
||||
<<"', '">>, S(C), <<"', '">>, S(M),
|
||||
<<"', '">>, XML, <<"');">>])
|
||||
end,
|
||||
SM = S(M),
|
||||
SC = S(C),
|
||||
?SQL_UPSERT_T(
|
||||
"pubsub_item",
|
||||
["!nodeid=%(Nidx)d",
|
||||
"!itemid=%(ItemId)s",
|
||||
"publisher=%(P)s",
|
||||
"modification=%(SM)s",
|
||||
"payload=%(XML)s",
|
||||
"-creation=%(SC)s"
|
||||
]),
|
||||
ok.
|
||||
|
||||
del_item(Nidx, ItemId) ->
|
||||
I = ejabberd_sql:escape(ItemId),
|
||||
catch ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid='">>,
|
||||
I, <<"' and nodeid='">>, Nidx, <<"';">>]).
|
||||
catch ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from pubsub_item where itemid=%(ItemId)s"
|
||||
" and nodeid=%(Nidx)d")).
|
||||
|
||||
del_items(_, []) ->
|
||||
ok;
|
||||
@@ -877,9 +894,10 @@ del_items(Nidx, [ItemId]) ->
|
||||
del_item(Nidx, ItemId);
|
||||
del_items(Nidx, ItemIds) ->
|
||||
I = str:join([[<<"'">>, ejabberd_sql:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
|
||||
SNidx = integer_to_binary(Nidx),
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
|
||||
I, <<") and nodeid='">>, Nidx, <<"';">>]).
|
||||
I, <<") and nodeid='">>, SNidx, <<"';">>]).
|
||||
|
||||
get_item_name(_Host, _Node, Id) ->
|
||||
Id.
|
||||
@@ -899,16 +917,16 @@ first_in_list(Pred, [H | T]) ->
|
||||
_ -> first_in_list(Pred, T)
|
||||
end.
|
||||
|
||||
itemids(Nidx, {U, S, R}) ->
|
||||
itemids(Nidx, encode_jid({U, S, R}));
|
||||
itemids(Nidx, SJID) ->
|
||||
itemids(Nidx, {_U, _S, _R} = JID) ->
|
||||
SJID = <<(ejabberd_sql:escape(encode_jid_like(JID)))/binary, "%">>,
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select itemid from pubsub_item where "
|
||||
"nodeid='">>, Nidx, <<"' and publisher like '">>, SJID,
|
||||
<<"%' order by modification desc;">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(itemid)s from pubsub_item where "
|
||||
"nodeid=%(Nidx)d and publisher like %(SJID)s escape '^' "
|
||||
"order by modification desc"))
|
||||
of
|
||||
{selected, [<<"itemid">>], RItems} ->
|
||||
[ItemId || [ItemId] <- RItems];
|
||||
{selected, RItems} ->
|
||||
[ItemId || {ItemId} <- RItems];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
@@ -916,11 +934,11 @@ itemids(Nidx, SJID) ->
|
||||
select_affiliation_subscriptions(Nidx, JID) ->
|
||||
J = encode_jid(JID),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select affiliation,subscriptions from "
|
||||
"pubsub_state where nodeid='">>,
|
||||
Nidx, <<"' and jid='">>, J, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(affiliation)s, @(subscriptions)s from "
|
||||
" pubsub_state where nodeid=%(Nidx)d and jid=%(J)s"))
|
||||
of
|
||||
{selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} ->
|
||||
{selected, [{A, S}]} ->
|
||||
{decode_affiliation(A), decode_subscriptions(S)};
|
||||
_ ->
|
||||
{none, []}
|
||||
@@ -936,33 +954,24 @@ select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
|
||||
update_affiliation(Nidx, JID, Affiliation) ->
|
||||
J = encode_jid(JID),
|
||||
A = encode_affiliation(Affiliation),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"update pubsub_state set affiliation='">>,
|
||||
A, <<"' where nodeid='">>, Nidx,
|
||||
<<"' and jid='">>, J, <<"';">>])
|
||||
of
|
||||
{updated, 1} ->
|
||||
ok;
|
||||
_ ->
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
|
||||
"values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
|
||||
end.
|
||||
?SQL_UPSERT_T(
|
||||
"pubsub_state",
|
||||
["!nodeid=%(Nidx)d",
|
||||
"!jid=%(J)s",
|
||||
"affiliation=%(A)s",
|
||||
"-subscriptions=''"
|
||||
]).
|
||||
|
||||
update_subscription(Nidx, JID, Subscription) ->
|
||||
J = encode_jid(JID),
|
||||
S = encode_subscriptions(Subscription),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
|
||||
<<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
|
||||
of
|
||||
{updated, 1} ->
|
||||
ok;
|
||||
_ ->
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
|
||||
"values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
|
||||
end.
|
||||
?SQL_UPSERT_T(
|
||||
"pubsub_state",
|
||||
["!nodeid=%(Nidx)d",
|
||||
"!jid=%(J)s",
|
||||
"subscriptions=%(S)s",
|
||||
"-affiliation='n'"
|
||||
]).
|
||||
|
||||
-spec(decode_jid/1 ::
|
||||
( SJID :: binary())
|
||||
@@ -1009,14 +1018,26 @@ decode_subscriptions(Subscriptions) ->
|
||||
-> binary()
|
||||
).
|
||||
encode_jid(JID) ->
|
||||
ejabberd_sql:escape(jid:to_string(JID)).
|
||||
jid:to_string(JID).
|
||||
|
||||
-spec(encode_jid_like/1 :: (JID :: ljid()) -> binary()).
|
||||
encode_jid_like(JID) ->
|
||||
ejabberd_sql:escape_like_arg_circumflex(jid:to_string(JID)).
|
||||
|
||||
-spec(encode_host/1 ::
|
||||
( Host :: host())
|
||||
-> binary()
|
||||
).
|
||||
encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
|
||||
encode_host(Host) -> ejabberd_sql:escape(Host).
|
||||
encode_host(Host) -> Host.
|
||||
|
||||
-spec(encode_host_like/1 ::
|
||||
( Host :: host())
|
||||
-> binary()
|
||||
).
|
||||
encode_host_like({_U, _S, _R} = LJID) -> ejabberd_sql:escape(encode_jid_like(LJID));
|
||||
encode_host_like(Host) ->
|
||||
ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(Host)).
|
||||
|
||||
-spec(encode_affiliation/1 ::
|
||||
( Arg :: atom())
|
||||
@@ -1050,12 +1071,14 @@ encode_subscriptions(Subscriptions) ->
|
||||
|
||||
state_to_raw(Nidx, State) ->
|
||||
{JID, _} = State#pubsub_state.stateid,
|
||||
J = encode_jid(JID),
|
||||
J = ejabberd_sql:escape(encode_jid(JID)),
|
||||
A = encode_affiliation(State#pubsub_state.affiliation),
|
||||
S = encode_subscriptions(State#pubsub_state.subscriptions),
|
||||
[<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>].
|
||||
|
||||
raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
|
||||
raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML});
|
||||
raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML}) ->
|
||||
JID = decode_jid(SJID),
|
||||
ToTime = fun (Str) ->
|
||||
[T1, T2, T3] = str:tokens(Str, <<":">>),
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -99,8 +99,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -77,8 +77,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+4
-3
@@ -46,7 +46,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -116,8 +116,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_pep:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_pep:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_pep:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_pep:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+4
-3
@@ -14,7 +14,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -89,8 +89,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload,
|
||||
PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -89,8 +89,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+4
-3
@@ -33,7 +33,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -99,8 +99,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+4
-3
@@ -37,7 +37,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -130,8 +130,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
{result, _} -> {result, []}
|
||||
end.
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+16
-13
@@ -37,7 +37,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -86,8 +86,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
{result, _} -> {result, []}
|
||||
end.
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
@@ -114,20 +115,21 @@ set_affiliation(Nidx, Owner, Affiliation) ->
|
||||
get_entity_subscriptions(_Host, Owner) ->
|
||||
SubKey = jid:tolower(Owner),
|
||||
GenKey = jid:remove_resource(SubKey),
|
||||
Host = node_flat_sql:encode_host(element(2, SubKey)),
|
||||
SJ = node_flat_sql:encode_jid(SubKey),
|
||||
GJ = node_flat_sql:encode_jid(GenKey),
|
||||
HostLike = node_flat_sql:encode_host_like(element(2, SubKey)),
|
||||
SJ = ejabberd_sql:escape(node_flat_sql:encode_jid(SubKey)),
|
||||
GJ = ejabberd_sql:escape(node_flat_sql:encode_jid(GenKey)),
|
||||
GJLike = ejabberd_sql:escape(node_flat_sql:encode_jid_like(GenKey)),
|
||||
Query = case SubKey of
|
||||
GenKey ->
|
||||
[<<"select host, node, type, i.nodeid, jid, "
|
||||
"subscriptions from pubsub_state i, pubsub_node n "
|
||||
"where i.nodeid = n.nodeid and jid "
|
||||
"like '">>, GJ, <<"%' and host like '%@">>, Host, <<"';">>];
|
||||
"like '">>, GJLike, <<"%' escape '^' and host like '%@">>, HostLike, <<"' escape '^';">>];
|
||||
_ ->
|
||||
[<<"select host, node, type, i.nodeid, jid, "
|
||||
"subscriptions from pubsub_state i, pubsub_node n "
|
||||
"where i.nodeid = n.nodeid and jid "
|
||||
"in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>]
|
||||
"in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, HostLike, <<"' escape '^';">>]
|
||||
end,
|
||||
Reply = case catch ejabberd_sql:sql_query_t(Query) of
|
||||
{selected,
|
||||
@@ -149,9 +151,10 @@ get_entity_subscriptions(_Host, Owner) ->
|
||||
get_entity_subscriptions_for_send_last(_Host, Owner) ->
|
||||
SubKey = jid:tolower(Owner),
|
||||
GenKey = jid:remove_resource(SubKey),
|
||||
Host = node_flat_sql:encode_host(element(2, SubKey)),
|
||||
SJ = node_flat_sql:encode_jid(SubKey),
|
||||
GJ = node_flat_sql:encode_jid(GenKey),
|
||||
HostLike = node_flat_sql:encode_host_like(element(2, SubKey)),
|
||||
SJ = ejabberd_sql:escape(node_flat_sql:encode_jid(SubKey)),
|
||||
GJ = ejabberd_sql:escape(node_flat_sql:encode_jid(GenKey)),
|
||||
GJLike = ejabberd_sql:escape(node_flat_sql:encode_jid_like(GenKey)),
|
||||
Query = case SubKey of
|
||||
GenKey ->
|
||||
[<<"select host, node, type, i.nodeid, jid, "
|
||||
@@ -159,14 +162,14 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
|
||||
"pubsub_node_option o where i.nodeid = n.nodeid "
|
||||
"and n.nodeid = o.nodeid and name='send_last_published_item' and "
|
||||
"val='on_sub_and_presence' and jid like '">>,
|
||||
GJ, <<"%' and host like '%@">>, Host, <<"';">>];
|
||||
GJLike, <<"%' escape '^' and host like '%@">>, HostLike, <<"' escape '^';">>];
|
||||
_ ->
|
||||
[<<"select host, node, type, i.nodeid, jid, "
|
||||
"subscriptions from pubsub_state i, pubsub_node n, "
|
||||
"pubsub_node_option o where i.nodeid = n.nodeid "
|
||||
"and n.nodeid = o.nodeid and name='send_last_published_item' and "
|
||||
"val='on_sub_and_presence' and jid in ",
|
||||
"('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>]
|
||||
"('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, HostLike, <<"' escape '^';">>]
|
||||
end,
|
||||
Reply = case catch ejabberd_sql:sql_query_t(Query) of
|
||||
{selected,
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+4
-3
@@ -33,7 +33,7 @@
|
||||
-export([init/3, terminate/2, options/0, features/0,
|
||||
create_node_permission/6, create_node/2, delete_node/1,
|
||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||
publish_item/6, delete_item/4, remove_extra_items/3,
|
||||
publish_item/7, delete_item/4, remove_extra_items/3,
|
||||
get_entity_affiliations/2, get_node_affiliations/1,
|
||||
get_affiliation/2, set_affiliation/3,
|
||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||
@@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
|
||||
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
|
||||
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
|
||||
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
|
||||
Payload, PubOpts).
|
||||
|
||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
|
||||
|
||||
+60
-61
@@ -37,8 +37,11 @@
|
||||
-behaviour(gen_pubsub_nodetree).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-export([init/3, terminate/2, options/0, set_node/1,
|
||||
get_node/3, get_node/2, get_node/1, get_nodes/2,
|
||||
@@ -65,27 +68,27 @@ set_node(Record) when is_record(Record, pubsub_node) ->
|
||||
end,
|
||||
Type = Record#pubsub_node.type,
|
||||
H = node_flat_sql:encode_host(Host),
|
||||
N = ejabberd_sql:escape(Node),
|
||||
P = ejabberd_sql:escape(Parent),
|
||||
Nidx = case nodeidx(Host, Node) of
|
||||
{result, OldNidx} ->
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"delete from pubsub_node_option where "
|
||||
"nodeid='">>, OldNidx, <<"';">>]),
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from pubsub_node_option where "
|
||||
"nodeid=%(OldNidx)d")),
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"update pubsub_node set host='">>,
|
||||
H, <<"' node='">>, N,
|
||||
<<"' parent='">>, P,
|
||||
<<"' type='">>, Type,
|
||||
<<"' where nodeid='">>,
|
||||
OldNidx, <<"';">>]),
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("update pubsub_node set"
|
||||
" host=%(H)s"
|
||||
" node=%(Node)s"
|
||||
" parent=%(Parent)s"
|
||||
" type=%(Type)s "
|
||||
"where nodeid=%(OldNidx)d")),
|
||||
OldNidx;
|
||||
_ ->
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"insert into pubsub_node(host, node, "
|
||||
"parent, type) values('">>,
|
||||
H, <<"', '">>, N, <<"', '">>, P,
|
||||
<<"', '">>, Type, <<"');">>]),
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("insert into pubsub_node(host, node, "
|
||||
"parent, type) values("
|
||||
"%(H)s, %(Node)s, %(Parent)s, %(Type)s)")),
|
||||
case nodeidx(Host, Node) of
|
||||
{result, NewNidx} -> NewNidx;
|
||||
_ -> none % this should not happen
|
||||
@@ -98,14 +101,12 @@ set_node(Record) when is_record(Record, pubsub_node) ->
|
||||
_ ->
|
||||
lists:foreach(fun ({Key, Value}) ->
|
||||
SKey = iolist_to_binary(atom_to_list(Key)),
|
||||
SValue = ejabberd_sql:escape(
|
||||
list_to_binary(
|
||||
lists:flatten(io_lib:fwrite("~p", [Value])))),
|
||||
SValue = jlib:term_to_expr(Value),
|
||||
catch
|
||||
ejabberd_sql:sql_query_t([<<"insert into pubsub_node_option(nodeid, "
|
||||
"name, val) values('">>,
|
||||
Nidx, <<"', '">>,
|
||||
SKey, <<"', '">>, SValue, <<"');">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("insert into pubsub_node_option(nodeid, "
|
||||
"name, val) values ("
|
||||
"%(Nidx)d, %(SKey)s, %(SValue)s)"))
|
||||
end,
|
||||
Record#pubsub_node.options),
|
||||
{result, Nidx}
|
||||
@@ -116,14 +117,12 @@ get_node(Host, Node, _From) ->
|
||||
|
||||
get_node(Host, Node) ->
|
||||
H = node_flat_sql:encode_host(Host),
|
||||
N = ejabberd_sql:escape(Node),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
|
||||
"pubsub_node where host='">>,
|
||||
H, <<"' and node='">>, N, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from "
|
||||
"pubsub_node where host=%(H)s and node=%(Node)s"))
|
||||
of
|
||||
{selected,
|
||||
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], [RItem]} ->
|
||||
{selected, [RItem]} ->
|
||||
raw_to_node(Host, RItem);
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
|
||||
@@ -133,13 +132,12 @@ get_node(Host, Node) ->
|
||||
|
||||
get_node(Nidx) ->
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select host, node, parent, type from "
|
||||
"pubsub_node where nodeid='">>,
|
||||
Nidx, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(host)s, @(node)s, @(parent)s, @(type)s from "
|
||||
"pubsub_node where nodeid=%(Nidx)d"))
|
||||
of
|
||||
{selected,
|
||||
[<<"host">>, <<"node">>, <<"parent">>, <<"type">>], [[Host, Node, Parent, Type]]} ->
|
||||
raw_to_node(Host, [Node, Parent, Type, Nidx]);
|
||||
{selected, [{Host, Node, Parent, Type}]} ->
|
||||
raw_to_node(Host, {Node, Parent, Type, Nidx});
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
|
||||
_ ->
|
||||
@@ -152,11 +150,11 @@ get_nodes(Host, _From) ->
|
||||
get_nodes(Host) ->
|
||||
H = node_flat_sql:encode_host(Host),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
|
||||
"pubsub_node where host='">>, H, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from "
|
||||
"pubsub_node where host=%(H)s"))
|
||||
of
|
||||
{selected,
|
||||
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
|
||||
{selected, RItems} ->
|
||||
[raw_to_node(Host, Item) || Item <- RItems];
|
||||
_ ->
|
||||
[]
|
||||
@@ -178,14 +176,12 @@ get_subnodes(Host, Node, _From) ->
|
||||
|
||||
get_subnodes(Host, Node) ->
|
||||
H = node_flat_sql:encode_host(Host),
|
||||
N = ejabberd_sql:escape(Node),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
|
||||
"pubsub_node where host='">>,
|
||||
H, <<"' and parent='">>, N, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from "
|
||||
"pubsub_node where host=%(H)s and parent=%(Node)s"))
|
||||
of
|
||||
{selected,
|
||||
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
|
||||
{selected, RItems} ->
|
||||
[raw_to_node(Host, Item) || Item <- RItems];
|
||||
_ ->
|
||||
[]
|
||||
@@ -196,14 +192,14 @@ get_subnodes_tree(Host, Node, _From) ->
|
||||
|
||||
get_subnodes_tree(Host, Node) ->
|
||||
H = node_flat_sql:encode_host(Host),
|
||||
N = ejabberd_sql:escape(Node),
|
||||
N = <<(ejabberd_sql:escape_like_arg_circumflex(Node))/binary, "%">>,
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
|
||||
"pubsub_node where host='">>,
|
||||
H, <<"' and node like '">>, N, <<"%';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from "
|
||||
"pubsub_node where host=%(H)s"
|
||||
" and node like %(N)s escape '^'"))
|
||||
of
|
||||
{selected,
|
||||
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
|
||||
{selected, RItems} ->
|
||||
[raw_to_node(Host, Item) || Item <- RItems];
|
||||
_ ->
|
||||
[]
|
||||
@@ -256,20 +252,24 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
|
||||
|
||||
delete_node(Host, Node) ->
|
||||
H = node_flat_sql:encode_host(Host),
|
||||
N = ejabberd_sql:escape(Node),
|
||||
N = <<(ejabberd_sql:escape_like_arg_circumflex(Node))/binary, "%">>,
|
||||
Removed = get_subnodes_tree(Host, Node),
|
||||
catch ejabberd_sql:sql_query_t([<<"delete from pubsub_node where host='">>,
|
||||
H, <<"' and node like '">>, N, <<"%';">>]),
|
||||
catch ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from pubsub_node where host=%(H)s"
|
||||
" and node like %(N)s escape '^'")),
|
||||
Removed.
|
||||
|
||||
%% helpers
|
||||
raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
|
||||
raw_to_node(Host, {Node, Parent, Type, binary_to_integer(Nidx)});
|
||||
raw_to_node(Host, {Node, Parent, Type, Nidx}) ->
|
||||
Options = case catch
|
||||
ejabberd_sql:sql_query_t([<<"select name,val from pubsub_node_option "
|
||||
"where nodeid='">>, Nidx, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(name)s, @(val)s from pubsub_node_option "
|
||||
"where nodeid=%(Nidx)d"))
|
||||
of
|
||||
{selected, [<<"name">>, <<"val">>], ROptions} ->
|
||||
DbOpts = lists:map(fun ([Key, Value]) ->
|
||||
{selected, ROptions} ->
|
||||
DbOpts = lists:map(fun ({Key, Value}) ->
|
||||
RKey = jlib:binary_to_atom(Key),
|
||||
Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
|
||||
RValue = element(2, erl_parse:parse_term(Tokens)),
|
||||
@@ -295,13 +295,12 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
|
||||
|
||||
nodeidx(Host, Node) ->
|
||||
H = node_flat_sql:encode_host(Host),
|
||||
N = ejabberd_sql:escape(Node),
|
||||
case catch
|
||||
ejabberd_sql:sql_query_t([<<"select nodeid from pubsub_node where "
|
||||
"host='">>,
|
||||
H, <<"' and node='">>, N, <<"';">>])
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(nodeid)d from pubsub_node where "
|
||||
"host=%(H)s and node=%(Node)s"))
|
||||
of
|
||||
{selected, [<<"nodeid">>], [[Nidx]]} ->
|
||||
{selected, [{Nidx}]} ->
|
||||
{result, Nidx};
|
||||
{'EXIT', _Reason} ->
|
||||
{error, db_fail};
|
||||
|
||||
+5
-1
@@ -124,9 +124,13 @@ update(#maxrate{} = State, Size) ->
|
||||
true -> 0
|
||||
end,
|
||||
NextNow = p1_time_compat:system_time(micro_seconds) + Pause * 1000,
|
||||
Div = case NextNow - State#maxrate.lasttime of
|
||||
0 -> 1;
|
||||
V -> V
|
||||
end,
|
||||
{State#maxrate{lastrate =
|
||||
(State#maxrate.lastrate +
|
||||
1000000 * Size / (NextNow - State#maxrate.lasttime))
|
||||
1000000 * Size / Div)
|
||||
/ 2,
|
||||
lasttime = NextNow},
|
||||
Pause}.
|
||||
|
||||
+36
-34
@@ -42,7 +42,7 @@
|
||||
get_roster_groups/3, del_user_roster_t/2,
|
||||
get_roster_by_jid/3, get_rostergroup_by_jid/3,
|
||||
del_roster/3, del_roster_sql/2, update_roster/5,
|
||||
update_roster_sql/4, roster_subscribe/1,
|
||||
update_roster_sql/2, roster_subscribe/1,
|
||||
get_subscription/3, set_private_data/4,
|
||||
set_private_data_sql/3, get_private_data/3,
|
||||
get_private_data/2, del_user_private_storage/2,
|
||||
@@ -231,12 +231,12 @@ list_users(LServer,
|
||||
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_binary(Prefix) and is_integer(Limit) and
|
||||
is_integer(Offset) ->
|
||||
SPrefix = ejabberd_sql:escape_like_arg(Prefix),
|
||||
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
|
||||
SPrefix2 = <<SPrefix/binary, $%>>,
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s from users "
|
||||
"where username like %(SPrefix2)s "
|
||||
"where username like %(SPrefix2)s escape '^' "
|
||||
"order by username "
|
||||
"limit %(Limit)d offset %(Offset)d")).
|
||||
|
||||
@@ -264,18 +264,17 @@ users_number(LServer) ->
|
||||
|
||||
users_number(LServer, [{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
SPrefix = ejabberd_sql:escape_like_arg(Prefix),
|
||||
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
|
||||
SPrefix2 = <<SPrefix/binary, $%>>,
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(count(*))d from users "
|
||||
"where username like %(SPrefix2)s"));
|
||||
"where username like %(SPrefix2)s escape '^'"));
|
||||
users_number(LServer, []) ->
|
||||
users_number(LServer).
|
||||
|
||||
add_spool_sql(Username, XML) ->
|
||||
[<<"insert into spool(username, xml) values ('">>,
|
||||
Username, <<"', '">>, XML, <<"');">>].
|
||||
add_spool_sql(LUser, XML) ->
|
||||
?SQL("insert into spool(username, xml) values (%(LUser)s, %(XML)s)").
|
||||
|
||||
add_spool(LServer, Queries) ->
|
||||
ejabberd_sql:sql_transaction(LServer, Queries).
|
||||
@@ -368,24 +367,27 @@ update_roster(_LServer, LUser, SJID, ItemVals,
|
||||
end,
|
||||
ItemGroups).
|
||||
|
||||
update_roster_sql(Username, SJID, ItemVals,
|
||||
update_roster_sql({LUser, SJID, Name, SSubscription, SAsk, AskMessage},
|
||||
ItemGroups) ->
|
||||
[[<<"delete from rosterusers where "
|
||||
"username='">>,
|
||||
Username, <<"' and jid='">>, SJID, <<"';">>],
|
||||
[<<"insert into rosterusers( "
|
||||
" username, jid, nick, "
|
||||
" subscription, ask, askmessage, "
|
||||
" server, subscribe, type) "
|
||||
"values ('">>,
|
||||
join(ItemVals, <<"', '">>), <<"');">>],
|
||||
[<<"delete from rostergroups where "
|
||||
"username='">>,
|
||||
Username, <<"' and jid='">>, SJID, <<"';">>]]
|
||||
[?SQL("delete from rosterusers where"
|
||||
" username=%(LUser)s and jid=%(SJID)s;"),
|
||||
?SQL("insert into rosterusers("
|
||||
" username, jid, nick,"
|
||||
" subscription, ask, askmessage,"
|
||||
" server, subscribe, type) "
|
||||
"values ("
|
||||
"%(LUser)s, "
|
||||
"%(SJID)s, "
|
||||
"%(Name)s, "
|
||||
"%(SSubscription)s, "
|
||||
"%(SAsk)s, "
|
||||
"%(AskMessage)s, "
|
||||
"'N', '', 'item');"),
|
||||
?SQL("delete from rostergroups where"
|
||||
" username=%(LUser)s and jid=%(SJID)s;")]
|
||||
++
|
||||
[[<<"insert into rostergroups( "
|
||||
" username, jid, grp) values ('">>,
|
||||
join(ItemGroup, <<"', '">>), <<"');">>]
|
||||
[?SQL("insert into rostergroups(username, jid, grp) "
|
||||
"values (%(LUser)s, %(SJID)s, %(ItemGroup)s)")
|
||||
|| ItemGroup <- ItemGroups].
|
||||
|
||||
roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) ->
|
||||
@@ -414,13 +416,12 @@ set_private_data(_LServer, LUser, XMLNS, SData) ->
|
||||
"!namespace=%(XMLNS)s",
|
||||
"data=%(SData)s"]).
|
||||
|
||||
set_private_data_sql(Username, LXMLNS, SData) ->
|
||||
[[<<"delete from private_storage where username='">>,
|
||||
Username, <<"' and namespace='">>, LXMLNS, <<"';">>],
|
||||
[<<"insert into private_storage(username, "
|
||||
"namespace, data) values ('">>,
|
||||
Username, <<"', '">>, LXMLNS, <<"', '">>, SData,
|
||||
<<"');">>]].
|
||||
set_private_data_sql(LUser, XMLNS, SData) ->
|
||||
[?SQL("delete from private_storage where"
|
||||
" username=%(LUser)s and namespace=%(XMLNS)s;"),
|
||||
?SQL("insert into private_storage(username, "
|
||||
"namespace, data) values ("
|
||||
"%(LUser)s, %(XMLNS)s, %(SData)s);")].
|
||||
|
||||
get_private_data(LServer, LUser, XMLNS) ->
|
||||
ejabberd_sql:sql_query(
|
||||
@@ -628,9 +629,10 @@ get_roster_version(LServer, LUser) ->
|
||||
" where username = %(LUser)s")).
|
||||
|
||||
set_roster_version(LUser, Version) ->
|
||||
update_t(<<"roster_version">>,
|
||||
[<<"username">>, <<"version">>], [LUser, Version],
|
||||
[<<"username = '">>, LUser, <<"'">>]).
|
||||
?SQL_UPSERT_T(
|
||||
"roster_version",
|
||||
["!username=%(LUser)s",
|
||||
"version=%(Version)s"]).
|
||||
|
||||
opt_type(sql_type) ->
|
||||
fun (pgsql) -> pgsql;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user