diff options
Diffstat (limited to 'src')
42 files changed, 1299 insertions, 762 deletions
diff --git a/src/acl.erl b/src/acl.erl index 06202c67e..57675112d 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -29,12 +29,12 @@ -author('alexey@process-one.net'). --export([start/0, to_record/3, add/3, add_list/3, - add_local/3, add_list_local/3, load_from_config/0, - match_rule/3, match_access/4, match_acl/3, transform_options/1, - opt_type/1]). - -export([add_access/3, clear/0]). +-export([start/0, add/3, add_list/3, add_local/3, add_list_local/3, + load_from_config/0, match_rule/3, + transform_options/1, opt_type/1, acl_rule_matches/3, + acl_rule_verify/1, access_matches/3, + transform_access_rules_config/1]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -92,12 +92,6 @@ start() -> load_from_config(), ok. --spec to_record(binary(), atom(), aclspec()) -> acl(). - -to_record(Host, ACLName, ACLSpec) -> - #acl{aclname = {ACLName, Host}, - aclspec = normalize_spec(ACLSpec)}. - -spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}. add(Host, ACLName, ACLSpec) -> @@ -188,6 +182,10 @@ load_from_config() -> {acl, Host}, fun(V) -> V end, []), AccessRules = ejabberd_config:get_option( {access, Host}, fun(V) -> V end, []), + AccessRulesNew = ejabberd_config:get_option( + {access_rules, Host}, fun(V) -> V end, []), + ShaperRules = ejabberd_config:get_option( + {shaper_rules, Host}, fun(V) -> V end, []), lists:foreach( fun({ACLName, SpecList}) -> lists:foreach( @@ -203,8 +201,19 @@ load_from_config() -> end, ACLs), lists:foreach( fun({Access, Rules}) -> + NRules = lists:map(fun({ACL, Type}) -> + {Type, [{acl, ACL}]} + end, Rules), + add_access(Host, Access, NRules ++ [{deny, [all]}]) + end, AccessRules), + lists:foreach( + fun({Access, Rules}) -> + add_access(Host, Access, Rules) + end, AccessRulesNew), + lists:foreach( + fun({Access, Rules}) -> add_access(Host, Access, Rules) - end, AccessRules) + end, ShaperRules) end, Hosts). %% Delete all previous set ACLs and Access rules @@ -225,19 +234,28 @@ nameprep(S) -> resourceprep(S) -> jid:resourceprep(b(S)). +split_user_server(Str, NormFunUsr, NormFunSrv) -> + case binary:split(Str, <<"@">>) of + [U, S] -> + {NormFunUsr(U), NormFunSrv(S)}; + _ -> + NormFunUsr(Str) + end. + normalize_spec(Spec) -> case Spec of all -> all; none -> none; + {acl, N} -> {acl, N}; {user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}}; - {user, U} -> {user, nodeprep(U)}; + {user, U} -> {user, split_user_server(U, fun nodeprep/1, fun nameprep/1)}; {shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}}; - {shared_group, G} -> {shared_group, b(G)}; + {shared_group, G} -> {shared_group, split_user_server(G, fun b/1, fun nameprep/1)}; {user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}}; - {user_regexp, UR} -> {user_regexp, b(UR)}; + {user_regexp, UR} -> {user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)}; {node_regexp, {UR, SR}} -> {node_regexp, {b(UR), b(SR)}}; {user_glob, {UR, S}} -> {user_glob, {b(UR), nameprep(S)}}; - {user_glob, UR} -> {user_glob, b(UR)}; + {user_glob, UR} -> {user_glob, split_user_server(UR, fun b/1, fun nameprep/1)}; {node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}}; {server, S} -> {server, nameprep(S)}; {resource, R} -> {resource, resourceprep(R)}; @@ -255,130 +273,177 @@ normalize_spec(Spec) -> end end. --spec match_access(global | binary(), access_name(), - jid() | ljid() | inet:ip_address(), - atom()) -> any(). - -match_access(_Host, all, _JID, _Default) -> - allow; -match_access(_Host, none, _JID, _Default) -> - deny; -match_access(_Host, {user, UserPattern}, JID, Default) -> - match_user_spec({user, UserPattern}, JID, Default); -match_access(Host, AccessRule, JID, _Default) -> - match_rule(Host, AccessRule, JID). - -spec match_rule(global | binary(), access_name(), jid() | ljid() | inet:ip_address()) -> any(). -match_rule(_Host, all, _JID) -> - allow; -match_rule(_Host, none, _JID) -> - deny; +match_rule(Host, Access, IP) when tuple_size(IP) == 4; + tuple_size(IP) == 8 -> + access_matches(Access, #{ip => IP}, Host); match_rule(Host, Access, JID) -> - GAccess = ets:lookup(access, {Access, global}), - LAccess = if Host /= global -> - ets:lookup(access, {Access, Host}); - true -> - [] - end, - case GAccess ++ LAccess of - [] -> - deny; - AccessList -> - Rules = lists:flatmap( - fun(#access{rules = Rs}) -> - Rs - end, AccessList), - match_acls(Rules, JID, Host) - end. - -match_acls([], _, _Host) -> deny; -match_acls([{ACL, Access} | ACLs], JID, Host) -> - case match_acl(ACL, JID, Host) of - true -> Access; - _ -> match_acls(ACLs, JID, Host) - end. + access_matches(Access, #{usr => jid:tolower(JID)}, Host). --spec match_acl(atom(), - jid() | ljid() | inet:ip_address(), - binary()) -> boolean(). +-spec acl_rule_verify(aclspec()) -> boolean(). -match_acl(all, _JID, _Host) -> +acl_rule_verify(all) -> true; -match_acl(none, _JID, _Host) -> - false; -match_acl(ACL, IP, Host) when tuple_size(IP) == 4; - tuple_size(IP) == 8 -> - lists:any( - fun(#acl{aclspec = {ip, {Net, Mask}}}) -> - is_ip_match(IP, Net, Mask); - (_) -> - false - end, get_aclspecs(ACL, Host)); -match_acl(ACL, JID, Host) -> - {User, Server, Resource} = jid:tolower(JID), - lists:any( - fun(#acl{aclspec = Spec}) -> - case Spec of - all -> true; - {user, {U, S}} -> U == User andalso S == Server; - {user, U} -> - U == User andalso - lists:member(Server, ?MYHOSTS); - {server, S} -> S == Server; - {resource, R} -> R == Resource; - {shared_group, {G, H}} -> - Mod = loaded_shared_roster_module(H), - Mod:is_user_in_group({User, Server}, G, H); - {shared_group, G} -> - Mod = loaded_shared_roster_module(Host), - Mod:is_user_in_group({User, Server}, G, Host); - {user_regexp, {UR, S}} -> - S == Server andalso is_regexp_match(User, UR); - {user_regexp, UR} -> - lists:member(Server, ?MYHOSTS) - andalso is_regexp_match(User, UR); - {server_regexp, SR} -> - is_regexp_match(Server, SR); - {resource_regexp, RR} -> - is_regexp_match(Resource, RR); - {node_regexp, {UR, SR}} -> - is_regexp_match(Server, SR) andalso - is_regexp_match(User, UR); - {user_glob, {UR, S}} -> - S == Server andalso is_glob_match(User, UR); - {user_glob, UR} -> - lists:member(Server, ?MYHOSTS) - andalso is_glob_match(User, UR); - {server_glob, SR} -> is_glob_match(Server, SR); - {resource_glob, RR} -> - is_glob_match(Resource, RR); - {node_glob, {UR, SR}} -> - is_glob_match(Server, SR) andalso - is_glob_match(User, UR); - WrongSpec -> - ?ERROR_MSG("Wrong ACL expression: ~p~nCheck your " - "config file and reload it with the override_a" - "cls option enabled", - [WrongSpec]), - false - end - end, - get_aclspecs(ACL, Host)). +acl_rule_verify(none) -> + true; +acl_rule_verify({ip, {{A,B,C,D}, Mask}}) + when is_integer(A), is_integer(B), is_integer(C), is_integer(D), + A >= 0, A =< 255, B >= 0, B =< 255, C >= 0, C =< 255, D >= 0, D =< 255, + is_integer(Mask), Mask >= 0, Mask =< 32 -> + true; +acl_rule_verify({ip, {{A,B,C,D,E,F,G,H}, Mask}}) when + is_integer(A), is_integer(B), is_integer(C), is_integer(D), + is_integer(E), is_integer(F), is_integer(G), is_integer(H), + A >= 0, A =< 65535, B >= 0, B =< 65535, C >= 0, C =< 65535, D >= 0, D =< 65535, + E >= 0, E =< 65535, F >= 0, F =< 65535, G >= 0, G =< 65535, H >= 0, H =< 65535, + is_integer(Mask), Mask >= 0, Mask =< 64 -> + true; +acl_rule_verify({user, {U, S}}) when is_binary(U), is_binary(S) -> + true; +acl_rule_verify({user, U}) when is_binary(U) -> + true; +acl_rule_verify({server, S}) when is_binary(S) -> + true; +acl_rule_verify({resource, R}) when is_binary(R) -> + true; +acl_rule_verify({shared_group, {G, H}}) when is_binary(G), is_binary(H) -> + true; +acl_rule_verify({shared_group, G}) when is_binary(G) -> + true; +acl_rule_verify({user_regexp, {UR, S}}) when is_binary(UR), is_binary(S) -> + true; +acl_rule_verify({user_regexp, UR}) when is_binary(UR) -> + true; +acl_rule_verify({server_regexp, SR}) when is_binary(SR) -> + true; +acl_rule_verify({resource_regexp, RR}) when is_binary(RR) -> + true; +acl_rule_verify({node_regexp, {UR, SR}}) when is_binary(UR), is_binary(SR) -> + true; +acl_rule_verify({user_glob, {UR, S}}) when is_binary(UR), is_binary(S) -> + true; +acl_rule_verify({user_glob, UR}) when is_binary(UR) -> + true; +acl_rule_verify({server_glob, SR}) when is_binary(SR) -> + true; +acl_rule_verify({resource_glob, RR}) when is_binary(RR) -> + true; +acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) -> + true; +acl_rule_verify(_Spec) -> + false. -get_aclspecs(ACL, Host) -> - ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}). +all_acl_rules_matches([], _Data, _Host) -> + false; +all_acl_rules_matches(Rules, Data, Host) -> + all_acl_rules_matches2(Rules, Data, Host). + +all_acl_rules_matches2([Rule | Tail], Data, Host) -> + case acl_rule_matches(Rule, Data, Host) of + true -> + all_acl_rules_matches2(Tail, Data, Host); + false -> + false + end; +all_acl_rules_matches2([], _Data, _Host) -> + true. + +-spec acl_rule_matches(aclspec(), any(), global|binary()) -> boolean(). + +acl_rule_matches(all, _Data, _Host) -> + true; +acl_rule_matches({acl, all}, _Data, _Host) -> + true; +acl_rule_matches({acl, Name}, Data, Host) -> + ACLs = get_aclspecs(Name, Host), + RawACLs = lists:map(fun(#acl{aclspec = R}) -> R end, ACLs), + all_acl_rules_matches(RawACLs, Data, Host); +acl_rule_matches({ip, {Net, Mask}}, #{ip := {IP, _Port}}, _Host) -> + is_ip_match(IP, Net, Mask); +acl_rule_matches({ip, {Net, Mask}}, #{ip := IP}, _Host) -> + is_ip_match(IP, Net, Mask); +acl_rule_matches({user, {U, S}}, #{usr := {U, S, _}}, _Host) -> + true; +acl_rule_matches({user, U}, #{usr := {U, S, _}}, _Host) -> + lists:member(S, ?MYHOSTS); +acl_rule_matches({server, S}, #{usr := {_, S, _}}, _Host) -> + true; +acl_rule_matches({resource, R}, #{usr := {_, _, R}}, _Host) -> + true; +acl_rule_matches({shared_group, {G, H}}, #{usr := {U, S, _}}, _Host) -> + Mod = loaded_shared_roster_module(H), + Mod:is_user_in_group({U, S}, G, H); +acl_rule_matches({shared_group, G}, #{usr := {U, S, _}}, Host) -> + Mod = loaded_shared_roster_module(Host), + Mod:is_user_in_group({U, S}, G, Host); +acl_rule_matches({user_regexp, {UR, S}}, #{usr := {U, S, _}}, _Host) -> + is_regexp_match(U, UR); +acl_rule_matches({user_regexp, UR}, #{usr := {U, S, _}}, _Host) -> + lists:member(S, ?MYHOSTS) andalso is_regexp_match(U, UR); +acl_rule_matches({server_regexp, SR}, #{usr := {_, S, _}}, _Host) -> + is_regexp_match(S, SR); +acl_rule_matches({resource_regexp, RR}, #{usr := {_, _, R}}, _Host) -> + is_regexp_match(R, RR); +acl_rule_matches({node_regexp, {UR, SR}}, #{usr := {U, S, _}}, _Host) -> + is_regexp_match(U, UR) andalso is_regexp_match(S, SR); +acl_rule_matches({user_glob, {UR, S}}, #{usr := {U, S, _}}, _Host) -> + is_glob_match(U, UR); +acl_rule_matches({user_glob, UR}, #{usr := {U, S, _}}, _Host) -> + lists:member(S, ?MYHOSTS) andalso is_glob_match(U, UR); +acl_rule_matches({server_glob, SR}, #{usr := {_, S, _}}, _Host) -> + is_glob_match(S, SR); +acl_rule_matches({resource_glob, RR}, #{usr := {_, _, R}}, _Host) -> + is_glob_match(R, RR); +acl_rule_matches({node_glob, {UR, SR}}, #{usr := {U, S, _}}, _Host) -> + is_glob_match(U, UR) andalso is_glob_match(S, SR); +acl_rule_matches(_ACL, _Data, _Host) -> + false. -match_user_spec(Spec, JID, Default) -> - case do_match_user_spec(Spec, jid:tolower(JID)) of - true -> Default; - false -> deny - end. +-spec access_matches(atom()|list(), any(), global|binary()) -> any(). +access_matches(all, _Data, _Host) -> + allow; +access_matches(none, _Data, _Host) -> + deny; +access_matches(Name, Data, Host) when is_atom(Name) -> + GAccess = ets:lookup(access, {Name, global}), + LAccess = + if Host /= global -> ets:lookup(access, {Name, Host}); + true -> [] + end, + case GAccess ++ LAccess of + [] -> + deny; + AccessList -> + Rules = lists:flatmap( + fun(#access{rules = Rs}) -> + Rs + end, AccessList), + access_rules_matches(Rules, Data, Host) + end; +access_matches(Rules, Data, Host) when is_list(Rules) -> + access_rules_matches(Rules, Data, Host). + + +-spec access_rules_matches(list(), any(), global|binary()) -> any(). + +access_rules_matches(AR, Data, Host) -> + access_rules_matches(AR, Data, Host, deny). + +access_rules_matches([{Type, Acls} | Rest], Data, Host, Default) -> + case all_acl_rules_matches(Acls, Data, Host) of + false -> + access_rules_matches(Rest, Data, Host, Default); + true -> + Type + end; +access_rules_matches([], _Data, _Host, Default) -> + Default. -do_match_user_spec({user, {U, S}}, {User, Server, _Resource}) -> - U == User andalso S == Server. +get_aclspecs(ACL, Host) -> + ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}). is_regexp_match(String, RegExp) -> case ejabberd_regexp:run(String, RegExp) of @@ -450,6 +515,18 @@ parse_ip_netmask(S) -> _ -> error end. +transform_access_rules_config(Config) -> + lists:map(fun transform_access_rules_config2/1, lists:flatten(Config)). + +transform_access_rules_config2({Res, Rules}) when is_list(Rules) -> + {Res, lists:map(fun({Type, Args}) when is_list(Args) -> + normalize_spec({Type, hd(lists:flatten(Args))}); + (V) -> normalize_spec(V) + end, lists:flatten(Rules))}; +transform_access_rules_config2({Res, Rule}) -> + {Res, [Rule]}. + + transform_options(Opts) -> Opts1 = lists:foldl(fun transform_options/2, [], Opts), {ACLOpts, Opts2} = lists:mapfoldl( @@ -464,6 +541,18 @@ transform_options(Opts) -> (O, Acc) -> {[], [O|Acc]} end, [], Opts2), + {NewAccessOpts, Opts4} = lists:mapfoldl( + fun({access_rules, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts3), + {ShaperOpts, Opts5} = lists:mapfoldl( + fun({shaper_rules, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts4), ACLOpts1 = ejabberd_config:collect_options(lists:flatten(ACLOpts)), AccessOpts1 = case ejabberd_config:collect_options( lists:flatten(AccessOpts)) of @@ -477,7 +566,21 @@ transform_options(Opts) -> [] -> []; L2 -> [{acl, L2}] end, - ACLOpts2 ++ AccessOpts1 ++ Opts3. + NewAccessOpts1 = case lists:map( + fun({NAName, Os}) -> + {NAName, transform_access_rules_config(Os)} + end, lists:flatten(NewAccessOpts)) of + [] -> []; + L3 -> [{access_rules, L3}] + end, + ShaperOpts1 = case lists:map( + fun({SName, Ss}) -> + {SName, transform_access_rules_config(Ss)} + end, lists:flatten(ShaperOpts)) of + [] -> []; + L4 -> [{shaper_rules, L4}] + end, + ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5. transform_options({acl, Name, Type}, Opts) -> T = case Type of @@ -508,5 +611,7 @@ transform_options(Opt, Opts) -> [Opt|Opts]. opt_type(access) -> fun (V) -> V end; +opt_type(access_rules) -> fun (V) -> V end; +opt_type(shaper_rules) -> fun (V) -> V end; opt_type(acl) -> fun (V) -> V end; -opt_type(_) -> [access, acl]. +opt_type(_) -> [access, acl, acces_rules, shaper_rules]. diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 44d0db626..703614f63 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -84,9 +84,9 @@ start(_, _) -> %% before shutting down the processes of the application. prep_stop(State) -> ejabberd_listener:stop_listeners(), - gen_mod:stop_modules(), ejabberd_admin:stop(), broadcast_c2s_shutdown(), + gen_mod:stop_modules(), timer:sleep(5000), State. diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index ef7c97551..bba65af1c 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -78,13 +78,15 @@ store_type() -> external. check_password(User, AuthzId, Server, Password) -> if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - case get_cache_option(Server) of - false -> check_password_extauth(User, AuthzId, Server, Password); - {true, CacheTime} -> - check_password_cache(User, AuthzId, Server, Password, CacheTime) - end + false; + true -> + case get_cache_option(Server) of + false -> + check_password_extauth(User, AuthzId, Server, Password); + {true, CacheTime} -> + check_password_cache(User, AuthzId, Server, Password, + CacheTime) + end end. check_password(User, AuthzId, Server, Password, _Digest, diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 51b466ef4..3cb04b596 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -118,16 +118,15 @@ store_type() -> external. check_password(User, AuthzId, Server, Password) -> if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - if Password == <<"">> -> false; + false; true -> - case catch check_password_ldap(User, Server, Password) - of - {'EXIT', _} -> false; - Result -> Result - end - end + if Password == <<"">> -> false; + true -> + case catch check_password_ldap(User, Server, Password) of + {'EXIT', _} -> false; + Result -> Result + end + end end. check_password(User, AuthzId, Server, Password, _Digest, diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index 58e22c79c..2a4554d15 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -91,51 +91,48 @@ store_type() -> check_password(User, AuthzId, Server, Password) -> if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - case catch mnesia:dirty_read({passwd, US}) of - [#passwd{password = Password}] - when is_binary(Password) -> - Password /= <<"">>; - [#passwd{password = Scram}] - when is_record(Scram, scram) -> - is_password_scram_valid(Password, Scram); - _ -> false - end + false; + true -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), + US = {LUser, LServer}, + case catch mnesia:dirty_read({passwd, US}) of + [#passwd{password = Password}] when is_binary(Password) -> + Password /= <<"">>; + [#passwd{password = Scram}] when is_record(Scram, scram) -> + is_password_scram_valid(Password, Scram); + _ -> false + end end. check_password(User, AuthzId, Server, Password, Digest, DigestGen) -> if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - case catch mnesia:dirty_read({passwd, US}) of - [#passwd{password = Passwd}] when is_binary(Passwd) -> - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - [#passwd{password = Scram}] - when is_record(Scram, scram) -> - Passwd = jlib:decode_base64(Scram#scram.storedkey), - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - _ -> false - end + false; + true -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), + US = {LUser, LServer}, + case catch mnesia:dirty_read({passwd, US}) of + [#passwd{password = Passwd}] when is_binary(Passwd) -> + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + [#passwd{password = Scram}] when is_record(Scram, scram) -> + Passwd = jlib:decode_base64(Scram#scram.storedkey), + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + _ -> false + end end. %% @spec (User::string(), Server::string(), Password::string()) -> diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl index 17e3e517b..d6d945e02 100644 --- a/src/ejabberd_auth_sql.erl +++ b/src/ejabberd_auth_sql.erl @@ -88,7 +88,7 @@ check_password(User, AuthzId, Server, Password) -> serverkey = ServerKey, salt = Salt, iterationcount = IterationCount}, - is_password_scram_valid(Password, Scram); + is_password_scram_valid_stored(Password, Scram, LUser, LServer); {selected, []} -> false; %% Account does not exist {error, _Error} -> @@ -417,6 +417,15 @@ password_to_scram(Password, IterationCount) -> salt = jlib:encode_base64(Salt), iterationcount = IterationCount}. +is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) -> + ?INFO_MSG("Apparently, SQL auth method and scram password formatting are " + "enabled, but the password of user '~s' in the 'users' table is not " + "scrammed. You may want to execute this command: " + "ejabberdctl convert_to_scram ~s", [LUser, LServer]), + false; +is_password_scram_valid_stored(Password, Scram, _, _) -> + is_password_scram_valid(Password, Scram). + is_password_scram_valid(Password, Scram) -> IterationCount = Scram#scram.iterationcount, Salt = jlib:decode_base64(Scram#scram.salt), diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 080880bec..bda3bbd5f 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -104,7 +104,6 @@ ip, aux_fields = [], csi_state = active, - csi_queue = [], mgmt_state, mgmt_xmlns, mgmt_queue, @@ -167,27 +166,32 @@ (Xmlns == ?NS_STREAM_MGMT_2) or (Xmlns == ?NS_STREAM_MGMT_3)). --define(MGMT_FAILED(Condition, Xmlns), +-define(MGMT_FAILED(Condition, Attrs), #xmlel{name = <<"failed">>, - attrs = [{<<"xmlns">>, Xmlns}], + attrs = Attrs, children = [#xmlel{name = Condition, attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}]}). -define(MGMT_BAD_REQUEST(Xmlns), - ?MGMT_FAILED(<<"bad-request">>, Xmlns)). - --define(MGMT_ITEM_NOT_FOUND(Xmlns), - ?MGMT_FAILED(<<"item-not-found">>, Xmlns)). + ?MGMT_FAILED(<<"bad-request">>, [{<<"xmlns">>, Xmlns}])). -define(MGMT_SERVICE_UNAVAILABLE(Xmlns), - ?MGMT_FAILED(<<"service-unavailable">>, Xmlns)). + ?MGMT_FAILED(<<"service-unavailable">>, [{<<"xmlns">>, Xmlns}])). -define(MGMT_UNEXPECTED_REQUEST(Xmlns), - ?MGMT_FAILED(<<"unexpected-request">>, Xmlns)). + ?MGMT_FAILED(<<"unexpected-request">>, [{<<"xmlns">>, Xmlns}])). -define(MGMT_UNSUPPORTED_VERSION(Xmlns), - ?MGMT_FAILED(<<"unsupported-version">>, Xmlns)). + ?MGMT_FAILED(<<"unsupported-version">>, [{<<"xmlns">>, Xmlns}])). + +-define(MGMT_ITEM_NOT_FOUND(Xmlns), + ?MGMT_FAILED(<<"item-not-found">>, [{<<"xmlns">>, Xmlns}])). + +-define(MGMT_ITEM_NOT_FOUND_H(Xmlns, NumStanzasIn), + ?MGMT_FAILED(<<"item-not-found">>, + [{<<"xmlns">>, Xmlns}, + {<<"h">>, jlib:integer_to_binary(NumStanzasIn)}])). %%%---------------------------------------------------------------------- %%% API @@ -620,9 +624,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> {auth, _ID, set, {U, P, D, R}} -> JID = jid:make(U, StateData#state.server, R), case JID /= error andalso - acl:match_rule(StateData#state.server, - StateData#state.access, JID) - == allow + acl:access_matches(StateData#state.access, + #{usr => jid:split(JID), ip => StateData#state.ip}, + StateData#state.server) == allow of true -> DGen = fun (PW) -> @@ -1099,8 +1103,10 @@ open_session(StateData) -> R = StateData#state.resource, JID = StateData#state.jid, Lang = StateData#state.lang, - case acl:match_rule(StateData#state.server, - StateData#state.access, JID) of + IP = StateData#state.ip, + case acl:access_matches(StateData#state.access, + #{usr => jid:split(JID), ip => IP}, + StateData#state.server) of allow -> ?INFO_MSG("(~w) Opened session for ~s", [StateData#state.socket, jid:to_string(JID)]), @@ -1147,7 +1153,7 @@ session_established({xmlstreamelement, #xmlel{name = <<"active">>, attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}}, StateData) -> - NewStateData = csi_queue_flush(StateData), + NewStateData = csi_flush_queue(StateData), fsm_next_state(session_established, NewStateData#state{csi_state = active}); session_established({xmlstreamelement, #xmlel{name = <<"inactive">>, @@ -1281,7 +1287,7 @@ wait_for_resume({xmlstreamelement, _El} = Event, StateData) -> wait_for_resume(timeout, StateData) -> ?DEBUG("Timed out waiting for resumption of stream for ~s", [jid:to_string(StateData#state.jid)]), - {stop, normal, StateData}; + {stop, normal, StateData#state{mgmt_state = timeout}}; wait_for_resume(Event, StateData) -> ?DEBUG("Ignoring event while waiting for resumption: ~p", [Event]), fsm_next_state(wait_for_resume, StateData). @@ -1792,6 +1798,18 @@ terminate(_Reason, StateName, StateData) -> presence_broadcast(StateData, From, StateData#state.pres_a, Packet) end, + case StateData#state.mgmt_state of + timeout -> + Info = [{num_stanzas_in, + StateData#state.mgmt_stanzas_in}], + ejabberd_sm:set_offline_info(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource, + Info); + _ -> + ok + end, handle_unacked_stanzas(StateData) end, bounce_messages(); @@ -1808,8 +1826,9 @@ terminate(_Reason, StateName, StateData) -> %%%---------------------------------------------------------------------- change_shaper(StateData, JID) -> - Shaper = acl:match_rule(StateData#state.server, - StateData#state.shaper, JID), + Shaper = acl:access_matches(StateData#state.shaper, + #{usr => jid:split(JID), ip => StateData#state.ip}, + StateData#state.server), (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). @@ -2727,6 +2746,8 @@ handle_resume(StateData, Attrs) -> case inherit_session_state(StateData, PrevID) of {ok, InheritedState} -> {ok, InheritedState, H}; + {error, Err, InH} -> + {error, ?MGMT_ITEM_NOT_FOUND_H(Xmlns, InH), Err}; {error, Err} -> {error, ?MGMT_ITEM_NOT_FOUND(Xmlns), Err} end; @@ -2763,7 +2784,7 @@ handle_resume(StateData, Attrs) -> #xmlel{name = <<"r">>, attrs = [{<<"xmlns">>, AttrXmlns}], children = []}), - FlushedState = csi_queue_flush(NewState), + FlushedState = csi_flush_queue(NewState), NewStateData = FlushedState#state{csi_state = active}, ?INFO_MSG("Resumed session for ~s", [jid:to_string(NewStateData#state.jid)]), @@ -2966,7 +2987,17 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) -> {term, {R, Time}} -> case ejabberd_sm:get_session_pid(U, S, R) of none -> - {error, <<"Previous session PID not found">>}; + case ejabberd_sm:get_offline_info(Time, U, S, R) of + none -> + {error, <<"Previous session PID not found">>}; + Info -> + case proplists:get_value(num_stanzas_in, Info) of + undefined -> + {error, <<"Previous session timed out">>}; + H -> + {error, <<"Previous session timed out">>, H} + end + end; OldPID -> OldSID = {Time, OldPID}, case catch resume_session(OldSID) of @@ -2995,7 +3026,6 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) -> privacy_list = OldStateData#state.privacy_list, aux_fields = OldStateData#state.aux_fields, csi_state = OldStateData#state.csi_state, - csi_queue = OldStateData#state.csi_queue, mgmt_xmlns = OldStateData#state.mgmt_xmlns, mgmt_queue = OldStateData#state.mgmt_queue, mgmt_timeout = OldStateData#state.mgmt_timeout, @@ -3028,65 +3058,25 @@ add_resent_delay_info(#state{server = From}, El, Time) -> %%% XEP-0352 %%%---------------------------------------------------------------------- -csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData, +csi_filter_stanza(#state{csi_state = CsiState, server = Server} = StateData, Stanza) -> - Action = ejabberd_hooks:run_fold(csi_filter_stanza, - StateData#state.server, - send, [Stanza]), - ?DEBUG("Going to ~p stanza for inactive client ~p", - [Action, jid:to_string(JID)]), - case Action of - queue -> csi_queue_add(StateData, Stanza); - drop -> StateData; - send -> - From = fxml:get_tag_attr_s(<<"from">>, Stanza), - StateData1 = csi_queue_send(StateData, From), - StateData2 = send_stanza(StateData1#state{csi_state = active}, - Stanza), - StateData2#state{csi_state = CsiState} - end. - -csi_queue_add(#state{csi_queue = Queue} = StateData, Stanza) -> - case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of - true -> csi_queue_add(csi_queue_flush(StateData), Stanza); - false -> - From = fxml:get_tag_attr_s(<<"from">>, Stanza), - NewQueue = lists:keystore(From, 1, Queue, {From, p1_time_compat:timestamp(), Stanza}), - StateData#state{csi_queue = NewQueue} - end. - -csi_queue_send(#state{csi_queue = Queue, csi_state = CsiState, server = Host} = - StateData, From) -> - case lists:keytake(From, 1, Queue) of - {value, {From, Time, Stanza}, NewQueue} -> - NewStanza = jlib:add_delay_info(Stanza, Host, Time, - <<"Client Inactive">>), - NewStateData = send_stanza(StateData#state{csi_state = active}, - NewStanza), - NewStateData#state{csi_queue = NewQueue, csi_state = CsiState}; - false -> StateData - end. - -csi_queue_flush(#state{csi_queue = Queue, csi_state = CsiState, jid = JID, - server = Host} = StateData) -> - ?DEBUG("Flushing CSI queue for ~s", [jid:to_string(JID)]), - NewStateData = - lists:foldl(fun({_From, Time, Stanza}, AccState) -> - NewStanza = - jlib:add_delay_info(Stanza, Host, Time, - <<"Client Inactive">>), - send_stanza(AccState, NewStanza) - end, StateData#state{csi_state = active}, Queue), - NewStateData#state{csi_queue = [], csi_state = CsiState}. - -%% Make sure we won't push too many messages to the XEP-0198 queue when the -%% client becomes 'active' again. Otherwise, the client might not manage to -%% acknowledge the message flood in time. Also, don't let the queue grow to -%% more than 100 stanzas. -csi_max_queue(#state{mgmt_max_queue = infinity}) -> 100; -csi_max_queue(#state{mgmt_max_queue = Max}) when Max > 200 -> 100; -csi_max_queue(#state{mgmt_max_queue = Max}) when Max < 2 -> 1; -csi_max_queue(#state{mgmt_max_queue = Max}) -> Max div 2. + {StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_filter_stanza, Server, + {StateData, [Stanza]}, + [Server, Stanza]), + StateData2 = lists:foldl(fun(CurStanza, AccState) -> + send_stanza(AccState, CurStanza) + end, StateData1#state{csi_state = active}, + Stanzas), + StateData2#state{csi_state = CsiState}. + +csi_flush_queue(#state{csi_state = CsiState, server = Server} = StateData) -> + {StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_flush_queue, Server, + {StateData, []}, [Server]), + StateData2 = lists:foldl(fun(CurStanza, AccState) -> + send_stanza(AccState, CurStanza) + end, StateData1#state{csi_state = active}, + Stanzas), + StateData2#state{csi_state = CsiState}. %%%---------------------------------------------------------------------- %%% JID Set memory footprint reduction code diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index 55ecba5d1..1b7d777e6 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -230,6 +230,7 @@ execute_command/3, execute_command/4, execute_command/5, + execute_command/6, opt_type/1, get_commands_spec/0 ]). @@ -352,7 +353,7 @@ get_command_format(Name, Auth) -> {[aterm()], rterm()}. get_command_format(Name, Auth, Version) -> - Admin = is_admin(Name, Auth), + Admin = is_admin(Name, Auth, #{}), #ejabberd_commands{args = Args, result = Result, policy = Policy} = @@ -489,13 +490,16 @@ execute_command(AccessCommands, Auth, Name, Arguments) -> %% Can return the following exceptions: %% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided execute_command(AccessCommands1, Auth1, Name, Arguments, Version) -> - Auth = case is_admin(Name, Auth1) of +execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}). + +execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) -> + Auth = case is_admin(Name, Auth1, CallerInfo) of true -> admin; false -> Auth1 end, Command = get_command_definition(Name, Version), AccessCommands = get_access_commands(AccessCommands1, Version), - case check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of + case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of ok -> execute_command2(Auth, Command, Arguments) end. @@ -573,9 +577,9 @@ get_tags_commands(Version) -> %% At least one AccessCommand must be satisfied. %% It may throw {error, Error} where: %% Error = account_unprivileged | invalid_account_data -check_access_commands([], _Auth, _Method, _Command, _Arguments) -> +check_access_commands([], _Auth, _Method, _Command, _Arguments, _CallerInfo) -> ok; -check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) -> +check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerInfo) -> Command = case {Command1#ejabberd_commands.policy, Auth} of {user, {_, _, _, _}} -> @@ -590,7 +594,7 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) -> AccessCommandsAllowed = lists:filter( fun({Access, Commands, ArgumentRestrictions}) -> - case check_access(Command, Access, Auth) of + case check_access(Command, Access, Auth, CallerInfo) of true -> check_access_command(Commands, Command, ArgumentRestrictions, @@ -600,7 +604,7 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) -> end; ({Access, Commands}) -> ArgumentRestrictions = [], - case check_access(Command, Access, Auth) of + case check_access(Command, Access, Auth, CallerInfo) of true -> check_access_command(Commands, Command, ArgumentRestrictions, @@ -637,31 +641,38 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) -> _ -> throw({error, invalid_account_data}) end. -check_access(Command, ?POLICY_ACCESS, _) +check_access(Command, ?POLICY_ACCESS, _, _) when Command#ejabberd_commands.policy == open -> true; -check_access(_Command, _Access, admin) -> +check_access(_Command, _Access, admin, _) -> true; -check_access(_Command, _Access, {_User, _Server, _, true}) -> +check_access(_Command, _Access, {_User, _Server, _, true}, _) -> false; -check_access(Command, Access, Auth) +check_access(Command, Access, Auth, CallerInfo) when Access =/= ?POLICY_ACCESS; Command#ejabberd_commands.policy == open; Command#ejabberd_commands.policy == user -> case check_auth(Command, Auth) of {ok, User, Server} -> - check_access2(Access, User, Server); + check_access2(Access, CallerInfo#{usr => jid:split(jid:make(User, Server, <<>>))}, Server); + no_auth_provided -> + case Command#ejabberd_commands.policy of + user -> + false; + _ -> + check_access2(Access, CallerInfo, global) + end; _ -> false end; -check_access(_Command, _Access, _Auth) -> +check_access(_Command, _Access, _Auth, _CallerInfo) -> false. -check_access2(?POLICY_ACCESS, _User, _Server) -> +check_access2(?POLICY_ACCESS, _CallerInfo, _Server) -> true; -check_access2(Access, User, Server) -> +check_access2(Access, AccessInfo, Server) -> %% Check this user has access permission - case acl:match_rule(Server, Access, jid:make(User, Server, <<"">>)) of + case acl:access_matches(Access, AccessInfo, Server) of allow -> true; deny -> false end. @@ -737,22 +748,26 @@ get_commands(Version) -> end, AdminCmds ++ UserCmds, Opts), Cmds. -is_admin(_Name, noauth) -> - false; -is_admin(_Name, admin) -> +is_admin(_Name, admin, _Extra) -> true; -is_admin(_Name, {_User, _Server, _, false}) -> +is_admin(_Name, {_User, _Server, _, false}, _Extra) -> false; -is_admin(Name, {User, Server, _, true} = Auth) -> +is_admin(Name, Auth, Extra) -> + {ACLInfo, Server} = case Auth of + {U, S, _, _} -> + {Extra#{usr=>jid:split(jid:make(U, S, <<>>))}, S}; + _ -> + {Extra, global} + end, AdminAccess = ejabberd_config:get_option( commands_admin_access, fun(A) when is_atom(A) -> A end, none), - case acl:match_rule(Server, AdminAccess, - jid:make(User, Server, <<"">>)) of + case acl:access_matches(AdminAccess, ACLInfo, Server) of allow -> case catch check_auth(get_command_definition(Name), Auth) of {ok, _, _} -> true; + no_auth_provided -> true; _ -> false end; deny -> false diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 16eebc0e3..8c17e577f 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -498,8 +498,8 @@ include_config_files(Terms) -> include_config_file(File, Opts) end, lists:flatten(FileOpts)), - M1 = merge_configs(transform_terms(Terms1), #{}), - M2 = merge_configs(transform_terms(Terms2), M1), + M1 = merge_configs(Terms1, #{}), + M2 = merge_configs(Terms2, M1), maps_to_lists(M2). transform_include_option({include_config_file, File}) when is_list(File) -> diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 5ee652cce..8d94bc6aa 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -47,6 +47,8 @@ set_presence/7, unset_presence/6, close_session_unset_presence/5, + set_offline_info/5, + get_offline_info/4, dirty_get_sessions_list/0, dirty_get_my_sessions_list/0, get_vh_session_list/1, @@ -178,14 +180,14 @@ get_user_resources(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = get_sm_backend(LServer), - Ss = Mod:get_sessions(LUser, LServer), + Ss = online(Mod:get_sessions(LUser, LServer)), [element(3, S#session.usr) || S <- clean_session_list(Ss)]. -spec get_user_present_resources(binary(), binary()) -> [tuple()]. get_user_present_resources(LUser, LServer) -> Mod = get_sm_backend(LServer), - Ss = Mod:get_sessions(LUser, LServer), + Ss = online(Mod:get_sessions(LUser, LServer)), [{S#session.priority, element(3, S#session.usr)} || S <- clean_session_list(Ss), is_integer(S#session.priority)]. @@ -196,7 +198,7 @@ get_user_ip(User, Server, Resource) -> LServer = jid:nameprep(Server), LResource = jid:resourceprep(Resource), Mod = get_sm_backend(LServer), - case Mod:get_sessions(LUser, LServer, LResource) of + case online(Mod:get_sessions(LUser, LServer, LResource)) of [] -> undefined; Ss -> @@ -211,7 +213,7 @@ get_user_info(User, Server, Resource) -> LServer = jid:nameprep(Server), LResource = jid:resourceprep(Resource), Mod = get_sm_backend(LServer), - case Mod:get_sessions(LUser, LServer, LResource) of + case online(Mod:get_sessions(LUser, LServer, LResource)) of [] -> offline; Ss -> @@ -261,17 +263,42 @@ get_session_pid(User, Server, Resource) -> LServer = jid:nameprep(Server), LResource = jid:resourceprep(Resource), Mod = get_sm_backend(LServer), - case Mod:get_sessions(LUser, LServer, LResource) of + case online(Mod:get_sessions(LUser, LServer, LResource)) of [#session{sid = {_, Pid}}] -> Pid; _ -> none end. +-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok. + +set_offline_info({Time, _Pid}, User, Server, Resource, Info) -> + SID = {Time, undefined}, + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), + LResource = jid:resourceprep(Resource), + set_session(SID, LUser, LServer, LResource, undefined, Info). + +-spec get_offline_info(erlang:timestamp(), binary(), binary(), + binary()) -> none | info(). + +get_offline_info(Time, User, Server, Resource) -> + SID = {Time, undefined}, + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), + LResource = jid:resourceprep(Resource), + Mod = get_sm_backend(LServer), + case Mod:get_sessions(LUser, LServer, LResource) of + [#session{sid = SID, info = Info}] -> + Info; + _ -> + none + end. + -spec dirty_get_sessions_list() -> [ljid()]. dirty_get_sessions_list() -> lists:flatmap( fun(Mod) -> - [S#session.usr || S <- Mod:get_sessions()] + [S#session.usr || S <- online(Mod:get_sessions())] end, get_sm_backends()). -spec dirty_get_my_sessions_list() -> [#session{}]. @@ -279,7 +306,7 @@ dirty_get_sessions_list() -> dirty_get_my_sessions_list() -> lists:flatmap( fun(Mod) -> - [S || S <- Mod:get_sessions(), + [S || S <- online(Mod:get_sessions()), node(element(2, S#session.sid)) == node()] end, get_sm_backends()). @@ -288,14 +315,14 @@ dirty_get_my_sessions_list() -> get_vh_session_list(Server) -> LServer = jid:nameprep(Server), Mod = get_sm_backend(LServer), - [S#session.usr || S <- Mod:get_sessions(LServer)]. + [S#session.usr || S <- online(Mod:get_sessions(LServer))]. -spec get_all_pids() -> [pid()]. get_all_pids() -> lists:flatmap( fun(Mod) -> - [element(2, S#session.sid) || S <- Mod:get_sessions()] + [element(2, S#session.sid) || S <- online(Mod:get_sessions())] end, get_sm_backends()). -spec get_vh_session_number(binary()) -> non_neg_integer(). @@ -303,7 +330,7 @@ get_all_pids() -> get_vh_session_number(Server) -> LServer = jid:nameprep(Server), Mod = get_sm_backend(LServer), - length(Mod:get_sessions(LServer)). + length(online(Mod:get_sessions(LServer))). register_iq_handler(Host, XMLNS, Module, Fun) -> ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}. @@ -395,6 +422,15 @@ set_session(SID, User, Server, Resource, Priority, Info) -> Mod:set_session(#session{sid = SID, usr = USR, us = US, priority = Priority, info = Info}). +-spec online([#session{}]) -> [#session{}]. + +online(Sessions) -> + lists:filter(fun(#session{sid = {_, undefined}}) -> + false; + (_) -> + true + end, Sessions). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% do_route(From, To, {broadcast, _} = Packet) -> @@ -409,7 +445,7 @@ do_route(From, To, {broadcast, _} = Packet) -> _ -> {U, S, R} = jid:tolower(To), Mod = get_sm_backend(S), - case Mod:get_sessions(U, S, R) of + case online(Mod:get_sessions(U, S, R)) of [] -> ?DEBUG("packet dropped~n", []); Ss -> @@ -511,8 +547,8 @@ do_route(From, To, #xmlel{} = Packet) -> _ -> ok end; _ -> - Mod = get_sm_backend(LServer), - case Mod:get_sessions(LUser, LServer, LResource) of + Mod = get_sm_backend(LServer), + case online(Mod:get_sessions(LUser, LServer, LResource)) of [] -> case Name of <<"message">> -> @@ -584,8 +620,8 @@ route_message(From, To, Packet, Type) -> (P >= 0) and (Type == headline) -> LResource = jid:resourceprep(R), Mod = get_sm_backend(LServer), - case Mod:get_sessions(LUser, LServer, - LResource) of + case online(Mod:get_sessions(LUser, LServer, + LResource)) of [] -> ok; % Race condition Ss -> @@ -646,7 +682,11 @@ check_existing_resources(LUser, LServer, LResource) -> if SIDs == [] -> ok; true -> MaxSID = lists:max(SIDs), - lists:foreach(fun ({_, Pid} = S) when S /= MaxSID -> + lists:foreach(fun ({_, undefined} = S) -> + Mod = get_sm_backend(LServer), + Mod:delete_session(LUser, LServer, LResource, + S); + ({_, Pid} = S) when S /= MaxSID -> Pid ! replaced; (_) -> ok end, @@ -663,11 +703,11 @@ get_resource_sessions(User, Server, Resource) -> LServer = jid:nameprep(Server), LResource = jid:resourceprep(Resource), Mod = get_sm_backend(LServer), - [S#session.sid || S <- Mod:get_sessions(LUser, LServer, LResource)]. + [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))]. check_max_sessions(LUser, LServer) -> Mod = get_sm_backend(LServer), - SIDs = [S#session.sid || S <- Mod:get_sessions(LUser, LServer)], + SIDs = [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer))], MaxSessions = get_max_user_sessions(LUser, LServer), if length(SIDs) =< MaxSessions -> ok; true -> {_, Pid} = lists:min(SIDs), Pid ! replaced @@ -721,7 +761,7 @@ process_iq(From, To, Packet) -> force_update_presence({LUser, LServer}) -> Mod = get_sm_backend(LServer), - Ss = Mod:get_sessions(LUser, LServer), + Ss = online(Mod:get_sessions(LUser, LServer)), lists:foreach(fun (#session{sid = {_, Pid}}) -> Pid ! {force_update_presence, LUser, LServer} end, diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index f503a749f..a480a1bd3 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -41,6 +41,7 @@ sql_bloc/2, sql_query_to_iolist/1, escape/1, + standard_escape/1, escape_like/1, escape_like_arg/1, escape_like_arg_circumflex/1, @@ -216,6 +217,8 @@ escape_like_arg_circumflex(S) when is_binary(S) -> escape_like_arg_circumflex($%) -> <<"^%">>; escape_like_arg_circumflex($_) -> <<"^_">>; escape_like_arg_circumflex($^) -> <<"^^">>; +escape_like_arg_circumflex($[) -> <<"^[">>; % For MSSQL +escape_like_arg_circumflex($]) -> <<"^]">>; escape_like_arg_circumflex(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>. to_bool(<<"t">>) -> true; diff --git a/src/gen_pubsub_node.erl b/src/gen_pubsub_node.erl index 0148da2e2..27cb032bd 100644 --- a/src/gen_pubsub_node.erl +++ b/src/gen_pubsub_node.erl @@ -35,6 +35,7 @@ -type(pubsubState() :: mod_pubsub:pubsubState()). -type(pubsubItem() :: mod_pubsub:pubsubItem()). -type(subOptions() :: mod_pubsub:subOptions()). +-type(pubOptions() :: mod_pubsub:pubOptions()). -type(affiliation() :: mod_pubsub:affiliation()). -type(subscription() :: mod_pubsub:subscription()). -type(subId() :: mod_pubsub:subId()). @@ -109,7 +110,8 @@ PublishModel :: publishModel(), Max_Items :: non_neg_integer(), ItemId :: <<>> | itemId(), - Payload :: payload()) -> + Payload :: payload(), + Options :: pubOptions()) -> {result, {default, broadcast, [itemId()]}} | {error, xmlel()}. diff --git a/src/jlib.erl b/src/jlib.erl index bad46898d..e5cba3180 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -43,7 +43,7 @@ get_iq_namespace/1, iq_query_info/1, iq_query_or_response_info/1, is_iq_request_type/1, iq_to_xml/1, parse_xdata_submit/1, - is_standalone_chat_state/1, + unwrap_carbon/1, is_standalone_chat_state/1, add_delay_info/3, add_delay_info/4, timestamp_to_legacy/1, timestamp_to_iso_basic/1, timestamp_to_iso/2, now_to_utc_string/1, now_to_local_string/1, @@ -529,14 +529,64 @@ rsm_encode_count(Count, Arr) -> children = [{xmlcdata, i2l(Count)}]} | Arr]. +-spec unwrap_carbon(xmlel()) -> xmlel(). + +unwrap_carbon(#xmlel{name = <<"message">>} = Stanza) -> + case unwrap_carbon(Stanza, <<"sent">>) of + #xmlel{} = Payload -> + Payload; + false -> + case unwrap_carbon(Stanza, <<"received">>) of + #xmlel{} = Payload -> + Payload; + false -> + Stanza + end + end; +unwrap_carbon(Stanza) -> Stanza. + +-spec unwrap_carbon(xmlel(), binary()) -> xmlel() | false. + +unwrap_carbon(Stanza, Direction) -> + case fxml:get_subtag(Stanza, Direction) of + #xmlel{name = Direction, attrs = Attrs} = El -> + case fxml:get_attr_s(<<"xmlns">>, Attrs) of + NS when NS == ?NS_CARBONS_2; + NS == ?NS_CARBONS_1 -> + case fxml:get_subtag_with_xmlns(El, <<"forwarded">>, + ?NS_FORWARD) of + #xmlel{children = Els} -> + case fxml:remove_cdata(Els) of + [#xmlel{} = Payload] -> + Payload; + _ -> + false + end; + false -> + false + end; + _NS -> + false + end; + false -> + false + end. + -spec is_standalone_chat_state(xmlel()) -> boolean(). -is_standalone_chat_state(#xmlel{name = <<"message">>, children = Els}) -> - Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els, - fxml:get_attr_s(<<"xmlns">>, Attrs) /= ?NS_CHATSTATES, - Name /= <<"thread">>], - Stripped == []; -is_standalone_chat_state(_El) -> false. +is_standalone_chat_state(Stanza) -> + case unwrap_carbon(Stanza) of + #xmlel{name = <<"message">>, children = Els} -> + IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY], + Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els, + not lists:member(fxml:get_attr_s(<<"xmlns">>, + Attrs), + IgnoreNS), + Name /= <<"thread">>], + Stripped == []; + #xmlel{} -> + false + end. -spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp()) -> xmlel(). diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index b12a80007..5aebd3ff2 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -863,26 +863,36 @@ connected_users_vhost(Host) -> dirty_get_sessions_list2() -> mnesia:dirty_select( session, - [{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4', _ = '_'}, - [], - [['$1', '$2', '$3', '$4']]}]). + [{#session{usr = '$1', sid = {'$2', '$3'}, priority = '$4', info = '$5', + _ = '_'}, + [{is_pid, '$3'}], + [['$1', {{'$2', '$3'}}, '$4', '$5']]}]). %% Make string more print-friendly stringize(String) -> %% Replace newline characters with other code ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>). +set_presence(User, Host, Resource, Type, Show, Status, Priority) + when is_integer(Priority) -> + BPriority = integer_to_binary(Priority), + set_presence(User, Host, Resource, Type, Show, Status, BPriority); set_presence(User, Host, Resource, Type, Show, Status, Priority) -> - Pid = ejabberd_sm:get_session_pid(User, Host, Resource), - USR = jid:to_string(jid:make(User, Host, Resource)), - US = jid:to_string(jid:make(User, Host, <<>>)), - Message = {route_xmlstreamelement, - {xmlel, <<"presence">>, - [{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}], - [{xmlel, <<"show">>, [], [{xmlcdata, Show}]}, - {xmlel, <<"status">>, [], [{xmlcdata, Status}]}, - {xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}}, - Pid ! Message. + case ejabberd_sm:get_session_pid(User, Host, Resource) of + none -> + error; + Pid -> + USR = jid:to_string(jid:make(User, Host, Resource)), + US = jid:to_string(jid:make(User, Host, <<>>)), + Message = {route_xmlstreamelement, + {xmlel, <<"presence">>, + [{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}], + [{xmlel, <<"show">>, [], [{xmlcdata, Show}]}, + {xmlel, <<"status">>, [], [{xmlcdata, Status}]}, + {xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}}, + Pid ! Message, + ok + end. user_sessions_info(User, Host) -> CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}), @@ -891,7 +901,9 @@ user_sessions_info(User, Host) -> {'EXIT', _Reason} -> []; Ss -> - Ss + lists:filter(fun(#session{sid = {_, Pid}}) -> + is_pid(Pid) + end, Ss) end, lists:map( fun(Session) -> diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl index 790e808f1..651f15c25 100644 --- a/src/mod_client_state.erl +++ b/src/mod_client_state.erl @@ -30,22 +30,44 @@ -behavior(gen_mod). --export([start/2, stop/1, add_stream_feature/2, - filter_presence/2, filter_chat_states/2, - mod_opt_type/1]). +%% gen_mod callbacks. +-export([start/2, stop/1, mod_opt_type/1]). + +%% ejabberd_hooks callbacks. +-export([filter_presence/3, filter_chat_states/3, filter_pep/3, filter_other/3, + flush_queue/2, add_stream_feature/2]). -include("ejabberd.hrl"). -include("logger.hrl"). -include("jlib.hrl"). +-define(CSI_QUEUE_MAX, 100). + +-type csi_type() :: presence | chatstate | {pep, binary()}. +-type csi_key() :: {ljid(), csi_type()}. +-type csi_stanza() :: {csi_key(), erlang:timestamp(), xmlel()}. +-type csi_queue() :: [csi_stanza()]. + +%%-------------------------------------------------------------------- +%% gen_mod callbacks. +%%-------------------------------------------------------------------- + +-spec start(binary(), gen_mod:opts()) -> ok. + start(Host, Opts) -> - QueuePresence = gen_mod:get_opt(queue_presence, Opts, - fun(B) when is_boolean(B) -> B end, - true), - DropChatStates = gen_mod:get_opt(drop_chat_states, Opts, - fun(B) when is_boolean(B) -> B end, - true), - if QueuePresence; DropChatStates -> + QueuePresence = + gen_mod:get_opt(queue_presence, Opts, + fun(B) when is_boolean(B) -> B end, + true), + QueueChatStates = + gen_mod:get_opt(queue_chat_states, Opts, + fun(B) when is_boolean(B) -> B end, + true), + QueuePEP = + gen_mod:get_opt(queue_pep, Opts, + fun(B) when is_boolean(B) -> B end, + false), + if QueuePresence; QueueChatStates; QueuePEP -> ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE, add_stream_feature, 50), if QueuePresence -> @@ -53,23 +75,39 @@ start(Host, Opts) -> filter_presence, 50); true -> ok end, - if DropChatStates -> + if QueueChatStates -> ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE, filter_chat_states, 50); true -> ok - end; + end, + if QueuePEP -> + ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE, + filter_pep, 50); + true -> ok + end, + ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE, + filter_other, 100), + ejabberd_hooks:add(csi_flush_queue, Host, ?MODULE, + flush_queue, 50); true -> ok - end, - ok. + end. + +-spec stop(binary()) -> ok. stop(Host) -> - QueuePresence = gen_mod:get_module_opt(Host, ?MODULE, queue_presence, - fun(B) when is_boolean(B) -> B end, - true), - DropChatStates = gen_mod:get_module_opt(Host, ?MODULE, drop_chat_states, - fun(B) when is_boolean(B) -> B end, - true), - if QueuePresence; DropChatStates -> + QueuePresence = + gen_mod:get_module_opt(Host, ?MODULE, queue_presence, + fun(B) when is_boolean(B) -> B end, + true), + QueueChatStates = + gen_mod:get_module_opt(Host, ?MODULE, queue_chat_states, + fun(B) when is_boolean(B) -> B end, + true), + QueuePEP = + gen_mod:get_module_opt(Host, ?MODULE, queue_pep, + fun(B) when is_boolean(B) -> B end, + false), + if QueuePresence; QueueChatStates; QueuePEP -> ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE, add_stream_feature, 50), if QueuePresence -> @@ -77,45 +115,188 @@ stop(Host) -> filter_presence, 50); true -> ok end, - if DropChatStates -> + if QueueChatStates -> ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE, filter_chat_states, 50); true -> ok - end; + end, + if QueuePEP -> + ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE, + filter_pep, 50); + true -> ok + end, + ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE, + filter_other, 100), + ejabberd_hooks:delete(csi_flush_queue, Host, ?MODULE, + flush_queue, 50); true -> ok - end, - ok. + end. -add_stream_feature(Features, _Host) -> - Feature = #xmlel{name = <<"csi">>, - attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}], - children = []}, - [Feature | Features]. +-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. + +mod_opt_type(queue_presence) -> + fun(B) when is_boolean(B) -> B end; +mod_opt_type(queue_chat_states) -> + fun(B) when is_boolean(B) -> B end; +mod_opt_type(queue_pep) -> + fun(B) when is_boolean(B) -> B end; +mod_opt_type(_) -> [queue_presence, queue_chat_states, queue_pep]. + +%%-------------------------------------------------------------------- +%% ejabberd_hooks callbacks. +%%-------------------------------------------------------------------- + +-spec filter_presence({term(), [xmlel()]}, binary(), xmlel()) + -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. -filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) -> +filter_presence({C2SState, _OutStanzas} = Acc, Host, + #xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) -> case fxml:get_attr(<<"type">>, Attrs) of {value, Type} when Type /= <<"unavailable">> -> - ?DEBUG("Got important presence stanza", []), - {stop, send}; + Acc; _ -> ?DEBUG("Got availability presence stanza", []), - {stop, queue} + queue_add(presence, Stanza, Host, C2SState) end; -filter_presence(Action, _Stanza) -> Action. +filter_presence(Acc, _Host, _Stanza) -> Acc. -filter_chat_states(_Action, #xmlel{name = <<"message">>} = Stanza) -> +-spec filter_chat_states({term(), [xmlel()]}, binary(), xmlel()) + -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. + +filter_chat_states({C2SState, _OutStanzas} = Acc, Host, + #xmlel{name = <<"message">>} = Stanza) -> case jlib:is_standalone_chat_state(Stanza) of true -> - ?DEBUG("Got standalone chat state notification", []), - {stop, drop}; + From = fxml:get_tag_attr_s(<<"from">>, Stanza), + To = fxml:get_tag_attr_s(<<"to">>, Stanza), + case {jid:from_string(From), jid:from_string(To)} of + {#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} -> + %% Don't queue (carbon copies of) chat states from other + %% resources, as they might be used to sync the state of + %% conversations across clients. + Acc; + _ -> + ?DEBUG("Got standalone chat state notification", []), + queue_add(chatstate, Stanza, Host, C2SState) + end; false -> - ?DEBUG("Got message stanza", []), - {stop, send} + Acc end; -filter_chat_states(Action, _Stanza) -> Action. +filter_chat_states(Acc, _Host, _Stanza) -> Acc. -mod_opt_type(drop_chat_states) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type(queue_presence) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type(_) -> [drop_chat_states, queue_presence]. +-spec filter_pep({term(), [xmlel()]}, binary(), xmlel()) + -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. + +filter_pep({C2SState, _OutStanzas} = Acc, Host, + #xmlel{name = <<"message">>} = Stanza) -> + case get_pep_node(Stanza) of + {value, Node} -> + ?DEBUG("Got PEP notification", []), + queue_add({pep, Node}, Stanza, Host, C2SState); + false -> + Acc + end; +filter_pep(Acc, _Host, _Stanza) -> Acc. + +-spec filter_other({term(), [xmlel()]}, binary(), xmlel()) + -> {stop, {term(), [xmlel()]}}. + +filter_other({C2SState, _OutStanzas}, Host, Stanza) -> + ?DEBUG("Won't add stanza to CSI queue", []), + queue_take(Stanza, Host, C2SState). + +-spec flush_queue({term(), [xmlel()]}, binary()) -> {term(), [xmlel()]}. + +flush_queue({C2SState, _OutStanzas}, Host) -> + ?DEBUG("Going to flush CSI queue", []), + Queue = get_queue(C2SState), + NewState = set_queue([], C2SState), + {NewState, get_stanzas(Queue, Host)}. + +-spec add_stream_feature([xmlel()], binary) -> [xmlel()]. + +add_stream_feature(Features, _Host) -> + Feature = #xmlel{name = <<"csi">>, + attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}], + children = []}, + [Feature | Features]. + +%%-------------------------------------------------------------------- +%% Internal functions. +%%-------------------------------------------------------------------- + +-spec queue_add(csi_type(), xmlel(), binary(), term()) + -> {stop, {term(), [xmlel()]}}. + +queue_add(Type, Stanza, Host, C2SState) -> + case get_queue(C2SState) of + Queue when length(Queue) >= ?CSI_QUEUE_MAX -> + ?DEBUG("CSI queue too large, going to flush it", []), + NewState = set_queue([], C2SState), + {stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}}; + Queue -> + ?DEBUG("Adding stanza to CSI queue", []), + From = fxml:get_tag_attr_s(<<"from">>, Stanza), + Key = {jid:tolower(jid:from_string(From)), Type}, + Entry = {Key, p1_time_compat:timestamp(), Stanza}, + NewQueue = lists:keystore(Key, 1, Queue, Entry), + NewState = set_queue(NewQueue, C2SState), + {stop, {NewState, []}} + end. + +-spec queue_take(xmlel(), binary(), term()) -> {stop, {term(), [xmlel()]}}. + +queue_take(Stanza, Host, C2SState) -> + From = fxml:get_tag_attr_s(<<"from">>, Stanza), + {LUser, LServer, _LResource} = jid:tolower(jid:from_string(From)), + {Selected, Rest} = lists:partition( + fun({{{U, S, _R}, _Type}, _Time, _Stanza}) -> + U == LUser andalso S == LServer + end, get_queue(C2SState)), + NewState = set_queue(Rest, C2SState), + {stop, {NewState, get_stanzas(Selected, Host) ++ [Stanza]}}. + +-spec set_queue(csi_queue(), term()) -> term(). + +set_queue(Queue, C2SState) -> + ejabberd_c2s:set_aux_field(csi_queue, Queue, C2SState). + +-spec get_queue(term()) -> csi_queue(). + +get_queue(C2SState) -> + case ejabberd_c2s:get_aux_field(csi_queue, C2SState) of + {ok, Queue} -> + Queue; + error -> + [] + end. + +-spec get_stanzas(csi_queue(), binary()) -> [xmlel()]. + +get_stanzas(Queue, Host) -> + lists:map(fun({_Key, Time, Stanza}) -> + jlib:add_delay_info(Stanza, Host, Time, + <<"Client Inactive">>) + end, Queue). + +-spec get_pep_node(xmlel()) -> {value, binary()} | false. + +get_pep_node(#xmlel{name = <<"message">>} = Stanza) -> + From = fxml:get_tag_attr_s(<<"from">>, Stanza), + case jid:from_string(From) of + #jid{luser = <<>>} -> % It's not PEP. + false; + _ -> + case fxml:get_subtag_with_xmlns(Stanza, <<"event">>, + ?NS_PUBSUB_EVENT) of + #xmlel{children = Els} -> + case fxml:remove_cdata(Els) of + [#xmlel{name = <<"items">>, attrs = ItemsAttrs}] -> + fxml:get_attr(<<"node">>, ItemsAttrs); + _ -> + false + end; + false -> + false + end + end. diff --git a/src/mod_configure.erl b/src/mod_configure.erl index a836c33bd..515127684 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -1917,17 +1917,19 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>), case JID#jid.lresource of <<>> -> SIDs = mnesia:dirty_select(session, - [{#session{sid = '$1', + [{#session{sid = {'$1', '$2'}, usr = {LUser, LServer, '_'}, _ = '_'}, - [], ['$1']}]), + [{is_pid, '$2'}], + [{{'$1', '$2'}}]}]), [Pid ! {kick, kicked_by_admin, Xmlelement} || {_, Pid} <- SIDs]; R -> [{_, Pid}] = mnesia:dirty_select(session, - [{#session{sid = '$1', + [{#session{sid = {'$1', '$2'}, usr = {LUser, LServer, R}, _ = '_'}, - [], ['$1']}]), + [{is_pid, '$2'}], + [{{'$1', '$2'}}]}]), Pid ! {kick, kicked_by_admin, Xmlelement} end, {result, []}; diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl index c4fae2022..1962e1d0e 100644 --- a/src/mod_http_api.erl +++ b/src/mod_http_api.erl @@ -188,9 +188,8 @@ check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) -> true -> {allowed, Call, admin}; _ -> unauthorized_response() end; - E -> - ?DEBUG("Unauthorized: ~p", [E]), - unauthorized_response() + _E -> + {allowed, Call, noauth} end; check_permissions2(_Request, _Call, _Policy) -> unauthorized_response(). @@ -209,7 +208,7 @@ oauth_check_token(Scope, Token) -> process(_, #request{method = 'POST', data = <<>>}) -> ?DEBUG("Bad Request: no data", []), badrequest_response(<<"Missing POST data">>); -process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) -> +process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} = Req) -> Version = get_api_version(Req), try Args = case jiffy:decode(Data) of @@ -217,10 +216,10 @@ process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) -> {List} when is_list(List) -> List; Other -> [Other] end, - log(Call, Args, IP), + log(Call, Args, IPPort), case check_permissions(Req, Call) of {allowed, Cmd, Auth} -> - {Code, Result} = handle(Cmd, Auth, Args, Version), + {Code, Result} = handle(Cmd, Auth, Args, Version, IP), json_response(Code, jiffy:encode(Result)); %% Warning: check_permission direcly formats 401 reply if not authorized ErrorResponse -> @@ -243,7 +242,7 @@ process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) -> log(Call, Args, IP), case check_permissions(Req, Call) of {allowed, Cmd, Auth} -> - {Code, Result} = handle(Cmd, Auth, Args, Version), + {Code, Result} = handle(Cmd, Auth, Args, Version, IP), json_response(Code, jiffy:encode(Result)); %% Warning: check_permission direcly formats 401 reply if not authorized ErrorResponse -> @@ -279,7 +278,7 @@ get_api_version([]) -> %% ---------------- % generic ejabberd command handler -handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> +handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) -> case ejabberd_commands:get_command_format(Call, Auth, Version) of {ArgsSpec, _} when is_list(ArgsSpec) -> Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args], @@ -296,7 +295,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> [{Key, undefined}|Acc] end, [], ArgsSpec), try - handle2(Call, Auth, match(Args2, Spec), Version) + handle2(Call, Auth, match(Args2, Spec), Version, IP) catch throw:not_found -> {404, <<"not_found">>}; throw:{not_found, Why} when is_atom(Why) -> @@ -333,10 +332,10 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> {400, <<"Error">>} end. -handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> +handle2(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) -> {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version), ArgsFormatted = format_args(Args, ArgsF), - ejabberd_command(Auth, Call, ArgsFormatted, Version). + ejabberd_command(Auth, Call, ArgsFormatted, Version, IP). get_elem_delete(A, L) -> case proplists:get_all_values(A, L) of @@ -416,12 +415,12 @@ process_unicode_codepoints(Str) -> match(Args, Spec) -> [{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec]. -ejabberd_command(Auth, Cmd, Args, Version) -> +ejabberd_command(Auth, Cmd, Args, Version, IP) -> Access = case Auth of admin -> []; _ -> undefined end, - case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version) of + case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version, #{ip => IP}) of {error, Error} -> throw(Error); Res -> diff --git a/src/mod_last.erl b/src/mod_last.erl index c39681f69..92694cf13 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -37,7 +37,7 @@ process_sm_iq/3, on_presence_update/4, import/1, import/3, store_last_info/4, get_last_info/2, remove_user/2, transform_options/1, mod_opt_type/1, - opt_type/1]). + opt_type/1, register_user/2]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -64,12 +64,16 @@ start(Host, Opts) -> ?NS_LAST, ?MODULE, process_local_iq, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_LAST, ?MODULE, process_sm_iq, IQDisc), + ejabberd_hooks:add(register_user, Host, ?MODULE, + register_user, 50), ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE, on_presence_update, 50). stop(Host) -> + ejabberd_hooks:delete(register_user, Host, ?MODULE, + register_user, 50), ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50), ejabberd_hooks:delete(unset_presence_hook, Host, @@ -198,6 +202,13 @@ get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) -> children = []}]} end. +register_user(User, Server) -> + on_presence_update( + User, + Server, + <<"RegisterResource">>, + <<"Registered but didn't login">>). + on_presence_update(User, Server, _Resource, Status) -> TimeStamp = p1_time_compat:system_time(seconds), store_last_info(User, Server, TimeStamp, Status). diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl index 007ef5eba..10b98daf7 100644 --- a/src/mod_mam_mnesia.erl +++ b/src/mod_mam_mnesia.erl @@ -16,6 +16,7 @@ -include_lib("stdlib/include/ms_transform.hrl"). -include("jlib.hrl"). +-include("logger.hrl"). -include("mod_mam.hrl"). -define(BIN_GREATER_THAN(A, B), @@ -25,6 +26,8 @@ ((A < B andalso byte_size(A) == byte_size(B)) orelse byte_size(A) < byte_size(B))). +-define(TABLE_SIZE_LIMIT, 2000000000). % A bit less than 2 GiB. + %%%=================================================================== %%% API %%%=================================================================== @@ -49,37 +52,68 @@ remove_room(_LServer, LName, LHost) -> remove_user(LName, LHost). delete_old_messages(global, TimeStamp, Type) -> - MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS, - type = MsgType} = Msg) - when MsgTS < TimeStamp, - MsgType == Type orelse Type == all -> - Msg - end), - OldMsgs = mnesia:dirty_select(archive_msg, MS), - lists:foreach(fun(Rec) -> - ok = mnesia:dirty_delete_object(Rec) - end, OldMsgs). + delete_old_user_messages(mnesia:dirty_first(archive_msg), TimeStamp, Type). + +delete_old_user_messages('$end_of_table', _TimeStamp, _Type) -> + ok; +delete_old_user_messages(User, TimeStamp, Type) -> + F = fun() -> + Msgs = mnesia:read(archive_msg, User), + Keep = lists:filter( + fun(#archive_msg{timestamp = MsgTS, + type = MsgType}) -> + MsgTS >= TimeStamp orelse (Type /= all andalso + Type /= MsgType) + end, Msgs), + if length(Keep) < length(Msgs) -> + mnesia:delete({archive_msg, User}), + lists:foreach(fun(Msg) -> mnesia:write(Msg) end, Keep); + true -> + ok + end + end, + case mnesia:transaction(F) of + {atomic, ok} -> + delete_old_user_messages(mnesia:dirty_next(archive_msg, User), + TimeStamp, Type); + {aborted, Err} -> + ?ERROR_MSG("Cannot delete old MAM messages: ~s", [Err]), + Err + end. extended_fields() -> []. store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) -> - LPeer = {PUser, PServer, _} = jid:tolower(Peer), - TS = p1_time_compat:timestamp(), - ID = jlib:integer_to_binary(now_to_usec(TS)), - case mnesia:dirty_write( - #archive_msg{us = {LUser, LServer}, - id = ID, - timestamp = TS, - peer = LPeer, - bare_peer = {PUser, PServer, <<>>}, - type = Type, - nick = Nick, - packet = Pkt}) of - ok -> - {ok, ID}; - Err -> - Err + case {mnesia:table_info(archive_msg, disc_only_copies), + mnesia:table_info(archive_msg, memory)} of + {[_|_], TableSize} when TableSize > ?TABLE_SIZE_LIMIT -> + ?ERROR_MSG("MAM archives too large, won't store message for ~s@~s", + [LUser, LServer]), + {error, overflow}; + _ -> + LPeer = {PUser, PServer, _} = jid:tolower(Peer), + TS = p1_time_compat:timestamp(), + ID = jlib:integer_to_binary(now_to_usec(TS)), + F = fun() -> + mnesia:write( + #archive_msg{us = {LUser, LServer}, + id = ID, + timestamp = TS, + peer = LPeer, + bare_peer = {PUser, PServer, <<>>}, + type = Type, + nick = Nick, + packet = Pkt}) + end, + case mnesia:transaction(F) of + {atomic, ok} -> + {ok, ID}; + {aborted, Err} -> + ?ERROR_MSG("Cannot add message to MAM archive of ~s@~s: ~s", + [LUser, LServer, Err]), + Err + end end. write_prefs(_LUser, _LServer, Prefs, _ServerHost) -> diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index d88806499..bbbe543f5 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -92,14 +92,16 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default, never = Never, always = Always}, ServerHost) -> - SUser = ejabberd_sql:escape(LUser), SDefault = erlang:atom_to_binary(Default, utf8), - SAlways = ejabberd_sql:encode_term(Always), - SNever = ejabberd_sql:encode_term(Never), - case update(ServerHost, <<"archive_prefs">>, - [<<"username">>, <<"def">>, <<"always">>, <<"never">>], - [SUser, SDefault, SAlways, SNever], - [<<"username='">>, SUser, <<"'">>]) of + SAlways = jlib:term_to_expr(Always), + SNever = jlib:term_to_expr(Never), + case ?SQL_UPSERT( + ServerHost, + "archive_prefs", + ["!username=%(LUser)s", + "def=%(SDefault)s", + "always=%(SAlways)s", + "never=%(SNever)s"]) of {updated, _} -> ok; Err -> @@ -109,10 +111,9 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default, get_prefs(LUser, LServer) -> case ejabberd_sql:sql_query( LServer, - [<<"select def, always, never from archive_prefs ">>, - <<"where username='">>, - ejabberd_sql:escape(LUser), <<"';">>]) of - {selected, _, [[SDefault, SAlways, SNever]]} -> + ?SQL("select @(def)s, @(always)s, @(never)s from archive_prefs" + " where username=%(LUser)s")) of + {selected, [{SDefault, SAlways, SNever}]} -> Default = erlang:binary_to_existing_atom(SDefault, utf8), Always = ejabberd_sql:decode_term(SAlways), Never = ejabberd_sql:decode_term(SNever), @@ -211,6 +212,12 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> ODBCType = ejabberd_config:get_option( {sql_type, LServer}, ejabberd_sql:opt_type(sql_type)), + Escape = + case ODBCType of + mssql -> fun ejabberd_sql:standard_escape/1; + sqlite -> fun ejabberd_sql:standard_escape/1; + _ -> fun ejabberd_sql:escape/1 + end, LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql -> [<<" limit ">>, jlib:integer_to_binary(Max+1)]; true -> @@ -226,14 +233,14 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> []; {text, Txt} -> [<<" and match (txt) against ('">>, - ejabberd_sql:escape(Txt), <<"')">>]; + Escape(Txt), <<"')">>]; {_, _, <<>>} -> [<<" and bare_peer='">>, - ejabberd_sql:escape(jid:to_string(With)), + Escape(jid:to_string(With)), <<"'">>]; {_, _, _} -> [<<" and peer='">>, - ejabberd_sql:escape(jid:to_string(With)), + Escape(jid:to_string(With)), <<"'">>]; none -> [] @@ -265,7 +272,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> _ -> [] end, - SUser = ejabberd_sql:escape(User), + SUser = Escape(User), Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick" " FROM archive WHERE username='">>, diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index c9c785757..eabff103d 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -266,6 +266,8 @@ normal_state({route, From, <<"">>, none -> NSD = set_affiliation(IJID, member, StateData), + send_affiliation(IJID, member, + StateData), case (NSD#state.config)#config.persistent of @@ -1801,8 +1803,8 @@ add_new_user(From, Nick, 10), Collision = nick_collision(From, Nick, StateData), case {(ServiceAffiliation == owner orelse - (Affiliation == admin orelse Affiliation == owner) - andalso NUsers < MaxAdminUsers + ((Affiliation == admin orelse Affiliation == owner) + andalso NUsers < MaxAdminUsers) orelse NUsers < MaxUsers) andalso NConferences < MaxConferences, Collision, @@ -2440,6 +2442,51 @@ send_nick_changing(JID, OldNick, StateData, end, (?DICT):to_list(StateData#state.users)). +maybe_send_affiliation(JID, Affiliation, StateData) -> + LJID = jid:tolower(JID), + IsOccupant = case LJID of + {LUser, LServer, <<"">>} -> + not (?DICT):is_empty( + (?DICT):filter(fun({U, S, _}, _) -> + U == LUser andalso + S == LServer + end, StateData#state.users)); + {_LUser, _LServer, _LResource} -> + (?DICT):is_key(LJID, StateData#state.users) + end, + case IsOccupant of + true -> + ok; % The new affiliation is published via presence. + false -> + send_affiliation(LJID, Affiliation, StateData) + end. + +send_affiliation(LJID, Affiliation, StateData) -> + ItemAttrs = [{<<"jid">>, jid:to_string(LJID)}, + {<<"affiliation">>, affiliation_to_list(Affiliation)}, + {<<"role">>, <<"none">>}], + Message = #xmlel{name = <<"message">>, + attrs = [{<<"id">>, randoms:get_string()}], + children = + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = ItemAttrs}]}]}, + Recipients = case (StateData#state.config)#config.anonymous of + true -> + (?DICT):filter(fun(_, #user{role = moderator}) -> + true; + (_, _) -> + false + end, StateData#state.users); + false -> + StateData#state.users + end, + send_multiple(StateData#state.jid, + StateData#state.server_host, + Recipients, Message). + status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) -> Status = case IsInitialPresence of true -> @@ -2722,11 +2769,13 @@ process_item_change(E, SD, UJID) -> <<"321">>, none, SD), + maybe_send_affiliation(JID, none, SD), SD1 = set_affiliation(JID, none, SD), set_role(JID, none, SD1); _ -> SD1 = set_affiliation(JID, none, SD), send_update_presence(JID, SD1, SD), + maybe_send_affiliation(JID, none, SD1), SD1 end; {JID, affiliation, outcast, Reason} -> @@ -2736,6 +2785,7 @@ process_item_change(E, SD, UJID) -> <<"301">>, outcast, SD), + maybe_send_affiliation(JID, outcast, SD), set_affiliation(JID, outcast, set_role(JID, none, SD), @@ -2745,11 +2795,13 @@ process_item_change(E, SD, UJID) -> SD1 = set_affiliation(JID, A, SD, Reason), SD2 = set_role(JID, moderator, SD1), send_update_presence(JID, Reason, SD2, SD), + maybe_send_affiliation(JID, A, SD2), SD2; {JID, affiliation, member, Reason} -> SD1 = set_affiliation(JID, member, SD, Reason), SD2 = set_role(JID, participant, SD1), send_update_presence(JID, Reason, SD2, SD), + maybe_send_affiliation(JID, member, SD2), SD2; {JID, role, Role, Reason} -> SD1 = set_role(JID, Role, SD), @@ -2759,6 +2811,7 @@ process_item_change(E, SD, UJID) -> {JID, affiliation, A, _Reason} -> SD1 = set_affiliation(JID, A, SD), send_update_presence(JID, SD1, SD), + maybe_send_affiliation(JID, A, SD1), SD1 end of @@ -3092,14 +3145,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, StateData#state.users), SAffiliation = affiliation_to_list(Affiliation), BannedJIDString = jid:to_string(RealJID), - case MJID /= <<"">> of - true -> - {ok, #user{nick = ActorNick}} = - (?DICT):find(jid:tolower(MJID), - StateData#state.users); - false -> - ActorNick = <<"">> - end, + ActorNick = get_actor_nick(MJID, StateData), lists:foreach(fun ({_LJID, Info}) -> JidAttrList = case Info#user.role == moderator orelse (StateData#state.config)#config.anonymous @@ -3154,6 +3200,14 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, end, (?DICT):to_list(StateData#state.users)). +get_actor_nick(<<"">>, _StateData) -> + <<"">>; +get_actor_nick(MJID, StateData) -> + case (?DICT):find(jid:tolower(MJID), StateData#state.users) of + {ok, #user{nick = ActorNick}} -> ActorNick; + _ -> <<"">> + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Owner stuff diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index 4d9455570..a368e8e31 100644 --- a/src/mod_offline_sql.erl +++ b/src/mod_offline_sql.erl @@ -38,8 +38,7 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) -> true -> Query = lists:map( fun(M) -> - Username = - ejabberd_sql:escape((M#offline_msg.to)#jid.luser), + LUser = (M#offline_msg.to)#jid.luser, From = M#offline_msg.from, To = M#offline_msg.to, Packet = @@ -49,9 +48,8 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) -> jlib:add_delay_info(Packet, Host, M#offline_msg.timestamp, <<"Offline Storage">>), - XML = - ejabberd_sql:escape(fxml:element_to_binary(NewPacket)), - sql_queries:add_spool_sql(Username, XML) + XML = fxml:element_to_binary(NewPacket), + sql_queries:add_spool_sql(LUser, XML) end, Msgs), sql_queries:add_spool(Host, Query) @@ -95,19 +93,18 @@ remove_user(LUser, LServer) -> sql_queries:del_spool_msg(LServer, LUser). read_message_headers(LUser, LServer) -> - Username = ejabberd_sql:escape(LUser), case catch ejabberd_sql:sql_query( - LServer, [<<"select xml, seq from spool where username ='">>, - Username, <<"' order by seq;">>]) of - {selected, [<<"xml">>, <<"seq">>], Rows} -> + LServer, + ?SQL("select @(xml)s, @(seq)d from spool" + " where username=%(LUser)s order by seq")) of + {selected, Rows} -> lists:flatmap( - fun([XML, Seq]) -> + fun({XML, Seq}) -> case xml_to_offline_msg(XML) of {ok, #offline_msg{from = From, to = To, packet = El}} -> - Seq0 = binary_to_integer(Seq), - [{Seq0, From, To, El}]; + [{Seq, From, To, El}]; _ -> [] end @@ -117,13 +114,11 @@ read_message_headers(LUser, LServer) -> end. read_message(LUser, LServer, Seq) -> - Username = ejabberd_sql:escape(LUser), - SSeq = ejabberd_sql:escape(integer_to_binary(Seq)), case ejabberd_sql:sql_query( LServer, - [<<"select xml from spool where username='">>, Username, - <<"' and seq='">>, SSeq, <<"';">>]) of - {selected, [<<"xml">>], [[RawXML]|_]} -> + ?SQL("select @(xml)s from spool where username=%(LUser)s" + " and seq=%(Seq)d")) of + {selected, [{RawXML}|_]} -> case xml_to_offline_msg(RawXML) of {ok, Msg} -> {ok, Msg}; @@ -135,12 +130,10 @@ read_message(LUser, LServer, Seq) -> end. remove_message(LUser, LServer, Seq) -> - Username = ejabberd_sql:escape(LUser), - SSeq = ejabberd_sql:escape(integer_to_binary(Seq)), ejabberd_sql:sql_query( LServer, - [<<"delete from spool where username='">>, Username, - <<"' and seq='">>, SSeq, <<"';">>]), + ?SQL("delete from spool where username=%(LUser)s" + " and seq=%(Seq)d")), ok. read_all_messages(LUser, LServer) -> @@ -180,14 +173,13 @@ export(_Server) -> timestamp = TimeStamp, from = From, to = To, packet = Packet}) when LServer == Host -> - Username = ejabberd_sql:escape(LUser), Packet1 = jlib:replace_from_to(From, To, Packet), Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp, <<"Offline Storage">>), - XML = ejabberd_sql:escape(fxml:element_to_binary(Packet2)), - [[<<"delete from spool where username='">>, Username, <<"';">>], - [<<"insert into spool(username, xml) values ('">>, - Username, <<"', '">>, XML, <<"');">>]]; + XML = fxml:element_to_binary(Packet2), + [?SQL("delete from spool where username=%(LUser)s;"), + ?SQL("insert into spool(username, xml) values (" + "%(LUser)s, %(XML)s);")]; (_Host, _R) -> [] end}]. diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 35173a4f2..de278c838 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -107,6 +107,8 @@ nodeOptions/0, subOption/0, subOptions/0, + pubOption/0, + pubOptions/0, %% affiliation/0, subscription/0, @@ -1289,7 +1291,16 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> [#xmlel{name = <<"item">>, attrs = ItemAttrs, children = Payload}] -> ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs), - publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access); + PubOpts = case [C || #xmlel{name = <<"publish-options">>, + children = [C]} <- Rest] of + [XEl] -> + case jlib:parse_xdata_submit(XEl) of + invalid -> []; + Form -> Form + end; + _ -> [] + end, + publish_item(Host, ServerHost, Node, From, ItemId, Payload, PubOpts, Access); [] -> {error, extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; @@ -2185,10 +2196,10 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) -> | {error, xmlel()} ). publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> - publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, all). -publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, Access) -> - publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, Access); -publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) -> + publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, [], all). +publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, PubOpts, Access) -> + publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, PubOpts, Access); +publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access) -> Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) -> Features = plugin_features(Host, Type), PublishFeature = lists:member(<<"publish">>, Features), @@ -2220,7 +2231,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) -> extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; true -> node_call(Host, Type, publish_item, - [Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload]) + [Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, PubOpts]) end end, Reply = [#xmlel{name = <<"pubsub">>, @@ -2281,7 +2292,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) -> attrs = [{<<"xmlns">>, ?NS_PUBSUB}], children = [#xmlel{name = <<"create">>, attrs = [{<<"node">>, NewNode}]}]}]} -> - publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload); + publish_item(Host, ServerHost, NewNode, Publisher, ItemId, + Payload, PubOpts, Access); _ -> {error, ?ERR_ITEM_NOT_FOUND} end; @@ -3861,6 +3873,7 @@ set_configure(Host, Node, From, Els, Lang) -> set_node, [N#pubsub_node{options = NewOpts}]) of + {ok, Nidx} -> {result, ok}; ok -> {result, ok}; Err -> Err end; diff --git a/src/mod_register.erl b/src/mod_register.erl index c1a7cab81..27e484e85 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -441,6 +441,7 @@ try_register(User, Server, Password, SourceRaw, Lang) -> end end; false -> + remove_timeout(Source), ErrText = <<"The password is too weak">>, {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)} end; diff --git a/src/node_buddy.erl b/src/node_buddy.erl index aeacacda1..bdaa4a89a 100644 --- a/src/node_buddy.erl +++ b/src/node_buddy.erl @@ -33,7 +33,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -102,8 +102,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_club.erl b/src/node_club.erl index 64eff7791..7f6ae6520 100644 --- a/src/node_club.erl +++ b/src/node_club.erl @@ -33,7 +33,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_dag.erl b/src/node_dag.erl index 09ee85f91..afb610ca7 100644 --- a/src/node_dag.erl +++ b/src/node_dag.erl @@ -33,7 +33,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -72,7 +72,7 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> case nodetree_dag:get_node(Nidx) of #pubsub_node{options = Options} -> case find_opt(node_type, Options) of @@ -82,7 +82,7 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> ?ERR_EXTENDED(?ERRT_NOT_ALLOWED(?MYLANG, Txt), <<"publish">>)}; _ -> node_hometree:publish_item(Nidx, Publisher, Model, - MaxItems, ItemId, Payload) + MaxItems, ItemId, Payload, PubOpts) end; Err -> Err end. diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl index 178292d31..b3af69cd1 100644 --- a/src/node_dispatch.erl +++ b/src/node_dispatch.erl @@ -39,7 +39,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -99,13 +99,14 @@ subscribe_node(_Nidx, _Sender, _Subscriber, _AccessModel, _SendLast, _PresenceSu unsubscribe_node(_Nidx, _Sender, _Subscriber, _SubId) -> {error, ?ERR_FORBIDDEN}. -publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> +publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, + PubOpts) -> case nodetree_tree:get_node(Nidx) of #pubsub_node{nodeid = {Host, Node}} -> lists:foreach(fun (SubNode) -> node_hometree:publish_item(SubNode#pubsub_node.id, Publisher, PublishModel, MaxItems, - ItemId, Payload) + ItemId, Payload, PubOpts) end, nodetree_tree:get_subnodes(Host, Node, Publisher)), {result, {default, broadcast, []}}; diff --git a/src/node_flat.erl b/src/node_flat.erl index c2efe3e12..2fb24ee69 100644 --- a/src/node_flat.erl +++ b/src/node_flat.erl @@ -39,7 +39,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -345,7 +345,8 @@ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) -> %% to completly disable persistance.</li></ul> %% </p> %% <p>In the default plugin module, the record is unchanged.</p> -publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> +publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, + _PubOpts) -> SubKey = jid:tolower(Publisher), GenKey = jid:remove_resource(SubKey), GenState = get_state(Nidx, GenKey), diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl index ea3aa7d3c..7b8ee552c 100644 --- a/src/node_flat_sql.erl +++ b/src/node_flat_sql.erl @@ -33,13 +33,16 @@ -behaviour(gen_pubsub_node). -author('christophe.romain@process-one.net'). +-compile([{parse_transform, ejabberd_sql_pt}]). + -include("pubsub.hrl"). -include("jlib.hrl"). +-include("ejabberd_sql_pt.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -75,19 +78,25 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> create_node(Nidx, Owner) -> {_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)), - State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner}, - catch ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values(">>, state_to_raw(Nidx, State), <<");">>]), + J = encode_jid(OwnerKey), + A = encode_affiliation(owner), + S = encode_subscriptions([]), + catch ejabberd_sql:sql_query_t( + ?SQL("insert into pubsub_state(" + "nodeid, jid, affiliation, subscriptions) " + "values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")), {result, {default, broadcast}}. delete_node(Nodes) -> Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) -> Subscriptions = case catch - ejabberd_sql:sql_query_t([<<"select jid, subscriptions " - "from pubsub_state where nodeid='">>, Nidx, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(subscriptions)s " + "from pubsub_state where nodeid=%(Nidx)d")) of - {selected, [<<"jid">>, <<"subscriptions">>], RItems} -> - [{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems]; + {selected, RItems} -> + [{decode_jid(SJID), decode_subscriptions(Subs)} || + {SJID, Subs} <- RItems]; _ -> [] end, @@ -217,7 +226,8 @@ delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscripti _ -> update_subscription(Nidx, SubKey, NewSubs) end. -publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> +publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, + _PubOpts) -> SubKey = jid:tolower(Publisher), GenKey = jid:remove_resource(SubKey), {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), @@ -298,13 +308,14 @@ get_entity_affiliations(Host, Owner) -> H = encode_host(Host), J = encode_jid(GenKey), Reply = case catch - ejabberd_sql:sql_query_t([<<"select node, type, i.nodeid, affiliation " - "from pubsub_state i, pubsub_node n where " - "i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(node)s, @(type)s, @(i.nodeid)d, @(affiliation)s " + "from pubsub_state i, pubsub_node n where " + "i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s")) of - {selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} -> - [{nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)} - || [N, T, I, A] <- RItems]; + {selected, RItems} -> + [{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), decode_affiliation(A)} + || {N, T, I, A} <- RItems]; _ -> [] end, @@ -312,11 +323,12 @@ get_entity_affiliations(Host, Owner) -> get_node_affiliations(Nidx) -> Reply = case catch - ejabberd_sql:sql_query_t([<<"select jid, affiliation from pubsub_state " - "where nodeid='">>, Nidx, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(affiliation)s from pubsub_state " + "where nodeid=%(Nidx)d")) of - {selected, [<<"jid">>, <<"affiliation">>], RItems} -> - [{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems]; + {selected, RItems} -> + [{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems]; _ -> [] end, @@ -327,10 +339,11 @@ get_affiliation(Nidx, Owner) -> GenKey = jid:remove_resource(SubKey), J = encode_jid(GenKey), Reply = case catch - ejabberd_sql:sql_query_t([<<"select affiliation from pubsub_state " - "where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(affiliation)s from pubsub_state " + "where nodeid=%(Nidx)d and jid=%(J)s")) of - {selected, [<<"affiliation">>], [[A]]} -> + {selected, [{A}]} -> decode_affiliation(A); _ -> none @@ -352,23 +365,26 @@ get_entity_subscriptions(Host, Owner) -> H = encode_host(Host), SJ = encode_jid(SubKey), GJ = encode_jid(GenKey), - GJLike = encode_jid_like(GenKey), - Query = case SubKey of - GenKey -> - [<<"select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid and jid like '">>, GJLike, - <<"%' escape '^' and host='">>, H, <<"';">>]; - _ -> - [<<"select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] - end, + GJLike = <<(encode_jid_like(GenKey))/binary, "%">>, + Query = + case SubKey of + GenKey -> + ?SQL("select @(node)s, @(type)s, @(i.nodeid)d," + " @(jid)s, @(subscriptions)s " + "from pubsub_state i, pubsub_node n " + "where i.nodeid = n.nodeid and jid like %(GJLike)s" + " escape '^' and host=%(H)s"); + _ -> + ?SQL("select @(node)s, @(type)s, @(i.nodeid)d," + " @(jid)s, @(subscriptions)s " + "from pubsub_state i, pubsub_node n " + "where i.nodeid = n.nodeid and jid in" + " (%(SJ)s, %(GJ)s) and host=%(H)s") + end, Reply = case catch ejabberd_sql:sql_query_t(Query) of - {selected, - [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} -> - lists:foldl(fun ([N, T, I, J, S], Acc) -> - Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), + {selected, RItems} -> + lists:foldl(fun ({N, T, I, J, S}, Acc) -> + Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), Jid = decode_jid(J), case decode_subscriptions(S) of [] -> @@ -403,25 +419,28 @@ get_entity_subscriptions_for_send_last(Host, Owner) -> H = encode_host(Host), SJ = encode_jid(SubKey), GJ = encode_jid(GenKey), - GJLike = encode_jid_like(GenKey), - Query = case SubKey of - GenKey -> - [<<"select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n, pubsub_node_option o " - "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' " - "and val='on_sub_and_presence' and jid like '">>, GJLike, - <<"%' escape '^' and host='">>, H, <<"';">>]; - _ -> - [<<"select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n, pubsub_node_option o " - "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' " - "and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] + GJLike = <<(encode_jid_like(GenKey))/binary, "%">>, + Query = + case SubKey of + GenKey -> + ?SQL("select @(node)s, @(type)s, @(i.nodeid)d," + " @(jid)s, @(subscriptions)s " + "from pubsub_state i, pubsub_node n, pubsub_node_option o " + "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' " + "and val='on_sub_and_presence' and jid like %(GJLike)s" + " escape '^' and host=%(H)s"); + _ -> + ?SQL("select @(node)s, @(type)s, @(i.nodeid)d," + " @(jid)s, @(subscriptions)s " + "from pubsub_state i, pubsub_node n, pubsub_node_option o " + "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' " + "and val='on_sub_and_presence' and" + " jid in (%(SJ)s, %(GJ)s) and host=%(H)s") end, Reply = case catch ejabberd_sql:sql_query_t(Query) of - {selected, - [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} -> - lists:foldl(fun ([N, T, I, J, S], Acc) -> - Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), + {selected, RItems} -> + lists:foldl(fun ({N, T, I, J, S}, Acc) -> + Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), Jid = decode_jid(J), case decode_subscriptions(S) of [] -> @@ -441,11 +460,12 @@ get_entity_subscriptions_for_send_last(Host, Owner) -> get_node_subscriptions(Nidx) -> Reply = case catch - ejabberd_sql:sql_query_t([<<"select jid, subscriptions from pubsub_state " - "where nodeid='">>, Nidx, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(subscriptions)s from pubsub_state " + "where nodeid=%(Nidx)d")) of - {selected, [<<"jid">>, <<"subscriptions">>], RItems} -> - lists:foldl(fun ([J, S], Acc) -> + {selected, RItems} -> + lists:foldl(fun ({J, S}, Acc) -> Jid = decode_jid(J), case decode_subscriptions(S) of [] -> @@ -467,10 +487,11 @@ get_subscriptions(Nidx, Owner) -> SubKey = jid:tolower(Owner), J = encode_jid(SubKey), Reply = case catch - ejabberd_sql:sql_query_t([<<"select subscriptions from pubsub_state where " - "nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(subscriptions)s from pubsub_state" + " where nodeid=%(Nidx)d and jid=%(J)s")) of - {selected, [<<"subscriptions">>], [[S]]} -> + {selected, [{S}]} -> decode_subscriptions(S); _ -> [] @@ -567,13 +588,13 @@ get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs} get_states(Nidx) -> case catch - ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions " - "from pubsub_state where nodeid='">>, Nidx, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s " + "from pubsub_state where nodeid=%(Nidx)d")) of - {selected, - [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} -> + {selected, RItems} -> {result, - lists:map(fun ([SJID, Aff, Subs]) -> + lists:map(fun ({SJID, Aff, Subs}) -> JID = decode_jid(SJID), #pubsub_state{stateid = {JID, Nidx}, items = itemids(Nidx, JID), @@ -598,11 +619,12 @@ get_state(Nidx, JID) -> get_state_without_itemids(Nidx, JID) -> J = encode_jid(JID), case catch - ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions " - "from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s " + "from pubsub_state " + "where nodeid=%(Nidx)d and jid=%(J)s")) of - {selected, - [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} -> + {selected, [{SJID, Aff, Subs}]} -> #pubsub_state{stateid = {decode_jid(SJID), Nidx}, affiliation = decode_affiliation(Aff), subscriptions = decode_subscriptions(Subs)}; @@ -619,24 +641,20 @@ set_state(Nidx, State) -> J = encode_jid(JID), S = encode_subscriptions(State#pubsub_state.subscriptions), A = encode_affiliation(State#pubsub_state.affiliation), - case catch - ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A, - <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> - ok; - _ -> - catch - ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('">>, - Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>]) - end, + ?SQL_UPSERT_T( + "pubsub_state", + ["!nodeid=%(Nidx)d", + "!jid=%(J)s", + "affiliation=%(A)s", + "subscriptions=%(S)s" + ]), ok. del_state(Nidx, JID) -> J = encode_jid(JID), - catch ejabberd_sql:sql_query_t([<<"delete from pubsub_state where jid='">>, - J, <<"' and nodeid='">>, Nidx, <<"';">>]), + catch ejabberd_sql:sql_query_t( + ?SQL("delete from pubsub_state" + " where jid=%(J)s and nodeid=%(Nidx)d")), ok. %get_items(Nidx, _From) -> @@ -654,12 +672,12 @@ del_state(Nidx, JID) -> get_items(Nidx, From, none) -> MaxItems = case catch - ejabberd_sql:sql_query_t([<<"select val from pubsub_node_option " - "where nodeid='">>, Nidx, <<"' and name='max_items';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(val)s from pubsub_node_option " + "where nodeid=%(Nidx)d and name='max_items'")) of - {selected, [<<"val">>], [[Value]]} -> - Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))), - element(2, erl_parse:parse_term(Tokens)); + {selected, [{Value}]} -> + jlib:expr_to_term(Value); _ -> ?MAXITEMS end, @@ -676,12 +694,13 @@ get_items(Nidx, _From, _ -> {<<"is not">>, <<"desc">>}% Can be better end, + SNidx = integer_to_binary(Nidx), [AttrName, Id] = case I of undefined when IncIndex =/= undefined -> case catch ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi " "where exists ( select count(*) as count1 " - "from pubsub_item where nodeid='">>, Nidx, + "from pubsub_item where nodeid='">>, SNidx, <<"' and modification > pi.modification having count1 = ">>, ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>]) of @@ -699,7 +718,7 @@ get_items(Nidx, _From, [A, <<"'", B/binary, "'">>] end, Count = case catch - ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>]) + ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, SNidx, <<"';">>]) of {selected, [_], [[C]]} -> C; _ -> <<"0">> @@ -708,13 +727,13 @@ get_items(Nidx, _From, ejabberd_sql:sql_query_t( [<<"select top ">>, jlib:i2l(Max), <<" itemid, publisher, creation, modification, payload " - "from pubsub_item where nodeid='">>, Nidx, + "from pubsub_item where nodeid='">>, SNidx, <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>, AttrName, <<" ">>, Order, <<";">>]); (_, _) -> ejabberd_sql:sql_query_t( [<<"select itemid, publisher, creation, modification, payload " - "from pubsub_item where nodeid='">>, Nidx, + "from pubsub_item where nodeid='">>, SNidx, <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>, AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>]) end, @@ -725,7 +744,7 @@ get_items(Nidx, _From, [[_, _, _, F, _]|_] -> Index = case catch ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item " - "where nodeid='">>, Nidx, <<"' and ">>, + "where nodeid='">>, SNidx, <<"' and ">>, AttrName, <<" > '">>, F, <<"';">>]) of %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")]; @@ -777,16 +796,17 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM get_last_items(Nidx, _From, Count) -> Limit = jlib:i2l(Count), + SNidx = integer_to_binary(Nidx), Query = fun(mssql, _) -> ejabberd_sql:sql_query_t( [<<"select top ">>, Limit, <<" itemid, publisher, creation, modification, payload " - "from pubsub_item where nodeid='">>, Nidx, + "from pubsub_item where nodeid='">>, SNidx, <<"' order by modification desc ;">>]); (_, _) -> ejabberd_sql:sql_query_t( [<<"select itemid, publisher, creation, modification, payload " - "from pubsub_item where nodeid='">>, Nidx, + "from pubsub_item where nodeid='">>, SNidx, <<"' order by modification desc limit ">>, Limit, <<";">>]) end, case catch ejabberd_sql:sql_query_t(Query) of @@ -798,16 +818,14 @@ get_last_items(Nidx, _From, Count) -> end. get_item(Nidx, ItemId) -> - I = ejabberd_sql:escape(ItemId), - case catch - ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, " - "modification, payload from pubsub_item " - "where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>]) + case catch ejabberd_sql:sql_query_t( + ?SQL("select @(itemid)s, @(publisher)s, @(creation)s," + " @(modification)s, @(payload)s from pubsub_item" + " where nodeid=%(Nidx)d and itemid=%(ItemId)s")) of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} -> + {selected, [RItem]} -> {result, raw_to_item(Nidx, RItem)}; - {selected, _, []} -> + {selected, []} -> {error, ?ERR_ITEM_NOT_FOUND}; {'EXIT', _} -> {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)} @@ -846,37 +864,31 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub set_item(Item) -> {ItemId, Nidx} = Item#pubsub_item.itemid, - I = ejabberd_sql:escape(ItemId), {C, _} = Item#pubsub_item.creation, {M, JID} = Item#pubsub_item.modification, P = encode_jid(JID), Payload = Item#pubsub_item.payload, - XML = ejabberd_sql:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)), + XML = str:join([fxml:element_to_binary(X) || X<-Payload], <<>>), S = fun ({T1, T2, T3}) -> str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>) end, - case catch - ejabberd_sql:sql_query_t([<<"update pubsub_item set publisher='">>, P, - <<"', modification='">>, S(M), - <<"', payload='">>, XML, - <<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>]) - of - {updated, 1} -> - ok; - _ -> - catch - ejabberd_sql:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, " - "publisher, creation, modification, payload) " - "values('">>, Nidx, <<"', '">>, I, <<"', '">>, P, - <<"', '">>, S(C), <<"', '">>, S(M), - <<"', '">>, XML, <<"');">>]) - end, + SM = S(M), + SC = S(C), + ?SQL_UPSERT_T( + "pubsub_item", + ["!nodeid=%(Nidx)d", + "!itemid=%(ItemId)s", + "publisher=%(P)s", + "modification=%(SM)s", + "payload=%(XML)s", + "-creation=%(SC)s" + ]), ok. del_item(Nidx, ItemId) -> - I = ejabberd_sql:escape(ItemId), - catch ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid='">>, - I, <<"' and nodeid='">>, Nidx, <<"';">>]). + catch ejabberd_sql:sql_query_t( + ?SQL("delete from pubsub_item where itemid=%(ItemId)s" + " and nodeid=%(Nidx)d")). del_items(_, []) -> ok; @@ -884,9 +896,10 @@ del_items(Nidx, [ItemId]) -> del_item(Nidx, ItemId); del_items(Nidx, ItemIds) -> I = str:join([[<<"'">>, ejabberd_sql:escape(X), <<"'">>] || X <- ItemIds], <<",">>), + SNidx = integer_to_binary(Nidx), catch ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>, - I, <<") and nodeid='">>, Nidx, <<"';">>]). + I, <<") and nodeid='">>, SNidx, <<"';">>]). get_item_name(_Host, _Node, Id) -> Id. @@ -907,14 +920,15 @@ first_in_list(Pred, [H | T]) -> end. itemids(Nidx, {_U, _S, _R} = JID) -> - SJID = encode_jid_like(JID), + SJID = <<(ejabberd_sql:escape(encode_jid_like(JID)))/binary, "%">>, case catch - ejabberd_sql:sql_query_t([<<"select itemid from pubsub_item where " - "nodeid='">>, Nidx, <<"' and publisher like '">>, SJID, - <<"%' escape '^' order by modification desc;">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(itemid)s from pubsub_item where " + "nodeid=%(Nidx)d and publisher like %(SJID)s escape '^' " + "order by modification desc")) of - {selected, [<<"itemid">>], RItems} -> - [ItemId || [ItemId] <- RItems]; + {selected, RItems} -> + [ItemId || {ItemId} <- RItems]; _ -> [] end. @@ -922,11 +936,11 @@ itemids(Nidx, {_U, _S, _R} = JID) -> select_affiliation_subscriptions(Nidx, JID) -> J = encode_jid(JID), case catch - ejabberd_sql:sql_query_t([<<"select affiliation,subscriptions from " - "pubsub_state where nodeid='">>, - Nidx, <<"' and jid='">>, J, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(affiliation)s, @(subscriptions)s from " + " pubsub_state where nodeid=%(Nidx)d and jid=%(J)s")) of - {selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} -> + {selected, [{A, S}]} -> {decode_affiliation(A), decode_subscriptions(S)}; _ -> {none, []} @@ -942,33 +956,24 @@ select_affiliation_subscriptions(Nidx, GenKey, SubKey) -> update_affiliation(Nidx, JID, Affiliation) -> J = encode_jid(JID), A = encode_affiliation(Affiliation), - case catch - ejabberd_sql:sql_query_t([<<"update pubsub_state set affiliation='">>, - A, <<"' where nodeid='">>, Nidx, - <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> - ok; - _ -> - catch - ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>]) - end. + ?SQL_UPSERT_T( + "pubsub_state", + ["!nodeid=%(Nidx)d", + "!jid=%(J)s", + "affiliation=%(A)s", + "-subscriptions=''" + ]). update_subscription(Nidx, JID, Subscription) -> J = encode_jid(JID), S = encode_subscriptions(Subscription), - case catch - ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, - <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> - ok; - _ -> - catch - ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>]) - end. + ?SQL_UPSERT_T( + "pubsub_state", + ["!nodeid=%(Nidx)d", + "!jid=%(J)s", + "subscriptions=%(S)s", + "-affiliation='n'" + ]). -spec(decode_jid/1 :: ( SJID :: binary()) @@ -1015,24 +1020,24 @@ decode_subscriptions(Subscriptions) -> -> binary() ). encode_jid(JID) -> - ejabberd_sql:escape(jid:to_string(JID)). + jid:to_string(JID). -spec(encode_jid_like/1 :: (JID :: ljid()) -> binary()). encode_jid_like(JID) -> - ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(jid:to_string(JID))). + ejabberd_sql:escape_like_arg_circumflex(jid:to_string(JID)). -spec(encode_host/1 :: ( Host :: host()) -> binary() ). encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID); -encode_host(Host) -> ejabberd_sql:escape(Host). +encode_host(Host) -> Host. -spec(encode_host_like/1 :: ( Host :: host()) -> binary() ). -encode_host_like({_U, _S, _R} = LJID) -> encode_jid_like(LJID); +encode_host_like({_U, _S, _R} = LJID) -> ejabberd_sql:escape(encode_jid_like(LJID)); encode_host_like(Host) -> ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(Host)). @@ -1068,12 +1073,14 @@ encode_subscriptions(Subscriptions) -> state_to_raw(Nidx, State) -> {JID, _} = State#pubsub_state.stateid, - J = encode_jid(JID), + J = ejabberd_sql:escape(encode_jid(JID)), A = encode_affiliation(State#pubsub_state.affiliation), S = encode_subscriptions(State#pubsub_state.subscriptions), [<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>]. raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) -> + raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML}); +raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML}) -> JID = decode_jid(SJID), ToTime = fun (Str) -> [T1, T2, T3] = str:tokens(Str, <<":">>), diff --git a/src/node_hometree.erl b/src/node_hometree.erl index 2d26c0eeb..def7b983d 100644 --- a/src/node_hometree.erl +++ b/src/node_hometree.erl @@ -33,7 +33,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -99,8 +99,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_hometree_sql.erl b/src/node_hometree_sql.erl index 4ce6f8554..d9af49843 100644 --- a/src/node_hometree_sql.erl +++ b/src/node_hometree_sql.erl @@ -33,7 +33,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -77,8 +77,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_mb.erl b/src/node_mb.erl index 0bfb51ecc..6c7f09780 100644 --- a/src/node_mb.erl +++ b/src/node_mb.erl @@ -46,7 +46,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -116,8 +116,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_pep:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_pep:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_pep:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_pep:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_mix.erl b/src/node_mix.erl index b57d58493..71a8d3180 100644 --- a/src/node_mix.erl +++ b/src/node_mix.erl @@ -14,7 +14,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -89,8 +89,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, + PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_mix_sql.erl b/src/node_mix_sql.erl index b5b9a94eb..cbe0b3d02 100644 --- a/src/node_mix_sql.erl +++ b/src/node_mix_sql.erl @@ -14,7 +14,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -89,8 +89,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_online.erl b/src/node_online.erl index ea492fc85..1c2ab5a03 100644 --- a/src/node_online.erl +++ b/src/node_online.erl @@ -33,7 +33,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -99,8 +99,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_pep.erl b/src/node_pep.erl index 2ac4da627..504e39fa3 100644 --- a/src/node_pep.erl +++ b/src/node_pep.erl @@ -37,7 +37,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -130,8 +130,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> {result, _} -> {result, []} end. -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl index 1f2c13d5c..1df173fd7 100644 --- a/src/node_pep_sql.erl +++ b/src/node_pep_sql.erl @@ -37,7 +37,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -86,8 +86,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> {result, _} -> {result, []} end. -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds). @@ -115,9 +116,9 @@ get_entity_subscriptions(_Host, Owner) -> SubKey = jid:tolower(Owner), GenKey = jid:remove_resource(SubKey), HostLike = node_flat_sql:encode_host_like(element(2, SubKey)), - SJ = node_flat_sql:encode_jid(SubKey), - GJ = node_flat_sql:encode_jid(GenKey), - GJLike = node_flat_sql:encode_jid_like(GenKey), + SJ = ejabberd_sql:escape(node_flat_sql:encode_jid(SubKey)), + GJ = ejabberd_sql:escape(node_flat_sql:encode_jid(GenKey)), + GJLike = ejabberd_sql:escape(node_flat_sql:encode_jid_like(GenKey)), Query = case SubKey of GenKey -> [<<"select host, node, type, i.nodeid, jid, " @@ -151,9 +152,9 @@ get_entity_subscriptions_for_send_last(_Host, Owner) -> SubKey = jid:tolower(Owner), GenKey = jid:remove_resource(SubKey), HostLike = node_flat_sql:encode_host_like(element(2, SubKey)), - SJ = node_flat_sql:encode_jid(SubKey), - GJ = node_flat_sql:encode_jid(GenKey), - GJLike = node_flat_sql:encode_jid_like(GenKey), + SJ = ejabberd_sql:escape(node_flat_sql:encode_jid(SubKey)), + GJ = ejabberd_sql:escape(node_flat_sql:encode_jid(GenKey)), + GJLike = ejabberd_sql:escape(node_flat_sql:encode_jid_like(GenKey)), Query = case SubKey of GenKey -> [<<"select host, node, type, i.nodeid, jid, " diff --git a/src/node_private.erl b/src/node_private.erl index 79dc5ed81..0cd04b9dd 100644 --- a/src/node_private.erl +++ b/src/node_private.erl @@ -33,7 +33,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/node_public.erl b/src/node_public.erl index cdb8abca3..0786d9995 100644 --- a/src/node_public.erl +++ b/src/node_public.erl @@ -33,7 +33,7 @@ -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, + publish_item/7, delete_item/4, remove_extra_items/3, get_entity_affiliations/2, get_node_affiliations/1, get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, @@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, + Payload, PubOpts). remove_extra_items(Nidx, MaxItems, ItemIds) -> node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl index 17ae91b52..1ad4046cd 100644 --- a/src/nodetree_tree_sql.erl +++ b/src/nodetree_tree_sql.erl @@ -37,8 +37,11 @@ -behaviour(gen_pubsub_nodetree). -author('christophe.romain@process-one.net'). +-compile([{parse_transform, ejabberd_sql_pt}]). + -include("pubsub.hrl"). -include("jlib.hrl"). +-include("ejabberd_sql_pt.hrl"). -export([init/3, terminate/2, options/0, set_node/1, get_node/3, get_node/2, get_node/1, get_nodes/2, @@ -65,27 +68,27 @@ set_node(Record) when is_record(Record, pubsub_node) -> end, Type = Record#pubsub_node.type, H = node_flat_sql:encode_host(Host), - N = ejabberd_sql:escape(Node), - P = ejabberd_sql:escape(Parent), Nidx = case nodeidx(Host, Node) of {result, OldNidx} -> catch - ejabberd_sql:sql_query_t([<<"delete from pubsub_node_option where " - "nodeid='">>, OldNidx, <<"';">>]), + ejabberd_sql:sql_query_t( + ?SQL("delete from pubsub_node_option where " + "nodeid=%(OldNidx)d")), catch - ejabberd_sql:sql_query_t([<<"update pubsub_node set host='">>, - H, <<"' node='">>, N, - <<"' parent='">>, P, - <<"' type='">>, Type, - <<"' where nodeid='">>, - OldNidx, <<"';">>]), + ejabberd_sql:sql_query_t( + ?SQL("update pubsub_node set" + " host=%(H)s" + " node=%(Node)s" + " parent=%(Parent)s" + " type=%(Type)s " + "where nodeid=%(OldNidx)d")), OldNidx; _ -> catch - ejabberd_sql:sql_query_t([<<"insert into pubsub_node(host, node, " - "parent, type) values('">>, - H, <<"', '">>, N, <<"', '">>, P, - <<"', '">>, Type, <<"');">>]), + ejabberd_sql:sql_query_t( + ?SQL("insert into pubsub_node(host, node, " + "parent, type) values(" + "%(H)s, %(Node)s, %(Parent)s, %(Type)s)")), case nodeidx(Host, Node) of {result, NewNidx} -> NewNidx; _ -> none % this should not happen @@ -98,14 +101,12 @@ set_node(Record) when is_record(Record, pubsub_node) -> _ -> lists:foreach(fun ({Key, Value}) -> SKey = iolist_to_binary(atom_to_list(Key)), - SValue = ejabberd_sql:escape( - list_to_binary( - lists:flatten(io_lib:fwrite("~p", [Value])))), + SValue = jlib:term_to_expr(Value), catch - ejabberd_sql:sql_query_t([<<"insert into pubsub_node_option(nodeid, " - "name, val) values('">>, - Nidx, <<"', '">>, - SKey, <<"', '">>, SValue, <<"');">>]) + ejabberd_sql:sql_query_t( + ?SQL("insert into pubsub_node_option(nodeid, " + "name, val) values (" + "%(Nidx)d, %(SKey)s, %(SValue)s)")) end, Record#pubsub_node.options), {result, Nidx} @@ -116,14 +117,12 @@ get_node(Host, Node, _From) -> get_node(Host, Node) -> H = node_flat_sql:encode_host(Host), - N = ejabberd_sql:escape(Node), case catch - ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from " - "pubsub_node where host='">>, - H, <<"' and node='">>, N, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from " + "pubsub_node where host=%(H)s and node=%(Node)s")) of - {selected, - [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], [RItem]} -> + {selected, [RItem]} -> raw_to_node(Host, RItem); {'EXIT', _Reason} -> {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}; @@ -133,13 +132,12 @@ get_node(Host, Node) -> get_node(Nidx) -> case catch - ejabberd_sql:sql_query_t([<<"select host, node, parent, type from " - "pubsub_node where nodeid='">>, - Nidx, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(host)s, @(node)s, @(parent)s, @(type)s from " + "pubsub_node where nodeid=%(Nidx)d")) of - {selected, - [<<"host">>, <<"node">>, <<"parent">>, <<"type">>], [[Host, Node, Parent, Type]]} -> - raw_to_node(Host, [Node, Parent, Type, Nidx]); + {selected, [{Host, Node, Parent, Type}]} -> + raw_to_node(Host, {Node, Parent, Type, Nidx}); {'EXIT', _Reason} -> {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}; _ -> @@ -152,11 +150,11 @@ get_nodes(Host, _From) -> get_nodes(Host) -> H = node_flat_sql:encode_host(Host), case catch - ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from " - "pubsub_node where host='">>, H, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from " + "pubsub_node where host=%(H)s")) of - {selected, - [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} -> + {selected, RItems} -> [raw_to_node(Host, Item) || Item <- RItems]; _ -> [] @@ -178,14 +176,12 @@ get_subnodes(Host, Node, _From) -> get_subnodes(Host, Node) -> H = node_flat_sql:encode_host(Host), - N = ejabberd_sql:escape(Node), case catch - ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from " - "pubsub_node where host='">>, - H, <<"' and parent='">>, N, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from " + "pubsub_node where host=%(H)s and parent=%(Node)s")) of - {selected, - [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} -> + {selected, RItems} -> [raw_to_node(Host, Item) || Item <- RItems]; _ -> [] @@ -196,14 +192,14 @@ get_subnodes_tree(Host, Node, _From) -> get_subnodes_tree(Host, Node) -> H = node_flat_sql:encode_host(Host), - N = ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(Node)), + N = <<(ejabberd_sql:escape_like_arg_circumflex(Node))/binary, "%">>, case catch - ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from " - "pubsub_node where host='">>, - H, <<"' and node like '">>, N, <<"%' escape '^';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from " + "pubsub_node where host=%(H)s" + " and node like %(N)s escape '^'")) of - {selected, - [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} -> + {selected, RItems} -> [raw_to_node(Host, Item) || Item <- RItems]; _ -> [] @@ -256,20 +252,24 @@ create_node(Host, Node, Type, Owner, Options, Parents) -> delete_node(Host, Node) -> H = node_flat_sql:encode_host(Host), - N = ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(Node)), + N = <<(ejabberd_sql:escape_like_arg_circumflex(Node))/binary, "%">>, Removed = get_subnodes_tree(Host, Node), - catch ejabberd_sql:sql_query_t([<<"delete from pubsub_node where host='">>, - H, <<"' and node like '">>, N, <<"%' escape '^';">>]), + catch ejabberd_sql:sql_query_t( + ?SQL("delete from pubsub_node where host=%(H)s" + " and node like %(N)s escape '^'")), Removed. %% helpers raw_to_node(Host, [Node, Parent, Type, Nidx]) -> + raw_to_node(Host, {Node, Parent, Type, binary_to_integer(Nidx)}); +raw_to_node(Host, {Node, Parent, Type, Nidx}) -> Options = case catch - ejabberd_sql:sql_query_t([<<"select name,val from pubsub_node_option " - "where nodeid='">>, Nidx, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(name)s, @(val)s from pubsub_node_option " + "where nodeid=%(Nidx)d")) of - {selected, [<<"name">>, <<"val">>], ROptions} -> - DbOpts = lists:map(fun ([Key, Value]) -> + {selected, ROptions} -> + DbOpts = lists:map(fun ({Key, Value}) -> RKey = jlib:binary_to_atom(Key), Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))), RValue = element(2, erl_parse:parse_term(Tokens)), @@ -295,13 +295,12 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) -> nodeidx(Host, Node) -> H = node_flat_sql:encode_host(Host), - N = ejabberd_sql:escape(Node), case catch - ejabberd_sql:sql_query_t([<<"select nodeid from pubsub_node where " - "host='">>, - H, <<"' and node='">>, N, <<"';">>]) + ejabberd_sql:sql_query_t( + ?SQL("select @(nodeid)d from pubsub_node where " + "host=%(H)s and node=%(Node)s")) of - {selected, [<<"nodeid">>], [[Nidx]]} -> + {selected, [{Nidx}]} -> {result, Nidx}; {'EXIT', _Reason} -> {error, db_fail}; diff --git a/src/sql_queries.erl b/src/sql_queries.erl index 121117574..2afa32ac8 100644 --- a/src/sql_queries.erl +++ b/src/sql_queries.erl @@ -273,9 +273,8 @@ users_number(LServer, [{prefix, Prefix}]) users_number(LServer, []) -> users_number(LServer). -add_spool_sql(Username, XML) -> - [<<"insert into spool(username, xml) values ('">>, - Username, <<"', '">>, XML, <<"');">>]. +add_spool_sql(LUser, XML) -> + ?SQL("insert into spool(username, xml) values (%(LUser)s, %(XML)s)"). add_spool(LServer, Queries) -> ejabberd_sql:sql_transaction(LServer, Queries). |