aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/acl.erl432
-rw-r--r--src/cyrsasl_digest.erl15
-rw-r--r--src/ejabberd_admin.erl6
-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.erl191
-rw-r--r--src/ejabberd_commands.erl66
-rw-r--r--src/ejabberd_config.erl57
-rw-r--r--src/ejabberd_oauth.erl4
-rw-r--r--src/ejabberd_s2s.erl4
-rw-r--r--src/ejabberd_sm.erl78
-rw-r--r--src/ejabberd_sql.erl3
-rw-r--r--src/ejabberd_xmlrpc.erl2
-rw-r--r--src/ext_mod.erl2
-rw-r--r--src/gen_mod.erl19
-rw-r--r--src/gen_pubsub_node.erl4
-rw-r--r--src/jlib.erl64
-rw-r--r--src/mod_admin_extra.erl43
-rw-r--r--src/mod_announce.erl4
-rw-r--r--src/mod_client_state.erl273
-rw-r--r--src/mod_configure.erl10
-rw-r--r--src/mod_fail2ban.erl4
-rw-r--r--src/mod_http_api.erl30
-rw-r--r--src/mod_http_upload.erl4
-rw-r--r--src/mod_http_upload_quota.erl8
-rw-r--r--src/mod_irc.erl4
-rw-r--r--src/mod_last.erl13
-rw-r--r--src/mod_mam.erl9
-rw-r--r--src/mod_mam_mnesia.erl89
-rw-r--r--src/mod_mam_sql.erl37
-rw-r--r--src/mod_muc.erl16
-rw-r--r--src/mod_muc_log.erl2
-rw-r--r--src/mod_muc_room.erl74
-rw-r--r--src/mod_multicast.erl4
-rw-r--r--src/mod_offline.erl6
-rw-r--r--src/mod_offline_sql.erl48
-rw-r--r--src/mod_proxy65_service.erl2
-rw-r--r--src/mod_proxy65_stream.erl2
-rw-r--r--src/mod_pubsub.erl27
-rw-r--r--src/mod_register.erl29
-rw-r--r--src/mod_register_web.erl2
-rw-r--r--src/mod_roster.erl14
-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.erl349
-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
63 files changed, 1529 insertions, 884 deletions
diff --git a/src/acl.erl b/src/acl.erl
index 06202c67e..95c9ebbd4 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -29,12 +29,13 @@
-author('alexey@process-one.net').
--export([start/0, to_record/3, add/3, add_list/3,
- add_local/3, add_list_local/3, load_from_config/0,
- match_rule/3, match_access/4, match_acl/3, transform_options/1,
- opt_type/1]).
-
-export([add_access/3, clear/0]).
+-export([start/0, add/3, add_list/3, add_local/3, add_list_local/3,
+ load_from_config/0, match_rule/3,
+ transform_options/1, opt_type/1, acl_rule_matches/3,
+ acl_rule_verify/1, access_matches/3,
+ transform_access_rules_config/1,
+ access_rules_validator/1, shaper_rules_validator/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -92,12 +93,6 @@ start() ->
load_from_config(),
ok.
--spec to_record(binary(), atom(), aclspec()) -> acl().
-
-to_record(Host, ACLName, ACLSpec) ->
- #acl{aclname = {ACLName, Host},
- aclspec = normalize_spec(ACLSpec)}.
-
-spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}.
add(Host, ACLName, ACLSpec) ->
@@ -188,6 +183,10 @@ load_from_config() ->
{acl, Host}, fun(V) -> V end, []),
AccessRules = ejabberd_config:get_option(
{access, Host}, fun(V) -> V end, []),
+ AccessRulesNew = ejabberd_config:get_option(
+ {access_rules, Host}, fun(V) -> V end, []),
+ ShaperRules = ejabberd_config:get_option(
+ {shaper_rules, Host}, fun(V) -> V end, []),
lists:foreach(
fun({ACLName, SpecList}) ->
lists:foreach(
@@ -203,8 +202,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, AccessRules)
+ end, AccessRulesNew),
+ lists:foreach(
+ fun({Access, Rules}) ->
+ add_access(Host, Access, Rules)
+ end, ShaperRules)
end, Hosts).
%% Delete all previous set ACLs and Access rules
@@ -225,19 +235,28 @@ nameprep(S) ->
resourceprep(S) ->
jid:resourceprep(b(S)).
+split_user_server(Str, NormFunUsr, NormFunSrv) ->
+ case binary:split(Str, <<"@">>) of
+ [U, S] ->
+ {NormFunUsr(U), NormFunSrv(S)};
+ _ ->
+ NormFunUsr(Str)
+ end.
+
normalize_spec(Spec) ->
case Spec of
all -> all;
none -> none;
+ {acl, N} -> {acl, N};
{user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}};
- {user, U} -> {user, nodeprep(U)};
+ {user, U} -> {user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
{shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
- {shared_group, G} -> {shared_group, b(G)};
+ {shared_group, G} -> {shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
{user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}};
- {user_regexp, UR} -> {user_regexp, b(UR)};
+ {user_regexp, UR} -> {user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_regexp, {UR, SR}} -> {node_regexp, {b(UR), b(SR)}};
{user_glob, {UR, S}} -> {user_glob, {b(UR), nameprep(S)}};
- {user_glob, UR} -> {user_glob, b(UR)};
+ {user_glob, UR} -> {user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}};
{server, S} -> {server, nameprep(S)};
{resource, R} -> {resource, resourceprep(R)};
@@ -255,130 +274,196 @@ 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.
+invalid_syntax(Msg, Data) ->
+ throw({invalid_syntax, iolist_to_binary(io_lib:format(Msg, Data))}).
+
+acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) ->
+ acl_rules_verify(Rest, true);
+acl_rules_verify([{acl, Name} = Rule | _Rest], false) when is_atom(Name) ->
+ invalid_syntax(<<"Using acl: rules not allowed: ~p">>, [Rule]);
+acl_rules_verify([Rule | Rest], AllowAcl) ->
+ case acl_rule_verify(Rule) of
+ false ->
+ invalid_syntax(<<"Invalid rule: ~p">>, [Rule]);
+ true ->
+ acl_rules_verify(Rest, AllowAcl)
+ end;
+acl_rules_verify([], _AllowAcl) ->
+ true;
+acl_rules_verify(Rules, _AllowAcl) ->
+ invalid_syntax(<<"Not a acl rules list: ~p">>, [Rules]).
-get_aclspecs(ACL, Host) ->
- ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
-match_user_spec(Spec, JID, Default) ->
- case do_match_user_spec(Spec, jid:tolower(JID)) of
- true -> Default;
- false -> deny
- end.
+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.
+
+-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 +535,63 @@ parse_ip_netmask(S) ->
_ -> error
end.
+transform_access_rules_config(Config) when is_list(Config) ->
+ lists:map(fun transform_access_rules_config2/1, lists:flatten(Config));
+transform_access_rules_config(Config) ->
+ transform_access_rules_config([Config]).
+
+transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) ->
+ {Type, [all]};
+transform_access_rules_config2({Type, ACL}) when is_atom(ACL) ->
+ {Type, [{acl, ACL}]};
+transform_access_rules_config2({Res, Rules}) when is_list(Rules) ->
+ T = lists:map(fun({Type, Args}) when is_list(Args) ->
+ normalize_spec({Type, hd(lists:flatten(Args))});
+ (V) -> normalize_spec(V)
+ end, lists:flatten(Rules)),
+ {Res, T};
+transform_access_rules_config2({Res, Rule}) ->
+ {Res, [Rule]}.
+
+access_rules_validator(Name) when is_atom(Name) ->
+ Name;
+access_rules_validator(Rules0) ->
+ Rules = transform_access_rules_config(Rules0),
+ access_shaper_rules_validator(Rules, fun(allow) -> true;
+ (deny) -> true;
+ (_) -> false
+ end),
+ throw({replace_with, Rules}).
+
+
+shaper_rules_validator(Name) when is_atom(Name) ->
+ Name;
+shaper_rules_validator(Rules0) ->
+ Rules = transform_access_rules_config(Rules0),
+ access_shaper_rules_validator(Rules, fun(V) when is_atom(V) -> true;
+ (V2) when is_integer(V2) -> true;
+ (_) -> false
+ end),
+ throw({replace_with, Rules}).
+
+access_shaper_rules_validator([{Type, Acls} = Rule | Rest], RuleTypeCheck) ->
+ case RuleTypeCheck(Type) of
+ true ->
+ case acl_rules_verify(Acls, true) of
+ true ->
+ access_shaper_rules_validator(Rest, RuleTypeCheck);
+ Err ->
+ Err
+ end;
+ false ->
+ invalid_syntax(<<"Invalid rule type: ~p in rule ~p">>, [Type, Rule])
+ end;
+access_shaper_rules_validator([], _RuleTypeCheck) ->
+ true;
+access_shaper_rules_validator(Value, _RuleTypeCheck) ->
+ invalid_syntax(<<"Not a rule definition: ~p">>, [Value]).
+
+
transform_options(Opts) ->
Opts1 = lists:foldl(fun transform_options/2, [], Opts),
{ACLOpts, Opts2} = lists:mapfoldl(
@@ -464,6 +606,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 +631,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 +676,7 @@ transform_options(Opt, Opts) ->
[Opt|Opts].
opt_type(access) -> fun (V) -> V end;
+opt_type(access_rules) -> fun (V) -> V end;
+opt_type(shaper_rules) -> fun (V) -> V end;
opt_type(acl) -> fun (V) -> V end;
-opt_type(_) -> [access, acl].
+opt_type(_) -> [access, acl, access_rules, shaper_rules].
diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl
index e58cb3035..0d408fc48 100644
--- a/src/cyrsasl_digest.erl
+++ b/src/cyrsasl_digest.erl
@@ -53,11 +53,11 @@
check_password = fun(_, _, _, _, _) -> false end :: check_password_fun(),
auth_module :: atom(),
host = <<"">> :: binary(),
- hostfqdn = <<"">> :: binary()}).
+ hostfqdn = <<"">> :: binary() | [binary()]}).
start(_Opts) ->
Fqdn = get_local_fqdn(),
- ?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
+ ?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p",
[Fqdn]),
cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
digest).
@@ -183,16 +183,16 @@ is_digesturi_valid(DigestURICase, JabberDomain,
DigestURI = stringprep:tolower(DigestURICase),
case catch str:tokens(DigestURI, <<"/">>) of
[<<"xmpp">>, Host] ->
- IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
+ IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
(Host == JabberDomain) or IsHostFqdn;
[<<"xmpp">>, Host, ServName] ->
- IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
+ IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
(ServName == JabberDomain) and IsHostFqdn;
_ ->
false
end.
-is_host_fqdn(Host, [Letter | _Tail] = Fqdn) when not is_list(Letter) ->
+is_host_fqdn(Host, Fqdn) when is_binary(Fqdn) ->
Host == Fqdn;
is_host_fqdn(_Host, []) ->
false;
@@ -204,6 +204,7 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
get_local_fqdn() ->
case catch get_local_fqdn2() of
Str when is_binary(Str) -> Str;
+ List when is_list(List) -> List;
_ ->
<<"unknown-fqdn, please configure fqdn "
"option in ejabberd.yml!">>
@@ -211,9 +212,11 @@ get_local_fqdn() ->
get_local_fqdn2() ->
case ejabberd_config:get_option(
- fqdn, fun iolist_to_binary/1) of
+ fqdn, fun(X) -> X end) of
ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
ConfiguredFqdn;
+ [A | _] = ConfiguredFqdns when is_binary(A) ->
+ ConfiguredFqdns;
undefined ->
{ok, Hostname} = inet:gethostname(),
{ok, {hostent, Fqdn, _, _, _, _}} =
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index a22f8f3d3..87ac76875 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -192,10 +192,6 @@ get_commands_spec() ->
module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}},
- #ejabberd_commands{name = export_sql, tags = [mnesia, sql],
- desc = "Export all tables as SQL queries to a file",
- module = ejd2sql, function = export,
- args = [{host, string}, {file, string}], result = {res, rescode}},
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
desc = "Export all tables as SQL queries to a file",
module = ejd2sql, function = delete,
@@ -228,7 +224,7 @@ get_commands_spec() ->
#ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL files",
module = ejd2sql, function = export,
- args = [{host, string}, {directory, string}],
+ args = [{host, string}, {file, string}],
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [mnesia],
desc = "Set master node of the clustered Mnesia tables",
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..794c04f5f 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
@@ -255,14 +259,10 @@ close(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed).
%%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
- Access = case lists:keysearch(access, 1, Opts) of
- {value, {_, A}} -> A;
- _ -> all
- end,
- Shaper = case lists:keysearch(shaper, 1, Opts) of
- {value, {_, S}} -> S;
- _ -> none
- end,
+ Access = gen_mod:get_opt(access, Opts,
+ fun acl:access_rules_validator/1, all),
+ Shaper = gen_mod:get_opt(shaper, Opts,
+ fun acl:shaper_rules_validator/1, none),
XMLSocket = case lists:keysearch(xml_socket, 1, Opts) of
{value, {_, XS}} -> XS;
_ -> false
@@ -620,9 +620,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 +1099,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 +1149,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 +1283,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).
@@ -1766,8 +1768,7 @@ terminate(_Reason, StateName, StateData) ->
StateData#state.resource,
<<"Replaced by new connection">>),
presence_broadcast(StateData, From,
- StateData#state.pres_a, Packet),
- handle_unacked_stanzas(StateData);
+ StateData#state.pres_a, Packet);
_ ->
?INFO_MSG("(~w) Close session for ~s",
[StateData#state.socket,
@@ -1792,8 +1793,20 @@ terminate(_Reason, StateName, StateData) ->
presence_broadcast(StateData, From,
StateData#state.pres_a, Packet)
end,
- handle_unacked_stanzas(StateData)
+ case StateData#state.mgmt_state of
+ timeout ->
+ Info = [{num_stanzas_in,
+ StateData#state.mgmt_stanzas_in}],
+ ejabberd_sm:set_offline_info(StateData#state.sid,
+ StateData#state.user,
+ StateData#state.server,
+ StateData#state.resource,
+ Info);
+ _ ->
+ ok
+ end
end,
+ handle_unacked_stanzas(StateData),
bounce_messages();
true ->
ok
@@ -1808,8 +1821,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).
@@ -2594,9 +2608,9 @@ stream_mgmt_enabled(#state{mgmt_state = disabled}) ->
stream_mgmt_enabled(_StateData) ->
true.
-dispatch_stream_mgmt(El, StateData)
- when StateData#state.mgmt_state == active;
- StateData#state.mgmt_state == pending ->
+dispatch_stream_mgmt(El, #state{mgmt_state = MgmtState} = StateData)
+ when MgmtState == active;
+ MgmtState == pending ->
perform_stream_mgmt(El, StateData);
dispatch_stream_mgmt(El, StateData) ->
negotiate_stream_mgmt(El, StateData).
@@ -2727,6 +2741,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 +2779,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)]),
@@ -2785,7 +2801,9 @@ check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H) ->
[jid:to_string(StateData#state.jid), H, NumStanzasOut]),
mgmt_queue_drop(StateData, H).
-update_num_stanzas_in(#state{mgmt_state = active} = StateData, El) ->
+update_num_stanzas_in(#state{mgmt_state = MgmtState} = StateData, El)
+ when MgmtState == active;
+ MgmtState == pending ->
NewNum = case {is_stanza(El), StateData#state.mgmt_stanzas_in} of
{true, 4294967295} ->
0;
@@ -2840,9 +2858,10 @@ check_queue_length(#state{mgmt_queue = Queue,
StateData
end.
-handle_unacked_stanzas(StateData, F)
- when StateData#state.mgmt_state == active;
- StateData#state.mgmt_state == pending ->
+handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F)
+ when MgmtState == active;
+ MgmtState == pending;
+ MgmtState == timeout ->
Queue = StateData#state.mgmt_queue,
case queue:len(Queue) of
0 ->
@@ -2862,9 +2881,10 @@ handle_unacked_stanzas(StateData, F)
handle_unacked_stanzas(_StateData, _F) ->
ok.
-handle_unacked_stanzas(StateData)
- when StateData#state.mgmt_state == active;
- StateData#state.mgmt_state == pending ->
+handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
+ when MgmtState == active;
+ MgmtState == pending;
+ MgmtState == timeout ->
ResendOnTimeout =
case StateData#state.mgmt_resend of
Resend when is_boolean(Resend) ->
@@ -2966,7 +2986,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 +3025,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 +3057,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..9d41f50c2 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,29 +748,32 @@ get_commands(Version) ->
end, AdminCmds ++ UserCmds, Opts),
Cmds.
-is_admin(_Name, noauth) ->
- false;
-is_admin(_Name, admin) ->
+is_admin(_Name, admin, _Extra) ->
true;
-is_admin(_Name, {_User, _Server, _, false}) ->
+is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
false;
-is_admin(Name, {User, Server, _, true} = Auth) ->
+is_admin(Name, Auth, Extra) ->
+ {ACLInfo, Server} = case Auth of
+ {U, S, _, _} ->
+ {Extra#{usr=>jid:split(jid:make(U, S, <<>>))}, S};
+ _ ->
+ {Extra, global}
+ end,
AdminAccess = ejabberd_config:get_option(
commands_admin_access,
- fun(A) when is_atom(A) -> A end,
+ fun(V) -> V end,
none),
- case acl:match_rule(Server, AdminAccess,
- jid:make(User, Server, <<"">>)) of
+ case acl:access_matches(AdminAccess, ACLInfo, Server) of
allow ->
case catch check_auth(get_command_definition(Name), Auth) of
{ok, _, _} -> true;
+ no_auth_provided -> true;
_ -> false
end;
deny -> false
end.
-opt_type(commands_admin_access) ->
- fun(A) when is_atom(A) -> A end;
+opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
opt_type(commands) ->
fun(V) when is_list(V) -> V end;
opt_type(_) -> [commands, commands_admin_access].
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 16eebc0e3..139047d9c 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) ->
@@ -770,22 +770,32 @@ add_option(Opt, Val) ->
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
prepare_opt_val(Opt, Val, F, Default) ->
- Res = case F of
- {Mod, Fun} ->
- catch Mod:Fun(Val);
- _ ->
- catch F(Val)
- end,
- case Res of
- {'EXIT', _} ->
+ Call = case F of
+ {Mod, Fun} ->
+ fun() -> Mod:Fun(Val) end;
+ _ ->
+ fun() -> F(Val) end
+ end,
+ try Call() of
+ Res ->
+ Res
+ catch {replace_with, NewRes} ->
+ NewRes;
+ {invalid_syntax, Error} ->
+ ?WARNING_MSG("incorrect value '~s' of option '~s', "
+ "using '~s' as fallback: ~s",
+ [format_term(Val),
+ format_term(Opt),
+ format_term(Default),
+ Error]),
+ Default;
+ _:_ ->
?WARNING_MSG("incorrect value '~s' of option '~s', "
"using '~s' as fallback",
[format_term(Val),
format_term(Opt),
format_term(Default)]),
- Default;
- _ ->
- Res
+ Default
end.
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
@@ -908,19 +918,26 @@ get_modules_with_options() ->
validate_opts(#state{opts = Opts} = State) ->
ModOpts = get_modules_with_options(),
- NewOpts = lists:filter(
- fun(#local_config{key = {Opt, _Host}, value = Val}) ->
+ NewOpts = lists:filtermap(
+ fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
case dict:find(Opt, ModOpts) of
{ok, [Mod|_]} ->
VFun = Mod:opt_type(Opt),
- case catch VFun(Val) of
- {'EXIT', _} ->
+ try VFun(Val) of
+ _ ->
+ true
+ catch {replace_with, NewVal} ->
+ {true, In#local_config{value = NewVal}};
+ {invalid_syntax, Error} ->
+ ?ERROR_MSG("ignoring option '~s' with "
+ "invalid value: ~p: ~s",
+ [Opt, Val, Error]),
+ false;
+ _:_ ->
?ERROR_MSG("ignoring option '~s' with "
"invalid value: ~p",
[Opt, Val]),
- false;
- _ ->
- true
+ false
end;
_ ->
?ERROR_MSG("unknown option '~s' will be likely"
diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl
index d8e0a435d..1c570bcb3 100644
--- a/src/ejabberd_oauth.erl
+++ b/src/ejabberd_oauth.erl
@@ -130,7 +130,7 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
Access =
ejabberd_config:get_option(
{oauth_access, JID#jid.lserver},
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
none),
case acl:match_rule(JID#jid.lserver, Access, JID) of
allow ->
@@ -486,5 +486,5 @@ logo() ->
opt_type(oauth_expire) ->
fun(I) when is_integer(I), I >= 0 -> I end;
opt_type(oauth_access) ->
- fun(A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
opt_type(_) -> [oauth_expire, oauth_access].
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index be1ee4659..19de64adb 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -539,7 +539,7 @@ allow_host2(MyServer, S2SHost) ->
allow_host1(MyHost, S2SHost) ->
Rule = ejabberd_config:get_option(
s2s_access,
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
all),
JID = jid:make(<<"">>, S2SHost, <<"">>),
case acl:match_rule(MyHost, Rule, JID) of
@@ -738,5 +738,5 @@ opt_type(route_subdomains) ->
(local) -> local
end;
opt_type(s2s_access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
opt_type(_) -> [route_subdomains, s2s_access].
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/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index c7e72d66d..aa37b643a 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -472,7 +472,7 @@ format_arg(undefined, binary) -> <<>>;
format_arg(undefined, string) -> "";
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
- error.
+ throw({error_formatting_argument, Arg, Format}).
process_unicode_codepoints(Str) ->
iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
diff --git a/src/ext_mod.erl b/src/ext_mod.erl
index 91526e71f..332d2c5e2 100644
--- a/src/ext_mod.erl
+++ b/src/ext_mod.erl
@@ -271,7 +271,7 @@ geturl(Url, Hdrs, UsrOpts) ->
[U, Pass] -> [{proxy_user, U}, {proxy_password, Pass}];
_ -> []
end,
- case httpc:request(get, {Url, Hdrs}, Host++User++UsrOpts, []) of
+ case httpc:request(get, {Url, Hdrs}, Host++User++UsrOpts++[{version, "HTTP/1.0"}], []) of
{ok, {{_, 200, _}, Headers, Response}} ->
{ok, Headers, Response};
{ok, {{_, Code, _}, _Headers, Response}} ->
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index f96397192..1cc65ac21 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -266,18 +266,25 @@ get_opt_host(Host, Opts, Default) ->
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
validate_opts(Module, Opts) ->
- lists:filter(
+ lists:filtermap(
fun({Opt, Val}) ->
case catch Module:mod_opt_type(Opt) of
VFun when is_function(VFun) ->
- case catch VFun(Val) of
- {'EXIT', _} ->
+ try VFun(Val) of
+ _ ->
+ true
+ catch {replace_with, NewVal} ->
+ {true, {Opt, NewVal}};
+ {invalid_syntax, Error} ->
+ ?ERROR_MSG("ignoring invalid value '~p' for "
+ "option '~s' of module '~s': ~s",
+ [Val, Opt, Module, Error]),
+ false;
+ _:_ ->
?ERROR_MSG("ignoring invalid value '~p' for "
"option '~s' of module '~s'",
[Val, Opt, Module]),
- false;
- _ ->
- true
+ false
end;
L when is_list(L) ->
SOpts = str:join([[$', atom_to_list(A), $'] || A <- L], <<", ">>),
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..562087d96 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) ->
@@ -1154,7 +1166,8 @@ subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) -
subscribe_roster({Name, Server, Group, Nick}, Roster);
%% Subscribe Name2 to Name1
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
- subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, <<"both">>, []),
+ subscribe(Name1, Server1, list_to_binary(Name2), list_to_binary(Server2),
+ list_to_binary(Nick2), list_to_binary(Group2), <<"both">>, []),
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
push_alltoall(S, G) ->
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index 9a9e665f5..fc57d6bd1 100644
--- a/src/mod_announce.erl
+++ b/src/mod_announce.erl
@@ -903,7 +903,7 @@ send_announcement_to_all(Host, SubjectS, BodyS) ->
get_access(Host) ->
gen_mod:get_module_opt(Host, ?MODULE, access,
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
none).
%%-------------------------------------------------------------------------
@@ -920,6 +920,6 @@ import(LServer, DBType, LA) ->
Mod:import(LServer, LA).
mod_opt_type(access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [access, db_type].
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_fail2ban.erl b/src/mod_fail2ban.erl
index fed106700..f0460e704 100644
--- a/src/mod_fail2ban.erl
+++ b/src/mod_fail2ban.erl
@@ -167,7 +167,7 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
is_whitelisted(Host, Addr) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access,
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
none),
acl:match_rule(Host, Access, Addr) == allow.
@@ -187,7 +187,7 @@ format_date({{Year, Month, Day}, {Hour, Minute, Second}}) ->
[Hour, Minute, Second, Day, Month, Year]).
mod_opt_type(access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(c2s_auth_ban_lifetime) ->
fun (T) when is_integer(T), T > 0 -> T end;
mod_opt_type(c2s_max_auth_failures) ->
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index c4fae2022..aadf09974 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -173,7 +173,7 @@ check_permissions2(_Request, Call, open) ->
{allowed, Call, noauth};
check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
- mod_opt_type(admin_ip_access),
+ fun(V) -> V end,
none),
Res = acl:match_rule(global, Access, IP),
case Res of
@@ -188,9 +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 ->
@@ -503,6 +502,5 @@ log(Call, Args, {Addr, Port}) ->
log(Call, Args, IP) ->
?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
-mod_opt_type(admin_ip_access) ->
- fun(Access) when is_atom(Access) -> Access end;
+mod_opt_type(admin_ip_access) -> fun acl:access_rules_validator/1;
mod_opt_type(_) -> [admin_ip_access].
diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl
index 0bb383700..a2aa75465 100644
--- a/src/mod_http_upload.erl
+++ b/src/mod_http_upload.erl
@@ -178,7 +178,7 @@ mod_opt_type(host) ->
mod_opt_type(name) ->
fun iolist_to_binary/1;
mod_opt_type(access) ->
- fun(A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(max_size) ->
fun(I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
@@ -235,7 +235,7 @@ init({ServerHost, Opts}) ->
fun iolist_to_binary/1,
<<"HTTP File Upload">>),
Access = gen_mod:get_opt(access, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:access_rules_validator/1,
local),
MaxSize = gen_mod:get_opt(max_size, Opts,
fun(I) when is_integer(I), I > 0 -> I;
diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl
index 4f9d95217..e08cd0b91 100644
--- a/src/mod_http_upload_quota.erl
+++ b/src/mod_http_upload_quota.erl
@@ -99,9 +99,9 @@ stop(ServerHost) ->
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
mod_opt_type(access_soft_quota) ->
- fun(A) when is_atom(A) -> A end;
+ fun acl:shaper_rules_validator/1;
mod_opt_type(access_hard_quota) ->
- fun(A) when is_atom(A) -> A end;
+ fun acl:shaper_rules_validator/1;
mod_opt_type(max_days) ->
fun(I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
@@ -118,10 +118,10 @@ mod_opt_type(_) ->
init({ServerHost, Opts}) ->
process_flag(trap_exit, true),
AccessSoftQuota = gen_mod:get_opt(access_soft_quota, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:shaper_rules_validator/1,
soft_upload_quota),
AccessHardQuota = gen_mod:get_opt(access_hard_quota, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:shaper_rules_validator/1,
hard_upload_quota),
MaxDays = gen_mod:get_opt(max_days, Opts,
fun(I) when is_integer(I), I > 0 -> I;
diff --git a/src/mod_irc.erl b/src/mod_irc.erl
index f6487a1a9..4bcf69c3b 100644
--- a/src/mod_irc.erl
+++ b/src/mod_irc.erl
@@ -117,7 +117,7 @@ init([Host, Opts]) ->
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
Access = gen_mod:get_opt(access, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:access_rules_validator/1,
all),
catch ets:new(irc_connection,
[named_table, public,
@@ -1252,7 +1252,7 @@ import(LServer, DBType, Data) ->
Mod:import(LServer, Data).
mod_opt_type(access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(default_encoding) ->
fun iolist_to_binary/1;
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.erl b/src/mod_mam.erl
index e3a165ca7..18bffd909 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -25,7 +25,7 @@
%%%-------------------------------------------------------------------
-module(mod_mam).
--protocol({xep, 313, '0.4'}).
+-protocol({xep, 313, '0.5.1'}).
-protocol({xep, 334, '0.2'}).
-behaviour(gen_mod).
@@ -337,7 +337,12 @@ message_is_archived(false, C2SState, Peer,
(never) -> never
end, never) of
if_enabled ->
- get_prefs(LUser, LServer);
+ case get_prefs(LUser, LServer) of
+ #archive_prefs{} = P ->
+ {ok, P};
+ error ->
+ error
+ end;
on_request ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
cache_tab:lookup(archive_prefs, {LUser, LServer},
diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl
index 007ef5eba..2463690fd 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,71 @@ remove_room(_LServer, LName, LHost) ->
remove_user(LName, LHost).
delete_old_messages(global, TimeStamp, Type) ->
- MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
- type = MsgType} = Msg)
- when MsgTS < TimeStamp,
- MsgType == Type orelse Type == all ->
- Msg
- end),
- OldMsgs = mnesia:dirty_select(archive_msg, MS),
- lists:foreach(fun(Rec) ->
- ok = mnesia:dirty_delete_object(Rec)
- end, OldMsgs).
+ mnesia:change_table_copy_type(archive_msg, node(), disc_copies),
+ Result = delete_old_user_messages(mnesia:dirty_first(archive_msg), TimeStamp, Type),
+ mnesia:change_table_copy_type(archive_msg, node(), disc_only_copies),
+ Result.
+
+delete_old_user_messages('$end_of_table', _TimeStamp, _Type) ->
+ ok;
+delete_old_user_messages(User, TimeStamp, Type) ->
+ F = fun() ->
+ Msgs = mnesia:read(archive_msg, User),
+ Keep = lists:filter(
+ fun(#archive_msg{timestamp = MsgTS,
+ type = MsgType}) ->
+ MsgTS >= TimeStamp orelse (Type /= all andalso
+ Type /= MsgType)
+ end, Msgs),
+ if length(Keep) < length(Msgs) ->
+ mnesia:delete({archive_msg, User}),
+ lists:foreach(fun(Msg) -> mnesia:write(Msg) end, Keep);
+ true ->
+ ok
+ end
+ end,
+ case mnesia:transaction(F) of
+ {atomic, ok} ->
+ delete_old_user_messages(mnesia:dirty_next(archive_msg, User),
+ TimeStamp, Type);
+ {aborted, Err} ->
+ ?ERROR_MSG("Cannot delete old MAM messages: ~s", [Err]),
+ Err
+ end.
extended_fields() ->
[].
store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) ->
- LPeer = {PUser, PServer, _} = jid:tolower(Peer),
- TS = p1_time_compat:timestamp(),
- ID = jlib:integer_to_binary(now_to_usec(TS)),
- case mnesia:dirty_write(
- #archive_msg{us = {LUser, LServer},
- id = ID,
- timestamp = TS,
- peer = LPeer,
- bare_peer = {PUser, PServer, <<>>},
- type = Type,
- nick = Nick,
- packet = Pkt}) of
- ok ->
- {ok, ID};
- Err ->
- Err
+ case {mnesia:table_info(archive_msg, disc_only_copies),
+ mnesia:table_info(archive_msg, memory)} of
+ {[_|_], TableSize} when TableSize > ?TABLE_SIZE_LIMIT ->
+ ?ERROR_MSG("MAM archives too large, won't store message for ~s@~s",
+ [LUser, LServer]),
+ {error, overflow};
+ _ ->
+ LPeer = {PUser, PServer, _} = jid:tolower(Peer),
+ TS = p1_time_compat:timestamp(),
+ ID = jlib:integer_to_binary(now_to_usec(TS)),
+ F = fun() ->
+ mnesia:write(
+ #archive_msg{us = {LUser, LServer},
+ id = ID,
+ timestamp = TS,
+ peer = LPeer,
+ bare_peer = {PUser, PServer, <<>>},
+ type = Type,
+ nick = Nick,
+ packet = Pkt})
+ end,
+ case mnesia:transaction(F) of
+ {atomic, ok} ->
+ {ok, ID};
+ {aborted, Err} ->
+ ?ERROR_MSG("Cannot add message to MAM archive of ~s@~s: ~s",
+ [LUser, LServer, Err]),
+ Err
+ end
end.
write_prefs(_LUser, _LServer, Prefs, _ServerHost) ->
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.erl b/src/mod_muc.erl
index 3d098e0d5..b46585066 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -193,14 +193,14 @@ init([Host, Opts]) ->
clean_table_from_bad_node(node(), MyHost),
mnesia:subscribe(system),
Access = gen_mod:get_opt(access, Opts,
- fun(A) when is_atom(A) -> A end, all),
+ fun acl:access_rules_validator/1, all),
AccessCreate = gen_mod:get_opt(access_create, Opts,
- fun(A) when is_atom(A) -> A end, all),
+ fun acl:access_rules_validator/1, all),
AccessAdmin = gen_mod:get_opt(access_admin, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:access_rules_validator/1,
none),
AccessPersistent = gen_mod:get_opt(access_persistent, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:access_rules_validator/1,
all),
HistorySize = gen_mod:get_opt(history_size, Opts,
fun(I) when is_integer(I), I>=0 -> I end,
@@ -925,13 +925,13 @@ import(LServer, DBType, Data) ->
Mod:import(LServer, Data).
mod_opt_type(access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(access_admin) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(access_create) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(access_persistent) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(default_room_options) ->
fun (L) when is_list(L) -> L end;
diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl
index 1f32f6f9b..167a96e37 100644
--- a/src/mod_muc_log.erl
+++ b/src/mod_muc_log.erl
@@ -141,7 +141,7 @@ init([Host, Opts]) ->
fun iolist_to_binary/1,
false),
AccessLog = gen_mod:get_opt(access_log, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:access_rules_validator/1,
muc_admin),
Timezone = gen_mod:get_opt(timezone, Opts,
fun(local) -> local;
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_multicast.erl b/src/mod_multicast.erl
index 83520c0be..cbe2a4e50 100644
--- a/src/mod_multicast.erl
+++ b/src/mod_multicast.erl
@@ -140,7 +140,7 @@ init([LServerS, Opts]) ->
LServiceS = gen_mod:get_opt_host(LServerS, Opts,
<<"multicast.@HOST@">>),
Access = gen_mod:get_opt(access, Opts,
- fun (A) when is_atom(A) -> A end, all),
+ fun acl:access_rules_validator/1, all),
SLimits =
build_service_limit_record(gen_mod:get_opt(limits, Opts,
fun (A) when is_list(A) ->
@@ -1220,7 +1220,7 @@ stj(String) -> jid:from_string(String).
jts(String) -> jid:to_string(String).
mod_opt_type(access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(limits) ->
fun (A) when is_list(A) -> A end;
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 4d8aba762..a794a0371 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -162,7 +162,7 @@ init([Host, Opts]) ->
?MODULE, handle_offline_query, IQDisc),
AccessMaxOfflineMsgs =
gen_mod:get_opt(access_max_user_messages, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:shaper_rules_validator/1,
max_user_offline_messages),
{ok,
#state{host = Host,
@@ -791,7 +791,7 @@ get_messages_subset(User, Host, MsgsAll) ->
fun(A) when is_atom(A) -> A end,
max_user_offline_messages),
MaxOfflineMsgs = case get_max_user_messages(Access,
- User, Host)
+ {User, Host}, Host)
of
Number when is_integer(Number) -> Number;
_ -> 100
@@ -866,7 +866,7 @@ import(LServer, DBType, Data) ->
Mod:import(LServer, Data).
mod_opt_type(access_max_user_messages) ->
- fun (A) -> A end;
+ fun acl:shaper_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(store_empty_body) ->
fun (V) when is_boolean(V) -> V;
diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl
index 4d9455570..feefd3dd0 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)
@@ -82,8 +80,8 @@ remove_old_messages(Days, LServer) ->
LServer,
[<<"DELETE FROM spool"
" WHERE created_at < "
- "DATE_SUB(CURDATE(), INTERVAL ">>,
- integer_to_list(Days), <<" DAY);">>]) of
+ "NOW() - INTERVAL '">>,
+ integer_to_list(Days), <<"';">>]) of
{updated, N} ->
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
_Error ->
@@ -95,19 +93,18 @@ remove_user(LUser, LServer) ->
sql_queries:del_spool_msg(LServer, LUser).
read_message_headers(LUser, LServer) ->
- Username = ejabberd_sql:escape(LUser),
case catch ejabberd_sql:sql_query(
- LServer, [<<"select xml, seq from spool where username ='">>,
- Username, <<"' order by seq;">>]) of
- {selected, [<<"xml">>, <<"seq">>], Rows} ->
+ LServer,
+ ?SQL("select @(xml)s, @(seq)d from spool"
+ " where username=%(LUser)s order by seq")) of
+ {selected, Rows} ->
lists:flatmap(
- fun([XML, Seq]) ->
+ fun({XML, Seq}) ->
case xml_to_offline_msg(XML) of
{ok, #offline_msg{from = From,
to = To,
packet = El}} ->
- Seq0 = binary_to_integer(Seq),
- [{Seq0, From, To, El}];
+ [{Seq, From, To, El}];
_ ->
[]
end
@@ -117,13 +114,11 @@ read_message_headers(LUser, LServer) ->
end.
read_message(LUser, LServer, Seq) ->
- Username = ejabberd_sql:escape(LUser),
- SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
case ejabberd_sql:sql_query(
LServer,
- [<<"select xml from spool where username='">>, Username,
- <<"' and seq='">>, SSeq, <<"';">>]) of
- {selected, [<<"xml">>], [[RawXML]|_]} ->
+ ?SQL("select @(xml)s from spool where username=%(LUser)s"
+ " and seq=%(Seq)d")) of
+ {selected, [{RawXML}|_]} ->
case xml_to_offline_msg(RawXML) of
{ok, Msg} ->
{ok, Msg};
@@ -135,12 +130,10 @@ read_message(LUser, LServer, Seq) ->
end.
remove_message(LUser, LServer, Seq) ->
- Username = ejabberd_sql:escape(LUser),
- SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
ejabberd_sql:sql_query(
LServer,
- [<<"delete from spool where username='">>, Username,
- <<"' and seq='">>, SSeq, <<"';">>]),
+ ?SQL("delete from spool where username=%(LUser)s"
+ " and seq=%(Seq)d")),
ok.
read_all_messages(LUser, LServer) ->
@@ -180,14 +173,13 @@ export(_Server) ->
timestamp = TimeStamp, from = From, to = To,
packet = Packet})
when LServer == Host ->
- Username = ejabberd_sql:escape(LUser),
Packet1 = jlib:replace_from_to(From, To, Packet),
Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
<<"Offline Storage">>),
- XML = ejabberd_sql:escape(fxml:element_to_binary(Packet2)),
- [[<<"delete from spool where username='">>, Username, <<"';">>],
- [<<"insert into spool(username, xml) values ('">>,
- Username, <<"', '">>, XML, <<"');">>]];
+ XML = fxml:element_to_binary(Packet2),
+ [?SQL("delete from spool where username=%(LUser)s;"),
+ ?SQL("insert into spool(username, xml) values ("
+ "%(LUser)s, %(XML)s);")];
(_Host, _R) ->
[]
end}].
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index d64afa04d..7db6f9da2 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -260,7 +260,7 @@ parse_options(ServerHost, Opts) ->
Port = gen_mod:get_opt(port, Opts,
fun(P) when is_integer(P), P>0, P<65536 -> P end,
7777),
- ACL = gen_mod:get_opt(access, Opts, fun(A) when is_atom(A) -> A end,
+ ACL = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1,
all),
Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
<<"SOCKS5 Bytestreams">>),
diff --git a/src/mod_proxy65_stream.erl b/src/mod_proxy65_stream.erl
index 840173299..e6362d48c 100644
--- a/src/mod_proxy65_stream.erl
+++ b/src/mod_proxy65_stream.erl
@@ -83,7 +83,7 @@ init([Socket, Host, Opts]) ->
(anonymous) -> anonymous
end, anonymous),
Shaper = gen_mod:get_opt(shaper, Opts,
- fun(A) when is_atom(A) -> A end,
+ fun acl:shaper_rules_validator/1,
none),
RecvBuf = gen_mod:get_opt(recbuf, Opts,
fun(I) when is_integer(I), I>0 -> I end,
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..43499d2e5 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -74,7 +74,7 @@ stop(Host) ->
stream_feature_register(Acc, Host) ->
AF = gen_mod:get_module_opt(Host, ?MODULE, access_from,
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
all),
case (AF /= none) and lists:keymember(<<"mechanisms">>, 2, Acc) of
true ->
@@ -126,7 +126,7 @@ process_iq(From, To,
RTag = fxml:get_subtag(SubEl, <<"remove">>),
Server = To#jid.lserver,
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
all),
AllowRemove = allow ==
acl:match_rule(Server, Access, From),
@@ -386,6 +386,10 @@ try_set_password(User, Server, Password, IQ, SubEl,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end;
+ error_preparing_password ->
+ ErrText = <<"The password contains unacceptable characters">>,
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]};
false ->
ErrText = <<"The password is too weak">>,
IQ#iq{type = error,
@@ -398,7 +402,7 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
_ ->
JID = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
all),
IPAccess = get_ip_access(Server),
case {acl:match_rule(Server, Access, JID),
@@ -440,7 +444,12 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
end;
+ error_preparing_password ->
+ remove_timeout(Source),
+ ErrText = <<"The password contains unacceptable characters">>,
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
false ->
+ remove_timeout(Source),
ErrText = <<"The password is too weak">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
end;
@@ -519,7 +528,7 @@ check_from(#jid{user = <<"">>, server = <<"">>},
allow;
check_from(JID, Server) ->
Access = gen_mod:get_module_opt(Server, ?MODULE, access_from,
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
none),
acl:match_rule(Server, Access, JID).
@@ -635,6 +644,14 @@ process_xdata_submit(El) ->
end.
is_strong_password(Server, Password) ->
+ case jid:resourceprep(Password) of
+ PP when is_binary(PP) ->
+ is_strong_password2(Server, Password);
+ error ->
+ error_preparing_password
+ end.
+
+is_strong_password2(Server, Password) ->
LServer = jid:nameprep(Server),
case gen_mod:get_module_opt(LServer, ?MODULE, password_strength,
fun(N) when is_number(N), N>=0 -> N end,
@@ -719,13 +736,13 @@ check_ip_access(IPAddress, IPAccess) ->
acl:match_rule(global, IPAccess, IPAddress).
mod_opt_type(access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(access_from) ->
fun (A) when is_atom(A) -> A end;
mod_opt_type(captcha_protected) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(ip_access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(password_strength) ->
fun (N) when is_number(N), N >= 0 -> N end;
diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl
index d93140312..2b7e5f532 100644
--- a/src/mod_register_web.erl
+++ b/src/mod_register_web.erl
@@ -491,7 +491,7 @@ form_del_get(Host, Lang) ->
%% {error, invalid_jid}
register_account(Username, Host, Password) ->
Access = gen_mod:get_module_opt(Host, mod_register, access,
- fun(A) when is_atom(A) -> A end,
+ fun(A) -> A end,
all),
case jid:make(Username, Host, <<"">>) of
error -> {error, invalid_jid};
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index b3a627f7c..f79061560 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -351,7 +351,7 @@ get_roster_by_jid_t(LUser, LServer, LJID) ->
try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
#jid{server = Server} = From,
- Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) when is_atom(A) -> A end, all),
+ Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all),
case acl:match_rule(Server, Access, From) of
deny ->
Txt = <<"Denied by ACL">>,
@@ -382,14 +382,14 @@ process_item_set(From, To,
Item = get_roster_by_jid_t(LUser, LServer, LJID),
Item1 = process_item_attrs_managed(Item, Attrs, Managed),
Item2 = process_item_els(Item1, Els),
- case Item2#roster.subscription of
- remove -> del_roster_t(LUser, LServer, LJID);
- _ -> update_roster_t(LUser, LServer, LJID, Item2)
- end,
- send_itemset_to_managers(From, Item2, Managed),
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2,
[LServer]),
+ case Item3#roster.subscription of
+ remove -> del_roster_t(LUser, LServer, LJID);
+ _ -> update_roster_t(LUser, LServer, LJID, Item3)
+ end,
+ send_itemset_to_managers(From, Item3, Managed),
case roster_version_on_db(LServer) of
true -> write_roster_version_t(LUser, LServer);
false -> ok
@@ -1235,7 +1235,7 @@ import(LServer, DBType, R) ->
Mod:import(LServer, R).
mod_opt_type(access) ->
- fun (A) when is_atom(A) -> A end;
+ fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(managers) ->
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..68116cf50 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,
@@ -668,20 +686,19 @@ get_items(Nidx, _From,
#rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
Max = ejabberd_sql:escape(jlib:i2l(M)),
{Way, Order} = case Direction of
- % aft -> {<<"<">>, <<"desc">>};
- % before when I == <<>> -> {<<"is not">>, <<"asc">>};
- % before -> {<<">">>, <<"asc">>};
- % _ when IncIndex =/= undefined ->
- % {<<"<">>, <<"desc">>}; % using index
- _ ->
- {<<"is not">>, <<"desc">>}% Can be better
+ aft when I == <<>> -> {<<"is not">>, <<"desc">>};
+ aft -> {<<"<">>, <<"desc">>};
+ before when I == <<>> -> {<<"is not">>, <<"asc">>};
+ before -> {<<">">>, <<"asc">>};
+ _ -> {<<"is not">>, <<"desc">>}
end,
+ SNidx = integer_to_binary(Nidx),
[AttrName, Id] = case I of
undefined when IncIndex =/= undefined ->
case catch
ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi "
"where exists ( select count(*) as count1 "
- "from pubsub_item where nodeid='">>, Nidx,
+ "from pubsub_item where nodeid='">>, SNidx,
<<"' and modification > pi.modification having count1 = ">>,
ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>])
of
@@ -699,7 +716,7 @@ get_items(Nidx, _From,
[A, <<"'", B/binary, "'">>]
end,
Count = case catch
- ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
+ ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, SNidx, <<"';">>])
of
{selected, [_], [[C]]} -> C;
_ -> <<"0">>
@@ -708,13 +725,13 @@ get_items(Nidx, _From,
ejabberd_sql:sql_query_t(
[<<"select top ">>, jlib:i2l(Max),
<<" itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
+ "from pubsub_item where nodeid='">>, SNidx,
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
AttrName, <<" ">>, Order, <<";">>]);
(_, _) ->
ejabberd_sql:sql_query_t(
[<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
+ "from pubsub_item where nodeid='">>, SNidx,
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
end,
@@ -725,7 +742,7 @@ get_items(Nidx, _From,
[[_, _, _, F, _]|_] ->
Index = case catch
ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item "
- "where nodeid='">>, Nidx, <<"' and ">>,
+ "where nodeid='">>, SNidx, <<"' and ">>,
AttrName, <<" > '">>, F, <<"';">>])
of
%{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
@@ -777,16 +794,17 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
get_last_items(Nidx, _From, Count) ->
Limit = jlib:i2l(Count),
+ SNidx = integer_to_binary(Nidx),
Query = fun(mssql, _) ->
ejabberd_sql:sql_query_t(
[<<"select top ">>, Limit,
<<" itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
+ "from pubsub_item where nodeid='">>, SNidx,
<<"' order by modification desc ;">>]);
(_, _) ->
ejabberd_sql:sql_query_t(
[<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
+ "from pubsub_item where nodeid='">>, SNidx,
<<"' order by modification desc limit ">>, Limit, <<";">>])
end,
case catch ejabberd_sql:sql_query_t(Query) of
@@ -798,16 +816,14 @@ get_last_items(Nidx, _From, Count) ->
end.
get_item(Nidx, ItemId) ->
- I = ejabberd_sql:escape(ItemId),
- case catch
- ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, "
- "modification, payload from pubsub_item "
- "where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
+ case catch ejabberd_sql:sql_query_t(
+ ?SQL("select @(itemid)s, @(publisher)s, @(creation)s,"
+ " @(modification)s, @(payload)s from pubsub_item"
+ " where nodeid=%(Nidx)d and itemid=%(ItemId)s"))
of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
+ {selected, [RItem]} ->
{result, raw_to_item(Nidx, RItem)};
- {selected, _, []} ->
+ {selected, []} ->
{error, ?ERR_ITEM_NOT_FOUND};
{'EXIT', _} ->
{error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
@@ -846,37 +862,31 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub
set_item(Item) ->
{ItemId, Nidx} = Item#pubsub_item.itemid,
- I = ejabberd_sql:escape(ItemId),
{C, _} = Item#pubsub_item.creation,
{M, JID} = Item#pubsub_item.modification,
P = encode_jid(JID),
Payload = Item#pubsub_item.payload,
- XML = ejabberd_sql:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)),
+ XML = str:join([fxml:element_to_binary(X) || X<-Payload], <<>>),
S = fun ({T1, T2, T3}) ->
str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
end,
- case catch
- ejabberd_sql:sql_query_t([<<"update pubsub_item set publisher='">>, P,
- <<"', modification='">>, S(M),
- <<"', payload='">>, XML,
- <<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_sql:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
- "publisher, creation, modification, payload) "
- "values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
- <<"', '">>, S(C), <<"', '">>, S(M),
- <<"', '">>, XML, <<"');">>])
- end,
+ SM = S(M),
+ SC = S(C),
+ ?SQL_UPSERT_T(
+ "pubsub_item",
+ ["!nodeid=%(Nidx)d",
+ "!itemid=%(ItemId)s",
+ "publisher=%(P)s",
+ "modification=%(SM)s",
+ "payload=%(XML)s",
+ "-creation=%(SC)s"
+ ]),
ok.
del_item(Nidx, ItemId) ->
- I = ejabberd_sql:escape(ItemId),
- catch ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid='">>,
- I, <<"' and nodeid='">>, Nidx, <<"';">>]).
+ catch ejabberd_sql:sql_query_t(
+ ?SQL("delete from pubsub_item where itemid=%(ItemId)s"
+ " and nodeid=%(Nidx)d")).
del_items(_, []) ->
ok;
@@ -884,9 +894,10 @@ del_items(Nidx, [ItemId]) ->
del_item(Nidx, ItemId);
del_items(Nidx, ItemIds) ->
I = str:join([[<<"'">>, ejabberd_sql:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
+ SNidx = integer_to_binary(Nidx),
catch
ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
- I, <<") and nodeid='">>, Nidx, <<"';">>]).
+ I, <<") and nodeid='">>, SNidx, <<"';">>]).
get_item_name(_Host, _Node, Id) ->
Id.
@@ -907,14 +918,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 +934,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 +954,24 @@ select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
update_affiliation(Nidx, JID, Affiliation) ->
J = encode_jid(JID),
A = encode_affiliation(Affiliation),
- case catch
- ejabberd_sql:sql_query_t([<<"update pubsub_state set affiliation='">>,
- A, <<"' where nodeid='">>, Nidx,
- <<"' and jid='">>, J, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
- end.
+ ?SQL_UPSERT_T(
+ "pubsub_state",
+ ["!nodeid=%(Nidx)d",
+ "!jid=%(J)s",
+ "affiliation=%(A)s",
+ "-subscriptions=''"
+ ]).
update_subscription(Nidx, JID, Subscription) ->
J = encode_jid(JID),
S = encode_subscriptions(Subscription),
- case catch
- ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
- <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
- end.
+ ?SQL_UPSERT_T(
+ "pubsub_state",
+ ["!nodeid=%(Nidx)d",
+ "!jid=%(J)s",
+ "subscriptions=%(S)s",
+ "-affiliation='n'"
+ ]).
-spec(decode_jid/1 ::
( SJID :: binary())
@@ -1015,24 +1018,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 +1071,14 @@ encode_subscriptions(Subscriptions) ->
state_to_raw(Nidx, State) ->
{JID, _} = State#pubsub_state.stateid,
- J = encode_jid(JID),
+ J = ejabberd_sql:escape(encode_jid(JID)),
A = encode_affiliation(State#pubsub_state.affiliation),
S = encode_subscriptions(State#pubsub_state.subscriptions),
[<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>].
raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
+ raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML});
+raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML}) ->
JID = decode_jid(SJID),
ToTime = fun (Str) ->
[T1, T2, T3] = str:tokens(Str, <<":">>),
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).