aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ejabberd.yml.example78
-rw-r--r--include/ejabberd_sm.hrl5
-rw-r--r--include/pubsub.hrl5
-rw-r--r--rebar.config2
-rw-r--r--rebar.config.script18
-rw-r--r--src/acl.erl367
-rw-r--r--src/ejabberd_app.erl2
-rw-r--r--src/ejabberd_auth_external.erl16
-rw-r--r--src/ejabberd_auth_ldap.erl17
-rw-r--r--src/ejabberd_auth_mnesia.erl77
-rw-r--r--src/ejabberd_auth_sql.erl11
-rw-r--r--src/ejabberd_c2s.erl150
-rw-r--r--src/ejabberd_commands.erl61
-rw-r--r--src/ejabberd_config.erl4
-rw-r--r--src/ejabberd_sm.erl78
-rw-r--r--src/ejabberd_sql.erl3
-rw-r--r--src/gen_pubsub_node.erl4
-rw-r--r--src/jlib.erl64
-rw-r--r--src/mod_admin_extra.erl40
-rw-r--r--src/mod_client_state.erl273
-rw-r--r--src/mod_configure.erl10
-rw-r--r--src/mod_http_api.erl25
-rw-r--r--src/mod_last.erl13
-rw-r--r--src/mod_mam_mnesia.erl86
-rw-r--r--src/mod_mam_sql.erl37
-rw-r--r--src/mod_muc_room.erl74
-rw-r--r--src/mod_offline_sql.erl44
-rw-r--r--src/mod_pubsub.erl27
-rw-r--r--src/mod_register.erl1
-rw-r--r--src/node_buddy.erl7
-rw-r--r--src/node_club.erl7
-rw-r--r--src/node_dag.erl6
-rw-r--r--src/node_dispatch.erl7
-rw-r--r--src/node_flat.erl5
-rw-r--r--src/node_flat_sql.erl337
-rw-r--r--src/node_hometree.erl7
-rw-r--r--src/node_hometree_sql.erl7
-rw-r--r--src/node_mb.erl7
-rw-r--r--src/node_mix.erl7
-rw-r--r--src/node_mix_sql.erl7
-rw-r--r--src/node_online.erl7
-rw-r--r--src/node_pep.erl7
-rw-r--r--src/node_pep_sql.erl19
-rw-r--r--src/node_private.erl7
-rw-r--r--src/node_public.erl7
-rw-r--r--src/nodetree_tree_sql.erl121
-rw-r--r--src/sql_queries.erl5
-rw-r--r--test/acl_test.exs222
-rw-r--r--test/ejabberd_SUITE.erl97
-rw-r--r--test/ejabberd_SUITE_data/cert.pem95
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.ldif22
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml7
-rw-r--r--test/ejabberd_commands_mock_test.exs18
-rw-r--r--test/elixir_SUITE.erl1
-rw-r--r--test/mod_admin_extra_test.exs1
-rw-r--r--test/mod_http_api_mock_test.exs4
-rw-r--r--test/mod_http_api_test.exs30
-rw-r--r--test/mod_roster_mock.exs67
-rw-r--r--test/suite.erl44
-rw-r--r--tools/xmpp_codec.erl45
-rw-r--r--tools/xmpp_codec.hrl1
-rw-r--r--tools/xmpp_codec.spec7
62 files changed, 1890 insertions, 940 deletions
diff --git a/ejabberd.yml.example b/ejabberd.yml.example
index b0f622149..32324d44f 100644
--- a/ejabberd.yml.example
+++ b/ejabberd.yml.example
@@ -462,54 +462,76 @@ acl:
## - "bob-local": "localhost"
###. ============
-###' ACCESS RULES
-access:
+###' SHAPER RULES
+
+shaper_rules:
## Maximum number of simultaneous sessions allowed for a single user:
- max_user_sessions:
- all: 10
+ max_user_sessions:
+ - 10:
+ - all
## Maximum number of offline messages that users can have:
- max_user_offline_messages:
- admin: 5000
- all: 100
- ## This rule allows access only for local users:
- local:
- local: allow
- ## Only non-blocked users can use c2s connections:
- c2s:
- blocked: deny
- all: allow
+ max_user_offline_messages:
+ - 5000:
+ - acl: admin
+ - 100:
+ - all
## For C2S connections, all users except admins use the "normal" shaper
- c2s_shaper:
- admin: none
- all: normal
+ c2s_shaper:
+ - none:
+ - acl: admin
+ - normal:
+ - all
## All S2S connections use the "fast" shaper
- s2s_shaper:
- all: fast
+ s2s_shaper:
+ - fast:
+ - all
+
+###. ============
+###' ACCESS RULES
+access_rules:
+ ## This rule allows access only for local users:
+ local:
+ - allow:
+ - acl: local
+ ## Only non-blocked users can use c2s connections:
+ c2s:
+ - deny:
+ - acl: blocked
+ - allow:
+ - all
## Only admins can send announcement messages:
- announce:
- admin: allow
+ announce:
+ - allow:
+ - acl: admin
## Only admins can use the configuration interface:
configure:
- admin: allow
+ - allow:
+ - acl: admin
## Admins of this server are also admins of the MUC service:
muc_admin:
- admin: allow
+ - allow:
+ - acl: admin
## Only accounts of the local ejabberd server can create rooms:
muc_create:
- local: allow
+ - allow:
+ - acl: local
## All users are allowed to use the MUC service:
muc:
- all: allow
+ - allow:
+ - all
## Only accounts on the local ejabberd server can create Pubsub nodes:
pubsub_createnode:
- local: allow
+ - allow:
+ - acl: local
## In-band registration allows registration of any possible username.
## To disable in-band registration, replace 'allow' with 'deny'.
register:
- all: allow
+ - allow:
+ - all
## Only allow to register from localhost
trusted_network:
- loopback: allow
+ - allow:
+ - acl: loopback
## Do not establish S2S connections with bad servers
## s2s:
## bad_servers: deny
diff --git a/include/ejabberd_sm.hrl b/include/ejabberd_sm.hrl
index bae60ccd3..38298d66a 100644
--- a/include/ejabberd_sm.hrl
+++ b/include/ejabberd_sm.hrl
@@ -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.
diff --git a/include/pubsub.hrl b/include/pubsub.hrl
index 3aa090d3b..a05807247 100644
--- a/include/pubsub.hrl
+++ b/include/pubsub.hrl
@@ -93,7 +93,12 @@
-type(subOptions() :: [mod_pubsub:subOption(),...]).
+-type(pubOption() ::
+ {Option::binary(),
+ Values::[binary()]
+}).
+-type(pubOptions() :: [mod_pubsub:pubOption()]).
-type(affiliation() :: 'none'
| 'owner'
diff --git a/rebar.config b/rebar.config
index dfce70753..a217dee93 100644
--- a/rebar.config
+++ b/rebar.config
@@ -117,7 +117,7 @@
{eunit_compile_opts, [{i, "tools"}]}.
-{cover_enabled, true}.
+{if_version_above, "17", {cover_enabled, true}}.
{cover_export_enabled, true}.
{post_hook_configure, [{"fast_tls", []},
diff --git a/rebar.config.script b/rebar.config.script
index 909284204..166f1cbec 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -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"},
diff --git a/src/acl.erl b/src/acl.erl
index 06202c67e..57675112d 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -29,12 +29,12 @@
-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]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -92,12 +92,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 +182,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(
@@ -203,8 +201,19 @@ load_from_config() ->
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, AccessRulesNew),
+ lists:foreach(
+ fun({Access, Rules}) ->
add_access(Host, Access, Rules)
- end, AccessRules)
+ end, ShaperRules)
end, Hosts).
%% Delete all previous set ACLs and Access rules
@@ -225,19 +234,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 +273,177 @@ 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.
-
-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.
+ access_matches(Access, #{usr => jid:tolower(JID)}, Host).
--spec match_acl(atom(),
- jid() | ljid() | inet:ip_address(),
- binary()) -> boolean().
+-spec acl_rule_verify(aclspec()) -> boolean().
-match_acl(all, _JID, _Host) ->
+acl_rule_verify(all) ->
true;
-match_acl(none, _JID, _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)).
+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.
-get_aclspecs(ACL, Host) ->
- ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
+all_acl_rules_matches([], _Data, _Host) ->
+ false;
+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.
+
+-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),
+ all_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.
-match_user_spec(Spec, JID, Default) ->
- case do_match_user_spec(Spec, jid:tolower(JID)) of
- true -> Default;
- false -> deny
- end.
+-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.
-do_match_user_spec({user, {U, S}}, {User, Server, _Resource}) ->
- U == User andalso S == Server.
+get_aclspecs(ACL, Host) ->
+ ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
@@ -450,6 +515,18 @@ parse_ip_netmask(S) ->
_ -> error
end.
+transform_access_rules_config(Config) ->
+ lists:map(fun transform_access_rules_config2/1, lists:flatten(Config)).
+
+transform_access_rules_config2({Res, Rules}) when is_list(Rules) ->
+ {Res, lists:map(fun({Type, Args}) when is_list(Args) ->
+ normalize_spec({Type, hd(lists:flatten(Args))});
+ (V) -> normalize_spec(V)
+ end, lists:flatten(Rules))};
+transform_access_rules_config2({Res, Rule}) ->
+ {Res, [Rule]}.
+
+
transform_options(Opts) ->
Opts1 = lists:foldl(fun transform_options/2, [], Opts),
{ACLOpts, Opts2} = lists:mapfoldl(
@@ -464,6 +541,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 +566,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 +611,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, acces_rules, shaper_rules].
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index 44d0db626..703614f63 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -84,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.
diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl
index ef7c97551..bba65af1c 100644
--- a/src/ejabberd_auth_external.erl
+++ b/src/ejabberd_auth_external.erl
@@ -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,
diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl
index 51b466ef4..3cb04b596 100644
--- a/src/ejabberd_auth_ldap.erl
+++ b/src/ejabberd_auth_ldap.erl
@@ -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,
diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl
index 58e22c79c..2a4554d15 100644
--- a/src/ejabberd_auth_mnesia.erl
+++ b/src/ejabberd_auth_mnesia.erl
@@ -91,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()) ->
diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl
index 17e3e517b..d6d945e02 100644
--- a/src/ejabberd_auth_sql.erl
+++ b/src/ejabberd_auth_sql.erl
@@ -88,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} ->
@@ -417,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),
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 080880bec..bda3bbd5f 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -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
@@ -620,9 +624,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) ->
@@ -1099,8 +1103,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)]),
@@ -1147,7 +1153,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">>,
@@ -1281,7 +1287,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).
@@ -1792,6 +1798,18 @@ terminate(_Reason, StateName, StateData) ->
presence_broadcast(StateData, From,
StateData#state.pres_a, Packet)
end,
+ 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,
handle_unacked_stanzas(StateData)
end,
bounce_messages();
@@ -1808,8 +1826,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).
@@ -2727,6 +2746,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;
@@ -2763,7 +2784,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)]),
@@ -2966,7 +2987,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
@@ -2995,7 +3026,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,
@@ -3028,65 +3058,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.
-
-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.
+ {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_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
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index 55ecba5d1..1b7d777e6 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -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,22 +748,26 @@ 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,
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
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 16eebc0e3..8c17e577f 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -498,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) ->
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 5ee652cce..8d94bc6aa 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -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,8 +547,8 @@ 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">> ->
@@ -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 ->
@@ -646,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,
@@ -663,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
@@ -721,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,
diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl
index f503a749f..a480a1bd3 100644
--- a/src/ejabberd_sql.erl
+++ b/src/ejabberd_sql.erl
@@ -41,6 +41,7 @@
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,
@@ -216,6 +217,8 @@ escape_like_arg_circumflex(S) when is_binary(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;
diff --git a/src/gen_pubsub_node.erl b/src/gen_pubsub_node.erl
index 0148da2e2..27cb032bd 100644
--- a/src/gen_pubsub_node.erl
+++ b/src/gen_pubsub_node.erl
@@ -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()}.
diff --git a/src/jlib.erl b/src/jlib.erl
index bad46898d..e5cba3180 100644
--- a/src/jlib.erl
+++ b/src/jlib.erl
@@ -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,
@@ -529,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().
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index b12a80007..5aebd3ff2 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -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) ->
diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl
index 790e808f1..651f15c25 100644
--- a/src/mod_client_state.erl
+++ b/src/mod_client_state.erl
@@ -30,22 +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(B) when is_boolean(B) -> B end,
- true),
- DropChatStates = gen_mod:get_opt(drop_chat_states, Opts,
- fun(B) when is_boolean(B) -> B 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 ->
@@ -53,23 +75,39 @@ 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) ->
- QueuePresence = gen_mod:get_module_opt(Host, ?MODULE, queue_presence,
- fun(B) when is_boolean(B) -> B end,
- true),
- DropChatStates = gen_mod:get_module_opt(Host, ?MODULE, drop_chat_states,
- fun(B) when is_boolean(B) -> B end,
- true),
- if QueuePresence; DropChatStates ->
+ 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 ->
@@ -77,45 +115,188 @@ stop(Host) ->
filter_presence, 50);
true -> ok
end,
- if DropChatStates ->
+ if QueueChatStates ->
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
filter_chat_states, 50);
true -> ok
- end;
+ 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,
- ok.
+ end.
-add_stream_feature(Features, _Host) ->
- Feature = #xmlel{name = <<"csi">>,
- attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}],
- children = []},
- [Feature | Features].
+-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(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
+filter_presence({C2SState, _OutStanzas} = Acc, Host,
+ #xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) ->
case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} when Type /= <<"unavailable">> ->
- ?DEBUG("Got important presence stanza", []),
- {stop, send};
+ Acc;
_ ->
?DEBUG("Got availability presence stanza", []),
- {stop, queue}
+ queue_add(presence, Stanza, Host, C2SState)
end;
-filter_presence(Action, _Stanza) -> Action.
+filter_presence(Acc, _Host, _Stanza) -> Acc.
-filter_chat_states(_Action, #xmlel{name = <<"message">>} = Stanza) ->
+-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 ->
- ?DEBUG("Got standalone chat state notification", []),
- {stop, drop};
+ 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 ->
- ?DEBUG("Got message stanza", []),
- {stop, send}
+ Acc
end;
-filter_chat_states(Action, _Stanza) -> Action.
+filter_chat_states(Acc, _Host, _Stanza) -> Acc.
-mod_opt_type(drop_chat_states) ->
- fun(B) when is_boolean(B) -> B end;
-mod_opt_type(queue_presence) ->
- fun(B) when is_boolean(B) -> B end;
-mod_opt_type(_) -> [drop_chat_states, queue_presence].
+-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">>,
+ attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}],
+ children = []},
+ [Feature | Features].
+
+%%--------------------------------------------------------------------
+%% 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;
+ _ ->
+ 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.
diff --git a/src/mod_configure.erl b/src/mod_configure.erl
index a836c33bd..515127684 100644
--- a/src/mod_configure.erl
+++ b/src/mod_configure.erl
@@ -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, []};
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index c4fae2022..1962e1d0e 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -188,9 +188,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 +208,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 +216,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 +242,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 +278,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 +295,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 +332,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 +415,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 ->
diff --git a/src/mod_last.erl b/src/mod_last.erl
index c39681f69..92694cf13 100644
--- a/src/mod_last.erl
+++ b/src/mod_last.erl
@@ -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).
diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl
index 007ef5eba..10b98daf7 100644
--- a/src/mod_mam_mnesia.erl
+++ b/src/mod_mam_mnesia.erl
@@ -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,68 @@ 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).
+ delete_old_user_messages(mnesia:dirty_first(archive_msg), TimeStamp, Type).
+
+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) ->
diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl
index d88806499..bbbe543f5 100644
--- a/src/mod_mam_sql.erl
+++ b/src/mod_mam_sql.erl
@@ -92,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 ->
@@ -109,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),
@@ -211,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 ->
@@ -226,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 ->
[]
@@ -265,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='">>,
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index c9c785757..eabff103d 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -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
diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl
index 4d9455570..a368e8e31 100644
--- a/src/mod_offline_sql.erl
+++ b/src/mod_offline_sql.erl
@@ -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)
@@ -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}].
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 35173a4f2..de278c838 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -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;
@@ -3861,6 +3873,7 @@ set_configure(Host, Node, From, Els, Lang) ->
set_node,
[N#pubsub_node{options = NewOpts}])
of
+ {ok, Nidx} -> {result, ok};
ok -> {result, ok};
Err -> Err
end;
diff --git a/src/mod_register.erl b/src/mod_register.erl
index c1a7cab81..27e484e85 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -441,6 +441,7 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
end
end;
false ->
+ remove_timeout(Source),
ErrText = <<"The password is too weak">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
end;
diff --git a/src/node_buddy.erl b/src/node_buddy.erl
index aeacacda1..bdaa4a89a 100644
--- a/src/node_buddy.erl
+++ b/src/node_buddy.erl
@@ -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).
diff --git a/src/node_club.erl b/src/node_club.erl
index 64eff7791..7f6ae6520 100644
--- a/src/node_club.erl
+++ b/src/node_club.erl
@@ -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).
diff --git a/src/node_dag.erl b/src/node_dag.erl
index 09ee85f91..afb610ca7 100644
--- a/src/node_dag.erl
+++ b/src/node_dag.erl
@@ -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.
diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl
index 178292d31..b3af69cd1 100644
--- a/src/node_dispatch.erl
+++ b/src/node_dispatch.erl
@@ -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, []}};
diff --git a/src/node_flat.erl b/src/node_flat.erl
index c2efe3e12..2fb24ee69 100644
--- a/src/node_flat.erl
+++ b/src/node_flat.erl
@@ -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),
diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl
index ea3aa7d3c..7b8ee552c 100644
--- a/src/node_flat_sql.erl
+++ b/src/node_flat_sql.erl
@@ -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,
@@ -75,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,
@@ -217,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),
@@ -298,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,
@@ -312,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,
@@ -327,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
@@ -352,23 +365,26 @@ get_entity_subscriptions(Host, Owner) ->
H = encode_host(Host),
SJ = encode_jid(SubKey),
GJ = encode_jid(GenKey),
- GJLike = encode_jid_like(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 '">>, GJLike,
- <<"%' escape '^' 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
[] ->
@@ -403,25 +419,28 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
H = encode_host(Host),
SJ = encode_jid(SubKey),
GJ = encode_jid(GenKey),
- GJLike = encode_jid_like(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 '">>, GJLike,
- <<"%' escape '^' 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
[] ->
@@ -441,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
[] ->
@@ -467,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);
_ ->
[]
@@ -567,13 +588,13 @@ 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]) ->
+ lists:map(fun ({SJID, Aff, Subs}) ->
JID = decode_jid(SJID),
#pubsub_state{stateid = {JID, Nidx},
items = itemids(Nidx, JID),
@@ -598,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)};
@@ -619,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) ->
@@ -654,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,
@@ -676,12 +694,13 @@ get_items(Nidx, _From,
_ ->
{<<"is not">>, <<"desc">>}% Can be better
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
@@ -699,7 +718,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">>
@@ -708,13 +727,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,
@@ -725,7 +744,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, $")];
@@ -777,16 +796,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
@@ -798,16 +818,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">>)}
@@ -846,37 +864,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;
@@ -884,9 +896,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.
@@ -907,14 +920,15 @@ first_in_list(Pred, [H | T]) ->
end.
itemids(Nidx, {_U, _S, _R} = JID) ->
- SJID = encode_jid_like(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,
- <<"%' escape '^' 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.
@@ -922,11 +936,11 @@ itemids(Nidx, {_U, _S, _R} = JID) ->
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, []}
@@ -942,33 +956,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())
@@ -1015,24 +1020,24 @@ 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(ejabberd_sql:escape_like_arg_circumflex(jid:to_string(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) -> encode_jid_like(LJID);
+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)).
@@ -1068,12 +1073,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, <<":">>),
diff --git a/src/node_hometree.erl b/src/node_hometree.erl
index 2d26c0eeb..def7b983d 100644
--- a/src/node_hometree.erl
+++ b/src/node_hometree.erl
@@ -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).
diff --git a/src/node_hometree_sql.erl b/src/node_hometree_sql.erl
index 4ce6f8554..d9af49843 100644
--- a/src/node_hometree_sql.erl
+++ b/src/node_hometree_sql.erl
@@ -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).
diff --git a/src/node_mb.erl b/src/node_mb.erl
index 0bfb51ecc..6c7f09780 100644
--- a/src/node_mb.erl
+++ b/src/node_mb.erl
@@ -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).
diff --git a/src/node_mix.erl b/src/node_mix.erl
index b57d58493..71a8d3180 100644
--- a/src/node_mix.erl
+++ b/src/node_mix.erl
@@ -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).
diff --git a/src/node_mix_sql.erl b/src/node_mix_sql.erl
index b5b9a94eb..cbe0b3d02 100644
--- a/src/node_mix_sql.erl
+++ b/src/node_mix_sql.erl
@@ -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).
diff --git a/src/node_online.erl b/src/node_online.erl
index ea492fc85..1c2ab5a03 100644
--- a/src/node_online.erl
+++ b/src/node_online.erl
@@ -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).
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 2ac4da627..504e39fa3 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -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).
diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl
index 1f2c13d5c..1df173fd7 100644
--- a/src/node_pep_sql.erl
+++ b/src/node_pep_sql.erl
@@ -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).
@@ -115,9 +116,9 @@ get_entity_subscriptions(_Host, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
HostLike = node_flat_sql:encode_host_like(element(2, SubKey)),
- SJ = node_flat_sql:encode_jid(SubKey),
- GJ = node_flat_sql:encode_jid(GenKey),
- GJLike = node_flat_sql:encode_jid_like(GenKey),
+ 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, "
@@ -151,9 +152,9 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
HostLike = node_flat_sql:encode_host_like(element(2, SubKey)),
- SJ = node_flat_sql:encode_jid(SubKey),
- GJ = node_flat_sql:encode_jid(GenKey),
- GJLike = node_flat_sql:encode_jid_like(GenKey),
+ 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, "
diff --git a/src/node_private.erl b/src/node_private.erl
index 79dc5ed81..0cd04b9dd 100644
--- a/src/node_private.erl
+++ b/src/node_private.erl
@@ -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).
diff --git a/src/node_public.erl b/src/node_public.erl
index cdb8abca3..0786d9995 100644
--- a/src/node_public.erl
+++ b/src/node_public.erl
@@ -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).
diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl
index 17ae91b52..1ad4046cd 100644
--- a/src/nodetree_tree_sql.erl
+++ b/src/nodetree_tree_sql.erl
@@ -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(ejabberd_sql:escape_like_arg_circumflex(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, <<"%' escape '^';">>])
+ 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(ejabberd_sql:escape_like_arg_circumflex(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, <<"%' escape '^';">>]),
+ 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};
diff --git a/src/sql_queries.erl b/src/sql_queries.erl
index 121117574..2afa32ac8 100644
--- a/src/sql_queries.erl
+++ b/src/sql_queries.erl
@@ -273,9 +273,8 @@ users_number(LServer, [{prefix, Prefix}])
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).
diff --git a/test/acl_test.exs b/test/acl_test.exs
index db6584308..ab2ecb2a4 100644
--- a/test/acl_test.exs
+++ b/test/acl_test.exs
@@ -36,7 +36,7 @@ defmodule ACLTest do
test "access rule match with user part ACL" do
:acl.add(:global, :basic_acl_1, {:user, "test1"})
- :acl.add_access(:global, :basic_rule_1, [{:basic_acl_1, :allow}])
+ :acl.add_access(:global, :basic_rule_1, [{:allow, [{:acl, :basic_acl_1}]}])
# JID can only be passes as jid record.
# => TODO: Support passing JID as binary.
assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test1@domain1")) == :allow
@@ -46,7 +46,7 @@ defmodule ACLTest do
assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test11@domain1")) == :deny
:acl.add(:global, :basic_acl_2, {:user, {"test2", "domain1"}})
- :acl.add_access(:global, :basic_rule_2, [{:basic_acl_2, :allow}])
+ :acl.add_access(:global, :basic_rule_2, [{:allow, [{:acl, :basic_acl_2}]}])
assert :acl.match_rule(:global, :basic_rule_2, :jid.from_string("test2@domain1")) == :allow
assert :acl.match_rule(:global, :basic_rule_2, :jid.from_string("test2@domain2")) == :deny
assert :acl.match_rule(:global, :basic_rule_2, :jid.from_string("test2@otherdomain")) == :deny
@@ -55,7 +55,7 @@ defmodule ACLTest do
test "IP based ACL" do
:acl.add(:global, :ip_acl_1, {:ip, "127.0.0.0/24"})
- :acl.add_access(:global, :ip_rule_1, [{:ip_acl_1, :allow}])
+ :acl.add_access(:global, :ip_rule_1, [{:allow, [{:acl, :ip_acl_1}]}])
# IP must be expressed as a tuple when calling match rule
assert :acl.match_rule(:global, :ip_rule_1, {127,0,0,1}) == :allow
assert :acl.match_rule(:global, :ip_rule_1, {127,0,1,1}) == :deny
@@ -65,7 +65,7 @@ defmodule ACLTest do
test "Access rule are evaluated sequentially" do
:acl.add(:global, :user_acl_1, {:user, {"test1", "domain2"}})
:acl.add(:global, :user_acl_2, {:user, "test1"})
- :acl.add_access(:global, :user_rule_1, [{:user_acl_1, :deny}, {:user_acl_2, :allow}])
+ :acl.add_access(:global, :user_rule_1, [{:deny, [{:acl, :user_acl_1}]}, {:allow, [{:acl, :user_acl_2}]}])
assert :acl.match_rule(:global, :user_rule_1, :jid.from_string("test1@domain1")) == :allow
assert :acl.match_rule(:global, :user_rule_1, :jid.from_string("test1@domain2")) == :deny
end
@@ -74,7 +74,7 @@ defmodule ACLTest do
test "Access rules providing values" do
:acl.add(:global, :user_acl, {:user_regexp, ""})
:acl.add(:global, :admin_acl, {:user, "admin"})
- :acl.add_access(:global, :value_rule_1, [{:admin_acl, 10}, {:user_acl, 5}])
+ :acl.add_access(:global, :value_rule_1, [{10, [{:acl, :admin_acl}]}, {5, [{:acl, :user_acl}]}])
assert :acl.match_rule(:global, :value_rule_1, :jid.from_string("test1@domain1")) == 5
assert :acl.match_rule(:global, :value_rule_1, :jid.from_string("admin@domain1")) == 10
@@ -91,21 +91,217 @@ defmodule ACLTest do
test "mixing IP and user access rules" do
:acl.add(:global, :user_acl_1, {:user, "test1"})
:acl.add(:global, :ip_acl_1, {:ip, "127.0.0.0/24"})
- :acl.add_access(:global, :mixed_rule_1, [{:user_acl_1, :allow}, {:ip_acl_1, :allow}])
+ :acl.add_access(:global, :mixed_rule_1, [{:allow, [{:acl, :user_acl_1}]}, {:allow, [{:acl, :ip_acl_1}]}])
assert :acl.match_rule(:global, :mixed_rule_1, :jid.from_string("test1@domain1")) == :allow
assert :acl.match_rule(:global, :mixed_rule_1, {127,0,0,1}) == :allow
- :acl.add_access(:global, :mixed_rule_2, [{:user_acl_1, :deny}, {:ip_acl_1, :allow}])
+ :acl.add_access(:global, :mixed_rule_2, [{:deny, [{:acl, :user_acl_1}]}, {:allow, [{:acl, :ip_acl_1}]}])
assert :acl.match_rule(:global, :mixed_rule_2, :jid.from_string("test1@domain1")) == :deny
assert :acl.match_rule(:global, :mixed_rule_2, {127,0,0,1}) == :allow
end
- test "acl:match_access can match directly on user pattern" do
- pattern = {:user, {"test1", "domain1"}}
- assert :acl.match_access(:global, pattern, :jid.from_string("test1@domain1"), :allow) == :allow
- assert :acl.match_access(:global, pattern, :jid.from_string("test2@domain1"), :allow) == :deny
+ test "access_matches works with predefined access rules" do
+ :acl.add(:global, :user_acl_2, {:user, "user"})
+ :acl.add_access(:global, :user_rule_2, [{:allow, [{:acl, :user_acl_2}]}, {:deny, [:all]}])
+
+ assert :acl.access_matches(:user_rule_2, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ assert :acl.access_matches(:user_rule_2, %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ end
+
+ test "access_matches rule all always matches" do
+ assert :acl.access_matches(:all, %{}, :global) == :allow
+ assert :acl.access_matches(:all, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ end
+
+ test "access_matches rule none never matches" do
+ assert :acl.access_matches(:none, %{}, :global) == :deny
+ assert :acl.access_matches(:none, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ end
+
+ test "access_matches with not existing rule never matches" do
+ assert :acl.access_matches(:bleble, %{}, :global) == :deny
+ assert :acl.access_matches(:bleble, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ end
+
+ test "access_matches works with inlined access rules" do
+ :acl.add(:global, :user_acl_3, {:user, "user"})
+
+ assert :acl.access_matches([{:allow, [{:acl, :user_acl_3}]}, {:deny, [:all]}],
+ %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ assert :acl.access_matches([{:allow, [{:acl, :user_acl_3}]}, {:deny, [:all]}],
+ %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ end
+
+ test "access_matches allow to have acl rules inlined" do
+ assert :acl.access_matches([{:allow, [{:user, "user"}]}, {:deny, [:all]}],
+ %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ assert :acl.access_matches([{:allow, [{:user, "user"}]}, {:deny, [:all]}],
+ %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ end
+
+ test "access_matches test have implicit deny at end" do
+ assert :acl.access_matches([{:allow, [{:user, "user"}]}],
+ %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ assert :acl.access_matches([{:allow, [{:user, "user"}]}],
+ %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ end
+
+ test "access_matches requires that all subrules match" do
+ rules = [{:allow, [{:user, "user"}, {:ip, {{127,0,0,1}, 32}}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,2}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ end
+
+ test "access_matches rules are matched in order" do
+ rules = [{:allow, [{:user, "user"}]}, {:deny, [{:user, "user2"}]}, {:allow, [{:user_regexp, "user"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user22", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ end
+
+ test "access_matches rules that require ip but no one is provided don't crash" do
+ rules = [{:allow, [{:ip, {{127,0,0,1}, 32}}]},
+ {:allow, [{:user, "user"}]},
+ {:allow, [{:user, "user2"}, {:ip, {{127,0,0,1}, 32}}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user2", "domain1", ""}}, :global) == :deny
+ end
+
+ test "access_matches rules that require usr but no one is provided don't crash" do
+ rules = [{:allow, [{:ip, {{127,0,0,1}, 32}}]},
+ {:allow, [{:user, "user"}]},
+ {:allow, [{:user, "user2"}, {:ip, {{127,0,0,2}, 32}}]}]
+ assert :acl.access_matches(rules, %{ip: {127,0,0,1}}, :global) == :allow
+ assert :acl.access_matches(rules, %{ip: {127,0,0,2}}, :global) == :deny
+ end
+
+ test "access_matches rules with all always matches" do
+ rules = [{:allow, [:all]}, {:deny, {:user, "user"}}]
+ assert :acl.access_matches(rules, %{}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ end
+
+ test "access_matches rules with {acl, all} always matches" do
+ rules = [{:allow, [{:acl, :all}]}, {:deny, {:user, "user"}}]
+ assert :acl.access_matches(rules, %{}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
+ end
+
+ test "access_matches rules with none never matches" do
+ rules = [{:allow, [:none]}, {:deny, [:all]}]
+ assert :acl.access_matches(rules, %{}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
+ end
+
+ test "access_matches with no rules never matches" do
+ assert :acl.access_matches([], %{}, :global) == :deny
+ assert :acl.access_matches([], %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
end
+ test "access_matches ip rule accepts {ip, port}" do
+ rules = [{:allow, [{:ip, {{127,0,0,1}, 32}}]}]
+ assert :acl.access_matches(rules, %{ip: {{127,0,0,1}, 5000}}, :global) == :allow
+ assert :acl.access_matches(rules, %{ip: {{127,0,0,2}, 5000}}, :global) == :deny
+ end
+
+ test "access_matches user rule works" do
+ rules = [{:allow, [{:user, "user1"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user1", "domain1", ""}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user2", "domain1", ""}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user1", "domain3", ""}}, :global) == :deny
+ end
+
+ test "access_matches 2 arg user rule works" do
+ rules = [{:allow, [{:user, {"user1", "server1"}}]}]
+ assert :acl.access_matches(rules, %{usr: {"user1", "server1", ""}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user1", "server2", ""}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user2", "server1", ""}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user2", "server2", ""}}, :global) == :deny
+ end
+
+ test "access_matches server rule works" do
+ rules = [{:allow, [{:server, "server1"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "server1", ""}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "server2", ""}}, :global) == :deny
+ end
+
+ test "access_matches resource rule works" do
+ rules = [{:allow, [{:resource, "res1"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res2"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user", "domain3", "res1"}}, :global) == :allow
+ end
+
+ test "access_matches user_regexp rule works" do
+ rules = [{:allow, [{:user_regexp, "user[0-9]"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user1", "domain1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"userA", "domain1", "res1"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user1", "domain3", "res1"}}, :global) == :deny
+ end
+
+ test "access_matches 2 arg user_regexp rule works" do
+ rules = [{:allow, [{:user_regexp, {"user[0-9]", "server1"}}]}]
+ assert :acl.access_matches(rules, %{usr: {"user1", "server1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"userA", "server1", "res1"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user1", "server2", "res1"}}, :global) == :deny
+ end
+
+ test "access_matches server_regexp rule works" do
+ rules = [{:allow, [{:server_regexp, "server[0-9]"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "server1", ""}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "serverA", ""}}, :global) == :deny
+ end
+
+ test "access_matches resource_regexp rule works" do
+ rules = [{:allow, [{:resource_regexp, "res[0-9]"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", "resA"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user", "domain3", "res1"}}, :global) == :allow
+ end
+
+ test "access_matches node_regexp rule works" do
+ rules = [{:allow, [{:node_regexp, {"user[0-9]", "server[0-9]"}}]}]
+ assert :acl.access_matches(rules, %{usr: {"user1", "server1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"userA", "server1", "res1"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user1", "serverA", "res1"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"userA", "serverA", "res1"}}, :global) == :deny
+ end
+
+ test "access_matches user_glob rule works" do
+ rules = [{:allow, [{:user_glob, "user?"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user1", "domain1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user11", "domain1", "res1"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user1", "domain3", "res1"}}, :global) == :deny
+ end
+
+ test "access_matches 2 arg user_glob rule works" do
+ rules = [{:allow, [{:user_glob, {"user?", "server1"}}]}]
+ assert :acl.access_matches(rules, %{usr: {"user1", "server1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user11", "server1", "res1"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user1", "server2", "res1"}}, :global) == :deny
+ end
+
+ test "access_matches server_glob rule works" do
+ rules = [{:allow, [{:server_glob, "server?"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "server1", ""}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "server11", ""}}, :global) == :deny
+ end
+
+ test "access_matches resource_glob rule works" do
+ rules = [{:allow, [{:resource_glob, "res?"}]}]
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res11"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user", "domain3", "res1"}}, :global) == :allow
+ end
+
+ test "access_matches node_glob rule works" do
+ rules = [{:allow, [{:node_glob, {"user?", "server?"}}]}]
+ assert :acl.access_matches(rules, %{usr: {"user1", "server1", "res1"}}, :global) == :allow
+ assert :acl.access_matches(rules, %{usr: {"user11", "server1", "res1"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user1", "server11", "res1"}}, :global) == :deny
+ assert :acl.access_matches(rules, %{usr: {"user11", "server11", "res1"}}, :global) == :deny
+ end
## Checking ACL on both user pattern and IP
## ========================================
@@ -115,8 +311,8 @@ defmodule ACLTest do
test "module can test both IP and user through two independent :acl.match_rule check (deprecated)" do
:acl.add(:global, :user_acl, {:user, {"test1", "domain1"}})
:acl.add(:global, :ip_acl, {:ip, "127.0.0.0/24"})
- :acl.add_access(:global, :user_rule, [{:user_acl, :allow}])
- :acl.add_access(:global, :ip_rule, [{:ip_acl, :allow}])
+ :acl.add_access(:global, :user_rule, [{:allow, [{:acl, :user_acl}]}])
+ :acl.add_access(:global, :ip_rule, [{:allow, [{:acl, :ip_acl}]}])
# acl module in 16.03 is not able to provide a function for compound result:
assert :acl.match_rule(:global, :user_rule, :jid.from_string("test1@domain1")) == :allow
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index aa465fb66..d3e7ec668 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -162,23 +162,23 @@ init_per_testcase(TestCase, OrigConfig) ->
IsMaster = lists:suffix("_master", Test),
IsSlave = lists:suffix("_slave", Test),
IsCarbons = lists:prefix("carbons_", Test),
- User = if IsMaster or IsCarbons -> <<"test_master">>;
- IsSlave -> <<"test_slave">>;
- true -> <<"test_single">>
+ User = if IsMaster or IsCarbons -> <<"test_master!#$%^*()`~+-;_=[]{}|\\">>;
+ IsSlave -> <<"test_slave!#$%^*()`~+-;_=[]{}|\\">>;
+ true -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>
end,
MyResource = if IsMaster and IsCarbons -> MasterResource;
IsSlave and IsCarbons -> SlaveResource;
true -> Resource
end,
Slave = if IsCarbons ->
- jid:make(<<"test_master">>, Server, SlaveResource);
+ jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, SlaveResource);
true ->
- jid:make(<<"test_slave">>, Server, Resource)
+ jid:make(<<"test_slave!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource)
end,
Master = if IsCarbons ->
- jid:make(<<"test_master">>, Server, MasterResource);
+ jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, MasterResource);
true ->
- jid:make(<<"test_master">>, Server, Resource)
+ jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource)
end,
Config = set_opt(user, User,
set_opt(slave, Slave,
@@ -203,6 +203,8 @@ init_per_testcase(TestCase, OrigConfig) ->
auth(connect(Config));
sm_resume ->
auth(connect(Config));
+ sm_resume_failed ->
+ auth(connect(Config));
test_open_session ->
bind(auth(connect(Config)));
_ when IsMaster or IsSlave ->
@@ -231,6 +233,7 @@ no_db_tests() ->
stats,
sm,
sm_resume,
+ sm_resume_failed,
disco]},
{test_proxy65, [parallel],
[proxy65_master, proxy65_slave]}].
@@ -641,6 +644,17 @@ sm_resume(Config) ->
?recv1(#message{from = ServerJID, to = MyJID, body = [Txt]}),
?recv1(#sm_r{}),
send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}),
+ %% Send another stanza to increment the server's 'h' for sm_resume_failed.
+ send(Config, #presence{to = ServerJID}),
+ close_socket(Config),
+ {save_config, set_opt(sm_previd, ID, Config)}.
+
+sm_resume_failed(Config) ->
+ {sm_resume, SMConfig} = ?config(saved_config, Config),
+ ID = ?config(sm_previd, SMConfig),
+ ct:sleep(5000), % Wait for session to time out.
+ send(Config, #sm_resume{previd = ID, h = 1, xmlns = ?NS_STREAM_MGMT_3}),
+ ?recv1(#sm_failed{reason = 'item-not-found', h = 4}),
disconnect(Config).
private(Config) ->
@@ -868,7 +882,7 @@ pubsub(Config) ->
true = lists:member(?NS_PUBSUB, Features),
%% Publish <presence/> element within node "presence"
ItemID = randoms:get_string(),
- Node = <<"presence">>,
+ Node = <<"presence!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>,
Item = #pubsub_item{id = ItemID,
xml_els = [xmpp_codec:encode(#presence{})]},
#iq{type = result,
@@ -1435,6 +1449,11 @@ muc_master(Config) ->
items = [#muc_item{affiliation = member,
jid = PeerJID,
role = participant}]}]}),
+ ?recv1(#message{from = Room,
+ sub_els = [#muc_user{
+ items = [#muc_item{affiliation = member,
+ jid = Localhost,
+ role = none}]}]}),
%% BUG: We should not receive any sub_els!
?recv1(#iq{type = result, id = I1, sub_els = [_|_]}),
%% Receive groupchat message from the peer
@@ -2255,15 +2274,46 @@ client_state_master(Config) ->
ChatState = #message{to = Peer, thread = <<"1">>,
sub_els = [#chatstate{type = active}]},
Message = ChatState#message{body = [#text{data = <<"body">>}]},
+ PepPayload = xmpp_codec:encode(#presence{}),
+ PepOne = #message{
+ to = Peer,
+ sub_els =
+ [#pubsub_event{
+ items =
+ [#pubsub_event_items{
+ node = <<"foo-1">>,
+ items =
+ [#pubsub_event_item{
+ id = <<"pep-1">>,
+ xml_els = [PepPayload]}]}]}]},
+ PepTwo = #message{
+ to = Peer,
+ sub_els =
+ [#pubsub_event{
+ items =
+ [#pubsub_event_items{
+ node = <<"foo-2">>,
+ items =
+ [#pubsub_event_item{
+ id = <<"pep-2">>,
+ xml_els = [PepPayload]}]}]}]},
%% Wait for the slave to become inactive.
wait_for_slave(Config),
- %% Should be dropped:
- send(Config, ChatState),
%% Should be queued (but see below):
send(Config, Presence),
%% Should replace the previous presence in the queue:
send(Config, Presence#presence{type = unavailable}),
- %% Should be sent immediately, together with the previous presence:
+ %% The following two PEP stanzas should be queued (but see below):
+ send(Config, PepOne),
+ send(Config, PepTwo),
+ %% The following two PEP stanzas should replace the previous two:
+ send(Config, PepOne),
+ send(Config, PepTwo),
+ %% Should be queued (but see below):
+ send(Config, ChatState),
+ %% Should replace the previous chat state in the queue:
+ send(Config, ChatState#message{sub_els = [#chatstate{type = composing}]}),
+ %% Should be sent immediately, together with the queued stanzas:
send(Config, Message),
%% Wait for the slave to become active.
wait_for_slave(Config),
@@ -2277,6 +2327,31 @@ client_state_slave(Config) ->
wait_for_master(Config),
?recv1(#presence{from = Peer, type = unavailable,
sub_els = [#delay{}]}),
+ #message{
+ from = Peer,
+ sub_els =
+ [#pubsub_event{
+ items =
+ [#pubsub_event_items{
+ node = <<"foo-1">>,
+ items =
+ [#pubsub_event_item{
+ id = <<"pep-1">>}]}]},
+ #delay{}]} = recv(),
+ #message{
+ from = Peer,
+ sub_els =
+ [#pubsub_event{
+ items =
+ [#pubsub_event_items{
+ node = <<"foo-2">>,
+ items =
+ [#pubsub_event_item{
+ id = <<"pep-2">>}]}]},
+ #delay{}]} = recv(),
+ ?recv1(#message{from = Peer, thread = <<"1">>,
+ sub_els = [#chatstate{type = composing},
+ #delay{}]}),
?recv1(#message{from = Peer, thread = <<"1">>,
body = [#text{data = <<"body">>}],
sub_els = [#chatstate{type = active}]}),
diff --git a/test/ejabberd_SUITE_data/cert.pem b/test/ejabberd_SUITE_data/cert.pem
index d6429dd68..11e18491f 100644
--- a/test/ejabberd_SUITE_data/cert.pem
+++ b/test/ejabberd_SUITE_data/cert.pem
@@ -1,51 +1,52 @@
-----BEGIN CERTIFICATE-----
-MIIDETCCAcmgAwIBAgIEUbsa1zANBgkqhkiG9w0BAQsFADAAMCIYDzIwMTMwNjE0
-MTMzMDAwWhgPMjAyMzA2MTIxMzMwMDhaMAAwggFSMA0GCSqGSIb3DQEBAQUAA4IB
-PwAwggE6AoIBMQCXdtt12OFu2j8tlF4x2Da/kbxyMxFnovJXHNzpx7CE/cGthAR5
-w7Cl92pECog2/d6ryIcjqzzCyCeOVQxIaE3Qz8z6+5UjKh3V/j6CKxcK5g1ER7Qe
-UgpE00ahHzvOpVANtrkYPGC0SFuTFL+PaylH4HW1xBSc1HD5/w7S1k1pDTz9x8ZC
-Z7JOb6NoYsz+rnmWYY2HOG6pyAyQBapIjgzCamgTStA6jTSgoXmCri/dZnJpqjZc
-V6AW7feNmMElhPvL30Cb3QB+9ODjN3pDXRR+Jqilu8ZSrpcvcFHOyKt943id1oC+
-Qu8orA0/kVInX7IuV//TciKzcH5FWz75Kb7hORPzH8M2DQcIKqKKVIwNVeJLKmcG
-RcUGsgTaz2j0JTa6YLJoczuasDWgRMT0goQpAgMBAAGjLzAtMAwGA1UdEwEB/wQC
-MAAwHQYDVR0OBBYEFBW6Si5OY8NPLagdth/JD8R18WMnMA0GCSqGSIb3DQEBCwUA
-A4IBMQAPiHxamUumu203pSVwvpWkpgKKOC2EswyFWQbNC6DWQ3LUkiR7MCiFViYt
-yiIyEh9wtfymWNF9uwaR2nVrJD5mK9Rt7xDiaT5ZOgNjLzmLeYqSlG41mCU1bmqg
-VbxmI1hvPvv3gQ/+WM0lBC6gPGJbVbzlWAIQ1cmevtL1KqOMveZl3VBPxDJD/K9c
-Rbrtx2nBKFDEl6hBljz6gsn4o8pxH3CO7qWpgY/MLwqQzEtTKYnaS9ecywNvj+/F
-ZE4SMoekw6AGRyE14/3i2xW6EmIpxVU4O6ahEFq6r6ZFbdtWnog5vT0y+/tRMgXp
-kCw8puxT2VsYNeJNOybW1IcyN5yluS/FY8iJokdL1JwvhVBVIWaim+T6iwrva7wC
-q1E9Nj30F8UbEkbkNqOdC3UlHQW4
+MIIGbDCCBVSgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMB4XDTE2MDUyNDE3NDIyNVoXDTQzMTAxMDE3NDIyNVowVjELMAkGA1UE
+BhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdp
+ZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAxMGYWN0aXZlMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQC+GTA1D1+yiXgLqUhJXkSj3hj5FiqlBAfJT/8OSXYifY4M4HYv
+VQrqER2Fs7jdCaeoGWDvwfK/UOV0b1ROnf+T/2bXFs8EOeqjOz4xG2oexNKVrYj9
+ICYAgmSh6Hf2cZJM/YCAISje93Xl2J2w/N7oFC1ZXasPoBIZv3Fgg7hTtQIDAQAB
+o4ID2DCCA9QwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5l
+cmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFEynWiCoZK4tLDk3KM1wMsbrz9Ug
+MB8GA1UdIwQYMBaAFND2ZsvHIjITekPKs0ywLfoNEen5MDMGA1UdHwQsMCowKKAm
+oCSGImh0dHA6Ly9sb2NhbGhvc3Q6NTI4MC9kYXRhL2NybC5kZXIwNgYIKwYBBQUH
+AQEEKjAoMCYGCCsGAQUFBzABhhpodHRwOi8vbG9jYWxob3N0OjUyODAvb2NzcDAL
+BgNVHQ8EBAMCBeAwEwYDVR0lBAwwCgYIKwYBBQUHAwkwggLIBgNVHREEggK/MIIC
+u6A4BggrBgEFBQcIBaAsDCp0ZXN0X3NpbmdsZSEjJCVeKigpYH4rLTtfPVtde318
+XEBsb2NhbGhvc3SgPwYIKwYBBQUHCAWgMwwxdGVzdF9zaW5nbGUhIyQlXiooKWB+
+Ky07Xz1bXXt9fFxAbW5lc2lhLmxvY2FsaG9zdKA+BggrBgEFBQcIBaAyDDB0ZXN0
+X3NpbmdsZSEjJCVeKigpYH4rLTtfPVtde318XEBteXNxbC5sb2NhbGhvc3SgPgYI
+KwYBBQUHCAWgMgwwdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcGdz
+cWwubG9jYWxob3N0oD8GCCsGAQUFBwgFoDMMMXRlc3Rfc2luZ2xlISMkJV4qKClg
+fistO189W117fXxcQHNxbGl0ZS5sb2NhbGhvc3SgQAYIKwYBBQUHCAWgNAwydGVz
+dF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAZXh0YXV0aC5sb2NhbGhvc3Sg
+PQYIKwYBBQUHCAWgMQwvdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxA
+bGRhcC5sb2NhbGhvc3SgPQYIKwYBBQUHCAWgMQwvdGVzdF9zaW5nbGUhIyQlXioo
+KWB+Ky07Xz1bXXt9fFxAcDFkYi5sb2NhbGhvc3SgPQYIKwYBBQUHCAWgMQwvdGVz
+dF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcmlhay5sb2NhbGhvc3SgPgYI
+KwYBBQUHCAWgMgwwdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcmVk
+aXMubG9jYWxob3N0oD4GCCsGAQUFBwgFoDIMMHRlc3Rfc2luZ2xlISMkJV4qKClg
+fistO189W117fXxcQG1zc3FsLmxvY2FsaG9zdDANBgkqhkiG9w0BAQUFAAOCAQEA
+et4jpmpwlE+2bw+/iqCt7sfU/5nPmQ8YtgMB+32wf7DINNJgkwOdkYJpzhlMXKrh
+/bn8+Ybmq6MbK0r2R91Uu858xQf8VKExQm44qaGSyL5Ug3jsAWb3GLZSaWQo37e9
+QdDeP8XijCEyr3rum19tRIdiImsRAxJqwfaE4pUSgfCEQMkvb+6//8HSf9RRPToD
+o6eAg8QerEtTfxerEdW/0K1ozOrzSrQembWOu+JjvANRl+p59j+1YOWHzS/yQeZl
+K3sjFoCvXPvocRnUznvT+TSdy3ORJSjwfEcP5Crim70amZZ6NeMAxfby9wwmmX0x
+zkwPCSUXliXke6T88Olj7Q==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
-MIIFegIBAAKCATEAl3bbddjhbto/LZReMdg2v5G8cjMRZ6LyVxzc6cewhP3BrYQE
-ecOwpfdqRAqINv3eq8iHI6s8wsgnjlUMSGhN0M/M+vuVIyod1f4+gisXCuYNREe0
-HlIKRNNGoR87zqVQDba5GDxgtEhbkxS/j2spR+B1tcQUnNRw+f8O0tZNaQ08/cfG
-QmeyTm+jaGLM/q55lmGNhzhuqcgMkAWqSI4MwmpoE0rQOo00oKF5gq4v3WZyaao2
-XFegFu33jZjBJYT7y99Am90AfvTg4zd6Q10UfiaopbvGUq6XL3BRzsirfeN4ndaA
-vkLvKKwNP5FSJ1+yLlf/03Iis3B+RVs++Sm+4TkT8x/DNg0HCCqiilSMDVXiSypn
-BkXFBrIE2s9o9CU2umCyaHM7mrA1oETE9IKEKQIDAQABAoIBMG8X8a4FbowFLhO7
-YD+FC9sFBMhqZpiyLrfwZqReID3bdeRUEYhSHU4OI/ZWF0Tmfh1Xjq992Koxbrn5
-7XFqd7DxybJJN0E8kfe0bJrDCjqnNBHh2d3nZLrIkGR7aT2PiSEV5bs+BdwVun0t
-2bdS7UtX+l5gvJGvTFJBXtkL8GleGV822Vc5gdIAFkXpOdyPkoTXdpw4qwqBnL8/
-TXMYBIgCrXMhEawcNbgPu4iFev2idoU9vXc7ZYD7+8jWB5LJ34cNngguGrnOjLoE
-9c3nZy6uYhhMWtcrSsQlrbN5MtY8w2fPH8nhfA651IxXXVxEajd24t2Csttnl7Vz
-WS5c+oPaWwt67naMrYCWG3q1zWhDqZUAulZR4DzWzGP+idLS/ojCRdTZO9D1O+XP
-fPi0wJECgZkAxh0rTSMCyrJ3VJqEgSPw3yAa1R9cdrTRvV4vRf13Dh8REaHtWt8W
-JeT5WLXL7dOii1St1Fgjo82+4iMqx3PQ2eR/1I6dA7Uy71PaSQTCQnupca2Xx8nT
-5KcrASBkDAudiKog01eC+zYrW+CbUb9AogMZLJzZinlWQ36pJVkWd9SOv25Eqcv6
-zJEmzYKpnow/m8WKNogVGpUCgZkAw7hQxs5VYVLp2XtDqRSmxfJsfsbUVo7tZnSU
-wmejgeNRs7415ZuT142k7qBImrFdYzFcfh2OZnf6D/VIz4Rl7u5YRYRCha/HOGIy
-wTe1huDckJ6lH/BkZ/6f9WSzXnNSNeXQY14WymU5V5qYCAdwECSf+xNuBYNwzA7o
-vOxPE690w3Ox2qghzRjzsBqAMgvqSyKlBpoMckUCgZhyKgD39IL5V5qYcGqHGLUH
-fzK3OdlItq5e19WaGZPv2Us2w/9JbGEQ+UAPNMQNivWSIPwC77+p9zhWjDlssnrZ
-9WkMjhpBNrvhWorhpRJkyWo9jfF3OgEXNJX9kjLVFiRzysYbw8RBC1g1G9ulYfbW
-5b4uDTz3JTDmuCi00v+1khGoktySlG80TzjzGKayLNPC6jTZc9XleQKBmA0STUrJ
-0wf5+qZMxjsPpwfHZhmde+cACrjyBlFpjJELNpSzmnPoTRpzWlWZnN/AAsWyMUQ3
-AyCy2J+iOSeq5wfrITgbWjoFgF+yp0MiTlxgvjpmbg7RBlOvvM0t2ZDwUMhKvf00
-9n6z/f1s1MSMgp6BY7HoHUv++FSYllCv06Qz7q9zFajN29wP046qZm9xPkegW7cy
-KKylAoGYTg94GOWlUTz7Pe9PDrDSFVEAi0LcDmul0ntorvEFDvU2pCRK14gyvl9O
-IJKVyYcDAqA3uvT+zMAniuf8KXNUCcYeEpfzpT+e2eznhczO8hI14M5U0X0LA8P2
-vn0Y+yUWb9Ppu/dcjvaUA+qR/UTHqjAlAr3hFTKRxXFoGwwzTXCXvZGKOnzJRTpj
-LpjI1RG7Weeoyx/8qDs=
+MIICXAIBAAKBgQC+GTA1D1+yiXgLqUhJXkSj3hj5FiqlBAfJT/8OSXYifY4M4HYv
+VQrqER2Fs7jdCaeoGWDvwfK/UOV0b1ROnf+T/2bXFs8EOeqjOz4xG2oexNKVrYj9
+ICYAgmSh6Hf2cZJM/YCAISje93Xl2J2w/N7oFC1ZXasPoBIZv3Fgg7hTtQIDAQAB
+AoGALddtJJ58eVVlOYqs/+RXsRyR8R9DUV/TcNx1qUBV2KNmafyHA4sCgsd10xQv
+9D2rzIGyOp8OpswfSSC/t+WqB9+ezSruzMuX6IURdHZbX6aWWX6maICtPKEEkCmI
+gaLxE/ojuOXnTEBTkVuVWtuFL9PsK/WGi/FIDzJbwqTWJ4ECQQDy9DrBAQM96B6u
+G4XpFzBsfgJZoS+NaMdCwK+/jgcEpI6oxobK8tuGB6drp5jNSuQ905W9n8XjA6Xq
+x8/GH9I5AkEAyE5g05HhMlxBWCq+P70pBDIamdHJcPQVL8+6NXkT+mTqqZxxkUy4
+nMfTh5zE6WfmqYNtrmNBDxXUyaoRSBydXQJACnFnCR7DBekxUGiMc/10LmWoMjQU
+eC6Vyg/APiqbsJ5mJ2kJKDYSK4uurZjxn3lloCa1HAZ/GgfxHMtj6e86OQJAetq3
+wIwE12KGIZF1xpo6gfxJHHbzWngaVozN5OYyPq2O0CDH9xpbUK2vK8oXbCDx9J5L
+s13lFV+Kd3X7y4LhcQJBAKSFg7ht33l8Sa0TdUkY6Tl1NBMCCLf+np+HYrAbQZux
+2NtR6nj2YqeOpEe1ibWZm8tj3dzlTm1FCOIpa+pm114=
-----END RSA PRIVATE KEY-----
diff --git a/test/ejabberd_SUITE_data/ejabberd.ldif b/test/ejabberd_SUITE_data/ejabberd.ldif
index 863a07e4f..a98036480 100644
--- a/test/ejabberd_SUITE_data/ejabberd.ldif
+++ b/test/ejabberd_SUITE_data/ejabberd.ldif
@@ -15,38 +15,38 @@ ou: groups
objectClass: organizationalUnit
dn: uid=test_single,ou=users,dc=localhost
-uid: test_single
-mail: test_single@localhost
+uid: test_single!#$%^*()`~+-;_=[]{}|\
+mail: test_single!#$%^*()`~+-;_=[]{}|\@localhost
objectClass: person
jpegPhoto:: /9g=
cn: Test Single
-password: password
+password: password!@#$%^&*()'"`~<>+-/;:_=[]{}|\
dn: uid=test_master,ou=users,dc=localhost
-uid: test_master
-mail: test_master@localhost
+uid: test_master!#$%^*()`~+-;_=[]{}|\
+mail: test_master!#$%^*()`~+-;_=[]{}|\@localhost
objectClass: person
jpegPhoto:: /9g=
cn: Test Master
-password: password
+password: password!@#$%^&*()'"`~<>+-/;:_=[]{}|\
dn: uid=test_slave,ou=users,dc=localhost
-uid: test_slave
-mail: test_slave@localhost
+uid: test_slave!#$%^*()`~+-;_=[]{}|\
+mail: test_slave!#$%^*()`~+-;_=[]{}|\@localhost
objectClass: person
jpegPhoto:: /9g=
cn: Test Slave
-password: password
+password: password!@#$%^&*()'"`~<>+-/;:_=[]{}|\
dn: uid=user2,ou=users,dc=localhost
uid: user2
mail: user2@localhost
objectClass: person
cn: Test User 2
-password: password
+password: password!@#$%^&*()'"`~<>+-/;:_=[]{}|\
dn: cn=group1,ou=groups,dc=localhost
objectClass: posixGroup
-memberUid: test_single
+memberUid: test_single!#$%^*()`~+-;_=[]{}|\
memberUid: user2
cn: group1
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index 869c24c7a..aca547d99 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -213,8 +213,9 @@ Welcome to this XMPP server."
db_type: internal
mod_carboncopy: []
mod_client_state:
- drop_chat_states: true
queue_presence: true
+ queue_chat_states: true
+ queue_pep: true
mod_adhoc: []
mod_configure: []
mod_disco: []
@@ -269,8 +270,9 @@ Welcome to this XMPP server."
db_type: internal
mod_carboncopy: []
mod_client_state:
- drop_chat_states: true
queue_presence: true
+ queue_chat_states: true
+ queue_pep: true
mod_adhoc: []
mod_configure: []
mod_disco: []
@@ -417,6 +419,7 @@ listen:
starttls: true
shaper: c2s_shaper
access: c2s
+ resume_timeout: 3
-
port: @@s2s_port@@
module: ejabberd_s2s_in
diff --git a/test/ejabberd_commands_mock_test.exs b/test/ejabberd_commands_mock_test.exs
index 285585034..487cf6a4b 100644
--- a/test/ejabberd_commands_mock_test.exs
+++ b/test/ejabberd_commands_mock_test.exs
@@ -127,6 +127,8 @@ defmodule EjabberdCommandsMockTest do
test "API command can be registered and executed" do
+ mock_commands_config
+
# Create & register a mocked command test() -> :result
command_name = :test
function = :test_command
@@ -142,6 +144,8 @@ defmodule EjabberdCommandsMockTest do
end
test "API command with versions can be registered and executed" do
+ mock_commands_config
+
command_name = :test
function1 = :test_command1
@@ -409,13 +413,13 @@ defmodule EjabberdCommandsMockTest do
:meck.expect(:ejabberd_config, :get_myhosts,
fn() -> [@domain] end)
:meck.new :acl
- :meck.expect(:acl, :match_rule,
- fn(@domain, :commands_admin_access, user) ->
- case :jlib.make_jid(@admin, @domain, "") do
- ^user -> :allow
- _ -> :deny
- end
- (@domain, :all, _user) ->
+ :meck.expect(:acl, :access_matches,
+ fn(:commands_admin_access, info, _scope) ->
+ case info do
+ %{usr: {@admin, @domain, _}} -> :allow
+ _ -> :deny
+ end;
+ (:all, _, _scope) ->
:allow
end)
end
diff --git a/test/elixir_SUITE.erl b/test/elixir_SUITE.erl
index 48cc94f8e..aaef9151d 100644
--- a/test/elixir_SUITE.erl
+++ b/test/elixir_SUITE.erl
@@ -18,6 +18,7 @@
-compile(export_all).
init_per_suite(Config) ->
+ suite:setup_ejabberd_lib_path(Config),
check_meck(),
code:add_pathz(filename:join(test_dir(), "../include")),
Config.
diff --git a/test/mod_admin_extra_test.exs b/test/mod_admin_extra_test.exs
index 2029cf686..761b07b7c 100644
--- a/test/mod_admin_extra_test.exs
+++ b/test/mod_admin_extra_test.exs
@@ -43,6 +43,7 @@ defmodule EjabberdModAdminExtraTest do
_ -> :ok
end
:ejabberd_commands.init
+ :ok = :ejabberd_config.start([@domain], [])
:mod_admin_extra.start(@domain, [])
:sel_application.start_app(:moka)
{:ok, _pid} = :ejabberd_hooks.start_link
diff --git a/test/mod_http_api_mock_test.exs b/test/mod_http_api_mock_test.exs
index 78832d7a5..47b1fe94a 100644
--- a/test/mod_http_api_mock_test.exs
+++ b/test/mod_http_api_mock_test.exs
@@ -75,7 +75,7 @@ defmodule ModHttpApiMockTest do
:meck.expect(:ejabberd_commands, :get_commands,
fn () -> [@acommand] end)
:meck.expect(:ejabberd_commands, :execute_command,
- fn (:undefined, {@user, @domain, @userpass, false}, @acommand, [], @version) ->
+ fn (:undefined, {@user, @domain, @userpass, false}, @acommand, [], @version, _) ->
:ok
end)
@@ -129,7 +129,7 @@ defmodule ModHttpApiMockTest do
fn () -> [@acommand] end)
:meck.expect(:ejabberd_commands, :execute_command,
fn (:undefined, {@user, @domain, {:oauth, _token}, false},
- @acommand, [], @version) ->
+ @acommand, [], @version, _) ->
:ok
end)
diff --git a/test/mod_http_api_test.exs b/test/mod_http_api_test.exs
index 9432ae471..99b8d9b28 100644
--- a/test/mod_http_api_test.exs
+++ b/test/mod_http_api_test.exs
@@ -39,6 +39,7 @@ defmodule ModHttpApiTest do
end
test "We can expose several commands to API at a time" do
+ setup_mocks()
:ejabberd_config.add_local_option(:commands, [[{:add_commands, [:open_cmd, :user_cmd]}]])
commands = :ejabberd_commands.get_commands()
assert Enum.member?(commands, :open_cmd)
@@ -46,21 +47,24 @@ defmodule ModHttpApiTest do
end
test "We can call open commands without authentication" do
+ setup_mocks()
:ejabberd_config.add_local_option(:commands, [[{:add_commands, [:open_cmd]}]])
- request = request(method: :POST, data: "[]")
+ request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
{200, _, _} = :mod_http_api.process(["open_cmd"], request)
end
# This related to the commands config file option
test "Attempting to access a command that is not exposed as HTTP API returns 401" do
+ setup_mocks()
:ejabberd_config.add_local_option(:commands, [])
- request = request(method: :POST, data: "[]")
+ request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
{401, _, _} = :mod_http_api.process(["open_cmd"], request)
end
test "Call to user, admin or restricted commands without authentication are rejected" do
+ setup_mocks()
:ejabberd_config.add_local_option(:commands, [[{:add_commands, [:user_cmd, :admin_cmd, :restricted]}]])
- request = request(method: :POST, data: "[]")
+ request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
{401, _, _} = :mod_http_api.process(["user_cmd"], request)
{401, _, _} = :mod_http_api.process(["admin_cmd"], request)
{401, _, _} = :mod_http_api.process(["restricted_cmd"], request)
@@ -68,6 +72,7 @@ defmodule ModHttpApiTest do
@tag pending: true
test "If admin_ip_access is enabled, we can call restricted API without authentication from that IP" do
+ setup_mocks()
end
# Define a set of test commands that we expose through API
@@ -86,10 +91,27 @@ defmodule ModHttpApiTest do
end
def open_cmd, do: :ok
- def user_cmd, do: :ok
+ def user_cmd(_, _), do: :ok
def admin_cmd, do: :ok
def restricted_cmd, do: :ok
+ defp setup_mocks() do
+ :meck.unload
+ mock(:gen_mod, :get_module_opt,
+ fn (_server, :mod_http_api, admin_ip_access, _, _) ->
+ [{:allow, [{:ip, {{127,0,0,2}, 32}}]}]
+ end)
+ end
+
+ defp mock(module, function, fun) do
+ try do
+ :meck.new(module)
+ catch
+ :error, {:already_started, _pid} -> :ok
+ end
+ :meck.expect(module, function, fun)
+ end
+
defp unregister_commands(commands) do
try do
:ejabberd_commands.unregister_commands(commands)
diff --git a/test/mod_roster_mock.exs b/test/mod_roster_mock.exs
index b1251ba86..ae990a6b1 100644
--- a/test/mod_roster_mock.exs
+++ b/test/mod_roster_mock.exs
@@ -45,41 +45,32 @@ defmodule ModRosterMock do
try do
module_mock = :moka.start(module)
- :moka.replace(module_mock, :mod_roster, :invalidate_roster_cache,
+ :moka.replace(module_mock, :mod_roster_mnesia, :invalidate_roster_cache,
fn (_user, _server) ->
:ok
end)
:moka.load(module_mock)
- roster_mock = :moka.start(:mod_roster)
-
- :moka.replace(roster_mock, :gen_mod, :db_type,
- fn (_host, _opts) ->
- {:none}
- end)
-
- :moka.replace(roster_mock, :gen_iq_handler, :add_iq_handler,
+ roster_mock0 = :moka.start(:mod_roster)
+ :moka.replace(roster_mock0, :gen_iq_handler, :add_iq_handler,
fn (_module, _host, _ns, _m, _f, _iqdisc) ->
:ok
end)
- :moka.replace(roster_mock, :gen_iq_handler, :remove_iq_handler,
+ :moka.replace(roster_mock0, :gen_iq_handler, :remove_iq_handler,
fn (_module, _host, _ns) ->
:ok
end)
-
- :moka.replace(roster_mock, :transaction,
- fn (_server, function) ->
- {:atomic, function.()}
+ :moka.replace(roster_mock0, :gen_mod, :db_mod,
+ fn (_host, _mod) ->
+ :mod_roster_mnesia
end)
-
- :moka.replace(roster_mock, :get_roster,
- fn (user, domain) ->
- to_records(get_roster(user, domain))
+ :moka.replace(roster_mock0, :gen_mod, :db_mod,
+ fn (_host, _opts, _mod) ->
+ :mod_roster_mnesia
end)
-
- :moka.replace(roster_mock, :update_roster_t,
+ :moka.replace(roster_mock0, :update_roster_t,
fn (user, domain, {u, d, _r}, item) ->
add_roster_item(user, domain, u<>"@"<>d,
roster(item, :name),
@@ -89,10 +80,32 @@ defmodule ModRosterMock do
roster(item, :askmessage))
end)
- :moka.replace(roster_mock, :del_roster_t,
+ :moka.replace(roster_mock0, :del_roster_t,
fn (user, domain, jid) ->
remove_roster_item(user, domain, :jid.to_string(jid))
end)
+ :moka.replace(roster_mock0, :get_roster,
+ fn (user, domain) ->
+ to_records(get_roster(user, domain))
+ end)
+
+ :moka.load(roster_mock0)
+
+ roster_mock = :moka.start(:mod_roster_mnesia)
+ :moka.replace(roster_mock, :gen_mod, :db_type,
+ fn (_host, _opts) ->
+ {:none}
+ end)
+
+ :moka.replace(roster_mock, :transaction,
+ fn (_server, function) ->
+ {:atomic, function.()}
+ end)
+
+ :moka.replace(roster_mock, :update_tables,
+ fn () ->
+ :ok
+ end)
:moka.load(roster_mock)
@@ -104,7 +117,7 @@ defmodule ModRosterMock do
def mock_with_meck do
# mock(:gen_mod, :db_type,
-# fn (_server, :mod_roster) ->
+# fn (_server, :mod_roster_mnesia) ->
# :mnesia
# end)
#
@@ -119,12 +132,16 @@ defmodule ModRosterMock do
# {:atomic, :ok}
# end)
- mock(:mod_roster, :transaction,
+ mock(:mod_roster_mnesia, :init,
+ fn (_server, _opts) ->
+ :ok
+ end)
+ mock(:mod_roster_mnesia, :transaction,
fn (_server, function) ->
{:atomic, function.()}
end)
- mock(:mod_roster, :update_roster_t,
+ mock(:mod_roster_mnesia, :update_roster_t,
fn (user, domain, {u, d, _r}, item) ->
add_roster_item(user, domain, u<>"@"<>d,
roster(item, :name),
@@ -134,7 +151,7 @@ defmodule ModRosterMock do
roster(item, :askmessage))
end)
- mock(:mod_roster, :invalidate_roster_cache,
+ mock(:mod_roster_mnesia, :invalidate_roster_cache,
fn (_user, _server) ->
:ok
end)
diff --git a/test/suite.erl b/test/suite.erl
index c605ed709..c5593c4cf 100644
--- a/test/suite.erl
+++ b/test/suite.erl
@@ -12,6 +12,7 @@
-compile(export_all).
-include("suite.hrl").
+-include_lib("kernel/include/file.hrl").
%%%===================================================================
%%% API
@@ -47,6 +48,7 @@ init_config(Config) ->
]),
ConfigPath = filename:join([CWD, "ejabberd.yml"]),
ok = file:write_file(ConfigPath, CfgContent),
+ setup_ejabberd_lib_path(Config),
ok = application:load(sasl),
ok = application:load(mnesia),
ok = application:load(ejabberd),
@@ -57,19 +59,40 @@ init_config(Config) ->
[{server_port, ct:get_config(c2s_port, 5222)},
{server_host, "localhost"},
{server, ?COMMON_VHOST},
- {user, <<"test_single">>},
- {master_nick, <<"master_nick">>},
- {slave_nick, <<"slave_nick">>},
- {room_subject, <<"hello, world!">>},
+ {user, <<"test_single!#$%^*()`~+-;_=[]{}|\\">>},
+ {master_nick, <<"master_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
+ {slave_nick, <<"slave_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
+ {room_subject, <<"hello, world!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
{certfile, CertFile},
{base_dir, BaseDir},
- {resource, <<"resource">>},
- {master_resource, <<"master_resource">>},
- {slave_resource, <<"slave_resource">>},
+ {resource, <<"resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
+ {master_resource, <<"master_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
+ {slave_resource, <<"slave_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
{password, <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
{backends, get_config_backends()}
|Config].
+find_top_dir(Dir) ->
+ case file:read_file_info(filename:join([Dir, ebin])) of
+ {ok, #file_info{type = directory}} ->
+ Dir;
+ _ ->
+ find_top_dir(filename:dirname(Dir))
+ end.
+
+setup_ejabberd_lib_path(Config) ->
+ case code:lib_dir(ejabberd) of
+ {error, _} ->
+ DataDir = proplists:get_value(data_dir, Config),
+ {ok, CWD} = file:get_cwd(),
+ NewEjPath = filename:join([CWD, "ejabberd-0.0.1"]),
+ TopDir = find_top_dir(DataDir),
+ ok = file:make_symlink(TopDir, NewEjPath),
+ code:replace_path(ejabberd, NewEjPath);
+ _ ->
+ ok
+ end.
+
%% Read environment variable CT_DB=riak,mysql to limit the backends to test.
%% You can thus limit the backend you want to test with:
%% CT_BACKENDS=riak,mysql rebar ct suites=ejabberd
@@ -294,7 +317,12 @@ sasl_new(<<"DIGEST-MD5">>, User, Server, Password) ->
MyResponse = response(User, Password, Nonce, AuthzId,
Realm, CNonce, DigestURI, NC, QOP,
<<"AUTHENTICATE">>),
- Resp = <<"username=\"", User/binary, "\",realm=\"",
+ SUser = << <<(case Char of
+ $" -> <<"\\\"">>;
+ $\\ -> <<"\\\\">>;
+ _ -> <<Char>>
+ end)/binary>> || <<Char>> <= User >>,
+ Resp = <<"username=\"", SUser/binary, "\",realm=\"",
Realm/binary, "\",nonce=\"", Nonce/binary,
"\",cnonce=\"", CNonce/binary, "\",nc=", NC/binary,
",qop=", QOP/binary, ",digest-uri=\"",
diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl
index 52950feac..ef1421962 100644
--- a/tools/xmpp_codec.erl
+++ b/tools/xmpp_codec.erl
@@ -2174,7 +2174,7 @@ encode({sm_resumed, _, _, _} = Resumed) ->
encode_sm_resumed(Resumed, []);
encode({sm_r, _} = R) -> encode_sm_r(R, []);
encode({sm_a, _, _} = A) -> encode_sm_a(A, []);
-encode({sm_failed, _, _} = Failed) ->
+encode({sm_failed, _, _, _} = Failed) ->
encode_sm_failed(Failed, []);
encode({offline_item, _, _} = Item) ->
encode_offline_item(Item,
@@ -2597,7 +2597,7 @@ pp(sm_resume, 3) -> [h, previd, xmlns];
pp(sm_resumed, 3) -> [h, previd, xmlns];
pp(sm_r, 1) -> [xmlns];
pp(sm_a, 2) -> [h, xmlns];
-pp(sm_failed, 2) -> [reason, xmlns];
+pp(sm_failed, 3) -> [reason, h, xmlns];
pp(offline_item, 2) -> [node, action];
pp(offline, 3) -> [items, purge, fetch];
pp(mix_join, 2) -> [jid, subscribe];
@@ -2963,9 +2963,9 @@ decode_sm_failed(__TopXMLNS, __IgnoreEls,
{xmlel, <<"failed">>, _attrs, _els}) ->
Reason = decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
_els, undefined),
- Xmlns = decode_sm_failed_attrs(__TopXMLNS, _attrs,
- undefined),
- {sm_failed, Reason, Xmlns}.
+ {H, Xmlns} = decode_sm_failed_attrs(__TopXMLNS, _attrs,
+ undefined, undefined),
+ {sm_failed, Reason, H, Xmlns}.
decode_sm_failed_els(__TopXMLNS, __IgnoreEls, [],
Reason) ->
@@ -3285,20 +3285,25 @@ decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
Reason).
decode_sm_failed_attrs(__TopXMLNS,
- [{<<"xmlns">>, _val} | _attrs], _Xmlns) ->
- decode_sm_failed_attrs(__TopXMLNS, _attrs, _val);
-decode_sm_failed_attrs(__TopXMLNS, [_ | _attrs],
+ [{<<"h">>, _val} | _attrs], _H, Xmlns) ->
+ decode_sm_failed_attrs(__TopXMLNS, _attrs, _val, Xmlns);
+decode_sm_failed_attrs(__TopXMLNS,
+ [{<<"xmlns">>, _val} | _attrs], H, _Xmlns) ->
+ decode_sm_failed_attrs(__TopXMLNS, _attrs, H, _val);
+decode_sm_failed_attrs(__TopXMLNS, [_ | _attrs], H,
Xmlns) ->
- decode_sm_failed_attrs(__TopXMLNS, _attrs, Xmlns);
-decode_sm_failed_attrs(__TopXMLNS, [], Xmlns) ->
- decode_sm_failed_attr_xmlns(__TopXMLNS, Xmlns).
+ decode_sm_failed_attrs(__TopXMLNS, _attrs, H, Xmlns);
+decode_sm_failed_attrs(__TopXMLNS, [], H, Xmlns) ->
+ {decode_sm_failed_attr_h(__TopXMLNS, H),
+ decode_sm_failed_attr_xmlns(__TopXMLNS, Xmlns)}.
-encode_sm_failed({sm_failed, Reason, Xmlns},
+encode_sm_failed({sm_failed, Reason, H, Xmlns},
_xmlns_attrs) ->
_els = lists:reverse('encode_sm_failed_$reason'(Reason,
[])),
_attrs = encode_sm_failed_attr_xmlns(Xmlns,
- _xmlns_attrs),
+ encode_sm_failed_attr_h(H,
+ _xmlns_attrs)),
{xmlel, <<"failed">>, _attrs, _els}.
'encode_sm_failed_$reason'(undefined, _acc) -> _acc;
@@ -3443,6 +3448,20 @@ encode_sm_failed({sm_failed, Reason, Xmlns},
<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
| _acc].
+decode_sm_failed_attr_h(__TopXMLNS, undefined) ->
+ undefined;
+decode_sm_failed_attr_h(__TopXMLNS, _val) ->
+ case catch dec_int(_val, 0, infinity) of
+ {'EXIT', _} ->
+ erlang:error({xmpp_codec,
+ {bad_attr_value, <<"h">>, <<"failed">>, __TopXMLNS}});
+ _res -> _res
+ end.
+
+encode_sm_failed_attr_h(undefined, _acc) -> _acc;
+encode_sm_failed_attr_h(_val, _acc) ->
+ [{<<"h">>, enc_int(_val)} | _acc].
+
decode_sm_failed_attr_xmlns(__TopXMLNS, undefined) ->
undefined;
decode_sm_failed_attr_xmlns(__TopXMLNS, _val) -> _val.
diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl
index b2773a6c1..6d4b750b6 100644
--- a/tools/xmpp_codec.hrl
+++ b/tools/xmpp_codec.hrl
@@ -440,6 +440,7 @@
-record(sasl_mechanisms, {list = [] :: [binary()]}).
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{},
+ h :: non_neg_integer(),
xmlns :: binary()}).
-record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait',
diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec
index 536a11dfb..acf9c3bb7 100644
--- a/tools/xmpp_codec.spec
+++ b/tools/xmpp_codec.spec
@@ -2351,8 +2351,11 @@
-xml(sm_failed,
#elem{name = <<"failed">>,
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
- result = {sm_failed, '$reason', '$xmlns'},
- attrs = [#attr{name = <<"xmlns">>}],
+ result = {sm_failed, '$reason', '$h', '$xmlns'},
+ attrs = [#attr{name = <<"h">>,
+ dec = {dec_int, [0, infinity]},
+ enc = {enc_int, []}},
+ #attr{name = <<"xmlns">>}],
refs = [#ref{name = error_bad_request,
min = 0, max = 1, label = '$reason'},
#ref{name = error_conflict,