mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +01:00
Merge SQL and Mnesia code into one module (EJAB-1560)
This commit is contained in:
parent
453e249de3
commit
437f68a9f3
10
src/acl.erl
10
src/acl.erl
@ -241,9 +241,9 @@ is_glob_match(String, Glob) ->
|
||||
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
|
||||
|
||||
loaded_shared_roster_module(Host) ->
|
||||
case {gen_mod:is_loaded(Host, mod_shared_roster_odbc),
|
||||
gen_mod:is_loaded(Host, mod_shared_roster_ldap)} of
|
||||
{true, _} -> mod_shared_roster_odbc;
|
||||
{_, true} -> mod_shared_roster_ldap;
|
||||
_ -> mod_shared_roster
|
||||
case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
|
||||
true ->
|
||||
mod_shared_roster_ldap;
|
||||
false ->
|
||||
mod_shared_roster
|
||||
end.
|
||||
|
@ -78,16 +78,12 @@
|
||||
mod_irc,
|
||||
mod_irc_connection,
|
||||
mod_last,
|
||||
mod_last_odbc,
|
||||
mod_muc,
|
||||
mod_muc_log,
|
||||
mod_muc_room,
|
||||
mod_offline,
|
||||
mod_offline_odbc,
|
||||
mod_privacy,
|
||||
mod_privacy_odbc,
|
||||
mod_private,
|
||||
mod_private_odbc,
|
||||
mod_proxy65,
|
||||
mod_proxy65_lib,
|
||||
mod_proxy65_service,
|
||||
@ -96,14 +92,12 @@
|
||||
mod_pubsub,
|
||||
mod_register,
|
||||
mod_roster,
|
||||
mod_roster_odbc,
|
||||
mod_service_log,
|
||||
mod_shared_roster,
|
||||
mod_stats,
|
||||
mod_time,
|
||||
mod_vcard,
|
||||
mod_vcard_ldap,
|
||||
mod_vcard_odbc,
|
||||
mod_version,
|
||||
node_buddy,
|
||||
node_club,
|
||||
|
@ -373,13 +373,16 @@ import_dir(Path) ->
|
||||
%%%
|
||||
|
||||
delete_expired_messages() ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(),
|
||||
ok.
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(Host)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
delete_old_messages(Days) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days),
|
||||
ok.
|
||||
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days, Host)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
%%%
|
||||
%%% Mnesia management
|
||||
|
@ -293,27 +293,20 @@ get_last_access(User, Server) ->
|
||||
get_last_info(User, Server) ->
|
||||
case get_mod_last_enabled(Server) of
|
||||
mod_last -> mod_last:get_last_info(User, Server);
|
||||
mod_last_odbc -> mod_last_odbc:get_last_info(User, Server);
|
||||
no_mod_last -> mod_last_required
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> mod_last | mod_last_odbc | no_mod_last
|
||||
%% @spec (Server) -> mod_last | no_mod_last
|
||||
get_mod_last_enabled(Server) ->
|
||||
ML = gen_mod:is_loaded(Server, mod_last),
|
||||
MLO = gen_mod:is_loaded(Server, mod_last_odbc),
|
||||
case {ML, MLO} of
|
||||
{true, _} -> mod_last;
|
||||
{false, true} -> mod_last_odbc;
|
||||
{false, false} -> no_mod_last
|
||||
case gen_mod:is_loaded(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
get_mod_last_configured(Server) ->
|
||||
ML = is_configured(Server, mod_last),
|
||||
MLO = is_configured(Server, mod_last_odbc),
|
||||
case {ML, MLO} of
|
||||
{true, _} -> mod_last;
|
||||
{false, true} -> mod_last_odbc;
|
||||
{false, false} -> no_mod_last
|
||||
case is_configured(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
is_configured(Host, Module) ->
|
||||
|
@ -467,6 +467,8 @@ process_host_term(Term, Host, State) ->
|
||||
State;
|
||||
{odbc_server, ODBC_server} ->
|
||||
add_option({odbc_server, Host}, ODBC_server, State);
|
||||
{modules, Modules} ->
|
||||
add_option({modules, Host}, replace_modules(Modules), State);
|
||||
{Opt, Val} ->
|
||||
add_option({Opt, Host}, Val, State)
|
||||
end.
|
||||
@ -610,3 +612,30 @@ is_file_readable(Path) ->
|
||||
{error, _Reason} ->
|
||||
false
|
||||
end.
|
||||
|
||||
replace_module(mod_announce_odbc) -> {mod_announce, odbc};
|
||||
replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
|
||||
replace_module(mod_irc_odbc) -> {mod_irc, odbc};
|
||||
replace_module(mod_last_odbc) -> {mod_last, odbc};
|
||||
replace_module(mod_muc_odbc) -> {mod_muc, odbc};
|
||||
replace_module(mod_offline_odbc) -> {mod_offline, odbc};
|
||||
replace_module(mod_privacy_odbc) -> {mod_privacy, odbc};
|
||||
replace_module(mod_private_odbc) -> {mod_private, odbc};
|
||||
replace_module(mod_roster_odbc) -> {mod_roster, odbc};
|
||||
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
|
||||
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
|
||||
replace_module(Module) -> Module.
|
||||
|
||||
replace_modules(Modules) ->
|
||||
lists:map(
|
||||
fun({Module, Opts}) ->
|
||||
case replace_module(Module) of
|
||||
{NewModule, DBType} ->
|
||||
NewOpts = [{db_type, DBType} |
|
||||
lists:keydelete(db_type, 1, Opts)],
|
||||
{NewModule, NewOpts};
|
||||
NewModule ->
|
||||
{NewModule, Opts}
|
||||
end
|
||||
end, Modules).
|
||||
|
@ -289,9 +289,10 @@ create_user(User,Password,Domain) ->
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
io:format("Trying to add/update roster list...",[]),
|
||||
case loaded_module(Domain,[mod_roster_odbc,mod_roster]) of
|
||||
{ok, M} ->
|
||||
case M:set_items(User, Domain, exmpp_xml:xmlel_to_xmlelement(El)) of
|
||||
case loaded_module(Domain, mod_roster) of
|
||||
{ok, _DBType} ->
|
||||
case mod_roster:set_items(User, Domain,
|
||||
exmpp_xml:xmlel_to_xmlelement(El)) of
|
||||
{atomic, ok} ->
|
||||
io:format(" DONE.~n",[]),
|
||||
ok;
|
||||
@ -302,7 +303,7 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
{error, not_found}
|
||||
end;
|
||||
E -> io:format(" ERROR: ~p~n",[E]),
|
||||
?ERROR_MSG("No modules loaded [mod_roster, mod_roster_odbc] ~s ~n",
|
||||
?ERROR_MSG("No modules loaded [mod_roster] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@ -330,10 +331,10 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
io:format("Trying to add/update vCards...",[]),
|
||||
case loaded_module(Domain,[mod_vcard,mod_vcard_odbc]) of
|
||||
{ok, M} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
|
||||
case loaded_module(Domain, mod_vcard) of
|
||||
{ok, _} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
|
||||
IQ = iq_to_old_iq(#iq{type = set, payload = El}),
|
||||
case M:process_sm_iq(FullUser, FullUser , IQ) of
|
||||
case mod_vcard:process_sm_iq(FullUser, FullUser , IQ) of
|
||||
{error,_Err} ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error processing vcard ~s : ~p ~n",
|
||||
@ -343,7 +344,7 @@ populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
end;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_vcard, mod_vcard_odbc] ~s ~n",
|
||||
?ERROR_MSG("No modules loaded [mod_vcard] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@ -356,8 +357,8 @@ populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
|
||||
io:format("Trying to add/update offline-messages...",[]),
|
||||
case loaded_module(Domain, [mod_offline, mod_offline_odbc]) of
|
||||
{ok, M} ->
|
||||
case loaded_module(Domain, mod_offline) of
|
||||
{ok, _DBType} ->
|
||||
ok = exmpp_xml:foreach(
|
||||
fun (_Element, {xmlcdata, _}) ->
|
||||
ok;
|
||||
@ -367,11 +368,11 @@ populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
|
||||
FullUser = jid_to_old_jid(exmpp_jid:make(User,
|
||||
Domain)),
|
||||
OldChild = exmpp_xml:xmlel_to_xmlelement(Child),
|
||||
_R = M:store_packet(FullFrom, FullUser, OldChild)
|
||||
_R = mod_offline:store_packet(FullFrom, FullUser, OldChild)
|
||||
end, El), io:format(" DONE.~n",[]);
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_offline, mod_offline_odbc] ~s ~n",
|
||||
?ERROR_MSG("No modules loaded [mod_offline] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@ -384,15 +385,15 @@ populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
|
||||
io:format("Trying to add/update private storage...",[]),
|
||||
case loaded_module(Domain,[mod_private_odbc,mod_private]) of
|
||||
{ok, M} ->
|
||||
case loaded_module(Domain, mod_private) of
|
||||
{ok, _DBType} ->
|
||||
FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
|
||||
IQ = iq_to_old_iq(#iq{type = set,
|
||||
ns = 'jabber:iq:private',
|
||||
kind = request,
|
||||
iq_ns = 'jabberd:client',
|
||||
payload = El}),
|
||||
case M:process_sm_iq(FullUser, FullUser, IQ ) of
|
||||
case mod_private:process_sm_iq(FullUser, FullUser, IQ ) of
|
||||
{error, _Err} ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error processing private storage ~s : ~p ~n",
|
||||
@ -401,7 +402,7 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
|
||||
end;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_private, mod_private_odbc] ~s ~n",
|
||||
?ERROR_MSG("No modules loaded [mod_private] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@ -415,13 +416,12 @@ populate_user(_User, _Domain, _El) ->
|
||||
%%%==================================
|
||||
%%%% Utilities
|
||||
|
||||
loaded_module(Domain,Options) ->
|
||||
LoadedModules = gen_mod:loaded_modules(Domain),
|
||||
case lists:filter(fun(Module) ->
|
||||
lists:member(Module, LoadedModules)
|
||||
end, Options) of
|
||||
[M|_] -> {ok, M};
|
||||
[] -> {error,not_found}
|
||||
loaded_module(Domain, Module) ->
|
||||
case gen_mod:is_loaded(Domain, Module) of
|
||||
true ->
|
||||
{ok, gen_mod:db_type(Domain, Module)};
|
||||
false ->
|
||||
{error, not_found}
|
||||
end.
|
||||
|
||||
jid_to_old_jid(Jid) ->
|
||||
@ -574,13 +574,13 @@ build_password_string(Password) when is_list(Password) ->
|
||||
|
||||
%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
|
||||
extract_user_info(roster, Username, Host) ->
|
||||
case loaded_module(Host,[mod_roster_odbc,mod_roster]) of
|
||||
{ok, M} ->
|
||||
case loaded_module(Host, mod_roster) of
|
||||
{ok, _DBType} ->
|
||||
From = To = jlib:make_jid(Username, Host, ""),
|
||||
SubelGet = {xmlelement, "query", [{"xmlns",?NS_ROSTER}], []},
|
||||
%%IQGet = #iq{type=get, xmlns=?NS_ROSTER, payload=SubelGet}, % this is for 3.0.0 version
|
||||
IQGet = {iq, "", get, ?NS_ROSTER, "" , SubelGet},
|
||||
Res = M:process_local_iq(From, To, IQGet),
|
||||
Res = mod_roster:process_local_iq(From, To, IQGet),
|
||||
%%[El] = Res#iq.payload, % this is for 3.0.0 version
|
||||
{iq, _, result, _, _, Els} = Res,
|
||||
case Els of
|
||||
@ -592,8 +592,8 @@ extract_user_info(roster, Username, Host) ->
|
||||
end;
|
||||
|
||||
extract_user_info(offline, Username, Host) ->
|
||||
case loaded_module(Host,[mod_offline,mod_offline_odbc]) of
|
||||
{ok, mod_offline} ->
|
||||
case loaded_module(Host, mod_offline) of
|
||||
{ok, mnesia} ->
|
||||
Els = mnesia_pop_offline_messages([], Username, Host),
|
||||
case Els of
|
||||
[] -> "";
|
||||
@ -601,30 +601,30 @@ extract_user_info(offline, Username, Host) ->
|
||||
OfEl = {xmlelement, "offline-messages", [], Els},
|
||||
exmpp_xml:document_to_list(OfEl)
|
||||
end;
|
||||
{ok, mod_offline_odbc} ->
|
||||
{ok, odbc} ->
|
||||
"";
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(private, Username, Host) ->
|
||||
case loaded_module(Host,[mod_private,mod_private_odbc]) of
|
||||
{ok, mod_private} ->
|
||||
case loaded_module(Host, mod_private) of
|
||||
{ok, mnesia} ->
|
||||
get_user_private_mnesia(Username, Host);
|
||||
{ok, mod_private_odbc} ->
|
||||
{ok, odbc} ->
|
||||
"";
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(vcard, Username, Host) ->
|
||||
case loaded_module(Host,[mod_vcard, mod_vcard_odbc, mod_vcard_odbc]) of
|
||||
{ok, M} ->
|
||||
case loaded_module(Host, mod_vcard) of
|
||||
{ok, _DBType} ->
|
||||
From = To = jlib:make_jid(Username, Host, ""),
|
||||
SubelGet = {xmlelement, "vCard", [{"xmlns",?NS_VCARD}], []},
|
||||
%%IQGet = #iq{type=get, xmlns=?NS_VCARD, payload=SubelGet}, % this is for 3.0.0 version
|
||||
IQGet = {iq, "", get, ?NS_VCARD, "" , SubelGet},
|
||||
Res = M:process_sm_iq(From, To, IQGet),
|
||||
Res = mod_vcard:process_sm_iq(From, To, IQGet),
|
||||
%%[El] = Res#iq.payload, % this is for 3.0.0 version
|
||||
{iq, _, result, _, _, Els} = Res,
|
||||
case Els of
|
||||
|
@ -383,7 +383,7 @@ export_privacy(Server, Output) ->
|
||||
fun({Name, List}) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
RItems = lists:map(
|
||||
fun mod_privacy_odbc:item_to_raw/1,
|
||||
fun mod_privacy:item_to_raw/1,
|
||||
List),
|
||||
ID = integer_to_list(get_id()),
|
||||
["delete from privacy_list "
|
||||
|
@ -34,6 +34,8 @@
|
||||
get_opt/2,
|
||||
get_opt/3,
|
||||
get_opt_host/3,
|
||||
db_type/1,
|
||||
db_type/2,
|
||||
get_module_opt/4,
|
||||
get_module_opt_host/3,
|
||||
loaded_modules/1,
|
||||
@ -192,6 +194,18 @@ get_opt_host(Host, Opts, Default) ->
|
||||
Val = get_opt(host, Opts, Default),
|
||||
ejabberd_regexp:greplace(Val, "@HOST@", Host).
|
||||
|
||||
db_type(Opts) ->
|
||||
case get_opt(db_type, Opts, mnesia) of
|
||||
odbc -> odbc;
|
||||
_ -> mnesia
|
||||
end.
|
||||
|
||||
db_type(Host, Module) ->
|
||||
case get_module_opt(Host, Module, db_type, mnesia) of
|
||||
odbc -> odbc;
|
||||
_ -> mnesia
|
||||
end.
|
||||
|
||||
loaded_modules(Host) ->
|
||||
ets:select(ejabberd_modules,
|
||||
[{#ejabberd_module{_ = '_', module_host = {'$1', Host}},
|
||||
|
@ -116,54 +116,28 @@ xdb_data(_User, _Server, {xmlcdata, _CData}) ->
|
||||
ok;
|
||||
xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) ->
|
||||
From = jlib:make_jid(User, Server, ""),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_AUTH ->
|
||||
Password = xml:get_tag_cdata(El),
|
||||
ejabberd_auth:set_password(User, Server, Password),
|
||||
ok;
|
||||
?NS_ROSTER ->
|
||||
case lists:member(mod_roster_odbc,
|
||||
gen_mod:loaded_modules(LServer)) of
|
||||
true ->
|
||||
catch mod_roster_odbc:set_items(User, Server, El);
|
||||
false ->
|
||||
catch mod_roster:set_items(User, Server, El)
|
||||
end,
|
||||
catch mod_roster:set_items(User, Server, El),
|
||||
ok;
|
||||
?NS_LAST ->
|
||||
TimeStamp = xml:get_attr_s("last", Attrs),
|
||||
Status = xml:get_tag_cdata(El),
|
||||
case lists:member(mod_last_odbc,
|
||||
gen_mod:loaded_modules(LServer)) of
|
||||
true ->
|
||||
catch mod_last_odbc:store_last_info(
|
||||
User,
|
||||
Server,
|
||||
list_to_integer(TimeStamp),
|
||||
Status);
|
||||
false ->
|
||||
catch mod_last:store_last_info(
|
||||
User,
|
||||
Server,
|
||||
list_to_integer(TimeStamp),
|
||||
Status)
|
||||
end,
|
||||
catch mod_last:store_last_info(
|
||||
User,
|
||||
Server,
|
||||
list_to_integer(TimeStamp),
|
||||
Status),
|
||||
ok;
|
||||
?NS_VCARD ->
|
||||
case lists:member(mod_vcard_odbc,
|
||||
gen_mod:loaded_modules(LServer)) of
|
||||
true ->
|
||||
catch mod_vcard_odbc:process_sm_iq(
|
||||
From,
|
||||
jlib:make_jid("", Server, ""),
|
||||
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El});
|
||||
false ->
|
||||
catch mod_vcard:process_sm_iq(
|
||||
From,
|
||||
jlib:make_jid("", Server, ""),
|
||||
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El})
|
||||
end,
|
||||
catch mod_vcard:process_sm_iq(
|
||||
From,
|
||||
jlib:make_jid("", Server, ""),
|
||||
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El}),
|
||||
ok;
|
||||
"jabber:x:offline" ->
|
||||
process_offline(Server, From, El),
|
||||
|
@ -56,12 +56,21 @@
|
||||
-define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
|
||||
tokenize(Node) -> string:tokens(Node, "/#").
|
||||
|
||||
start(Host, _Opts) ->
|
||||
mnesia:create_table(motd, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, motd)}]),
|
||||
mnesia:create_table(motd_users, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, motd_users)}]),
|
||||
update_tables(),
|
||||
start(Host, Opts) ->
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(motd,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, motd)}]),
|
||||
mnesia:create_table(motd_users,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, motd_users)}]),
|
||||
update_tables();
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, Host,
|
||||
?MODULE, announce, 50),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
|
||||
@ -743,16 +752,33 @@ announce_all_hosts_motd(From, To, Packet) ->
|
||||
end.
|
||||
|
||||
announce_motd(Host, Packet) ->
|
||||
announce_motd_update(Host, Packet),
|
||||
Sessions = ejabberd_sm:get_vh_session_list(Host),
|
||||
announce_online1(Sessions, Host, Packet),
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, S, _R}) ->
|
||||
mnesia:write(#motd_users{us = {U, S}})
|
||||
end, Sessions)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
LServer = jlib:nameprep(Host),
|
||||
announce_motd_update(LServer, Packet),
|
||||
Sessions = ejabberd_sm:get_vh_session_list(LServer),
|
||||
announce_online1(Sessions, LServer, Packet),
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, S, _R}) ->
|
||||
mnesia:write(#motd_users{us = {U, S}})
|
||||
end, Sessions)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
odbc ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, _S, _R}) ->
|
||||
Username = ejabberd_odbc:escape(U),
|
||||
odbc_queries:update_t(
|
||||
"motd",
|
||||
["username", "xml"],
|
||||
[Username, ""],
|
||||
["username='", Username, "'"])
|
||||
end, Sessions)
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F)
|
||||
end.
|
||||
|
||||
announce_motd_update(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
@ -778,10 +804,23 @@ announce_all_hosts_motd_update(From, To, Packet) ->
|
||||
|
||||
announce_motd_update(LServer, Packet) ->
|
||||
announce_motd_delete(LServer),
|
||||
F = fun() ->
|
||||
mnesia:write(#motd{server = LServer, packet = Packet})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia ->
|
||||
F = fun() ->
|
||||
mnesia:write(#motd{server = LServer, packet = Packet})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
odbc ->
|
||||
XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"motd",
|
||||
["username", "xml"],
|
||||
["", XML],
|
||||
["username=''"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F)
|
||||
end.
|
||||
|
||||
announce_motd_delete(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
@ -806,21 +845,32 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
|
||||
end.
|
||||
|
||||
announce_motd_delete(LServer) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({motd, LServer}),
|
||||
mnesia:write_lock_table(motd_users),
|
||||
Users = mnesia:select(
|
||||
motd_users,
|
||||
[{#motd_users{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]),
|
||||
lists:foreach(fun(US) ->
|
||||
mnesia:delete({motd_users, US})
|
||||
end, Users)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia ->
|
||||
F = fun() ->
|
||||
mnesia:delete({motd, LServer}),
|
||||
mnesia:write_lock_table(motd_users),
|
||||
Users = mnesia:select(
|
||||
motd_users,
|
||||
[{#motd_users{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]),
|
||||
lists:foreach(fun(US) ->
|
||||
mnesia:delete({motd_users, US})
|
||||
end, Users)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
odbc ->
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t(["delete from motd;"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F)
|
||||
end.
|
||||
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
|
||||
send_motd(JID) ->
|
||||
send_motd(JID, gen_mod:db_type(JID#jid.lserver, ?MODULE)).
|
||||
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
|
||||
case catch mnesia:dirty_read({motd, LServer}) of
|
||||
[#motd{packet = Packet}] ->
|
||||
US = {LUser, LServer},
|
||||
@ -837,15 +887,69 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
end;
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= "" ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select xml from motd where username='';"]) of
|
||||
{selected, ["xml"], [{XML}]} ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
ok;
|
||||
Packet ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select username from motd "
|
||||
"where username='", Username, "';"]) of
|
||||
{selected, ["username"], []} ->
|
||||
Local = jlib:make_jid("", LServer, ""),
|
||||
ejabberd_router:route(Local, JID, Packet),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"motd",
|
||||
["username", "xml"],
|
||||
[Username, ""],
|
||||
["username='", Username, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
send_motd(_, odbc) ->
|
||||
ok.
|
||||
|
||||
get_stored_motd(LServer) ->
|
||||
case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of
|
||||
{ok, Packet} ->
|
||||
{xml:get_subtag_cdata(Packet, "subject"),
|
||||
xml:get_subtag_cdata(Packet, "body")};
|
||||
error ->
|
||||
{"", ""}
|
||||
end.
|
||||
|
||||
get_stored_motd_packet(LServer, mnesia) ->
|
||||
case catch mnesia:dirty_read({motd, LServer}) of
|
||||
[#motd{packet = Packet}] ->
|
||||
{xml:get_subtag_cdata(Packet, "subject"),
|
||||
xml:get_subtag_cdata(Packet, "body")};
|
||||
{ok, Packet};
|
||||
_ ->
|
||||
{"", ""}
|
||||
error
|
||||
end;
|
||||
get_stored_motd_packet(LServer, odbc) ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select xml from motd where username='';"]) of
|
||||
{selected, ["xml"], [{XML}]} ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
error;
|
||||
Packet ->
|
||||
{ok, Packet}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
%% This function is similar to others, but doesn't perform any ACL verification
|
||||
|
@ -1,885 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_announce_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Manage announce messages
|
||||
%%% Created : 11 Aug 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% Implements a small subset of XEP-0133: Service Administration
|
||||
%%% Version 1.1 (2005-08-19)
|
||||
|
||||
-module(mod_announce_odbc).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
init/0,
|
||||
stop/1,
|
||||
announce/3,
|
||||
send_motd/1,
|
||||
disco_identity/5,
|
||||
disco_features/5,
|
||||
disco_items/5,
|
||||
send_announcement_to_all/3,
|
||||
announce_commands/4,
|
||||
announce_items/4]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("adhoc.hrl").
|
||||
|
||||
-define(PROCNAME, ejabberd_announce).
|
||||
|
||||
-define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
|
||||
tokenize(Node) -> string:tokens(Node, "/#").
|
||||
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, Host,
|
||||
?MODULE, announce, 50),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 50),
|
||||
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, announce_items, 50),
|
||||
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, announce_commands, 50),
|
||||
ejabberd_hooks:add(user_available_hook, Host,
|
||||
?MODULE, send_motd, 50),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
proc_lib:spawn(?MODULE, init, [])).
|
||||
|
||||
init() ->
|
||||
loop().
|
||||
|
||||
loop() ->
|
||||
receive
|
||||
{announce_all, From, To, Packet} ->
|
||||
announce_all(From, To, Packet),
|
||||
loop();
|
||||
{announce_all_hosts_all, From, To, Packet} ->
|
||||
announce_all_hosts_all(From, To, Packet),
|
||||
loop();
|
||||
{announce_online, From, To, Packet} ->
|
||||
announce_online(From, To, Packet),
|
||||
loop();
|
||||
{announce_all_hosts_online, From, To, Packet} ->
|
||||
announce_all_hosts_online(From, To, Packet),
|
||||
loop();
|
||||
{announce_motd, From, To, Packet} ->
|
||||
announce_motd(From, To, Packet),
|
||||
loop();
|
||||
{announce_all_hosts_motd, From, To, Packet} ->
|
||||
announce_all_hosts_motd(From, To, Packet),
|
||||
loop();
|
||||
{announce_motd_update, From, To, Packet} ->
|
||||
announce_motd_update(From, To, Packet),
|
||||
loop();
|
||||
{announce_all_hosts_motd_update, From, To, Packet} ->
|
||||
announce_all_hosts_motd_update(From, To, Packet),
|
||||
loop();
|
||||
{announce_motd_delete, From, To, Packet} ->
|
||||
announce_motd_delete(From, To, Packet),
|
||||
loop();
|
||||
{announce_all_hosts_motd_delete, From, To, Packet} ->
|
||||
announce_all_hosts_motd_delete(From, To, Packet),
|
||||
loop();
|
||||
_ ->
|
||||
loop()
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, announce_commands, 50),
|
||||
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, announce_items, 50),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 50),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 50),
|
||||
ejabberd_hooks:delete(local_send_to_resource_hook, Host,
|
||||
?MODULE, announce, 50),
|
||||
ejabberd_hooks:delete(user_available_hook, Host,
|
||||
?MODULE, send_motd, 50),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
exit(whereis(Proc), stop),
|
||||
{wait, Proc}.
|
||||
|
||||
%% Announcing via messages to a custom resource
|
||||
announce(From, To, Packet) ->
|
||||
case To of
|
||||
#jid{luser = "", lresource = Res} ->
|
||||
{xmlelement, Name, _Attrs, _Els} = Packet,
|
||||
Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME),
|
||||
case {Res, Name} of
|
||||
{"announce/all", "message"} ->
|
||||
Proc ! {announce_all, From, To, Packet},
|
||||
stop;
|
||||
{"announce/all-hosts/all", "message"} ->
|
||||
Proc ! {announce_all_hosts_all, From, To, Packet},
|
||||
stop;
|
||||
{"announce/online", "message"} ->
|
||||
Proc ! {announce_online, From, To, Packet},
|
||||
stop;
|
||||
{"announce/all-hosts/online", "message"} ->
|
||||
Proc ! {announce_all_hosts_online, From, To, Packet},
|
||||
stop;
|
||||
{"announce/motd", "message"} ->
|
||||
Proc ! {announce_motd, From, To, Packet},
|
||||
stop;
|
||||
{"announce/all-hosts/motd", "message"} ->
|
||||
Proc ! {announce_all_hosts_motd, From, To, Packet},
|
||||
stop;
|
||||
{"announce/motd/update", "message"} ->
|
||||
Proc ! {announce_motd_update, From, To, Packet},
|
||||
stop;
|
||||
{"announce/all-hosts/motd/update", "message"} ->
|
||||
Proc ! {announce_all_hosts_motd_update, From, To, Packet},
|
||||
stop;
|
||||
{"announce/motd/delete", "message"} ->
|
||||
Proc ! {announce_motd_delete, From, To, Packet},
|
||||
stop;
|
||||
{"announce/all-hosts/motd/delete", "message"} ->
|
||||
Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
|
||||
stop;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
%% Announcing via ad-hoc commands
|
||||
-define(INFO_COMMAND(Lang, Node),
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "automation"},
|
||||
{"type", "command-node"},
|
||||
{"name", get_title(Lang, Node)}], []}]).
|
||||
|
||||
disco_identity(Acc, _From, _To, Node, Lang) ->
|
||||
LNode = tokenize(Node),
|
||||
case LNode of
|
||||
?NS_ADMINL("announce") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("announce-allhosts") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("announce-all") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("announce-all-allhosts") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("set-motd") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("set-motd-allhosts") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("edit-motd") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("edit-motd-allhosts") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("delete-motd") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
?NS_ADMINL("delete-motd-allhosts") ->
|
||||
?INFO_COMMAND(Lang, Node);
|
||||
_ ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
-define(INFO_RESULT(Allow, Feats),
|
||||
case Allow of
|
||||
deny ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
allow ->
|
||||
{result, Feats}
|
||||
end).
|
||||
|
||||
disco_features(Acc, From, #jid{lserver = LServer} = _To,
|
||||
"announce", _Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false ->
|
||||
Acc;
|
||||
_ ->
|
||||
Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
|
||||
Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
case {acl:match_rule(LServer, Access1, From),
|
||||
acl:match_rule(global, Access2, From)} of
|
||||
{deny, deny} ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
_ ->
|
||||
{result, []}
|
||||
end
|
||||
end;
|
||||
|
||||
disco_features(Acc, From, #jid{lserver = LServer} = _To,
|
||||
Node, _Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false ->
|
||||
Acc;
|
||||
_ ->
|
||||
Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
|
||||
Allow = acl:match_rule(LServer, Access, From),
|
||||
AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
|
||||
case Node of
|
||||
?NS_ADMIN ++ "#announce" ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#announce-all" ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#set-motd" ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#edit-motd" ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#delete-motd" ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#announce-allhosts" ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#announce-all-allhosts" ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#set-motd-allhosts" ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#edit-motd-allhosts" ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
?NS_ADMIN ++ "#delete-motd-allhosts" ->
|
||||
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
end.
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
-define(NODE_TO_ITEM(Lang, Server, Node),
|
||||
{xmlelement, "item",
|
||||
[{"jid", Server},
|
||||
{"node", Node},
|
||||
{"name", get_title(Lang, Node)}],
|
||||
[]}).
|
||||
|
||||
-define(ITEMS_RESULT(Allow, Items),
|
||||
case Allow of
|
||||
deny ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
allow ->
|
||||
{result, Items}
|
||||
end).
|
||||
|
||||
disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To,
|
||||
"", Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false ->
|
||||
Acc;
|
||||
_ ->
|
||||
Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
|
||||
Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
case {acl:match_rule(LServer, Access1, From),
|
||||
acl:match_rule(global, Access2, From)} of
|
||||
{deny, deny} ->
|
||||
Acc;
|
||||
_ ->
|
||||
Items = case Acc of
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
Nodes = [?NODE_TO_ITEM(Lang, Server, "announce")],
|
||||
{result, Items ++ Nodes}
|
||||
end
|
||||
end;
|
||||
|
||||
disco_items(Acc, From, #jid{lserver = LServer} = To, "announce", Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false ->
|
||||
Acc;
|
||||
_ ->
|
||||
announce_items(Acc, From, To, Lang)
|
||||
end;
|
||||
|
||||
disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
case gen_mod:is_loaded(LServer, mod_adhoc) of
|
||||
false ->
|
||||
Acc;
|
||||
_ ->
|
||||
Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
|
||||
Allow = acl:match_rule(LServer, Access, From),
|
||||
AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
|
||||
case Node of
|
||||
?NS_ADMIN ++ "#announce" ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?NS_ADMIN ++ "#announce-all" ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?NS_ADMIN ++ "#set-motd" ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?NS_ADMIN ++ "#edit-motd" ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?NS_ADMIN ++ "#delete-motd" ->
|
||||
?ITEMS_RESULT(Allow, []);
|
||||
?NS_ADMIN ++ "#announce-allhosts" ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?NS_ADMIN ++ "#announce-all-allhosts" ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?NS_ADMIN ++ "#set-motd-allhosts" ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?NS_ADMIN ++ "#edit-motd-allhosts" ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
?NS_ADMIN ++ "#delete-motd-allhosts" ->
|
||||
?ITEMS_RESULT(AllowGlobal, []);
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
end.
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) ->
|
||||
Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
|
||||
Nodes1 = case acl:match_rule(LServer, Access1, From) of
|
||||
allow ->
|
||||
[?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce"),
|
||||
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all"),
|
||||
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd"),
|
||||
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd"),
|
||||
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd")];
|
||||
deny ->
|
||||
[]
|
||||
end,
|
||||
Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
Nodes2 = case acl:match_rule(global, Access2, From) of
|
||||
allow ->
|
||||
[?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-allhosts"),
|
||||
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all-allhosts"),
|
||||
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd-allhosts"),
|
||||
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd-allhosts"),
|
||||
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd-allhosts")];
|
||||
deny ->
|
||||
[]
|
||||
end,
|
||||
case {Nodes1, Nodes2} of
|
||||
{[], []} ->
|
||||
Acc;
|
||||
_ ->
|
||||
Items = case Acc of
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
{result, Items ++ Nodes1 ++ Nodes2}
|
||||
end.
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
commands_result(Allow, From, To, Request) ->
|
||||
case Allow of
|
||||
deny ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
allow ->
|
||||
announce_commands(From, To, Request)
|
||||
end.
|
||||
|
||||
|
||||
announce_commands(Acc, From, #jid{lserver = LServer} = To,
|
||||
#adhoc_request{ node = Node} = Request) ->
|
||||
LNode = tokenize(Node),
|
||||
F = fun() ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
Allow = acl:match_rule(global, Access, From),
|
||||
commands_result(Allow, From, To, Request)
|
||||
end,
|
||||
R = case LNode of
|
||||
?NS_ADMINL("announce-allhosts") -> F();
|
||||
?NS_ADMINL("announce-all-allhosts") -> F();
|
||||
?NS_ADMINL("set-motd-allhosts") -> F();
|
||||
?NS_ADMINL("edit-motd-allhosts") -> F();
|
||||
?NS_ADMINL("delete-motd-allhosts") -> F();
|
||||
_ ->
|
||||
Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
|
||||
Allow = acl:match_rule(LServer, Access, From),
|
||||
case LNode of
|
||||
?NS_ADMINL("announce") ->
|
||||
commands_result(Allow, From, To, Request);
|
||||
?NS_ADMINL("announce-all") ->
|
||||
commands_result(Allow, From, To, Request);
|
||||
?NS_ADMINL("set-motd") ->
|
||||
commands_result(Allow, From, To, Request);
|
||||
?NS_ADMINL("edit-motd") ->
|
||||
commands_result(Allow, From, To, Request);
|
||||
?NS_ADMINL("delete-motd") ->
|
||||
commands_result(Allow, From, To, Request);
|
||||
_ ->
|
||||
unknown
|
||||
end
|
||||
end,
|
||||
case R of
|
||||
unknown -> Acc;
|
||||
_ -> {stop, R}
|
||||
end.
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
announce_commands(From, To,
|
||||
#adhoc_request{lang = Lang,
|
||||
node = Node,
|
||||
action = Action,
|
||||
xdata = XData} = Request) ->
|
||||
%% If the "action" attribute is not present, it is
|
||||
%% understood as "execute". If there was no <actions/>
|
||||
%% element in the first response (which there isn't in our
|
||||
%% case), "execute" and "complete" are equivalent.
|
||||
ActionIsExecute = lists:member(Action,
|
||||
["", "execute", "complete"]),
|
||||
if Action == "cancel" ->
|
||||
%% User cancels request
|
||||
adhoc:produce_response(Request,
|
||||
#adhoc_response{status = canceled});
|
||||
XData == false, ActionIsExecute ->
|
||||
%% User requests form
|
||||
Elements = generate_adhoc_form(Lang, Node, To#jid.lserver),
|
||||
adhoc:produce_response(
|
||||
Request,
|
||||
#adhoc_response{status = executing,
|
||||
elements = [Elements]});
|
||||
XData /= false, ActionIsExecute ->
|
||||
%% User returns form.
|
||||
case jlib:parse_xdata_submit(XData) of
|
||||
invalid ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
Fields ->
|
||||
handle_adhoc_form(From, To, Request, Fields)
|
||||
end;
|
||||
true ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end.
|
||||
|
||||
-define(VVALUE(Val),
|
||||
{xmlelement, "value", [], [{xmlcdata, Val}]}).
|
||||
-define(TVFIELD(Type, Var, Val),
|
||||
{xmlelement, "field", [{"type", Type},
|
||||
{"var", Var}],
|
||||
vvaluel(Val)}).
|
||||
-define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)).
|
||||
|
||||
vvaluel(Val) ->
|
||||
case Val of
|
||||
"" -> [];
|
||||
_ -> [?VVALUE(Val)]
|
||||
end.
|
||||
|
||||
generate_adhoc_form(Lang, Node, ServerHost) ->
|
||||
LNode = tokenize(Node),
|
||||
{OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd"))
|
||||
or (LNode == ?NS_ADMINL("edit-motd-allhosts")) ->
|
||||
get_stored_motd(ServerHost);
|
||||
true ->
|
||||
{[], []}
|
||||
end,
|
||||
{xmlelement, "x",
|
||||
[{"xmlns", ?NS_XDATA},
|
||||
{"type", "form"}],
|
||||
[?HFIELD(),
|
||||
{xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}]
|
||||
++
|
||||
if (LNode == ?NS_ADMINL("delete-motd"))
|
||||
or (LNode == ?NS_ADMINL("delete-motd-allhosts")) ->
|
||||
[{xmlelement, "field",
|
||||
[{"var", "confirm"},
|
||||
{"type", "boolean"},
|
||||
{"label", translate:translate(Lang, "Really delete message of the day?")}],
|
||||
[{xmlelement, "value",
|
||||
[],
|
||||
[{xmlcdata, "true"}]}]}];
|
||||
true ->
|
||||
[{xmlelement, "field",
|
||||
[{"var", "subject"},
|
||||
{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "Subject")}],
|
||||
vvaluel(OldSubject)},
|
||||
{xmlelement, "field",
|
||||
[{"var", "body"},
|
||||
{"type", "text-multi"},
|
||||
{"label", translate:translate(Lang, "Message body")}],
|
||||
vvaluel(OldBody)}]
|
||||
end}.
|
||||
|
||||
join_lines([]) ->
|
||||
[];
|
||||
join_lines(Lines) ->
|
||||
join_lines(Lines, []).
|
||||
join_lines([Line|Lines], Acc) ->
|
||||
join_lines(Lines, ["\n",Line|Acc]);
|
||||
join_lines([], Acc) ->
|
||||
%% Remove last newline
|
||||
lists:flatten(lists:reverse(tl(Acc))).
|
||||
|
||||
handle_adhoc_form(From, #jid{lserver = LServer} = To,
|
||||
#adhoc_request{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID},
|
||||
Fields) ->
|
||||
Confirm = case lists:keysearch("confirm", 1, Fields) of
|
||||
{value, {"confirm", ["true"]}} ->
|
||||
true;
|
||||
{value, {"confirm", ["1"]}} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end,
|
||||
Subject = case lists:keysearch("subject", 1, Fields) of
|
||||
{value, {"subject", SubjectLines}} ->
|
||||
%% There really shouldn't be more than one
|
||||
%% subject line, but can we stop them?
|
||||
join_lines(SubjectLines);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
Body = case lists:keysearch("body", 1, Fields) of
|
||||
{value, {"body", BodyLines}} ->
|
||||
join_lines(BodyLines);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
Response = #adhoc_response{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID,
|
||||
status = completed},
|
||||
Packet = {xmlelement, "message", [{"type", "normal"}],
|
||||
if Subject /= [] ->
|
||||
[{xmlelement, "subject", [],
|
||||
[{xmlcdata, Subject}]}];
|
||||
true ->
|
||||
[]
|
||||
end ++
|
||||
if Body /= [] ->
|
||||
[{xmlelement, "body", [],
|
||||
[{xmlcdata, Body}]}];
|
||||
true ->
|
||||
[]
|
||||
end},
|
||||
|
||||
Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
|
||||
case {Node, Body} of
|
||||
{?NS_ADMIN ++ "#delete-motd", _} ->
|
||||
if Confirm ->
|
||||
Proc ! {announce_motd_delete, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
true ->
|
||||
adhoc:produce_response(Response)
|
||||
end;
|
||||
{?NS_ADMIN ++ "#delete-motd-allhosts", _} ->
|
||||
if Confirm ->
|
||||
Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
true ->
|
||||
adhoc:produce_response(Response)
|
||||
end;
|
||||
{_, []} ->
|
||||
%% An announce message with no body is definitely an operator error.
|
||||
%% Throw an error and give him/her a chance to send message again.
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(
|
||||
Lang,
|
||||
"No body provided for announce message")};
|
||||
%% Now send the packet to ?PROCNAME.
|
||||
%% We don't use direct announce_* functions because it
|
||||
%% leads to large delay in response and <iq/> queries processing
|
||||
{?NS_ADMIN ++ "#announce", _} ->
|
||||
Proc ! {announce_online, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
{?NS_ADMIN ++ "#announce-allhosts", _} ->
|
||||
Proc ! {announce_all_hosts_online, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
{?NS_ADMIN ++ "#announce-all", _} ->
|
||||
Proc ! {announce_all, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
{?NS_ADMIN ++ "#announce-all-allhosts", _} ->
|
||||
Proc ! {announce_all_hosts_all, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
{?NS_ADMIN ++ "#set-motd", _} ->
|
||||
Proc ! {announce_motd, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
{?NS_ADMIN ++ "#set-motd-allhosts", _} ->
|
||||
Proc ! {announce_all_hosts_motd, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
{?NS_ADMIN ++ "#edit-motd", _} ->
|
||||
Proc ! {announce_motd_update, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
{?NS_ADMIN ++ "#edit-motd-allhosts", _} ->
|
||||
Proc ! {announce_all_hosts_motd_update, From, To, Packet},
|
||||
adhoc:produce_response(Response);
|
||||
_ ->
|
||||
%% This can't happen, as we haven't registered any other
|
||||
%% command nodes.
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
get_title(Lang, "announce") ->
|
||||
translate:translate(Lang, "Announcements");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#announce-all") ->
|
||||
translate:translate(Lang, "Send announcement to all users");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#announce-all-allhosts") ->
|
||||
translate:translate(Lang, "Send announcement to all users on all hosts");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#announce") ->
|
||||
translate:translate(Lang, "Send announcement to all online users");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#announce-allhosts") ->
|
||||
translate:translate(Lang, "Send announcement to all online users on all hosts");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#set-motd") ->
|
||||
translate:translate(Lang, "Set message of the day and send to online users");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#set-motd-allhosts") ->
|
||||
translate:translate(Lang, "Set message of the day on all hosts and send to online users");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#edit-motd") ->
|
||||
translate:translate(Lang, "Update message of the day (don't send)");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#edit-motd-allhosts") ->
|
||||
translate:translate(Lang, "Update message of the day on all hosts (don't send)");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#delete-motd") ->
|
||||
translate:translate(Lang, "Delete message of the day");
|
||||
get_title(Lang, ?NS_ADMIN ++ "#delete-motd-allhosts") ->
|
||||
translate:translate(Lang, "Delete message of the day on all hosts").
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
|
||||
announce_all(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Local = jlib:make_jid("", To#jid.server, ""),
|
||||
lists:foreach(
|
||||
fun({User, Server}) ->
|
||||
Dest = jlib:make_jid(User, Server, ""),
|
||||
ejabberd_router:route(Local, Dest, Packet)
|
||||
end, ejabberd_auth:get_vh_registered_users(Host))
|
||||
end.
|
||||
|
||||
announce_all_hosts_all(From, To, Packet) ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Local = jlib:make_jid("", To#jid.server, ""),
|
||||
lists:foreach(
|
||||
fun({User, Server}) ->
|
||||
Dest = jlib:make_jid(User, Server, ""),
|
||||
ejabberd_router:route(Local, Dest, Packet)
|
||||
end, ejabberd_auth:dirty_get_registered_users())
|
||||
end.
|
||||
|
||||
announce_online(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_online1(ejabberd_sm:get_vh_session_list(Host),
|
||||
To#jid.server,
|
||||
Packet)
|
||||
end.
|
||||
|
||||
announce_all_hosts_online(From, To, Packet) ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
|
||||
To#jid.server,
|
||||
Packet)
|
||||
end.
|
||||
|
||||
announce_online1(Sessions, Server, Packet) ->
|
||||
Local = jlib:make_jid("", Server, ""),
|
||||
lists:foreach(
|
||||
fun({U, S, R}) ->
|
||||
Dest = jlib:make_jid(U, S, R),
|
||||
ejabberd_router:route(Local, Dest, Packet)
|
||||
end, Sessions).
|
||||
|
||||
announce_motd(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_motd(Host, Packet)
|
||||
end.
|
||||
|
||||
announce_all_hosts_motd(From, To, Packet) ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Hosts = ?MYHOSTS,
|
||||
[announce_motd(Host, Packet) || Host <- Hosts]
|
||||
end.
|
||||
|
||||
announce_motd(Host, Packet) ->
|
||||
announce_motd_update(Host, Packet),
|
||||
Sessions = ejabberd_sm:get_vh_session_list(Host),
|
||||
announce_online1(Sessions, Host, Packet),
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({U, _S, _R}) ->
|
||||
Username = ejabberd_odbc:escape(U),
|
||||
odbc_queries:update_t(
|
||||
"motd",
|
||||
["username", "xml"],
|
||||
[Username, ""],
|
||||
["username='", Username, "'"])
|
||||
end, Sessions)
|
||||
end,
|
||||
LServer = jlib:nameprep(Host),
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
announce_motd_update(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_motd_update(Host, Packet)
|
||||
end.
|
||||
|
||||
announce_all_hosts_motd_update(From, To, Packet) ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Hosts = ?MYHOSTS,
|
||||
[announce_motd_update(Host, Packet) || Host <- Hosts]
|
||||
end.
|
||||
|
||||
announce_motd_update(LServer, Packet) ->
|
||||
announce_motd_delete(LServer),
|
||||
XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"motd",
|
||||
["username", "xml"],
|
||||
["", XML],
|
||||
["username=''"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
announce_motd_delete(From, To, Packet) ->
|
||||
Host = To#jid.lserver,
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
|
||||
case acl:match_rule(Host, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
announce_motd_delete(Host)
|
||||
end.
|
||||
|
||||
announce_all_hosts_motd_delete(From, To, Packet) ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
|
||||
case acl:match_rule(global, Access, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Hosts = ?MYHOSTS,
|
||||
[announce_motd_delete(Host) || Host <- Hosts]
|
||||
end.
|
||||
|
||||
announce_motd_delete(LServer) ->
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t(["delete from motd;"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= "" ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select xml from motd where username='';"]) of
|
||||
{selected, ["xml"], [{XML}]} ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
ok;
|
||||
Packet ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select username from motd "
|
||||
"where username='", Username, "';"]) of
|
||||
{selected, ["username"], []} ->
|
||||
Local = jlib:make_jid("", LServer, ""),
|
||||
ejabberd_router:route(Local, JID, Packet),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"motd",
|
||||
["username", "xml"],
|
||||
[Username, ""],
|
||||
["username='", Username, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
send_motd(_) ->
|
||||
ok.
|
||||
|
||||
get_stored_motd(LServer) ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select xml from motd where username='';"]) of
|
||||
{selected, ["xml"], [{XML}]} ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
{"", ""};
|
||||
Packet ->
|
||||
{xml:get_subtag_cdata(Packet, "subject"),
|
||||
xml:get_subtag_cdata(Packet, "body")}
|
||||
end;
|
||||
_ ->
|
||||
{"", ""}
|
||||
end.
|
||||
|
||||
%% This function is similar to others, but doesn't perform any ACL verification
|
||||
send_announcement_to_all(Host, SubjectS, BodyS) ->
|
||||
SubjectEls = if SubjectS /= [] ->
|
||||
[{xmlelement, "subject", [], [{xmlcdata, SubjectS}]}];
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
BodyEls = if BodyS /= [] ->
|
||||
[{xmlelement, "body", [], [{xmlcdata, BodyS}]}];
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
Packet = {xmlelement, "message", [{"type", "normal"}], SubjectEls ++ BodyEls},
|
||||
Sessions = ejabberd_sm:dirty_get_sessions_list(),
|
||||
Local = jlib:make_jid("", Host, ""),
|
||||
lists:foreach(
|
||||
fun({U, S, R}) ->
|
||||
Dest = jlib:make_jid(U, S, R),
|
||||
ejabberd_router:route(Local, Dest, Packet)
|
||||
end, Sessions).
|
@ -92,16 +92,6 @@ process_iq_set(_, From, _To, #iq{xmlns = ?NS_BLOCKING,
|
||||
process_iq_set(Acc, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
is_list_needdb(Items) ->
|
||||
lists:any(
|
||||
fun(X) ->
|
||||
case X#listitem.type of
|
||||
subscription -> true;
|
||||
group -> true;
|
||||
_ -> false
|
||||
end
|
||||
end, Items).
|
||||
|
||||
list_to_blocklist_jids([], JIDs) ->
|
||||
JIDs;
|
||||
|
||||
@ -148,6 +138,35 @@ parse_blocklist_items([_ | Els], JIDs) ->
|
||||
parse_blocklist_items(Els, JIDs).
|
||||
|
||||
process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
Filter = fun(List) ->
|
||||
AlreadyBlocked = list_to_blocklist_jids(List, []),
|
||||
lists:foldr(
|
||||
fun(JID, List1) ->
|
||||
case lists:member(JID, AlreadyBlocked) of
|
||||
true ->
|
||||
List1;
|
||||
false ->
|
||||
[#listitem{type = jid,
|
||||
value = JID,
|
||||
action = deny,
|
||||
order = 0,
|
||||
match_all = true}
|
||||
| List1]
|
||||
end
|
||||
end, List, JIDs)
|
||||
end,
|
||||
case process_blocklist_block(LUser, LServer, Filter,
|
||||
gen_mod:db_type(LServer, mod_privacy)) of
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {block, JIDs}),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_blocklist_block(LUser, LServer, Filter, mnesia) ->
|
||||
F =
|
||||
fun() ->
|
||||
case mnesia:wread({privacy, {LUser, LServer}}) of
|
||||
@ -173,40 +192,72 @@ process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
List = []
|
||||
end
|
||||
end,
|
||||
|
||||
AlreadyBlocked = list_to_blocklist_jids(List, []),
|
||||
NewList =
|
||||
lists:foldr(fun(JID, List1) ->
|
||||
case lists:member(JID, AlreadyBlocked) of
|
||||
true ->
|
||||
List1;
|
||||
false ->
|
||||
[#listitem{type = jid,
|
||||
value = JID,
|
||||
action = deny,
|
||||
order = 0,
|
||||
match_all = true
|
||||
} | List1]
|
||||
end
|
||||
end, List, JIDs),
|
||||
NewList = Filter(List),
|
||||
NewLists = [{NewDefault, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{default = NewDefault,
|
||||
lists = NewLists}),
|
||||
{ok, NewDefault, NewList}
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_block(LUser, LServer, Filter, odbc) ->
|
||||
F = fun() ->
|
||||
Default =
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
Name = "Blocked contacts",
|
||||
mod_privacy:sql_add_privacy_list(LUser, Name),
|
||||
mod_privacy:sql_set_default_privacy_list(
|
||||
LUser, Name),
|
||||
Name;
|
||||
{selected, ["name"], [{Name}]} ->
|
||||
Name
|
||||
end,
|
||||
{selected, ["id"], [{ID}]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy:raw_to_item/1,
|
||||
RItems);
|
||||
_ ->
|
||||
List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
{ok, Default, NewList}
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
process_blocklist_unblock_all(LUser, LServer) ->
|
||||
Filter = fun(List) ->
|
||||
lists:filter(
|
||||
fun(#listitem{action = A}) ->
|
||||
A =/= deny
|
||||
end, List)
|
||||
end,
|
||||
case process_blocklist_unblock_all(
|
||||
LUser, LServer, Filter, gen_mod:db_type(LServer, mod_privacy)) of
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {block, JIDs}),
|
||||
broadcast_blocklist_event(LUser, LServer, unblock_all),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_blocklist_unblock_all(LUser, LServer) ->
|
||||
process_blocklist_unblock_all(LUser, LServer, Filter, mnesia) ->
|
||||
F =
|
||||
fun() ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
@ -218,12 +269,7 @@ process_blocklist_unblock_all(LUser, LServer) ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
% Default list, remove all deny items
|
||||
NewList =
|
||||
lists:filter(
|
||||
fun(#listitem{action = A}) ->
|
||||
A =/= deny
|
||||
end, List),
|
||||
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists}),
|
||||
@ -235,21 +281,67 @@ process_blocklist_unblock_all(LUser, LServer) ->
|
||||
end
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, ok} ->
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_unblock_all(LUser, LServer, Filter, odbc) ->
|
||||
F = fun() ->
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
ok;
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
{selected, ["id"], [{ID}]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(
|
||||
LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy:raw_to_item/1,
|
||||
RItems),
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
Filter = fun(List) ->
|
||||
lists:filter(
|
||||
fun(#listitem{action = deny,
|
||||
type = jid,
|
||||
value = JID}) ->
|
||||
not(lists:member(JID, JIDs));
|
||||
(_) ->
|
||||
true
|
||||
end, List)
|
||||
end,
|
||||
case process_blocklist_unblock(LUser, LServer, Filter,
|
||||
gen_mod:db_type(LServer, mod_privacy)) of
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, unblock_all),
|
||||
broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
process_blocklist_unblock(LUser, LServer, Filter, mnesia) ->
|
||||
F =
|
||||
fun() ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
@ -261,16 +353,7 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
% Default list, remove matching deny items
|
||||
NewList =
|
||||
lists:filter(
|
||||
fun(#listitem{action = deny,
|
||||
type = jid,
|
||||
value = JID}) ->
|
||||
not(lists:member(JID, JIDs));
|
||||
(_) ->
|
||||
true
|
||||
end, List),
|
||||
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists}),
|
||||
@ -282,22 +365,44 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
end
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_unblock(LUser, LServer, Filter, odbc) ->
|
||||
F = fun() ->
|
||||
case mod_privacy:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
ok;
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
{selected, ["id"], [{ID}]} =
|
||||
mod_privacy:sql_get_privacy_list_id_t(
|
||||
LUser, Default),
|
||||
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy:raw_to_item/1,
|
||||
RItems),
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
make_userlist(Name, List) ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
NeedDb = mod_privacy:is_list_needdb(List),
|
||||
#userlist{name = Name, list = List, needdb = NeedDb}.
|
||||
|
||||
broadcast_list_update(LUser, LServer, Name, UserList) ->
|
||||
@ -315,25 +420,52 @@ broadcast_blocklist_event(LUser, LServer, Event) ->
|
||||
[{blocking, Event}]}).
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case process_blocklist_get(
|
||||
LUser, LServer, gen_mod:db_type(LServer, mod_privacy)) of
|
||||
error ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
List ->
|
||||
JIDs = list_to_blocklist_jids(List, []),
|
||||
Items = lists:map(
|
||||
fun(JID) ->
|
||||
?DEBUG("JID: ~p",[JID]),
|
||||
{xmlelement, "item",
|
||||
[{"jid", jlib:jid_to_string(JID)}], []}
|
||||
end, JIDs),
|
||||
{result,
|
||||
[{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}],
|
||||
Items}]}
|
||||
end.
|
||||
|
||||
process_blocklist_get(LUser, LServer, mnesia) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
error;
|
||||
[] ->
|
||||
{result, [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], []}]};
|
||||
[];
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
JIDs = list_to_blocklist_jids(List, []),
|
||||
Items = lists:map(
|
||||
fun(JID) ->
|
||||
?DEBUG("JID: ~p",[JID]),
|
||||
{xmlelement, "item",
|
||||
[{"jid", jlib:jid_to_string(JID)}], []}
|
||||
end, JIDs),
|
||||
{result,
|
||||
[{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}],
|
||||
Items}]};
|
||||
List;
|
||||
_ ->
|
||||
{result, [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], []}]}
|
||||
[]
|
||||
end
|
||||
end;
|
||||
process_blocklist_get(LUser, LServer, odbc) ->
|
||||
case catch mod_privacy:sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, ["name"], []} ->
|
||||
[];
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
case catch mod_privacy:sql_get_privacy_list_data(
|
||||
LUser, LServer, Default) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
RItems} ->
|
||||
lists:map(fun mod_privacy:raw_to_item/1, RItems);
|
||||
{'EXIT', _} ->
|
||||
error
|
||||
end;
|
||||
{'EXIT', _} ->
|
||||
error
|
||||
end.
|
||||
|
@ -1,365 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_blocking_odbc.erl
|
||||
%%% Author : Stephan Maka
|
||||
%%% Purpose : XEP-0191: Simple Communications Blocking
|
||||
%%% Created : 24 Aug 2008 by Stephan Maka <stephan@spaceboyz.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_blocking_odbc).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1,
|
||||
process_iq/3,
|
||||
process_iq_set/4,
|
||||
process_iq_get/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
ejabberd_hooks:add(privacy_iq_get, Host,
|
||||
?MODULE, process_iq_get, 40),
|
||||
ejabberd_hooks:add(privacy_iq_set, Host,
|
||||
?MODULE, process_iq_set, 40),
|
||||
mod_disco:register_feature(Host, ?NS_BLOCKING),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING,
|
||||
?MODULE, process_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(privacy_iq_get, Host,
|
||||
?MODULE, process_iq_get, 40),
|
||||
ejabberd_hooks:delete(privacy_iq_set, Host,
|
||||
?MODULE, process_iq_set, 40),
|
||||
mod_disco:unregister_feature(Host, ?NS_BLOCKING),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING).
|
||||
|
||||
process_iq(_From, _To, IQ) ->
|
||||
SubEl = IQ#iq.sub_el,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
|
||||
process_iq_get(_, From, _To,
|
||||
#iq{xmlns = ?NS_BLOCKING,
|
||||
sub_el = {xmlelement, "blocklist", _, _}},
|
||||
_) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
{stop, process_blocklist_get(LUser, LServer)};
|
||||
|
||||
process_iq_get(Acc, _, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
process_iq_set(_, From, _To, #iq{xmlns = ?NS_BLOCKING,
|
||||
sub_el = {xmlelement, SubElName, _, SubEls}}) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
Res =
|
||||
case {SubElName, xml:remove_cdata(SubEls)} of
|
||||
{"block", []} ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
{"block", Els} ->
|
||||
JIDs = parse_blocklist_items(Els, []),
|
||||
process_blocklist_block(LUser, LServer, JIDs);
|
||||
{"unblock", []} ->
|
||||
process_blocklist_unblock_all(LUser, LServer);
|
||||
{"unblock", Els} ->
|
||||
JIDs = parse_blocklist_items(Els, []),
|
||||
process_blocklist_unblock(LUser, LServer, JIDs);
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end,
|
||||
{stop, Res};
|
||||
|
||||
process_iq_set(Acc, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
is_list_needdb(Items) ->
|
||||
lists:any(
|
||||
fun(X) ->
|
||||
case X#listitem.type of
|
||||
subscription -> true;
|
||||
group -> true;
|
||||
_ -> false
|
||||
end
|
||||
end, Items).
|
||||
|
||||
list_to_blocklist_jids([], JIDs) ->
|
||||
JIDs;
|
||||
|
||||
list_to_blocklist_jids([#listitem{type = jid,
|
||||
action = deny,
|
||||
value = JID} = Item | Items], JIDs) ->
|
||||
case Item of
|
||||
#listitem{match_all = true} ->
|
||||
Match = true;
|
||||
#listitem{match_iq = true,
|
||||
match_message = true,
|
||||
match_presence_in = true,
|
||||
match_presence_out = true} ->
|
||||
Match = true;
|
||||
_ ->
|
||||
Match = false
|
||||
end,
|
||||
if
|
||||
Match ->
|
||||
list_to_blocklist_jids(Items, [JID | JIDs]);
|
||||
true ->
|
||||
list_to_blocklist_jids(Items, JIDs)
|
||||
end;
|
||||
|
||||
% Skip Privacy List items than cannot be mapped to Blocking items
|
||||
list_to_blocklist_jids([_ | Items], JIDs) ->
|
||||
list_to_blocklist_jids(Items, JIDs).
|
||||
|
||||
parse_blocklist_items([], JIDs) ->
|
||||
JIDs;
|
||||
|
||||
parse_blocklist_items([{xmlelement, "item", Attrs, _} | Els], JIDs) ->
|
||||
case xml:get_attr("jid", Attrs) of
|
||||
{value, JID1} ->
|
||||
JID = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||
parse_blocklist_items(Els, [JID | JIDs]);
|
||||
false ->
|
||||
% Tolerate missing jid attribute
|
||||
parse_blocklist_items(Els, JIDs)
|
||||
end;
|
||||
|
||||
parse_blocklist_items([_ | Els], JIDs) ->
|
||||
% Tolerate unknown elements
|
||||
parse_blocklist_items(Els, JIDs).
|
||||
|
||||
process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
F = fun() ->
|
||||
Default =
|
||||
case mod_privacy_odbc:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
Name = "Blocked contacts",
|
||||
mod_privacy_odbc:sql_add_privacy_list(LUser, Name),
|
||||
mod_privacy_odbc:sql_set_default_privacy_list(
|
||||
LUser, Name),
|
||||
Name;
|
||||
{selected, ["name"], [{Name}]} ->
|
||||
Name
|
||||
end,
|
||||
{selected, ["id"], [{ID}]} =
|
||||
mod_privacy_odbc:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy_odbc:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy_odbc:raw_to_item/1,
|
||||
RItems);
|
||||
_ ->
|
||||
List = []
|
||||
end,
|
||||
AlreadyBlocked = list_to_blocklist_jids(List, []),
|
||||
NewList =
|
||||
lists:foldr(
|
||||
fun(JID, List1) ->
|
||||
case lists:member(JID, AlreadyBlocked) of
|
||||
true ->
|
||||
List1;
|
||||
false ->
|
||||
[#listitem{type = jid,
|
||||
value = JID,
|
||||
action = deny,
|
||||
order = 0,
|
||||
match_all = true
|
||||
} | List1]
|
||||
end
|
||||
end, List, JIDs),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy_odbc:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy_odbc:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
{ok, Default, NewList}
|
||||
end,
|
||||
case ejabberd_odbc:sql_transaction(LServer, F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {block, JIDs}),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_blocklist_unblock_all(LUser, LServer) ->
|
||||
F = fun() ->
|
||||
case mod_privacy_odbc:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
ok;
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
{selected, ["id"], [{ID}]} =
|
||||
mod_privacy_odbc:sql_get_privacy_list_id_t(
|
||||
LUser, Default),
|
||||
case mod_privacy_odbc:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy_odbc:raw_to_item/1,
|
||||
RItems),
|
||||
NewList =
|
||||
lists:filter(
|
||||
fun(#listitem{action = A}) ->
|
||||
A =/= deny
|
||||
end, List),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy_odbc:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy_odbc:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
case ejabberd_odbc:sql_transaction(LServer, F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, unblock_all),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
F = fun() ->
|
||||
case mod_privacy_odbc:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
ok;
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
{selected, ["id"], [{ID}]} =
|
||||
mod_privacy_odbc:sql_get_privacy_list_id_t(
|
||||
LUser, Default),
|
||||
case mod_privacy_odbc:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected,
|
||||
["t", "value", "action", "ord",
|
||||
"match_all", "match_iq", "match_message",
|
||||
"match_presence_in",
|
||||
"match_presence_out"],
|
||||
RItems = [_|_]} ->
|
||||
List = lists:map(
|
||||
fun mod_privacy_odbc:raw_to_item/1,
|
||||
RItems),
|
||||
NewList =
|
||||
lists:filter(
|
||||
fun(#listitem{action = deny,
|
||||
type = jid,
|
||||
value = JID}) ->
|
||||
not(lists:member(JID, JIDs));
|
||||
(_) ->
|
||||
true
|
||||
end, List),
|
||||
NewRItems = lists:map(
|
||||
fun mod_privacy_odbc:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy_odbc:sql_set_privacy_list(
|
||||
ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
case ejabberd_odbc:sql_transaction(LServer, F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}),
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
make_userlist(Name, List) ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
#userlist{name = Name, list = List, needdb = NeedDb}.
|
||||
|
||||
broadcast_list_update(LUser, LServer, Name, UserList) ->
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
{xmlelement, "broadcast", [],
|
||||
[{privacy_list, UserList, Name}]}).
|
||||
|
||||
broadcast_blocklist_event(LUser, LServer, Event) ->
|
||||
JID = jlib:make_jid(LUser, LServer, ""),
|
||||
ejabberd_router:route(
|
||||
JID, JID,
|
||||
{xmlelement, "broadcast", [],
|
||||
[{blocking, Event}]}).
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case catch mod_privacy_odbc:sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, ["name"], []} ->
|
||||
{result, [{xmlelement, "blocklist",
|
||||
[{"xmlns", ?NS_BLOCKING}], []}]};
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
case catch mod_privacy_odbc:sql_get_privacy_list_data(
|
||||
LUser, LServer, Default) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
RItems} ->
|
||||
List = lists:map(fun mod_privacy_odbc:raw_to_item/1, RItems),
|
||||
JIDs = list_to_blocklist_jids(List, []),
|
||||
Items = lists:map(
|
||||
fun(JID) ->
|
||||
?DEBUG("JID: ~p",[JID]),
|
||||
{xmlelement, "item",
|
||||
[{"jid", jlib:jid_to_string(JID)}], []}
|
||||
end, JIDs),
|
||||
{result,
|
||||
[{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}],
|
||||
Items}]};
|
||||
{'EXIT', _} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
{'EXIT', _} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
@ -1782,15 +1782,13 @@ stop_node(From, Host, ENode, Action, XData) ->
|
||||
|
||||
|
||||
get_last_info(User, Server) ->
|
||||
ML = lists:member(mod_last, gen_mod:loaded_modules(Server)),
|
||||
MLO = lists:member(mod_last_odbc, gen_mod:loaded_modules(Server)),
|
||||
case {ML, MLO} of
|
||||
{true, _} -> mod_last:get_last_info(User, Server);
|
||||
{false, true} -> mod_last_odbc:get_last_info(User, Server);
|
||||
{false, false} -> not_found
|
||||
case gen_mod:is_loaded(Server, mod_last) of
|
||||
true ->
|
||||
mod_last:get_last_info(User, Server);
|
||||
false ->
|
||||
not_found
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
adhoc_sm_commands(_Acc, From,
|
||||
|
@ -4,7 +4,7 @@ include ..\Makefile.inc
|
||||
EFLAGS = -I .. -pz ..
|
||||
|
||||
OUTDIR = ..
|
||||
BEAMS = ..\iconv.beam ..\mod_irc.beam ..\mod_irc_odbc.beam ..\mod_irc_connection.beam
|
||||
BEAMS = ..\iconv.beam ..\mod_irc.beam ..\mod_irc_connection.beam
|
||||
|
||||
SOURCE = iconv_erl.c
|
||||
OBJECT = iconv_erl.o
|
||||
@ -25,9 +25,6 @@ $(OUTDIR)\iconv.beam : iconv.erl
|
||||
$(OUTDIR)\mod_irc.beam : mod_irc.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_irc.erl
|
||||
|
||||
$(OUTDIR)\mod_irc_odbc.beam : mod_irc_odbc.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_irc_odbc.erl
|
||||
|
||||
$(OUTDIR)\mod_irc_connection.beam : mod_irc_connection.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_irc_connection.erl
|
||||
|
||||
|
@ -98,11 +98,17 @@ stop(Host) ->
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Opts]) ->
|
||||
iconv:start(),
|
||||
mnesia:create_table(irc_custom,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, irc_custom)}]),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "irc.@HOST@"),
|
||||
update_table(MyHost),
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(irc_custom,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, irc_custom)}]),
|
||||
update_table(MyHost);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
catch ets:new(irc_connection, [named_table,
|
||||
public,
|
||||
@ -218,7 +224,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
Info = ejabberd_hooks:run_fold(
|
||||
disco_info, ServerHost, [],
|
||||
[ServerHost, ?MODULE, "", ""]),
|
||||
case iq_disco(Node, Lang) of
|
||||
case iq_disco(ServerHost, Node, Lang) of
|
||||
[] ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
@ -263,7 +269,8 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS},
|
||||
{"node", Node}],
|
||||
command_items(Host, Lang)}]},
|
||||
command_items(ServerHost,
|
||||
Host, Lang)}]},
|
||||
Res = jlib:iq_to_xml(ResIQ);
|
||||
_ ->
|
||||
Res = jlib:make_error_reply(
|
||||
@ -273,7 +280,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
From,
|
||||
Res);
|
||||
#iq{xmlns = ?NS_REGISTER} = IQ ->
|
||||
process_register(Host, From, To, IQ);
|
||||
process_register(ServerHost, Host, From, To, IQ);
|
||||
#iq{type = get, xmlns = ?NS_VCARD = XMLNS,
|
||||
lang = Lang} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
@ -287,7 +294,8 @@ do_route1(Host, ServerHost, From, To, Packet) ->
|
||||
#iq{type = set, xmlns = ?NS_COMMANDS,
|
||||
lang = _Lang, sub_el = SubEl} = IQ ->
|
||||
Request = adhoc:parse_request(IQ),
|
||||
case lists:keysearch(Request#adhoc_request.node, 1, commands()) of
|
||||
case lists:keysearch(Request#adhoc_request.node,
|
||||
1, commands(ServerHost)) of
|
||||
{value, {_, _, Function}} ->
|
||||
case catch Function(From, To, Request) of
|
||||
{'EXIT', Reason} ->
|
||||
@ -394,7 +402,7 @@ closed_connection(Host, From, Server) ->
|
||||
ets:delete(irc_connection, {From, Server, Host}).
|
||||
|
||||
|
||||
iq_disco([], Lang) ->
|
||||
iq_disco(_ServerHost, [], Lang) ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "conference"},
|
||||
{"type", "irc"},
|
||||
@ -404,8 +412,8 @@ iq_disco([], Lang) ->
|
||||
{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}];
|
||||
iq_disco(Node, Lang) ->
|
||||
case lists:keysearch(Node, 1, commands()) of
|
||||
iq_disco(ServerHost, Node, Lang) ->
|
||||
case lists:keysearch(Node, 1, commands(ServerHost)) of
|
||||
{value, {_, Name, _}} ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "automation"},
|
||||
@ -428,20 +436,23 @@ iq_get_vcard(Lang) ->
|
||||
[{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++
|
||||
"\nCopyright (c) 2003-2012 ProcessOne"}]}].
|
||||
|
||||
command_items(Host, Lang) ->
|
||||
command_items(ServerHost, Host, Lang) ->
|
||||
lists:map(fun({Node, Name, _Function})
|
||||
-> {xmlelement, "item",
|
||||
[{"jid", Host},
|
||||
{"node", Node},
|
||||
{"name", translate:translate(Lang, Name)}], []}
|
||||
end, commands()).
|
||||
end, commands(ServerHost)).
|
||||
|
||||
commands() ->
|
||||
commands(ServerHost) ->
|
||||
[{"join", "Join channel", fun adhoc_join/3},
|
||||
{"register", "Configure username, encoding, port and password", fun adhoc_register/3}].
|
||||
{"register", "Configure username, encoding, port and password",
|
||||
fun(From, To, Request) ->
|
||||
adhoc_register(ServerHost, From, To, Request)
|
||||
end}].
|
||||
|
||||
process_register(Host, From, To, #iq{} = IQ) ->
|
||||
case catch process_irc_register(Host, From, To, IQ) of
|
||||
process_register(ServerHost, Host, From, To, #iq{} = IQ) ->
|
||||
case catch process_irc_register(ServerHost, Host, From, To, IQ) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]);
|
||||
ResIQ ->
|
||||
@ -471,7 +482,7 @@ find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
|
||||
find_xdata_el1([_ | Els]) ->
|
||||
find_xdata_el1(Els).
|
||||
|
||||
process_irc_register(Host, From, _To,
|
||||
process_irc_register(ServerHost, Host, From, _To,
|
||||
#iq{type = Type, xmlns = XMLNS,
|
||||
lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
@ -497,7 +508,8 @@ process_irc_register(Host, From, _To,
|
||||
xml:get_tag_attr_s("node", SubEl),
|
||||
"/"),
|
||||
case set_form(
|
||||
Host, From, Node, Lang, XData) of
|
||||
ServerHost, Host, From,
|
||||
Node, Lang, XData) of
|
||||
{result, Res} ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
@ -517,7 +529,7 @@ process_irc_register(Host, From, _To,
|
||||
get ->
|
||||
Node =
|
||||
string:tokens(xml:get_tag_attr_s("node", SubEl), "/"),
|
||||
case get_form(Host, From, Node, Lang) of
|
||||
case get_form(ServerHost, Host, From, Node, Lang) of
|
||||
{result, Res} ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
@ -530,23 +542,52 @@ process_irc_register(Host, From, _To,
|
||||
end
|
||||
end.
|
||||
|
||||
get_data(ServerHost, Host, From) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
get_data(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
|
||||
get_form(Host, From, [], Lang) ->
|
||||
#jid{user = User, server = Server,
|
||||
luser = LUser, lserver = LServer} = From,
|
||||
get_data(_LServer, Host, From, mnesia) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
|
||||
{'EXIT', _Reason} ->
|
||||
error;
|
||||
[] ->
|
||||
empty;
|
||||
[#irc_custom{data = Data}] ->
|
||||
Data
|
||||
end;
|
||||
get_data(LServer, Host, From, odbc) ->
|
||||
SJID = ejabberd_odbc:escape(
|
||||
jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(From)))),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select data from irc_custom where "
|
||||
"jid='", SJID, "' and host='", SHost, "';"]) of
|
||||
{selected, ["data"], [{SData}]} ->
|
||||
ejabberd_odbc:decode_term(SData);
|
||||
{'EXIT', _} ->
|
||||
error;
|
||||
{selected, _, _} ->
|
||||
empty
|
||||
end.
|
||||
|
||||
get_form(ServerHost, Host, From, [], Lang) ->
|
||||
#jid{user = User, server = Server} = From,
|
||||
DefaultEncoding = get_default_encoding(Host),
|
||||
Customs =
|
||||
case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
[] ->
|
||||
{User, []};
|
||||
[#irc_custom{data = Data}] ->
|
||||
{xml:get_attr_s(username, Data),
|
||||
xml:get_attr_s(connections_params, Data)}
|
||||
end,
|
||||
case get_data(ServerHost, Host, From) of
|
||||
error ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
empty ->
|
||||
{User, []};
|
||||
Data ->
|
||||
{xml:get_attr_s(username, Data),
|
||||
xml:get_attr_s(connections_params, Data)}
|
||||
end,
|
||||
case Customs of
|
||||
{error, _Error} ->
|
||||
Customs;
|
||||
@ -614,15 +655,41 @@ get_form(Host, From, [], Lang) ->
|
||||
]}]}
|
||||
end;
|
||||
|
||||
get_form(_Host, _, _, _Lang) ->
|
||||
get_form(_ServerHost, _Host, _, _, _Lang) ->
|
||||
{error, ?ERR_SERVICE_UNAVAILABLE}.
|
||||
|
||||
|
||||
set_data(ServerHost, Host, From, Data) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
set_data(LServer, Host, From, Data, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
|
||||
set_form(Host, From, [], _Lang, XData) ->
|
||||
set_data(_LServer, Host, From, Data, mnesia) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(From),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:write(#irc_custom{us_host = {US, Host}, data = Data})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
set_data(LServer, Host, From, Data, odbc) ->
|
||||
SJID = ejabberd_odbc:escape(
|
||||
jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(From)))),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
SData = ejabberd_odbc:encode_term(Data),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"irc_custom",
|
||||
["jid", "host", "data"],
|
||||
[SJID, SHost, SData],
|
||||
["jid='", SJID,
|
||||
"' and host='",
|
||||
SHost, "'"]),
|
||||
ok
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
set_form(ServerHost, Host, From, [], _Lang, XData) ->
|
||||
case {lists:keysearch("username", 1, XData),
|
||||
lists:keysearch("connections_params", 1, XData)} of
|
||||
{{value, {_, [Username]}}, {value, {_, Strings}}} ->
|
||||
@ -633,17 +700,11 @@ set_form(Host, From, [], _Lang, XData) ->
|
||||
{ok, Tokens, _} ->
|
||||
case erl_parse:parse_term(Tokens) of
|
||||
{ok, ConnectionsParams} ->
|
||||
case mnesia:transaction(
|
||||
fun() ->
|
||||
mnesia:write(
|
||||
#irc_custom{us_host =
|
||||
{US, Host},
|
||||
data =
|
||||
[{username,
|
||||
Username},
|
||||
{connections_params,
|
||||
ConnectionsParams}]})
|
||||
end) of
|
||||
case set_data(ServerHost, Host, From,
|
||||
[{username,
|
||||
Username},
|
||||
{connections_params,
|
||||
ConnectionsParams}]) of
|
||||
{atomic, _} ->
|
||||
{result, []};
|
||||
_ ->
|
||||
@ -660,7 +721,7 @@ set_form(Host, From, [], _Lang, XData) ->
|
||||
end;
|
||||
|
||||
|
||||
set_form(_Host, _, _, _Lang, _XData) ->
|
||||
set_form(_ServerHost, _Host, _, _, _Lang, _XData) ->
|
||||
{error, ?ERR_SERVICE_UNAVAILABLE}.
|
||||
|
||||
|
||||
@ -679,16 +740,14 @@ get_default_encoding(ServerHost) ->
|
||||
Result.
|
||||
|
||||
get_connection_params(Host, ServerHost, From, IRCServer) ->
|
||||
#jid{user = User, server = _Server,
|
||||
luser = LUser, lserver = LServer} = From,
|
||||
US = {LUser, LServer},
|
||||
#jid{user = User, server = _Server} = From,
|
||||
DefaultEncoding = get_default_encoding(ServerHost),
|
||||
case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
|
||||
{'EXIT', _Reason} ->
|
||||
case get_data(ServerHost, Host, From) of
|
||||
error ->
|
||||
{User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""};
|
||||
[] ->
|
||||
empty ->
|
||||
{User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""};
|
||||
[#irc_custom{data = Data}] ->
|
||||
Data ->
|
||||
Username = xml:get_attr_s(username, Data),
|
||||
{NewUsername, NewEncoding, NewPort, NewPassword} =
|
||||
case lists:keysearch(IRCServer, 1, xml:get_attr_s(connections_params, Data)) of
|
||||
@ -785,28 +844,27 @@ adhoc_join(From, To, #adhoc_request{lang = Lang,
|
||||
end
|
||||
end.
|
||||
|
||||
adhoc_register(_From, _To, #adhoc_request{action = "cancel"} = Request) ->
|
||||
adhoc_register(_ServerHost, _From, _To, #adhoc_request{action = "cancel"} = Request) ->
|
||||
adhoc:produce_response(Request,
|
||||
#adhoc_response{status = canceled});
|
||||
adhoc_register(From, To, #adhoc_request{lang = Lang,
|
||||
node = _Node,
|
||||
xdata = XData,
|
||||
action = Action} = Request) ->
|
||||
#jid{user = User, luser = LUser, lserver = LServer} = From,
|
||||
adhoc_register(ServerHost, From, To, #adhoc_request{lang = Lang,
|
||||
node = _Node,
|
||||
xdata = XData,
|
||||
action = Action} = Request) ->
|
||||
#jid{user = User} = From,
|
||||
#jid{lserver = Host} = To,
|
||||
US = {LUser, LServer},
|
||||
%% Generate form for setting username and encodings. If the user
|
||||
%% hasn't begun to fill out the form, generate an initial form
|
||||
%% based on current values.
|
||||
if XData == false ->
|
||||
case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
|
||||
{'EXIT', _Reason} ->
|
||||
case get_data(ServerHost, Host, From) of
|
||||
error ->
|
||||
Username = User,
|
||||
ConnectionsParams = [];
|
||||
[] ->
|
||||
empty ->
|
||||
Username = User,
|
||||
ConnectionsParams = [];
|
||||
[#irc_custom{data = Data}] ->
|
||||
Data ->
|
||||
Username = xml:get_attr_s(username, Data),
|
||||
ConnectionsParams = xml:get_attr_s(connections_params, Data)
|
||||
end,
|
||||
@ -832,17 +890,11 @@ adhoc_register(From, To, #adhoc_request{lang = Lang,
|
||||
if Error /= false ->
|
||||
Error;
|
||||
Action == "complete" ->
|
||||
case mnesia:transaction(
|
||||
fun () ->
|
||||
mnesia:write(
|
||||
#irc_custom{us_host =
|
||||
{US, Host},
|
||||
data =
|
||||
[{username,
|
||||
Username},
|
||||
{connections_params,
|
||||
ConnectionsParams}]})
|
||||
end) of
|
||||
case set_data(ServerHost, Host, From,
|
||||
[{username,
|
||||
Username},
|
||||
{connections_params,
|
||||
ConnectionsParams}]) of
|
||||
{atomic, _} ->
|
||||
adhoc:produce_response(Request, #adhoc_response{status = completed});
|
||||
_ ->
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -47,10 +47,16 @@
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
mnesia:create_table(last_activity,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, last_activity)}]),
|
||||
update_table(),
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(last_activity,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, last_activity)}]),
|
||||
update_table();
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST,
|
||||
?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_LAST,
|
||||
@ -145,6 +151,9 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
|
||||
get_last(LUser, LServer) ->
|
||||
get_last(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_last(LUser, LServer, mnesia) ->
|
||||
case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of
|
||||
{'EXIT', Reason} ->
|
||||
{error, Reason};
|
||||
@ -152,6 +161,21 @@ get_last(LUser, LServer) ->
|
||||
not_found;
|
||||
[#last_activity{timestamp = TimeStamp, status = Status}] ->
|
||||
{ok, TimeStamp, Status}
|
||||
end;
|
||||
get_last(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_last(LServer, Username) of
|
||||
{selected, ["seconds","state"], []} ->
|
||||
not_found;
|
||||
{selected, ["seconds","state"], [{STimeStamp, Status}]} ->
|
||||
case catch list_to_integer(STimeStamp) of
|
||||
TimeStamp when is_integer(TimeStamp) ->
|
||||
{ok, TimeStamp, Status};
|
||||
Reason ->
|
||||
{error, {invalid_timestamp, Reason}}
|
||||
end;
|
||||
Reason ->
|
||||
{error, {invalid_result, Reason}}
|
||||
end.
|
||||
|
||||
get_last_iq(IQ, SubEl, LUser, LServer) ->
|
||||
@ -186,13 +210,22 @@ on_presence_update(User, Server, _Resource, Status) ->
|
||||
store_last_info(User, Server, TimeStamp, Status) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
store_last_info(LUser, LServer, TimeStamp, Status, DBType).
|
||||
|
||||
store_last_info(LUser, LServer, TimeStamp, Status, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:write(#last_activity{us = US,
|
||||
timestamp = TimeStamp,
|
||||
status = Status})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
store_last_info(LUser, LServer, TimeStamp, Status, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
|
||||
State = ejabberd_odbc:escape(Status),
|
||||
odbc_queries:set_last_t(LServer, Username, Seconds, State).
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found
|
||||
@ -207,12 +240,18 @@ get_last_info(LUser, LServer) ->
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
remove_user(LUser, LServer, DBType).
|
||||
|
||||
remove_user(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({last_activity, US})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_last(LServer, Username).
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, last_activity),
|
||||
|
@ -1,204 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_last_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : jabber:iq:last support (XEP-0012)
|
||||
%%% Created : 24 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_last_odbc).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_local_iq/3,
|
||||
process_sm_iq/3,
|
||||
on_presence_update/4,
|
||||
store_last_info/4,
|
||||
get_last_info/2,
|
||||
remove_user/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?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(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:add(unset_presence_hook, Host,
|
||||
?MODULE, on_presence_update, 50).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(unset_presence_hook, Host,
|
||||
?MODULE, on_presence_update, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_LAST),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_LAST).
|
||||
|
||||
%%%
|
||||
%%% Uptime of ejabberd node
|
||||
%%%
|
||||
|
||||
process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
Sec = get_node_uptime(),
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_LAST},
|
||||
{"seconds", integer_to_list(Sec)}],
|
||||
[]}]}
|
||||
end.
|
||||
|
||||
%% @spec () -> integer()
|
||||
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
|
||||
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
|
||||
get_node_uptime() ->
|
||||
case ejabberd_config:get_local_option(node_start) of
|
||||
{_, _, _} = StartNow ->
|
||||
now_to_seconds(now()) - now_to_seconds(StartNow);
|
||||
_undefined ->
|
||||
trunc(element(1, erlang:statistics(wall_clock))/1000)
|
||||
end.
|
||||
|
||||
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Serve queries about user last online
|
||||
%%%
|
||||
process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
User = To#jid.luser,
|
||||
Server = To#jid.lserver,
|
||||
{Subscription, _Groups} =
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, Server,
|
||||
{none, []}, [User, Server, From]),
|
||||
if
|
||||
(Subscription == both) or (Subscription == from) ->
|
||||
UserListRecord = ejabberd_hooks:run_fold(
|
||||
privacy_get_user_list, Server,
|
||||
#userlist{},
|
||||
[User, Server]),
|
||||
case ejabberd_hooks:run_fold(
|
||||
privacy_check_packet, Server,
|
||||
allow,
|
||||
[User, Server, UserListRecord,
|
||||
{To, From,
|
||||
{xmlelement, "presence", [], []}},
|
||||
out]) of
|
||||
allow ->
|
||||
get_last_iq(IQ, SubEl, User, Server);
|
||||
deny ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end;
|
||||
true ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
|
||||
get_last(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_last(LServer, Username) of
|
||||
{selected, ["seconds","state"], []} ->
|
||||
not_found;
|
||||
{selected, ["seconds","state"], [{STimeStamp, Status}]} ->
|
||||
case catch list_to_integer(STimeStamp) of
|
||||
TimeStamp when is_integer(TimeStamp) ->
|
||||
{ok, TimeStamp, Status};
|
||||
Reason ->
|
||||
{error, {invalid_timestamp, Reason}}
|
||||
end;
|
||||
Reason ->
|
||||
{error, {invalid_result, Reason}}
|
||||
end.
|
||||
|
||||
get_last_iq(IQ, SubEl, LUser, LServer) ->
|
||||
case ejabberd_sm:get_user_resources(LUser, LServer) of
|
||||
[] ->
|
||||
case get_last(LUser, LServer) of
|
||||
{error, _Reason} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
|
||||
not_found ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
|
||||
{ok, TimeStamp, Status} ->
|
||||
TimeStamp2 = now_to_seconds(now()),
|
||||
Sec = TimeStamp2 - TimeStamp,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_LAST},
|
||||
{"seconds", integer_to_list(Sec)}],
|
||||
[{xmlcdata, Status}]}]}
|
||||
end;
|
||||
_ ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_LAST},
|
||||
{"seconds", "0"}],
|
||||
[]}]}
|
||||
end.
|
||||
|
||||
on_presence_update(User, Server, _Resource, Status) ->
|
||||
TimeStamp = now_to_seconds(now()),
|
||||
store_last_info(User, Server, TimeStamp, Status).
|
||||
|
||||
store_last_info(User, Server, TimeStamp, Status) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
|
||||
State = ejabberd_odbc:escape(Status),
|
||||
odbc_queries:set_last_t(LServer, Username, Seconds, State).
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found
|
||||
get_last_info(LUser, LServer) ->
|
||||
case get_last(LUser, LServer) of
|
||||
{error, _Reason} ->
|
||||
not_found;
|
||||
Res ->
|
||||
Res
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_last(LServer, Username).
|
@ -4,7 +4,7 @@ include ..\Makefile.inc
|
||||
EFLAGS = -I .. -pz ..
|
||||
|
||||
OUTDIR = ..
|
||||
BEAMS = ..\mod_muc.beam ..\mod_muc_odbc.beam ..\mod_muc_log.beam ..\mod_muc_room.beam
|
||||
BEAMS = ..\mod_muc.beam ..\mod_muc_log.beam ..\mod_muc_room.beam
|
||||
|
||||
ALL : $(BEAMS)
|
||||
|
||||
@ -14,9 +14,6 @@ CLEAN :
|
||||
$(OUTDIR)\mod_muc.beam : mod_muc.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc.erl
|
||||
|
||||
$(OUTDIR)\mod_muc_odbc.beam : mod_muc_odbc.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc_odbc.erl
|
||||
|
||||
$(OUTDIR)\mod_muc_log.beam : mod_muc_log.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc_log.erl
|
||||
|
||||
|
@ -111,26 +111,70 @@ create_room(Host, Name, From, Nick, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
|
||||
|
||||
store_room(_ServerHost, Host, Name, Opts) ->
|
||||
store_room(ServerHost, Host, Name, Opts) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
store_room(LServer, Host, Name, Opts, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
store_room(_LServer, Host, Name, Opts, mnesia) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#muc_room{name_host = {Name, Host},
|
||||
opts = Opts})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
store_room(LServer, Host, Name, Opts, odbc) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
SOpts = ejabberd_odbc:encode_term(Opts),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"muc_room",
|
||||
["name", "host", "opts"],
|
||||
[SName, SHost, SOpts],
|
||||
["name='", SName, "' and host='", SHost, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
restore_room(_ServerHost, Host, Name) ->
|
||||
restore_room(ServerHost, Host, Name) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
restore_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
restore_room(_LServer, Host, Name, mnesia) ->
|
||||
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
|
||||
[#muc_room{opts = Opts}] ->
|
||||
Opts;
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
restore_room(LServer, Host, Name, odbc) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select opts from muc_room where name='",
|
||||
SName, "' and host='", SHost, "';"]) of
|
||||
{selected, ["opts"], [{Opts}]} ->
|
||||
ejabberd_odbc:decode_term(Opts);
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
forget_room(_ServerHost, Host, Name) ->
|
||||
forget_room(ServerHost, Host, Name) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
forget_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
forget_room(_LServer, Host, Name, mnesia) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({muc_room, {Name, Host}})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
forget_room(LServer, Host, Name, odbc) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from muc_room where name='",
|
||||
SName, "' and host='", SHost, "';"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
|
||||
Rsm = jlib:rsm_decode(IQ),
|
||||
@ -144,7 +188,11 @@ process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
|
||||
|
||||
can_use_nick(_ServerHost, _Host, _JID, "") ->
|
||||
false;
|
||||
can_use_nick(_ServerHost, Host, JID, Nick) ->
|
||||
can_use_nick(ServerHost, Host, JID, Nick) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
can_use_nick(LServer, Host, JID, Nick, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(JID),
|
||||
LUS = {LUser, LServer},
|
||||
case catch mnesia:dirty_select(
|
||||
@ -160,6 +208,21 @@ can_use_nick(_ServerHost, Host, JID, Nick) ->
|
||||
true;
|
||||
[#muc_registered{us_host = {U, _Host}}] ->
|
||||
U == LUS
|
||||
end;
|
||||
can_use_nick(LServer, Host, JID, Nick, odbc) ->
|
||||
SJID = jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(JID))),
|
||||
SNick = ejabberd_odbc:escape(Nick),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select jid from muc_registered ",
|
||||
"where nick='", SNick, "' and host='",
|
||||
SHost, "';"]) of
|
||||
{selected, ["jid"], [{SJID1}]} ->
|
||||
SJID == SJID1;
|
||||
_ ->
|
||||
true
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
@ -174,21 +237,28 @@ can_use_nick(_ServerHost, Host, JID, Nick) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Opts]) ->
|
||||
mnesia:create_table(muc_room,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, muc_room)}]),
|
||||
mnesia:create_table(muc_registered,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, muc_registered)}]),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
update_tables(MyHost),
|
||||
mnesia:create_table(muc_room,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, muc_room)}]),
|
||||
mnesia:create_table(muc_registered,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, muc_registered)}]),
|
||||
mnesia:add_table_index(muc_registered, nick);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
mnesia:create_table(muc_online_room,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, muc_online_room)}]),
|
||||
mnesia:add_table_copy(muc_online_room, node(), ram_copies),
|
||||
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
|
||||
update_tables(MyHost),
|
||||
clean_table_from_bad_node(node(), MyHost),
|
||||
mnesia:add_table_index(muc_registered, nick),
|
||||
mnesia:subscribe(system),
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
|
||||
@ -238,7 +308,7 @@ handle_call({create, Room, From, Nick, Opts},
|
||||
Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, From,
|
||||
Nick, NewOpts, ?MODULE),
|
||||
Nick, NewOpts),
|
||||
register_room(Host, Room, Pid),
|
||||
{reply, ok, State}.
|
||||
|
||||
@ -379,7 +449,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
[{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
iq_get_register_info(
|
||||
Host, From, Lang)}]},
|
||||
ServerHost, Host, From, Lang)}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
@ -387,7 +457,8 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
xmlns = ?NS_REGISTER = XMLNS,
|
||||
lang = Lang,
|
||||
sub_el = SubEl} = IQ ->
|
||||
case process_iq_register_set(Host, From, SubEl, Lang) of
|
||||
case process_iq_register_set(
|
||||
ServerHost, Host, From, SubEl, Lang) of
|
||||
{result, IQRes} ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
@ -519,52 +590,72 @@ check_user_can_create_room(ServerHost, AccessCreate, From, RoomID) ->
|
||||
false
|
||||
end.
|
||||
|
||||
get_rooms(ServerHost, Host) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
get_rooms(LServer, Host, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
|
||||
get_rooms(_LServer, Host, mnesia) ->
|
||||
case catch mnesia:dirty_select(
|
||||
muc_room, [{#muc_room{name_host = {'_', Host}, _ = '_'},
|
||||
[],
|
||||
['$_']}]) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]),
|
||||
ok;
|
||||
[];
|
||||
Rs ->
|
||||
lists:foreach(
|
||||
fun(R) ->
|
||||
{Room, Host} = R#muc_room.name_host,
|
||||
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
||||
[] ->
|
||||
{ok, Pid} = mod_muc_room:start(
|
||||
Host,
|
||||
ServerHost,
|
||||
Access,
|
||||
Room,
|
||||
HistorySize,
|
||||
RoomShaper,
|
||||
R#muc_room.opts,
|
||||
?MODULE),
|
||||
register_room(Host, Room, Pid);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, Rs)
|
||||
Rs
|
||||
end;
|
||||
get_rooms(LServer, Host, odbc) ->
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select name, opts from muc_room ",
|
||||
"where host='", SHost, "';"]) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]),
|
||||
[];
|
||||
{selected, ["name", "opts"], RoomOpts} ->
|
||||
lists:map(
|
||||
fun({Room, Opts}) ->
|
||||
#muc_room{name_host = {Room, Host},
|
||||
opts = ejabberd_odbc:decode_term(Opts)}
|
||||
end, RoomOpts)
|
||||
end.
|
||||
|
||||
load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
|
||||
lists:foreach(
|
||||
fun(R) ->
|
||||
{Room, Host} = R#muc_room.name_host,
|
||||
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
||||
[] ->
|
||||
{ok, Pid} = mod_muc_room:start(
|
||||
Host,
|
||||
ServerHost,
|
||||
Access,
|
||||
Room,
|
||||
HistorySize,
|
||||
RoomShaper,
|
||||
R#muc_room.opts),
|
||||
register_room(Host, Room, Pid);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, get_rooms(ServerHost, Host)).
|
||||
|
||||
start_new_room(Host, ServerHost, Access, Room,
|
||||
HistorySize, RoomShaper, From,
|
||||
Nick, DefRoomOpts) ->
|
||||
case mnesia:dirty_read(muc_room, {Room, Host}) of
|
||||
[] ->
|
||||
case restore_room(ServerHost, Room, Host) of
|
||||
error ->
|
||||
?DEBUG("MUC: open new room '~s'~n", [Room]),
|
||||
mod_muc_room:start(Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, From,
|
||||
Nick, DefRoomOpts, ?MODULE);
|
||||
[#muc_room{opts = Opts}|_] ->
|
||||
Nick, DefRoomOpts);
|
||||
Opts ->
|
||||
?DEBUG("MUC: restore room '~s'~n", [Room]),
|
||||
mod_muc_room:start(Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, Opts, ?MODULE)
|
||||
RoomShaper, Opts)
|
||||
end.
|
||||
|
||||
register_room(Host, Room, Pid) ->
|
||||
@ -693,18 +784,44 @@ flush() ->
|
||||
iq_get_unique(From) ->
|
||||
{xmlcdata, sha:sha(term_to_binary([From, now(), randoms:get_string()]))}.
|
||||
|
||||
iq_get_register_info(Host, From, Lang) ->
|
||||
get_nick(ServerHost, Host, From) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
get_nick(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_nick(_LServer, Host, From, mnesia) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(From),
|
||||
LUS = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of
|
||||
{'EXIT', _Reason} ->
|
||||
error;
|
||||
[] ->
|
||||
error;
|
||||
[#muc_registered{nick = Nick}] ->
|
||||
Nick
|
||||
end;
|
||||
get_nick(LServer, Host, From, odbc) ->
|
||||
SJID = ejabberd_odbc:escape(
|
||||
jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(From)))),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select nick from muc_registered where "
|
||||
"jid='", SJID, "' and host='", SHost, "';"]) of
|
||||
{selected, ["nick"], [{Nick}]} ->
|
||||
Nick;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
iq_get_register_info(ServerHost, Host, From, Lang) ->
|
||||
{Nick, Registered} =
|
||||
case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of
|
||||
{'EXIT', _Reason} ->
|
||||
{"", []};
|
||||
[] ->
|
||||
{"", []};
|
||||
[#muc_registered{nick = N}] ->
|
||||
{N, [{xmlelement, "registered", [], []}]}
|
||||
end,
|
||||
case get_nick(ServerHost, Host, From) of
|
||||
error ->
|
||||
{"", []};
|
||||
N ->
|
||||
{N, [{xmlelement, "registered", [], []}]}
|
||||
end,
|
||||
Registered ++
|
||||
[{xmlelement, "instructions", [],
|
||||
[{xmlcdata,
|
||||
@ -722,7 +839,11 @@ iq_get_register_info(Host, From, Lang) ->
|
||||
Lang, "Enter nickname you want to register")}]},
|
||||
?XFIELD("text-single", "Nickname", "nick", Nick)]}].
|
||||
|
||||
iq_set_register_info(Host, From, Nick, Lang) ->
|
||||
set_nick(ServerHost, Host, From, Nick) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
set_nick(LServer, Host, From, Nick, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
set_nick(_LServer, Host, From, Nick, mnesia) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(From),
|
||||
LUS = {LUser, LServer},
|
||||
F = fun() ->
|
||||
@ -755,7 +876,48 @@ iq_set_register_info(Host, From, Nick, Lang) ->
|
||||
end
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
mnesia:transaction(F);
|
||||
set_nick(LServer, Host, From, Nick, odbc) ->
|
||||
JID = jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(From))),
|
||||
SJID = ejabberd_odbc:escape(JID),
|
||||
SNick = ejabberd_odbc:escape(Nick),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
F = fun() ->
|
||||
case Nick of
|
||||
"" ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from muc_registered where ",
|
||||
"jid='", SJID, "' and host='", Host, "';"]),
|
||||
ok;
|
||||
_ ->
|
||||
Allow =
|
||||
case ejabberd_odbc:sql_query_t(
|
||||
["select jid from muc_registered ",
|
||||
"where nick='", SNick, "' and host='",
|
||||
SHost, "';"]) of
|
||||
{selected, ["jid"], [{J}]} ->
|
||||
J == JID;
|
||||
_ ->
|
||||
true
|
||||
end,
|
||||
if Allow ->
|
||||
odbc_queries:update_t(
|
||||
"muc_registered",
|
||||
["jid", "host", "nick"],
|
||||
[SJID, SHost, SNick],
|
||||
["jid='", SJID, "' and host='", SHost, "'"]),
|
||||
ok;
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
iq_set_register_info(ServerHost, Host, From, Nick, Lang) ->
|
||||
case set_nick(ServerHost, Host, From, Nick) of
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
{atomic, false} ->
|
||||
@ -765,7 +927,7 @@ iq_set_register_info(Host, From, Nick, Lang) ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_iq_register_set(Host, From, SubEl, Lang) ->
|
||||
process_iq_register_set(ServerHost, Host, From, SubEl, Lang) ->
|
||||
{xmlelement, _Name, _Attrs, Els} = SubEl,
|
||||
case xml:get_subtag(SubEl, "remove") of
|
||||
false ->
|
||||
@ -783,7 +945,8 @@ process_iq_register_set(Host, From, SubEl, Lang) ->
|
||||
_ ->
|
||||
case lists:keysearch("nick", 1, XData) of
|
||||
{value, {_, [Nick]}} when Nick /= "" ->
|
||||
iq_set_register_info(Host, From, Nick, Lang);
|
||||
iq_set_register_info(ServerHost, Host,
|
||||
From, Nick, Lang);
|
||||
_ ->
|
||||
ErrText = "You must fill in field \"Nickname\" in the form",
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
|
||||
@ -796,7 +959,7 @@ process_iq_register_set(Host, From, SubEl, Lang) ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ ->
|
||||
iq_set_register_info(Host, From, "", Lang)
|
||||
iq_set_register_info(ServerHost, Host, From, "", Lang)
|
||||
end.
|
||||
|
||||
iq_get_vcard(Lang) ->
|
||||
|
@ -1,875 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_muc_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : MUC support (XEP-0045)
|
||||
%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_muc_odbc).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% API
|
||||
-export([start_link/2,
|
||||
start/2,
|
||||
stop/1,
|
||||
room_destroyed/4,
|
||||
store_room/4,
|
||||
restore_room/3,
|
||||
forget_room/3,
|
||||
create_room/5,
|
||||
process_iq_disco_items/4,
|
||||
broadcast_service_message/2,
|
||||
can_use_nick/4]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
|
||||
-record(muc_online_room, {name_host, pid}).
|
||||
|
||||
-record(state, {host,
|
||||
server_host,
|
||||
access,
|
||||
history_size,
|
||||
default_room_opts,
|
||||
room_shaper}).
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_muc).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
start(Host, Opts) ->
|
||||
start_supervisor(Host),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec =
|
||||
{Proc,
|
||||
{?MODULE, start_link, [Host, Opts]},
|
||||
temporary,
|
||||
1000,
|
||||
worker,
|
||||
[?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
stop_supervisor(Host),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
%% This function is called by a room in three situations:
|
||||
%% A) The owner of the room destroyed it
|
||||
%% B) The only participant of a temporary room leaves it
|
||||
%% C) mod_muc_odbc:stop was called, and each room is being terminated
|
||||
%% In this case, the mod_muc_odbc process died before the room processes
|
||||
%% So the message sending must be catched
|
||||
room_destroyed(Host, Room, Pid, ServerHost) ->
|
||||
catch gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
|
||||
{room_destroyed, {Room, Host}, Pid},
|
||||
ok.
|
||||
|
||||
%% @doc Create a room.
|
||||
%% If Opts = default, the default room options are used.
|
||||
%% Else use the passed options as defined in mod_muc_room.
|
||||
create_room(Host, Name, From, Nick, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
|
||||
|
||||
store_room(ServerHost, Host, Name, Opts) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
SOpts = ejabberd_odbc:encode_term(Opts),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"muc_room",
|
||||
["name", "host", "opts"],
|
||||
[SName, SHost, SOpts],
|
||||
["name='", SName, "' and host='", SHost, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
restore_room(ServerHost, Host, Name) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select opts from muc_room where name='",
|
||||
SName, "' and host='", SHost, "';"]) of
|
||||
{selected, ["opts"], [{Opts}]} ->
|
||||
ejabberd_odbc:decode_term(Opts);
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
forget_room(ServerHost, Host, Name) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from muc_room where name='",
|
||||
SName, "' and host='", SHost, "';"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
|
||||
Rsm = jlib:rsm_decode(IQ),
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_ITEMS}],
|
||||
iq_disco_items(Host, From, Lang, Rsm)}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res)).
|
||||
|
||||
can_use_nick(_ServerHost, _Host, _JID, "") ->
|
||||
false;
|
||||
can_use_nick(ServerHost, Host, JID, Nick) ->
|
||||
SJID = jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(JID))),
|
||||
SNick = ejabberd_odbc:escape(Nick),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select jid from muc_registered ",
|
||||
"where nick='", SNick, "' and host='",
|
||||
SHost, "';"]) of
|
||||
{selected, ["jid"], [{SJID1}]} ->
|
||||
SJID == SJID1;
|
||||
_ ->
|
||||
true
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Opts]) ->
|
||||
mnesia:create_table(muc_online_room,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, muc_online_room)}]),
|
||||
mnesia:add_table_copy(muc_online_room, node(), ram_copies),
|
||||
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
|
||||
clean_table_from_bad_node(node(), MyHost),
|
||||
mnesia:subscribe(system),
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
|
||||
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
|
||||
AccessPersistent = gen_mod:get_opt(access_persistent, Opts, all),
|
||||
HistorySize = gen_mod:get_opt(history_size, Opts, 20),
|
||||
DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []),
|
||||
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
|
||||
ejabberd_router:register_route(MyHost),
|
||||
load_permanent_rooms(MyHost, Host,
|
||||
{Access, AccessCreate, AccessAdmin, AccessPersistent},
|
||||
HistorySize,
|
||||
RoomShaper),
|
||||
{ok, #state{host = MyHost,
|
||||
server_host = Host,
|
||||
access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
|
||||
default_room_opts = DefRoomOpts,
|
||||
history_size = HistorySize,
|
||||
room_shaper = RoomShaper}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
|
||||
handle_call({create, Room, From, Nick, Opts},
|
||||
_From,
|
||||
#state{host = Host,
|
||||
server_host = ServerHost,
|
||||
access = Access,
|
||||
default_room_opts = DefOpts,
|
||||
history_size = HistorySize,
|
||||
room_shaper = RoomShaper} = State) ->
|
||||
?DEBUG("MUC: create new room '~s'~n", [Room]),
|
||||
NewOpts = case Opts of
|
||||
default -> DefOpts;
|
||||
_ -> Opts
|
||||
end,
|
||||
{ok, Pid} = mod_muc_room:start(
|
||||
Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, From,
|
||||
Nick, NewOpts, ?MODULE),
|
||||
register_room(Host, Room, Pid),
|
||||
{reply, ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({route, From, To, Packet},
|
||||
#state{host = Host,
|
||||
server_host = ServerHost,
|
||||
access = Access,
|
||||
default_room_opts = DefRoomOpts,
|
||||
history_size = HistorySize,
|
||||
room_shaper = RoomShaper} = State) ->
|
||||
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({room_destroyed, RoomHost, Pid}, State) ->
|
||||
F = fun() ->
|
||||
mnesia:delete_object(#muc_online_room{name_host = RoomHost,
|
||||
pid = Pid})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
{noreply, State};
|
||||
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
||||
clean_table_from_bad_node(Node),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_router:unregister_route(State#state.host),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
start_supervisor(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup),
|
||||
ChildSpec =
|
||||
{Proc,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
[Proc, mod_muc_room]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop_supervisor(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts) ->
|
||||
{AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
|
||||
case acl:match_rule(ServerHost, AccessRoute, From) of
|
||||
allow ->
|
||||
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts);
|
||||
_ ->
|
||||
{xmlelement, _Name, Attrs, _Els} = Packet,
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
ErrText = "Access denied by service policy",
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERRT_FORBIDDEN(Lang, ErrText)),
|
||||
ejabberd_router:route_error(To, From, Err, Packet)
|
||||
end.
|
||||
|
||||
|
||||
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts) ->
|
||||
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
|
||||
{Room, _, Nick} = jlib:jid_tolower(To),
|
||||
{xmlelement, Name, Attrs, _Els} = Packet,
|
||||
case Room of
|
||||
"" ->
|
||||
case Nick of
|
||||
"" ->
|
||||
case Name of
|
||||
"iq" ->
|
||||
case jlib:iq_query_info(Packet) of
|
||||
#iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
|
||||
sub_el = _SubEl, lang = Lang} = IQ ->
|
||||
Info = ejabberd_hooks:run_fold(
|
||||
disco_info, ServerHost, [],
|
||||
[ServerHost, ?MODULE, "", ""]),
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
iq_disco_info(Lang)
|
||||
++Info}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = get,
|
||||
xmlns = ?NS_DISCO_ITEMS} = IQ ->
|
||||
spawn(?MODULE,
|
||||
process_iq_disco_items,
|
||||
[Host, From, To, IQ]);
|
||||
#iq{type = get,
|
||||
xmlns = ?NS_REGISTER = XMLNS,
|
||||
lang = Lang,
|
||||
sub_el = _SubEl} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
iq_get_register_info(
|
||||
ServerHost, Host, From, Lang)}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = set,
|
||||
xmlns = ?NS_REGISTER = XMLNS,
|
||||
lang = Lang,
|
||||
sub_el = SubEl} = IQ ->
|
||||
case process_iq_register_set(
|
||||
ServerHost, Host, From, SubEl, Lang) of
|
||||
{result, IQRes} ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[{xmlelement, "query",
|
||||
[{"xmlns", XMLNS}],
|
||||
IQRes}]},
|
||||
ejabberd_router:route(
|
||||
To, From, jlib:iq_to_xml(Res));
|
||||
{error, Error} ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, Error),
|
||||
ejabberd_router:route(
|
||||
To, From, Err)
|
||||
end;
|
||||
#iq{type = get,
|
||||
xmlns = ?NS_VCARD = XMLNS,
|
||||
lang = Lang,
|
||||
sub_el = _SubEl} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[{xmlelement, "vCard",
|
||||
[{"xmlns", XMLNS}],
|
||||
iq_get_vcard(Lang)}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = get,
|
||||
xmlns = ?NS_MUC_UNIQUE
|
||||
} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[{xmlelement, "unique",
|
||||
[{"xmlns", ?NS_MUC_UNIQUE}],
|
||||
[iq_get_unique(From)]}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{} ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
"message" ->
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"error" ->
|
||||
ok;
|
||||
_ ->
|
||||
case acl:match_rule(ServerHost, AccessAdmin, From) of
|
||||
allow ->
|
||||
Msg = xml:get_path_s(
|
||||
Packet,
|
||||
[{elem, "body"}, cdata]),
|
||||
broadcast_service_message(Host, Msg);
|
||||
_ ->
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
ErrText = "Only service administrators "
|
||||
"are allowed to send service messages",
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_FORBIDDEN(Lang, ErrText)),
|
||||
ejabberd_router:route(
|
||||
To, From, Err)
|
||||
end
|
||||
end;
|
||||
"presence" ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"error" ->
|
||||
ok;
|
||||
"result" ->
|
||||
ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_ITEM_NOT_FOUND),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
||||
[] ->
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
case {Name, Type} of
|
||||
{"presence", ""} ->
|
||||
case check_user_can_create_room(ServerHost,
|
||||
AccessCreate, From,
|
||||
Room) of
|
||||
true ->
|
||||
{ok, Pid} = start_new_room(
|
||||
Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, From,
|
||||
Nick, DefRoomOpts),
|
||||
register_room(Host, Room, Pid),
|
||||
mod_muc_room:route(Pid, From, Nick, Packet),
|
||||
ok;
|
||||
false ->
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
ErrText = "Room creation is denied by service policy",
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
_ ->
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
ErrText = "Conference room does not exist",
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
[R] ->
|
||||
Pid = R#muc_online_room.pid,
|
||||
?DEBUG("MUC: send to process ~p~n", [Pid]),
|
||||
mod_muc_room:route(Pid, From, Nick, Packet),
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
check_user_can_create_room(ServerHost, AccessCreate, From, RoomID) ->
|
||||
case acl:match_rule(ServerHost, AccessCreate, From) of
|
||||
allow ->
|
||||
(length(RoomID) =< gen_mod:get_module_opt(ServerHost, ?MODULE,
|
||||
max_room_id, infinite));
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select name, opts from muc_room ",
|
||||
"where host='", SHost, "';"]) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]),
|
||||
ok;
|
||||
{selected, ["name", "opts"], RoomOpts} ->
|
||||
lists:foreach(
|
||||
fun({Room, Opts}) ->
|
||||
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
||||
[] ->
|
||||
{ok, Pid} = mod_muc_room:start(
|
||||
Host,
|
||||
ServerHost,
|
||||
Access,
|
||||
Room,
|
||||
HistorySize,
|
||||
RoomShaper,
|
||||
ejabberd_odbc:decode_term(Opts),
|
||||
?MODULE),
|
||||
register_room(Host, Room, Pid);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, RoomOpts)
|
||||
end.
|
||||
|
||||
start_new_room(Host, ServerHost, Access, Room,
|
||||
HistorySize, RoomShaper, From,
|
||||
Nick, DefRoomOpts) ->
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
SRoom = ejabberd_odbc:escape(Room),
|
||||
case ejabberd_odbc:sql_query(
|
||||
LServer, ["select opts from muc_room where name='", SRoom,
|
||||
"' and host='", SHost, "';"]) of
|
||||
{selected, ["opts"], []} ->
|
||||
?DEBUG("MUC: open new room '~s'~n", [Room]),
|
||||
mod_muc_room:start(Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, From,
|
||||
Nick, DefRoomOpts, ?MODULE);
|
||||
{selected, ["opts"], [{Opts}|_]} ->
|
||||
?DEBUG("MUC: restore room '~s'~n", [Room]),
|
||||
mod_muc_room:start(Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, ejabberd_odbc:decode_term(Opts),
|
||||
?MODULE)
|
||||
end.
|
||||
|
||||
register_room(Host, Room, Pid) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#muc_online_room{name_host = {Room, Host},
|
||||
pid = Pid})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
|
||||
iq_disco_info(Lang) ->
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "conference"},
|
||||
{"type", "text"},
|
||||
{"name", translate:translate(Lang, "Chatrooms")}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_MUC}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_MUC_UNIQUE}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_RSM}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}].
|
||||
|
||||
|
||||
iq_disco_items(Host, From, Lang, none) ->
|
||||
lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
|
||||
case catch gen_fsm:sync_send_all_state_event(
|
||||
Pid, {get_disco_item, From, Lang}, 100) of
|
||||
{item, Desc} ->
|
||||
flush(),
|
||||
{true,
|
||||
{xmlelement, "item",
|
||||
[{"jid", jlib:jid_to_string({Name, Host, ""})},
|
||||
{"name", Desc}], []}};
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end, get_vh_rooms(Host));
|
||||
|
||||
iq_disco_items(Host, From, Lang, Rsm) ->
|
||||
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
|
||||
RsmOut = jlib:rsm_encode(RsmO),
|
||||
lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
|
||||
case catch gen_fsm:sync_send_all_state_event(
|
||||
Pid, {get_disco_item, From, Lang}, 100) of
|
||||
{item, Desc} ->
|
||||
flush(),
|
||||
{true,
|
||||
{xmlelement, "item",
|
||||
[{"jid", jlib:jid_to_string({Name, Host, ""})},
|
||||
{"name", Desc}], []}};
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end, Rooms) ++ RsmOut.
|
||||
|
||||
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
|
||||
AllRooms = lists:sort(get_vh_rooms(Host)),
|
||||
Count = erlang:length(AllRooms),
|
||||
Guard = case Direction of
|
||||
_ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
|
||||
aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
|
||||
before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
|
||||
_ -> [{'==', {element, 2, '$1'}, Host}]
|
||||
end,
|
||||
L = lists:sort(
|
||||
mnesia:dirty_select(muc_online_room,
|
||||
[{#muc_online_room{name_host = '$1', _ = '_'},
|
||||
Guard,
|
||||
['$_']}])),
|
||||
L2 = if
|
||||
Index == undefined andalso Direction == before ->
|
||||
lists:reverse(lists:sublist(lists:reverse(L), 1, M));
|
||||
Index == undefined ->
|
||||
lists:sublist(L, 1, M);
|
||||
Index > Count orelse Index < 0 ->
|
||||
[];
|
||||
true ->
|
||||
lists:sublist(L, Index+1, M)
|
||||
end,
|
||||
if
|
||||
L2 == [] ->
|
||||
{L2, #rsm_out{count=Count}};
|
||||
true ->
|
||||
H = hd(L2),
|
||||
NewIndex = get_room_pos(H, AllRooms),
|
||||
T=lists:last(L2),
|
||||
{F, _}=H#muc_online_room.name_host,
|
||||
{Last, _}=T#muc_online_room.name_host,
|
||||
{L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}}
|
||||
end.
|
||||
|
||||
%% @doc Return the position of desired room in the list of rooms.
|
||||
%% The room must exist in the list. The count starts in 0.
|
||||
%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
|
||||
get_room_pos(Desired, Rooms) ->
|
||||
get_room_pos(Desired, Rooms, 0).
|
||||
get_room_pos(Desired, [HeadRoom | _], HeadPosition)
|
||||
when (Desired#muc_online_room.name_host ==
|
||||
HeadRoom#muc_online_room.name_host) ->
|
||||
HeadPosition;
|
||||
get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
|
||||
get_room_pos(Desired, Rooms, HeadPosition + 1).
|
||||
|
||||
flush() ->
|
||||
receive
|
||||
_ ->
|
||||
flush()
|
||||
after 0 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-define(XFIELD(Type, Label, Var, Val),
|
||||
{xmlelement, "field", [{"type", Type},
|
||||
{"label", translate:translate(Lang, Label)},
|
||||
{"var", Var}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
|
||||
|
||||
%% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of
|
||||
%% the requester JID, the local time and a random salt.
|
||||
%%
|
||||
%% "pseudo" because we don't verify that there is not a room
|
||||
%% with the returned Name already created, nor mark the generated Name
|
||||
%% as "already used". But in practice, it is unique enough. See
|
||||
%% http://xmpp.org/extensions/xep-0045.html#createroom-unique
|
||||
iq_get_unique(From) ->
|
||||
{xmlcdata, sha:sha(term_to_binary([From, now(), randoms:get_string()]))}.
|
||||
|
||||
iq_get_register_info(ServerHost, Host, From, Lang) ->
|
||||
SJID = ejabberd_odbc:escape(
|
||||
jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(From)))),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
{Nick, Registered} =
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select nick from muc_registered where "
|
||||
"jid='", SJID, "' and host='", SHost, "';"]) of
|
||||
{selected, ["nick"], [{N}]} ->
|
||||
{N, [{xmlelement, "registered", [], []}]};
|
||||
_ ->
|
||||
{"", []}
|
||||
end,
|
||||
Registered ++
|
||||
[{xmlelement, "instructions", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(
|
||||
Lang, "You need a client that supports x:data to register the nickname")}]},
|
||||
{xmlelement, "x",
|
||||
[{"xmlns", ?NS_XDATA}],
|
||||
[{xmlelement, "title", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(
|
||||
Lang, "Nickname Registration at ") ++ Host}]},
|
||||
{xmlelement, "instructions", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(
|
||||
Lang, "Enter nickname you want to register")}]},
|
||||
?XFIELD("text-single", "Nickname", "nick", Nick)]}].
|
||||
|
||||
iq_set_register_info(ServerHost, Host, From, Nick, Lang) ->
|
||||
JID = jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(From))),
|
||||
SJID = ejabberd_odbc:escape(JID),
|
||||
SNick = ejabberd_odbc:escape(Nick),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
F = fun() ->
|
||||
case Nick of
|
||||
"" ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from muc_registered where ",
|
||||
"jid='", SJID, "' and host='", Host, "';"]),
|
||||
ok;
|
||||
_ ->
|
||||
Allow =
|
||||
case ejabberd_odbc:sql_query_t(
|
||||
["select jid from muc_registered ",
|
||||
"where nick='", SNick, "' and host='",
|
||||
SHost, "';"]) of
|
||||
{selected, ["jid"], [{J}]} ->
|
||||
J == JID;
|
||||
_ ->
|
||||
true
|
||||
end,
|
||||
if Allow ->
|
||||
odbc_queries:update_t(
|
||||
"muc_registered",
|
||||
["jid", "host", "nick"],
|
||||
[SJID, SHost, SNick],
|
||||
["jid='", SJID, "' and host='", SHost, "'"]),
|
||||
ok;
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end,
|
||||
case catch ejabberd_odbc:sql_transaction(LServer, F) of
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
{atomic, false} ->
|
||||
ErrText = "That nickname is registered by another person",
|
||||
{error, ?ERRT_CONFLICT(Lang, ErrText)};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_iq_register_set(ServerHost, Host, From, SubEl, Lang) ->
|
||||
{xmlelement, _Name, _Attrs, Els} = SubEl,
|
||||
case xml:get_subtag(SubEl, "remove") of
|
||||
false ->
|
||||
case xml:remove_cdata(Els) of
|
||||
[{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
|
||||
case {xml:get_tag_attr_s("xmlns", XEl),
|
||||
xml:get_tag_attr_s("type", XEl)} of
|
||||
{?NS_XDATA, "cancel"} ->
|
||||
{result, []};
|
||||
{?NS_XDATA, "submit"} ->
|
||||
XData = jlib:parse_xdata_submit(XEl),
|
||||
case XData of
|
||||
invalid ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
_ ->
|
||||
case lists:keysearch("nick", 1, XData) of
|
||||
{value, {_, [Nick]}} when Nick /= "" ->
|
||||
iq_set_register_info(ServerHost, Host,
|
||||
From, Nick, Lang);
|
||||
_ ->
|
||||
ErrText = "You must fill in field \"Nickname\" in the form",
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ ->
|
||||
iq_set_register_info(ServerHost, Host, From, "", Lang)
|
||||
end.
|
||||
|
||||
iq_get_vcard(Lang) ->
|
||||
[{xmlelement, "FN", [],
|
||||
[{xmlcdata, "ejabberd/mod_muc"}]},
|
||||
{xmlelement, "URL", [],
|
||||
[{xmlcdata, ?EJABBERD_URI}]},
|
||||
{xmlelement, "DESC", [],
|
||||
[{xmlcdata, translate:translate(Lang, "ejabberd MUC module") ++
|
||||
"\nCopyright (c) 2003-2012 ProcessOne"}]}].
|
||||
|
||||
|
||||
broadcast_service_message(Host, Msg) ->
|
||||
lists:foreach(
|
||||
fun(#muc_online_room{pid = Pid}) ->
|
||||
gen_fsm:send_all_state_event(
|
||||
Pid, {service_message, Msg})
|
||||
end, get_vh_rooms(Host)).
|
||||
|
||||
get_vh_rooms(Host) ->
|
||||
mnesia:dirty_select(muc_online_room,
|
||||
[{#muc_online_room{name_host = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, Host}],
|
||||
['$_']}]).
|
||||
|
||||
|
||||
clean_table_from_bad_node(Node) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
muc_online_room,
|
||||
[{#muc_online_room{pid = '$1', _ = '_'},
|
||||
[{'==', {node, '$1'}, Node}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
||||
|
||||
clean_table_from_bad_node(Node, Host) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
muc_online_room,
|
||||
[{#muc_online_room{pid = '$1',
|
||||
name_host = {'_', Host},
|
||||
_ = '_'},
|
||||
[{'==', {node, '$1'}, Node}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
@ -31,10 +31,10 @@
|
||||
|
||||
|
||||
%% External exports
|
||||
-export([start_link/10,
|
||||
start_link/8,
|
||||
start/10,
|
||||
start/8,
|
||||
-export([start_link/9,
|
||||
start_link/7,
|
||||
start/9,
|
||||
start/7,
|
||||
route/4]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
@ -65,38 +65,38 @@
|
||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||
-define(SUPERVISOR_START,
|
||||
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Creator, Nick, DefRoomOpts, Mod],
|
||||
RoomShaper, Creator, Nick, DefRoomOpts],
|
||||
?FSMOPTS)).
|
||||
-else.
|
||||
-define(SUPERVISOR_START,
|
||||
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
|
||||
supervisor:start_child(
|
||||
Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||
Creator, Nick, DefRoomOpts, Mod])).
|
||||
Creator, Nick, DefRoomOpts])).
|
||||
-endif.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||
Creator, Nick, DefRoomOpts, Mod) ->
|
||||
Creator, Nick, DefRoomOpts) ->
|
||||
?SUPERVISOR_START.
|
||||
|
||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, Mod) ->
|
||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
|
||||
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
|
||||
supervisor:start_child(
|
||||
Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||
Opts, Mod]).
|
||||
Opts]).
|
||||
|
||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||
Creator, Nick, DefRoomOpts, Mod) ->
|
||||
Creator, Nick, DefRoomOpts) ->
|
||||
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Creator, Nick, DefRoomOpts, Mod],
|
||||
RoomShaper, Creator, Nick, DefRoomOpts],
|
||||
?FSMOPTS).
|
||||
|
||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, Mod) ->
|
||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
|
||||
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Opts, Mod],
|
||||
RoomShaper, Opts],
|
||||
?FSMOPTS).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
@ -110,14 +110,12 @@ start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, Mod) -
|
||||
%% ignore |
|
||||
%% {stop, StopReason}
|
||||
%%----------------------------------------------------------------------
|
||||
init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick,
|
||||
DefRoomOpts, Mod]) ->
|
||||
init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, DefRoomOpts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Shaper = shaper:new(RoomShaper),
|
||||
State = set_affiliation(Creator, owner,
|
||||
#state{host = Host,
|
||||
server_host = ServerHost,
|
||||
mod = Mod,
|
||||
access = Access,
|
||||
room = Room,
|
||||
history = lqueue_new(HistorySize),
|
||||
@ -130,12 +128,11 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick,
|
||||
add_to_log(room_existence, created, State1),
|
||||
add_to_log(room_existence, started, State1),
|
||||
{ok, normal_state, State1};
|
||||
init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, Mod]) ->
|
||||
init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Shaper = shaper:new(RoomShaper),
|
||||
State = set_opts(Opts, #state{host = Host,
|
||||
server_host = ServerHost,
|
||||
mod = Mod,
|
||||
access = Access,
|
||||
room = Room,
|
||||
history = lqueue_new(HistorySize),
|
||||
@ -164,8 +161,7 @@ normal_state({route, From, "",
|
||||
MinMessageInterval =
|
||||
trunc(gen_mod:get_module_opt(
|
||||
StateData#state.server_host,
|
||||
StateData#state.mod,
|
||||
min_message_interval, 0) * 1000000),
|
||||
mod_muc, min_message_interval, 0) * 1000000),
|
||||
Size = element_size(Packet),
|
||||
{MessageShaper, MessageShaperInterval} =
|
||||
shaper:update(Activity#activity.message_shaper, Size),
|
||||
@ -286,7 +282,7 @@ normal_state({route, From, "",
|
||||
StateData),
|
||||
case (NSD#state.config)#config.persistent of
|
||||
true ->
|
||||
(NSD#state.mod):store_room(
|
||||
mod_muc:store_room(
|
||||
NSD#state.server_host,
|
||||
NSD#state.host,
|
||||
NSD#state.room,
|
||||
@ -485,7 +481,7 @@ normal_state({route, From, Nick,
|
||||
MinPresenceInterval =
|
||||
trunc(gen_mod:get_module_opt(
|
||||
StateData#state.server_host,
|
||||
StateData#state.mod, min_presence_interval, 0) * 1000000),
|
||||
mod_muc, min_presence_interval, 0) * 1000000),
|
||||
if
|
||||
(Now >= Activity#activity.presence_time + MinPresenceInterval) and
|
||||
(Activity#activity.presence == undefined) ->
|
||||
@ -855,9 +851,8 @@ terminate(Reason, _StateName, StateData) ->
|
||||
tab_remove_online_user(LJID, StateData)
|
||||
end, [], StateData#state.users),
|
||||
add_to_log(room_existence, stopped, StateData),
|
||||
(StateData#state.mod):room_destroyed(
|
||||
StateData#state.host, StateData#state.room, self(),
|
||||
StateData#state.server_host),
|
||||
mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(),
|
||||
StateData#state.server_host),
|
||||
ok.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
@ -892,7 +887,7 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
|
||||
FromNick},
|
||||
case (NSD#state.config)#config.persistent of
|
||||
true ->
|
||||
(NSD#state.mod):store_room(
|
||||
mod_muc:store_room(
|
||||
NSD#state.server_host,
|
||||
NSD#state.host,
|
||||
NSD#state.room,
|
||||
@ -1031,9 +1026,9 @@ process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = Packet,
|
||||
case is_nick_change(From, Nick, StateData) of
|
||||
true ->
|
||||
case {nick_collision(From, Nick, StateData),
|
||||
(StateData#state.mod):can_use_nick(
|
||||
StateData#state.server_host,
|
||||
StateData#state.host, From, Nick),
|
||||
mod_muc:can_use_nick(
|
||||
StateData#state.server_host,
|
||||
StateData#state.host, From, Nick),
|
||||
{(StateData#state.config)#config.allow_visitor_nickchange,
|
||||
is_visitor(From, StateData)}} of
|
||||
{_, _, {false, true}} ->
|
||||
@ -1428,11 +1423,11 @@ get_max_users(StateData) ->
|
||||
|
||||
get_service_max_users(StateData) ->
|
||||
gen_mod:get_module_opt(StateData#state.server_host,
|
||||
StateData#state.mod, max_users, ?MAX_USERS_DEFAULT).
|
||||
mod_muc, max_users, ?MAX_USERS_DEFAULT).
|
||||
|
||||
get_max_users_admin_threshold(StateData) ->
|
||||
gen_mod:get_module_opt(StateData#state.server_host,
|
||||
StateData#state.mod, max_users_admin_threshold, 5).
|
||||
mod_muc, max_users_admin_threshold, 5).
|
||||
|
||||
get_user_activity(JID, StateData) ->
|
||||
case treap:lookup(jlib:jid_tolower(JID),
|
||||
@ -1442,11 +1437,11 @@ get_user_activity(JID, StateData) ->
|
||||
MessageShaper =
|
||||
shaper:new(gen_mod:get_module_opt(
|
||||
StateData#state.server_host,
|
||||
StateData#state.mod, user_message_shaper, none)),
|
||||
mod_muc, user_message_shaper, none)),
|
||||
PresenceShaper =
|
||||
shaper:new(gen_mod:get_module_opt(
|
||||
StateData#state.server_host,
|
||||
StateData#state.mod, user_presence_shaper, none)),
|
||||
mod_muc, user_presence_shaper, none)),
|
||||
#activity{message_shaper = MessageShaper,
|
||||
presence_shaper = PresenceShaper}
|
||||
end.
|
||||
@ -1455,11 +1450,11 @@ store_user_activity(JID, UserActivity, StateData) ->
|
||||
MinMessageInterval =
|
||||
gen_mod:get_module_opt(
|
||||
StateData#state.server_host,
|
||||
StateData#state.mod, min_message_interval, 0),
|
||||
mod_muc, min_message_interval, 0),
|
||||
MinPresenceInterval =
|
||||
gen_mod:get_module_opt(
|
||||
StateData#state.server_host,
|
||||
StateData#state.mod, min_presence_interval, 0),
|
||||
mod_muc, min_presence_interval, 0),
|
||||
Key = jlib:jid_tolower(JID),
|
||||
Now = now_to_usec(now()),
|
||||
Activity1 = clean_treap(StateData#state.activity, {1, -Now}),
|
||||
@ -1740,7 +1735,7 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
|
||||
NConferences = tab_count_user(From),
|
||||
MaxConferences = gen_mod:get_module_opt(
|
||||
StateData#state.server_host,
|
||||
StateData#state.mod, max_user_conferences, 10),
|
||||
mod_muc, max_user_conferences, 10),
|
||||
Collision = nick_collision(From, Nick, StateData),
|
||||
case {(ServiceAffiliation == owner orelse
|
||||
((Affiliation == admin orelse Affiliation == owner) andalso
|
||||
@ -1748,8 +1743,9 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
|
||||
NUsers < MaxUsers) andalso
|
||||
NConferences < MaxConferences,
|
||||
Collision,
|
||||
(StateData#state.mod):can_use_nick(StateData#state.server_host,
|
||||
StateData#state.host, From, Nick),
|
||||
mod_muc:can_use_nick(
|
||||
StateData#state.server_host,
|
||||
StateData#state.host, From, Nick),
|
||||
get_default_role(Affiliation, StateData)} of
|
||||
{false, _, _, _} ->
|
||||
% max user reached and user is not admin or owner
|
||||
@ -2589,9 +2585,9 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
|
||||
end, StateData, lists:flatten(Res)),
|
||||
case (NSD#state.config)#config.persistent of
|
||||
true ->
|
||||
(NSD#state.mod):store_room(NSD#state.server_host,
|
||||
NSD#state.host, NSD#state.room,
|
||||
make_opts(NSD));
|
||||
mod_muc:store_room(NSD#state.server_host,
|
||||
NSD#state.host, NSD#state.room,
|
||||
make_opts(NSD));
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
@ -3091,8 +3087,8 @@ is_allowed_room_name_desc_limits(XEl, StateData) ->
|
||||
jlib:parse_xdata_submit(XEl)) of
|
||||
{value, {_, [N]}} ->
|
||||
length(N) =< gen_mod:get_module_opt(StateData#state.server_host,
|
||||
StateData#state.mod,
|
||||
max_room_name, infinite);
|
||||
mod_muc, max_room_name,
|
||||
infinite);
|
||||
_ ->
|
||||
true
|
||||
end,
|
||||
@ -3101,8 +3097,8 @@ is_allowed_room_name_desc_limits(XEl, StateData) ->
|
||||
jlib:parse_xdata_submit(XEl)) of
|
||||
{value, {_, [D]}} ->
|
||||
length(D) =< gen_mod:get_module_opt(StateData#state.server_host,
|
||||
StateData#state.mod,
|
||||
max_room_desc, infinite);
|
||||
mod_muc, max_room_desc,
|
||||
infinite);
|
||||
_ ->
|
||||
true
|
||||
end,
|
||||
@ -3173,9 +3169,7 @@ is_password_settings_correct(XEl, StateData) ->
|
||||
|| JID <- JIDList]}).
|
||||
|
||||
get_default_room_maxusers(RoomState) ->
|
||||
DefRoomOpts = gen_mod:get_module_opt(
|
||||
RoomState#state.server_host,
|
||||
RoomState#state.mod, default_room_options, []),
|
||||
DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, mod_muc, default_room_options, []),
|
||||
RoomState2 = set_opts(DefRoomOpts, RoomState),
|
||||
(RoomState2#state.config)#config.max_users.
|
||||
|
||||
@ -3487,14 +3481,14 @@ set_xoption([_ | _Opts], _Config) ->
|
||||
|
||||
change_config(Config, StateData) ->
|
||||
NSD = StateData#state{config = Config},
|
||||
Mod = StateData#state.mod,
|
||||
case {(StateData#state.config)#config.persistent,
|
||||
Config#config.persistent} of
|
||||
{_, true} ->
|
||||
Mod:store_room(NSD#state.server_host, NSD#state.host,
|
||||
NSD#state.room, make_opts(NSD));
|
||||
mod_muc:store_room(NSD#state.server_host, NSD#state.host,
|
||||
NSD#state.room, make_opts(NSD));
|
||||
{true, false} ->
|
||||
Mod:forget_room(NSD#state.server_host, NSD#state.host, NSD#state.room);
|
||||
mod_muc:forget_room(NSD#state.server_host, NSD#state.host,
|
||||
NSD#state.room);
|
||||
{false, false} ->
|
||||
ok
|
||||
end,
|
||||
@ -3625,7 +3619,7 @@ destroy_room(DEl, StateData) ->
|
||||
end, ?DICT:to_list(StateData#state.users)),
|
||||
case (StateData#state.config)#config.persistent of
|
||||
true ->
|
||||
(StateData#state.mod):forget_room(
|
||||
mod_muc:forget_room(
|
||||
StateData#state.server_host,
|
||||
StateData#state.host, StateData#state.room);
|
||||
false ->
|
||||
|
@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_offline.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Store and manage offline messages in Mnesia database.
|
||||
%%% Purpose : Store and manage offline messages.
|
||||
%%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@ -29,15 +29,17 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([count_offline_messages/2]).
|
||||
|
||||
-export([start/2,
|
||||
loop/1,
|
||||
loop/2,
|
||||
stop/1,
|
||||
store_packet/3,
|
||||
resend_offline_messages/2,
|
||||
pop_offline_messages/3,
|
||||
get_sm_features/5,
|
||||
remove_expired_messages/0,
|
||||
remove_old_messages/1,
|
||||
remove_expired_messages/1,
|
||||
remove_old_messages/2,
|
||||
remove_user/2,
|
||||
get_queue_length/2,
|
||||
webadmin_page/3,
|
||||
@ -58,11 +60,17 @@
|
||||
-define(MAX_USER_MESSAGES, infinity).
|
||||
|
||||
start(Host, Opts) ->
|
||||
mnesia:create_table(offline_msg,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, offline_msg)}]),
|
||||
update_table(),
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(offline_msg,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes,
|
||||
record_info(fields, offline_msg)}]),
|
||||
update_table();
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:add(offline_message_hook, Host,
|
||||
?MODULE, store_packet, 50),
|
||||
ejabberd_hooks:add(resend_offline_messages_hook, Host,
|
||||
@ -83,44 +91,88 @@ start(Host, Opts) ->
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, loop, [AccessMaxOfflineMsgs])).
|
||||
spawn(?MODULE, loop, [Host, AccessMaxOfflineMsgs])).
|
||||
|
||||
loop(AccessMaxOfflineMsgs) ->
|
||||
loop(Host, AccessMaxOfflineMsgs) ->
|
||||
receive
|
||||
#offline_msg{us=US} = Msg ->
|
||||
Msgs = receive_all(US, [Msg]),
|
||||
#offline_msg{us = User} = Msg ->
|
||||
DBType = gen_mod:db_type(Host, ?MODULE),
|
||||
Msgs = receive_all(User, [Msg], DBType),
|
||||
Len = length(Msgs),
|
||||
{User, Host} = US,
|
||||
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
|
||||
User, Host),
|
||||
F = fun() ->
|
||||
%% Only count messages if needed:
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len + p1_mnesia:count_records(
|
||||
offline_msg,
|
||||
#offline_msg{us=US, _='_'});
|
||||
true ->
|
||||
0
|
||||
end,
|
||||
if
|
||||
Count > MaxOfflineMsgs ->
|
||||
discard_warn_sender(Msgs);
|
||||
true ->
|
||||
if
|
||||
Len >= ?OFFLINE_TABLE_LOCK_THRESHOLD ->
|
||||
mnesia:write_lock_table(offline_msg);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(fun(M) ->
|
||||
mnesia:write(M)
|
||||
end, Msgs)
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
loop(AccessMaxOfflineMsgs);
|
||||
_ ->
|
||||
loop(AccessMaxOfflineMsgs)
|
||||
store_offline_msg(Host, User, Msgs, Len, MaxOfflineMsgs, DBType),
|
||||
loop(Host, AccessMaxOfflineMsgs);
|
||||
_ ->
|
||||
loop(Host, AccessMaxOfflineMsgs)
|
||||
end.
|
||||
|
||||
store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs, mnesia) ->
|
||||
F = fun() ->
|
||||
%% Only count messages if needed:
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len + p1_mnesia:count_records(
|
||||
offline_msg,
|
||||
#offline_msg{us=US, _='_'});
|
||||
true ->
|
||||
0
|
||||
end,
|
||||
if
|
||||
Count > MaxOfflineMsgs ->
|
||||
discard_warn_sender(Msgs);
|
||||
true ->
|
||||
if
|
||||
Len >= ?OFFLINE_TABLE_LOCK_THRESHOLD ->
|
||||
mnesia:write_lock_table(offline_msg);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(fun(M) ->
|
||||
mnesia:write(M)
|
||||
end, Msgs)
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
store_offline_msg(Host, User, Msgs, Len, MaxOfflineMsgs, odbc) ->
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len + count_offline_messages(User, Host);
|
||||
true -> 0
|
||||
end,
|
||||
if
|
||||
Count > MaxOfflineMsgs ->
|
||||
discard_warn_sender(Msgs);
|
||||
true ->
|
||||
Query = lists:map(
|
||||
fun(M) ->
|
||||
Username =
|
||||
ejabberd_odbc:escape(
|
||||
(M#offline_msg.to)#jid.luser),
|
||||
From = M#offline_msg.from,
|
||||
To = M#offline_msg.to,
|
||||
{xmlelement, Name, Attrs, Els} =
|
||||
M#offline_msg.packet,
|
||||
Attrs2 = jlib:replace_from_to_attrs(
|
||||
jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
Packet = {xmlelement, Name, Attrs2,
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(
|
||||
M#offline_msg.timestamp),
|
||||
utc,
|
||||
jlib:make_jid("", Host, ""),
|
||||
"Offline Storage"),
|
||||
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
|
||||
jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(
|
||||
M#offline_msg.timestamp))]},
|
||||
XML =
|
||||
ejabberd_odbc:escape(
|
||||
xml:element_to_binary(Packet)),
|
||||
odbc_queries:add_spool_sql(Username, XML)
|
||||
end, Msgs),
|
||||
odbc_queries:add_spool(Host, Query)
|
||||
end.
|
||||
|
||||
%% Function copied from ejabberd_sm.erl:
|
||||
@ -132,15 +184,27 @@ get_max_user_messages(AccessRule, LUser, Host) ->
|
||||
_ -> ?MAX_USER_MESSAGES
|
||||
end.
|
||||
|
||||
receive_all(US, Msgs) ->
|
||||
receive_all(US, Msgs, DBType) ->
|
||||
receive
|
||||
#offline_msg{us=US} = Msg ->
|
||||
receive_all(US, [Msg | Msgs])
|
||||
receive_all(US, [Msg | Msgs], DBType)
|
||||
after 0 ->
|
||||
Msgs
|
||||
%% FIXME: the diff between mnesia and odbc version:
|
||||
%%
|
||||
%% after 0 ->
|
||||
%% - Msgs
|
||||
%% + lists:reverse(Msgs)
|
||||
%% end.
|
||||
%%
|
||||
%% Is it a bug in mnesia version?
|
||||
case DBType of
|
||||
mnesia ->
|
||||
Msgs;
|
||||
odbc ->
|
||||
lists:reverse(Msgs)
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||
?MODULE, store_packet, 50),
|
||||
@ -325,6 +389,10 @@ resend_offline_messages(User, Server) ->
|
||||
pop_offline_messages(Ls, User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
pop_offline_messages(Ls, LUser, LServer,
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
pop_offline_messages(Ls, LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
Rs = mnesia:wread({offline_msg, US}),
|
||||
@ -346,7 +414,7 @@ pop_offline_messages(Ls, User, Server) ->
|
||||
calendar:now_to_universal_time(
|
||||
R#offline_msg.timestamp),
|
||||
utc,
|
||||
jlib:make_jid("", Server, ""),
|
||||
jlib:make_jid("", LServer, ""),
|
||||
"Offline Storage"),
|
||||
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
|
||||
jlib:timestamp_to_xml(
|
||||
@ -365,10 +433,39 @@ pop_offline_messages(Ls, User, Server) ->
|
||||
lists:keysort(#offline_msg.timestamp, Rs)));
|
||||
_ ->
|
||||
Ls
|
||||
end;
|
||||
pop_offline_messages(Ls, LUser, LServer, odbc) ->
|
||||
EUser = ejabberd_odbc:escape(LUser),
|
||||
case odbc_queries:get_and_del_spool_msg_t(LServer, EUser) of
|
||||
{atomic, {selected, ["username","xml"], Rs}} ->
|
||||
Ls ++ lists:flatmap(
|
||||
fun({_, XML}) ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _Reason} ->
|
||||
[];
|
||||
El ->
|
||||
To = jlib:string_to_jid(
|
||||
xml:get_tag_attr_s("to", El)),
|
||||
From = jlib:string_to_jid(
|
||||
xml:get_tag_attr_s("from", El)),
|
||||
if
|
||||
(To /= error) and
|
||||
(From /= error) ->
|
||||
[{route, From, To, El}];
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
end, Rs);
|
||||
_ ->
|
||||
Ls
|
||||
end.
|
||||
|
||||
remove_expired_messages(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
remove_expired_messages(LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
remove_expired_messages() ->
|
||||
remove_expired_messages(_LServer, mnesia) ->
|
||||
TimeStamp = now(),
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(offline_msg),
|
||||
@ -387,9 +484,16 @@ remove_expired_messages() ->
|
||||
end
|
||||
end, ok, offline_msg)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
remove_expired_messages(_LServer, odbc) ->
|
||||
%% TODO
|
||||
{atomic, ok}.
|
||||
|
||||
remove_old_messages(Days) ->
|
||||
remove_old_messages(Days, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
remove_old_messages(Days, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
remove_old_messages(Days, _LServer, mnesia) ->
|
||||
{MegaSecs, Secs, _MicroSecs} = now(),
|
||||
S = MegaSecs * 1000000 + Secs - 60 * 60 * 24 * Days,
|
||||
MegaSecs1 = S div 1000000,
|
||||
@ -404,16 +508,25 @@ remove_old_messages(Days) ->
|
||||
(_Rec, _Acc) -> ok
|
||||
end, ok, offline_msg)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
remove_old_messages(_Days, _LServer, odbc) ->
|
||||
%% TODO
|
||||
{atomic, ok}.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
remove_user(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({offline_msg, US})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_spool_msg(LServer, Username).
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, offline_msg),
|
||||
@ -527,36 +640,76 @@ webadmin_page(_, Host,
|
||||
|
||||
webadmin_page(Acc, _, _) -> Acc.
|
||||
|
||||
read_all_msgs(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
lists:keysort(#offline_msg.timestamp,
|
||||
mnesia:dirty_read({offline_msg, US}));
|
||||
read_all_msgs(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select xml from spool"
|
||||
" where username='", Username, "'"
|
||||
" order by seq;"]) of
|
||||
{selected, ["username", "xml"], Rs} ->
|
||||
lists:flatmap(
|
||||
fun({XML}) ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _Reason} ->
|
||||
[];
|
||||
El ->
|
||||
[El]
|
||||
end
|
||||
end, Rs);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
format_user_queue(Msgs, mnesia) ->
|
||||
lists:map(
|
||||
fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
|
||||
packet = {xmlelement, Name, Attrs, Els}} = Msg) ->
|
||||
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
||||
calendar:now_to_local_time(TimeStamp),
|
||||
Time = lists:flatten(
|
||||
io_lib:format(
|
||||
"~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
[Year, Month, Day, Hour, Minute, Second])),
|
||||
SFrom = jlib:jid_to_string(From),
|
||||
STo = jlib:jid_to_string(To),
|
||||
Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
|
||||
Packet = {xmlelement, Name, Attrs2, Els},
|
||||
FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
|
||||
?XE("tr",
|
||||
[?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
|
||||
?XAC("td", [{"class", "valign"}], Time),
|
||||
?XAC("td", [{"class", "valign"}], SFrom),
|
||||
?XAC("td", [{"class", "valign"}], STo),
|
||||
?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
|
||||
)
|
||||
end, Msgs);
|
||||
format_user_queue(Msgs, odbc) ->
|
||||
lists:map(
|
||||
fun({xmlelement, _Name, _Attrs, _Els} = Msg) ->
|
||||
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
|
||||
Packet = Msg,
|
||||
FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
|
||||
?XE("tr",
|
||||
[?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
|
||||
?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
|
||||
)
|
||||
end, Msgs).
|
||||
|
||||
user_queue(User, Server, Query, Lang) ->
|
||||
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
|
||||
Res = user_queue_parse_query(US, Query),
|
||||
MsgsAll = lists:keysort(#offline_msg.timestamp,
|
||||
mnesia:dirty_read({offline_msg, US})),
|
||||
Msgs = get_messages_subset(User, Server, MsgsAll),
|
||||
FMsgs =
|
||||
lists:map(
|
||||
fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
|
||||
packet = {xmlelement, Name, Attrs, Els}} = Msg) ->
|
||||
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
||||
calendar:now_to_local_time(TimeStamp),
|
||||
Time = lists:flatten(
|
||||
io_lib:format(
|
||||
"~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
[Year, Month, Day, Hour, Minute, Second])),
|
||||
SFrom = jlib:jid_to_string(From),
|
||||
STo = jlib:jid_to_string(To),
|
||||
Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
|
||||
Packet = {xmlelement, Name, Attrs2, Els},
|
||||
FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
|
||||
?XE("tr",
|
||||
[?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
|
||||
?XAC("td", [{"class", "valign"}], Time),
|
||||
?XAC("td", [{"class", "valign"}], SFrom),
|
||||
?XAC("td", [{"class", "valign"}], STo),
|
||||
?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
|
||||
)
|
||||
end, Msgs),
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
Res = user_queue_parse_query(LUser, LServer, Query, DBType),
|
||||
MsgsAll = read_all_msgs(LUser, LServer, DBType),
|
||||
Msgs = get_messages_subset(User, Server, MsgsAll, DBType),
|
||||
FMsgs = format_user_queue(Msgs, DBType),
|
||||
[?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
|
||||
[us_to_list(US)]))] ++
|
||||
case Res of
|
||||
@ -587,7 +740,8 @@ user_queue(User, Server, Query, Lang) ->
|
||||
?INPUTT("submit", "delete", "Delete Selected")
|
||||
])].
|
||||
|
||||
user_queue_parse_query(US, Query) ->
|
||||
user_queue_parse_query(LUser, LServer, Query, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
case lists:keysearch("delete", 1, Query) of
|
||||
{value, _} ->
|
||||
Msgs = lists:keysort(#offline_msg.timestamp,
|
||||
@ -609,15 +763,74 @@ user_queue_parse_query(US, Query) ->
|
||||
ok;
|
||||
false ->
|
||||
nothing
|
||||
end;
|
||||
user_queue_parse_query(LUser, LServer, Query, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case lists:keysearch("delete", 1, Query) of
|
||||
{value, _} ->
|
||||
Msgs = case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select xml, seq from spool"
|
||||
" where username='", Username, "'"
|
||||
" order by seq;"]) of
|
||||
{selected, ["xml", "seq"], Rs} ->
|
||||
lists:flatmap(
|
||||
fun({XML, Seq}) ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _Reason} ->
|
||||
[];
|
||||
El ->
|
||||
[{El, Seq}]
|
||||
end
|
||||
end, Rs);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({Msg, Seq}) ->
|
||||
ID = jlib:encode_base64(
|
||||
binary_to_list(term_to_binary(Msg))),
|
||||
case lists:member({"selected", ID}, Query) of
|
||||
true ->
|
||||
SSeq = ejabberd_odbc:escape(Seq),
|
||||
catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["delete from spool"
|
||||
" where username='", Username, "'"
|
||||
" and seq='", SSeq, "';"]);
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end, Msgs)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok;
|
||||
false ->
|
||||
nothing
|
||||
end.
|
||||
|
||||
us_to_list({User, Server}) ->
|
||||
jlib:jid_to_string({User, Server, ""}).
|
||||
|
||||
get_queue_length(User, Server) ->
|
||||
length(mnesia:dirty_read({offline_msg, {User, Server}})).
|
||||
get_queue_length(LUser, LServer) ->
|
||||
get_queue_length(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_messages_subset(User, Host, MsgsAll) ->
|
||||
get_queue_length(LUser, LServer, mnesia) ->
|
||||
length(mnesia:dirty_read({offline_msg, {LUser, LServer}}));
|
||||
get_queue_length(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select count(*) from spool"
|
||||
" where username='", Username, "';"]) of
|
||||
{selected, [_], [{SCount}]} ->
|
||||
list_to_integer(SCount);
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_messages_subset(User, Host, MsgsAll, DBType) ->
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
|
||||
max_user_offline_messages),
|
||||
MaxOfflineMsgs = case get_max_user_messages(Access, User, Host) of
|
||||
@ -625,17 +838,23 @@ get_messages_subset(User, Host, MsgsAll) ->
|
||||
_ -> 100
|
||||
end,
|
||||
Length = length(MsgsAll),
|
||||
get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll).
|
||||
get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll, DBType).
|
||||
|
||||
get_messages_subset2(Max, Length, MsgsAll) when Length =< Max*2 ->
|
||||
get_messages_subset2(Max, Length, MsgsAll, _DBType) when Length =< Max*2 ->
|
||||
MsgsAll;
|
||||
get_messages_subset2(Max, Length, MsgsAll) ->
|
||||
get_messages_subset2(Max, Length, MsgsAll, mnesia) ->
|
||||
FirstN = Max,
|
||||
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
|
||||
MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2),
|
||||
NoJID = jlib:make_jid("...", "...", ""),
|
||||
IntermediateMsg = #offline_msg{timestamp = now(), from = NoJID, to = NoJID,
|
||||
packet = {xmlelement, "...", [], []}},
|
||||
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN;
|
||||
get_messages_subset2(Max, Length, MsgsAll, odbc) ->
|
||||
FirstN = Max,
|
||||
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
|
||||
MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2),
|
||||
IntermediateMsg = {xmlelement, "...", [], []},
|
||||
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
|
||||
|
||||
webadmin_user(Acc, User, Server, Lang) ->
|
||||
@ -644,16 +863,29 @@ webadmin_user(Acc, User, Server, Lang) ->
|
||||
integer_to_list(QueueLen))],
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||
|
||||
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
|
||||
US = {User, Server},
|
||||
delete_all_msgs(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
delete_all_msgs(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
delete_all_msgs(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(offline_msg),
|
||||
lists:foreach(
|
||||
fun(Msg) ->
|
||||
mnesia:delete_object(Msg)
|
||||
end, mnesia:dirty_read({offline_msg, US}))
|
||||
mnesia:write_lock_table(offline_msg),
|
||||
lists:foreach(
|
||||
fun(Msg) ->
|
||||
mnesia:delete_object(Msg)
|
||||
end, mnesia:dirty_read({offline_msg, US}))
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
mnesia:transaction(F);
|
||||
delete_all_msgs(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_spool_msg(LServer, Username),
|
||||
%% TODO: process the output
|
||||
{atomic, ok}.
|
||||
|
||||
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
|
||||
case delete_all_msgs(User, Server) of
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
@ -663,3 +895,14 @@ webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
|
||||
end;
|
||||
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
|
||||
Acc.
|
||||
|
||||
%% Returns as integer the number of offline messages for a given user
|
||||
count_offline_messages(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:count_records_where(
|
||||
LServer, "spool", "where username='" ++ Username ++ "'") of
|
||||
{selected, [_], [{Res}]} ->
|
||||
list_to_integer(Res);
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
@ -1,548 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_offline_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Store and manage offline messages in relational database.
|
||||
%%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_offline_odbc).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([count_offline_messages/2]).
|
||||
|
||||
-export([start/2,
|
||||
loop/2,
|
||||
stop/1,
|
||||
store_packet/3,
|
||||
pop_offline_messages/3,
|
||||
get_sm_features/5,
|
||||
remove_user/2,
|
||||
get_queue_length/2,
|
||||
webadmin_page/3,
|
||||
webadmin_user/4,
|
||||
webadmin_user_parse_query/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("web/ejabberd_http.hrl").
|
||||
-include("web/ejabberd_web_admin.hrl").
|
||||
|
||||
-record(offline_msg, {user, timestamp, expire, from, to, packet}).
|
||||
|
||||
-define(PROCNAME, ejabberd_offline).
|
||||
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
|
||||
|
||||
%% default value for the maximum number of user messages
|
||||
-define(MAX_USER_MESSAGES, infinity).
|
||||
|
||||
start(Host, Opts) ->
|
||||
ejabberd_hooks:add(offline_message_hook, Host,
|
||||
?MODULE, store_packet, 50),
|
||||
ejabberd_hooks:add(resend_offline_messages_hook, Host,
|
||||
?MODULE, pop_offline_messages, 50),
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:add(anonymous_purge_hook, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:add(disco_sm_features, Host,
|
||||
?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:add(disco_local_features, Host,
|
||||
?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:add(webadmin_page_host, Host,
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:add(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:add(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, loop, [Host, AccessMaxOfflineMsgs])).
|
||||
|
||||
loop(Host, AccessMaxOfflineMsgs) ->
|
||||
receive
|
||||
#offline_msg{user = User} = Msg ->
|
||||
Msgs = receive_all(User, [Msg]),
|
||||
Len = length(Msgs),
|
||||
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
|
||||
User, Host),
|
||||
|
||||
%% Only count existing messages if needed:
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len + count_offline_messages(User, Host);
|
||||
true -> 0
|
||||
end,
|
||||
if
|
||||
Count > MaxOfflineMsgs ->
|
||||
discard_warn_sender(Msgs);
|
||||
true ->
|
||||
Query = lists:map(
|
||||
fun(M) ->
|
||||
Username =
|
||||
ejabberd_odbc:escape(
|
||||
(M#offline_msg.to)#jid.luser),
|
||||
From = M#offline_msg.from,
|
||||
To = M#offline_msg.to,
|
||||
{xmlelement, Name, Attrs, Els} =
|
||||
M#offline_msg.packet,
|
||||
Attrs2 = jlib:replace_from_to_attrs(
|
||||
jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
Packet = {xmlelement, Name, Attrs2,
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(
|
||||
M#offline_msg.timestamp),
|
||||
utc,
|
||||
jlib:make_jid("", Host, ""),
|
||||
"Offline Storage"),
|
||||
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
|
||||
jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(
|
||||
M#offline_msg.timestamp))]},
|
||||
XML =
|
||||
ejabberd_odbc:escape(
|
||||
xml:element_to_binary(Packet)),
|
||||
odbc_queries:add_spool_sql(Username, XML)
|
||||
end, Msgs),
|
||||
case catch odbc_queries:add_spool(Host, Query) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~n", [Reason]);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("~p~n", [Reason]);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
loop(Host, AccessMaxOfflineMsgs);
|
||||
_ ->
|
||||
loop(Host, AccessMaxOfflineMsgs)
|
||||
end.
|
||||
|
||||
%% Function copied from ejabberd_sm.erl:
|
||||
get_max_user_messages(AccessRule, LUser, Host) ->
|
||||
case acl:match_rule(
|
||||
Host, AccessRule, jlib:make_jid(LUser, Host, "")) of
|
||||
Max when is_integer(Max) -> Max;
|
||||
infinity -> infinity;
|
||||
_ -> ?MAX_USER_MESSAGES
|
||||
end.
|
||||
|
||||
receive_all(Username, Msgs) ->
|
||||
receive
|
||||
#offline_msg{user=Username} = Msg ->
|
||||
receive_all(Username, [Msg | Msgs])
|
||||
after 0 ->
|
||||
lists:reverse(Msgs)
|
||||
end.
|
||||
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||
?MODULE, store_packet, 50),
|
||||
ejabberd_hooks:delete(resend_offline_messages_hook, Host,
|
||||
?MODULE, pop_offline_messages, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:delete(webadmin_page_host, Host,
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:delete(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
exit(whereis(Proc), stop),
|
||||
ok.
|
||||
|
||||
get_sm_features(Acc, _From, _To, "", _Lang) ->
|
||||
Feats = case Acc of
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
{result, Feats ++ [?NS_FEATURE_MSGOFFLINE]};
|
||||
|
||||
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
|
||||
%% override all lesser features...
|
||||
{result, []};
|
||||
|
||||
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
|
||||
store_packet(From, To, Packet) ->
|
||||
Type = xml:get_tag_attr_s("type", Packet),
|
||||
if
|
||||
(Type /= "error") and (Type /= "groupchat") and
|
||||
(Type /= "headline") ->
|
||||
case check_event_chatstates(From, To, Packet) of
|
||||
true ->
|
||||
#jid{luser = LUser} = To,
|
||||
TimeStamp = now(),
|
||||
{xmlelement, _Name, _Attrs, Els} = Packet,
|
||||
Expire = find_x_expire(TimeStamp, Els),
|
||||
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
|
||||
#offline_msg{user = LUser,
|
||||
timestamp = TimeStamp,
|
||||
expire = Expire,
|
||||
from = From,
|
||||
to = To,
|
||||
packet = Packet},
|
||||
stop;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% Check if the packet has any content about XEP-0022 or XEP-0085
|
||||
check_event_chatstates(From, To, Packet) ->
|
||||
{xmlelement, Name, Attrs, Els} = Packet,
|
||||
case find_x_event_chatstates(Els, {false, false, false}) of
|
||||
%% There wasn't any x:event or chatstates subelements
|
||||
{false, false, _} ->
|
||||
true;
|
||||
%% There a chatstates subelement and other stuff, but no x:event
|
||||
{false, CEl, true} when CEl /= false ->
|
||||
true;
|
||||
%% There was only a subelement: a chatstates
|
||||
{false, CEl, false} when CEl /= false ->
|
||||
%% Don't allow offline storage
|
||||
false;
|
||||
%% There was an x:event element, and maybe also other stuff
|
||||
{El, _, _} when El /= false ->
|
||||
case xml:get_subtag(El, "id") of
|
||||
false ->
|
||||
case xml:get_subtag(El, "offline") of
|
||||
false ->
|
||||
true;
|
||||
_ ->
|
||||
ID = case xml:get_tag_attr_s("id", Packet) of
|
||||
"" ->
|
||||
{xmlelement, "id", [], []};
|
||||
S ->
|
||||
{xmlelement, "id", [],
|
||||
[{xmlcdata, S}]}
|
||||
end,
|
||||
ejabberd_router:route(
|
||||
To, From, {xmlelement, Name, Attrs,
|
||||
[{xmlelement, "x",
|
||||
[{"xmlns", ?NS_EVENT}],
|
||||
[ID,
|
||||
{xmlelement, "offline", [], []}]}]
|
||||
}),
|
||||
true
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
|
||||
find_x_event_chatstates([], Res) ->
|
||||
Res;
|
||||
find_x_event_chatstates([{xmlcdata, _} | Els], Res) ->
|
||||
find_x_event_chatstates(Els, Res);
|
||||
find_x_event_chatstates([El | Els], {A, B, C}) ->
|
||||
case xml:get_tag_attr_s("xmlns", El) of
|
||||
?NS_EVENT ->
|
||||
find_x_event_chatstates(Els, {El, B, C});
|
||||
?NS_CHATSTATES ->
|
||||
find_x_event_chatstates(Els, {A, El, C});
|
||||
_ ->
|
||||
find_x_event_chatstates(Els, {A, B, true})
|
||||
end.
|
||||
|
||||
find_x_expire(_, []) ->
|
||||
never;
|
||||
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
|
||||
find_x_expire(TimeStamp, Els);
|
||||
find_x_expire(TimeStamp, [El | Els]) ->
|
||||
case xml:get_tag_attr_s("xmlns", El) of
|
||||
?NS_EXPIRE ->
|
||||
Val = xml:get_tag_attr_s("seconds", El),
|
||||
case catch list_to_integer(Val) of
|
||||
{'EXIT', _} ->
|
||||
never;
|
||||
Int when Int > 0 ->
|
||||
{MegaSecs, Secs, MicroSecs} = TimeStamp,
|
||||
S = MegaSecs * 1000000 + Secs + Int,
|
||||
MegaSecs1 = S div 1000000,
|
||||
Secs1 = S rem 1000000,
|
||||
{MegaSecs1, Secs1, MicroSecs};
|
||||
_ ->
|
||||
never
|
||||
end;
|
||||
_ ->
|
||||
find_x_expire(TimeStamp, Els)
|
||||
end.
|
||||
|
||||
|
||||
pop_offline_messages(Ls, User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
EUser = ejabberd_odbc:escape(LUser),
|
||||
case odbc_queries:get_and_del_spool_msg_t(LServer, EUser) of
|
||||
{atomic, {selected, ["username","xml"], Rs}} ->
|
||||
Ls ++ lists:flatmap(
|
||||
fun({_, XML}) ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _Reason} ->
|
||||
[];
|
||||
El ->
|
||||
To = jlib:string_to_jid(
|
||||
xml:get_tag_attr_s("to", El)),
|
||||
From = jlib:string_to_jid(
|
||||
xml:get_tag_attr_s("from", El)),
|
||||
if
|
||||
(To /= error) and
|
||||
(From /= error) ->
|
||||
[{route, From, To, El}];
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
end, Rs);
|
||||
_ ->
|
||||
Ls
|
||||
end.
|
||||
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_spool_msg(LServer, Username).
|
||||
|
||||
|
||||
%% Helper functions:
|
||||
|
||||
%% TODO: Warning - This function is a duplicate from mod_offline.erl
|
||||
%% It is duplicate to stay consistent (many functions are duplicated
|
||||
%% in this module). It will be refactored later on.
|
||||
%% Warn senders that their messages have been discarded:
|
||||
discard_warn_sender(Msgs) ->
|
||||
lists:foreach(
|
||||
fun(#offline_msg{from=From, to=To, packet=Packet}) ->
|
||||
ErrText = "Your contact offline message queue is full. The message has been discarded.",
|
||||
Lang = xml:get_tag_attr_s("xml:lang", Packet),
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)),
|
||||
ejabberd_router:route(
|
||||
To,
|
||||
From, Err)
|
||||
end, Msgs).
|
||||
|
||||
|
||||
webadmin_page(_, Host,
|
||||
#request{us = _US,
|
||||
path = ["user", U, "queue"],
|
||||
q = Query,
|
||||
lang = Lang} = _Request) ->
|
||||
Res = user_queue(U, Host, Query, Lang),
|
||||
{stop, Res};
|
||||
|
||||
webadmin_page(Acc, _, _) -> Acc.
|
||||
|
||||
user_queue(User, Server, Query, Lang) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
US = {LUser, LServer},
|
||||
Res = user_queue_parse_query(Username, LServer, Query),
|
||||
MsgsAll = case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select username, xml from spool"
|
||||
" where username='", Username, "'"
|
||||
" order by seq;"]) of
|
||||
{selected, ["username", "xml"], Rs} ->
|
||||
lists:flatmap(
|
||||
fun({_, XML}) ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _Reason} ->
|
||||
[];
|
||||
El ->
|
||||
[El]
|
||||
end
|
||||
end, Rs);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
Msgs = get_messages_subset(User, Server, MsgsAll),
|
||||
FMsgs =
|
||||
lists:map(
|
||||
fun({xmlelement, _Name, _Attrs, _Els} = Msg) ->
|
||||
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
|
||||
Packet = Msg,
|
||||
FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
|
||||
?XE("tr",
|
||||
[?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
|
||||
?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
|
||||
)
|
||||
end, Msgs),
|
||||
[?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
|
||||
[us_to_list(US)]))] ++
|
||||
case Res of
|
||||
ok -> [?XREST("Submitted")];
|
||||
nothing -> []
|
||||
end ++
|
||||
[?XAE("form", [{"action", ""}, {"method", "post"}],
|
||||
[?XE("table",
|
||||
[?XE("thead",
|
||||
[?XE("tr",
|
||||
[?X("td"),
|
||||
?XCT("td", "Packet")
|
||||
])]),
|
||||
?XE("tbody",
|
||||
if
|
||||
FMsgs == [] ->
|
||||
[?XE("tr",
|
||||
[?XAC("td", [{"colspan", "4"}], " ")]
|
||||
)];
|
||||
true ->
|
||||
FMsgs
|
||||
end
|
||||
)]),
|
||||
?BR,
|
||||
?INPUTT("submit", "delete", "Delete Selected")
|
||||
])].
|
||||
|
||||
user_queue_parse_query(Username, LServer, Query) ->
|
||||
case lists:keysearch("delete", 1, Query) of
|
||||
{value, _} ->
|
||||
Msgs = case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select xml, seq from spool"
|
||||
" where username='", Username, "'"
|
||||
" order by seq;"]) of
|
||||
{selected, ["xml", "seq"], Rs} ->
|
||||
lists:flatmap(
|
||||
fun({XML, Seq}) ->
|
||||
case xml_stream:parse_element(XML) of
|
||||
{error, _Reason} ->
|
||||
[];
|
||||
El ->
|
||||
[{El, Seq}]
|
||||
end
|
||||
end, Rs);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({Msg, Seq}) ->
|
||||
ID = jlib:encode_base64(
|
||||
binary_to_list(term_to_binary(Msg))),
|
||||
case lists:member({"selected", ID}, Query) of
|
||||
true ->
|
||||
SSeq = ejabberd_odbc:escape(Seq),
|
||||
catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["delete from spool"
|
||||
" where username='", Username, "'"
|
||||
" and seq='", SSeq, "';"]);
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end, Msgs)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok;
|
||||
false ->
|
||||
nothing
|
||||
end.
|
||||
|
||||
us_to_list({User, Server}) ->
|
||||
jlib:jid_to_string({User, Server, ""}).
|
||||
|
||||
get_queue_length(Username, LServer) ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select count(*) from spool"
|
||||
" where username='", Username, "';"]) of
|
||||
{selected, [_], [{SCount}]} ->
|
||||
SCount;
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_messages_subset(User, Host, MsgsAll) ->
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
|
||||
max_user_offline_messages),
|
||||
MaxOfflineMsgs = case get_max_user_messages(Access, User, Host) of
|
||||
Number when is_integer(Number) -> Number;
|
||||
_ -> 100
|
||||
end,
|
||||
Length = length(MsgsAll),
|
||||
get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll).
|
||||
|
||||
get_messages_subset2(Max, Length, MsgsAll) when Length =< Max*2 ->
|
||||
MsgsAll;
|
||||
get_messages_subset2(Max, Length, MsgsAll) ->
|
||||
FirstN = Max,
|
||||
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
|
||||
MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2),
|
||||
IntermediateMsg = {xmlelement, "...", [], []},
|
||||
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
|
||||
|
||||
webadmin_user(Acc, User, Server, Lang) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
QueueLen = get_queue_length(Username, LServer),
|
||||
FQueueLen = [?AC("queue/", QueueLen)],
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||
|
||||
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
|
||||
case catch odbc_queries:del_spool_msg(Server, User) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
_ ->
|
||||
?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]),
|
||||
{stop, ok}
|
||||
end;
|
||||
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
|
||||
Acc.
|
||||
|
||||
%% ------------------------------------------------
|
||||
%% mod_offline: number of messages quota management
|
||||
|
||||
%% Returns as integer the number of offline messages for a given user
|
||||
count_offline_messages(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:count_records_where(
|
||||
LServer, "spool", "where username='" ++ Username ++ "'") of
|
||||
{selected, [_], [{Res}]} ->
|
||||
list_to_integer(Res);
|
||||
_ ->
|
||||
0
|
||||
end.
|
@ -36,8 +36,21 @@
|
||||
get_user_list/3,
|
||||
check_packet/6,
|
||||
remove_user/2,
|
||||
item_to_raw/1,
|
||||
raw_to_item/1,
|
||||
is_list_needdb/1,
|
||||
updated_list/3]).
|
||||
|
||||
%% For mod_blocking
|
||||
-export([sql_add_privacy_list/2,
|
||||
sql_get_default_privacy_list/2,
|
||||
sql_get_default_privacy_list_t/1,
|
||||
sql_get_privacy_list_data/3,
|
||||
sql_get_privacy_list_data_by_id_t/1,
|
||||
sql_get_privacy_list_id_t/2,
|
||||
sql_set_default_privacy_list/2,
|
||||
sql_set_privacy_list/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
@ -45,9 +58,15 @@
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
mnesia:create_table(privacy, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, privacy)}]),
|
||||
update_table(),
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(privacy,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, privacy)}]),
|
||||
update_table();
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:add(privacy_iq_get, Host,
|
||||
?MODULE, process_iq_get, 50),
|
||||
ejabberd_hooks:add(privacy_iq_set, Host,
|
||||
@ -102,68 +121,116 @@ process_iq_get(_, From, _To, #iq{sub_el = SubEl},
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end.
|
||||
|
||||
|
||||
process_lists_get(LUser, LServer, Active) ->
|
||||
case process_lists_get(LUser, LServer, Active,
|
||||
gen_mod:db_type(LServer, ?MODULE)) of
|
||||
error ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{_Default, []} ->
|
||||
{result, [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}], []}]};
|
||||
{Default, LItems} ->
|
||||
DItems =
|
||||
case Default of
|
||||
none ->
|
||||
LItems;
|
||||
_ ->
|
||||
[{xmlelement, "default",
|
||||
[{"name", Default}], []} | LItems]
|
||||
end,
|
||||
ADItems =
|
||||
case Active of
|
||||
none ->
|
||||
DItems;
|
||||
_ ->
|
||||
[{xmlelement, "active",
|
||||
[{"name", Active}], []} | DItems]
|
||||
end,
|
||||
{result,
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
|
||||
ADItems}]}
|
||||
end.
|
||||
|
||||
process_lists_get(LUser, LServer, _Active, mnesia) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
error;
|
||||
[] ->
|
||||
{result, [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}], []}]};
|
||||
{none, []};
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
case Lists of
|
||||
[] ->
|
||||
{result, [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_PRIVACY}], []}]};
|
||||
_ ->
|
||||
LItems = lists:map(
|
||||
fun({N, _}) ->
|
||||
{xmlelement, "list",
|
||||
[{"name", N}], []}
|
||||
end, Lists),
|
||||
DItems =
|
||||
case Default of
|
||||
none ->
|
||||
LItems;
|
||||
_ ->
|
||||
[{xmlelement, "default",
|
||||
[{"name", Default}], []} | LItems]
|
||||
end,
|
||||
ADItems =
|
||||
case Active of
|
||||
none ->
|
||||
DItems;
|
||||
_ ->
|
||||
[{xmlelement, "active",
|
||||
[{"name", Active}], []} | DItems]
|
||||
end,
|
||||
{result,
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
|
||||
ADItems}]}
|
||||
end
|
||||
LItems = lists:map(
|
||||
fun({N, _}) ->
|
||||
{xmlelement, "list", [{"name", N}], []}
|
||||
end, Lists),
|
||||
{Default, LItems}
|
||||
end;
|
||||
process_lists_get(LUser, LServer, _Active, odbc) ->
|
||||
Default = case catch sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, ["name"], []} ->
|
||||
none;
|
||||
{selected, ["name"], [{DefName}]} ->
|
||||
DefName;
|
||||
_ ->
|
||||
none
|
||||
end,
|
||||
case catch sql_get_privacy_list_names(LUser, LServer) of
|
||||
{selected, ["name"], Names} ->
|
||||
LItems = lists:map(
|
||||
fun({N}) ->
|
||||
{xmlelement, "list", [{"name", N}], []}
|
||||
end, Names),
|
||||
{Default, LItems};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
process_list_get(LUser, LServer, {value, Name}) ->
|
||||
case process_list_get(LUser, LServer, Name,
|
||||
gen_mod:db_type(LServer, ?MODULE)) of
|
||||
error ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
not_found ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
Items ->
|
||||
LItems = lists:map(fun item_to_xml/1, Items),
|
||||
{result,
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
|
||||
[{xmlelement, "list",
|
||||
[{"name", Name}], LItems}]}]}
|
||||
end;
|
||||
process_list_get(_LUser, _LServer, false) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
process_list_get(LUser, LServer, Name, mnesia) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
error;
|
||||
[] ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
%{result, [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}], []}]};
|
||||
not_found;
|
||||
[#privacy{lists = Lists}] ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
LItems = lists:map(fun item_to_xml/1, List),
|
||||
{result,
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
|
||||
[{xmlelement, "list",
|
||||
[{"name", Name}], LItems}]}]};
|
||||
List;
|
||||
_ ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
not_found
|
||||
end
|
||||
end;
|
||||
|
||||
process_list_get(_LUser, _LServer, false) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
process_list_get(LUser, LServer, Name, odbc) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
|
||||
{selected, ["id"], []} ->
|
||||
not_found;
|
||||
{selected, ["id"], [{ID}]} ->
|
||||
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
RItems} ->
|
||||
lists:map(fun raw_to_item/1, RItems);
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
item_to_xml(Item) ->
|
||||
Attrs1 = [{"action", action_to_list(Item#listitem.action)},
|
||||
@ -269,98 +336,194 @@ process_iq_set(_, From, _To, #iq{sub_el = SubEl}) ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end.
|
||||
|
||||
process_default_set(LUser, LServer, Value) ->
|
||||
case process_default_set(LUser, LServer, Value,
|
||||
gen_mod:db_type(LServer, ?MODULE)) of
|
||||
{atomic, error} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{atomic, not_found} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
{atomic, ok} ->
|
||||
{result, []};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_default_set(LUser, LServer, {value, Name}) ->
|
||||
process_default_set(LUser, LServer, {value, Name}, mnesia) ->
|
||||
F = fun() ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
not_found;
|
||||
[#privacy{lists = Lists} = P] ->
|
||||
case lists:keymember(Name, 1, Lists) of
|
||||
true ->
|
||||
mnesia:write(P#privacy{default = Name,
|
||||
lists = Lists}),
|
||||
{result, []};
|
||||
ok;
|
||||
false ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
not_found
|
||||
end
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, {result, _} = Res} ->
|
||||
Res;
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
|
||||
process_default_set(LUser, LServer, false) ->
|
||||
mnesia:transaction(F);
|
||||
process_default_set(LUser, LServer, {value, Name}, odbc) ->
|
||||
F = fun() ->
|
||||
case sql_get_privacy_list_names_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
not_found;
|
||||
{selected, ["name"], Names} ->
|
||||
case lists:member({Name}, Names) of
|
||||
true ->
|
||||
sql_set_default_privacy_list(LUser, Name),
|
||||
ok;
|
||||
false ->
|
||||
not_found
|
||||
end
|
||||
end
|
||||
end,
|
||||
odbc_queries:sql_transaction(LServer, F);
|
||||
process_default_set(LUser, LServer, false, mnesia) ->
|
||||
F = fun() ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
{result, []};
|
||||
ok;
|
||||
[R] ->
|
||||
mnesia:write(R#privacy{default = none}),
|
||||
{result, []}
|
||||
mnesia:write(R#privacy{default = none})
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, {result, _} = Res} ->
|
||||
Res;
|
||||
mnesia:transaction(F);
|
||||
process_default_set(LUser, LServer, false, odbc) ->
|
||||
case catch sql_unset_default_privacy_list(LUser, LServer) of
|
||||
{'EXIT', _Reason} ->
|
||||
{atomic, error};
|
||||
{error, _Reason} ->
|
||||
{atomic, error};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
{atomic, ok}
|
||||
end.
|
||||
|
||||
|
||||
process_active_set(LUser, LServer, {value, Name}) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
[] ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
[#privacy{lists = Lists}] ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
{result, [], #userlist{name = Name, list = List, needdb = NeedDb}};
|
||||
false ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
end
|
||||
case process_active_set(LUser, LServer, Name,
|
||||
gen_mod:db_type(LServer, ?MODULE)) of
|
||||
error ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
Items ->
|
||||
NeedDb = is_list_needdb(Items),
|
||||
{result, [], #userlist{name = Name, list = Items, needdb = NeedDb}}
|
||||
end;
|
||||
|
||||
process_active_set(_LUser, _LServer, false) ->
|
||||
{result, [], #userlist{}}.
|
||||
|
||||
process_active_set(LUser, LServer, Name, mnesia) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
[] ->
|
||||
error;
|
||||
[#privacy{lists = Lists}] ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
List;
|
||||
false ->
|
||||
error
|
||||
end
|
||||
end;
|
||||
process_active_set(LUser, LServer, Name, odbc) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
|
||||
{selected, ["id"], []} ->
|
||||
error;
|
||||
{selected, ["id"], [{ID}]} ->
|
||||
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
RItems} ->
|
||||
lists:map(fun raw_to_item/1, RItems);
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
remove_privacy_list(LUser, LServer, Name, mnesia) ->
|
||||
F = fun() ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
ok;
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
%% TODO: check active
|
||||
if
|
||||
Name == Default ->
|
||||
conflict;
|
||||
true ->
|
||||
NewLists =
|
||||
lists:keydelete(Name, 1, Lists),
|
||||
mnesia:write(
|
||||
P#privacy{lists = NewLists})
|
||||
end
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
remove_privacy_list(LUser, LServer, Name, odbc) ->
|
||||
F = fun() ->
|
||||
case sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
sql_remove_privacy_list(LUser, Name),
|
||||
ok;
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
%% TODO: check active
|
||||
if
|
||||
Name == Default ->
|
||||
conflict;
|
||||
true ->
|
||||
sql_remove_privacy_list(LUser, Name),
|
||||
ok
|
||||
end
|
||||
end
|
||||
end,
|
||||
odbc_queries:sql_transaction(LServer, F).
|
||||
|
||||
set_privacy_list(LUser, LServer, Name, List, mnesia) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
NewLists = [{Name, List}],
|
||||
mnesia:write(#privacy{us = {LUser, LServer},
|
||||
lists = NewLists});
|
||||
[#privacy{lists = Lists} = P] ->
|
||||
NewLists1 = lists:keydelete(Name, 1, Lists),
|
||||
NewLists = [{Name, List} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists})
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
set_privacy_list(LUser, LServer, Name, List, odbc) ->
|
||||
RItems = lists:map(fun item_to_raw/1, List),
|
||||
F = fun() ->
|
||||
ID =
|
||||
case sql_get_privacy_list_id_t(LUser, Name) of
|
||||
{selected, ["id"], []} ->
|
||||
sql_add_privacy_list(LUser, Name),
|
||||
{selected, ["id"], [{I}]} =
|
||||
sql_get_privacy_list_id_t(LUser, Name),
|
||||
I;
|
||||
{selected, ["id"], [{I}]} ->
|
||||
I
|
||||
end,
|
||||
sql_set_privacy_list(ID, RItems),
|
||||
ok
|
||||
end,
|
||||
odbc_queries:sql_transaction(LServer, F).
|
||||
|
||||
process_list_set(LUser, LServer, {value, Name}, Els) ->
|
||||
case parse_items(Els) of
|
||||
false ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
remove ->
|
||||
F =
|
||||
fun() ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
{result, []};
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
% TODO: check active
|
||||
if
|
||||
Name == Default ->
|
||||
{error, ?ERR_CONFLICT};
|
||||
true ->
|
||||
NewLists =
|
||||
lists:keydelete(Name, 1, Lists),
|
||||
mnesia:write(
|
||||
P#privacy{lists = NewLists}),
|
||||
{result, []}
|
||||
end
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, {result, _} = Res} ->
|
||||
case remove_privacy_list(LUser, LServer, Name,
|
||||
gen_mod:db_type(LServer, ?MODULE)) of
|
||||
{atomic, conflict} ->
|
||||
{error, ?ERR_CONFLICT};
|
||||
{atomic, ok} ->
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
@ -368,30 +531,14 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
|
||||
[{privacy_list,
|
||||
#userlist{name = Name, list = []},
|
||||
Name}]}),
|
||||
Res;
|
||||
{result, []};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
List ->
|
||||
F =
|
||||
fun() ->
|
||||
case mnesia:wread({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
NewLists = [{Name, List}],
|
||||
mnesia:write(#privacy{us = {LUser, LServer},
|
||||
lists = NewLists}),
|
||||
{result, []};
|
||||
[#privacy{lists = Lists} = P] ->
|
||||
NewLists1 = lists:keydelete(Name, 1, Lists),
|
||||
NewLists = [{Name, List} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists}),
|
||||
{result, []}
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, {result, _} = Res} ->
|
||||
case set_privacy_list(LUser, LServer, Name, List,
|
||||
gen_mod:db_type(LServer, ?MODULE)) of
|
||||
{atomic, ok} ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
@ -400,7 +547,7 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
|
||||
[{privacy_list,
|
||||
#userlist{name = Name, list = List, needdb = NeedDb},
|
||||
Name}]}),
|
||||
Res;
|
||||
{result, []};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end
|
||||
@ -532,30 +679,51 @@ is_list_needdb(Items) ->
|
||||
end
|
||||
end, Items).
|
||||
|
||||
get_user_list(_, User, Server) ->
|
||||
get_user_list(Acc, User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
{Default, Items} = get_user_list(Acc, LUser, LServer,
|
||||
gen_mod:db_type(LServer, ?MODULE)),
|
||||
NeedDb = is_list_needdb(Items),
|
||||
#userlist{name = Default, list = Items, needdb = NeedDb}.
|
||||
|
||||
get_user_list(_, LUser, LServer, mnesia) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
[] ->
|
||||
#userlist{};
|
||||
{none, []};
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
case Default of
|
||||
none ->
|
||||
#userlist{};
|
||||
{none, []};
|
||||
_ ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
#userlist{name = Default, list = List, needdb = NeedDb};
|
||||
{Default, List};
|
||||
_ ->
|
||||
#userlist{}
|
||||
{none, []}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
#userlist{}
|
||||
{none, []}
|
||||
end;
|
||||
get_user_list(_, LUser, LServer, odbc) ->
|
||||
case catch sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, ["name"], []} ->
|
||||
{none, []};
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
case catch sql_get_privacy_list_data(LUser, LServer, Default) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
RItems} ->
|
||||
{Default, lists:map(fun raw_to_item/1, RItems)};
|
||||
_ ->
|
||||
{none, []}
|
||||
end;
|
||||
_ ->
|
||||
{none, []}
|
||||
end.
|
||||
|
||||
|
||||
%% From is the sender, To is the destination.
|
||||
%% If Dir = out, User@Server is the sender account (From).
|
||||
%% If Dir = in, User@Server is the destination account (To).
|
||||
@ -699,12 +867,16 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
remove_user(LUser, LServer, mnesia) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({privacy,
|
||||
{LUser, LServer}})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
sql_del_privacy_lists(LUser, LServer).
|
||||
|
||||
updated_list(_,
|
||||
#userlist{name = OldName} = Old,
|
||||
@ -716,6 +888,160 @@ updated_list(_,
|
||||
Old
|
||||
end.
|
||||
|
||||
raw_to_item({SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
|
||||
SMatchMessage, SMatchPresenceIn, SMatchPresenceOut}) ->
|
||||
{Type, Value} =
|
||||
case SType of
|
||||
"n" ->
|
||||
{none, none};
|
||||
"j" ->
|
||||
case jlib:string_to_jid(SValue) of
|
||||
#jid{} = JID ->
|
||||
{jid, jlib:jid_tolower(JID)}
|
||||
end;
|
||||
"g" ->
|
||||
{group, SValue};
|
||||
"s" ->
|
||||
case SValue of
|
||||
"none" ->
|
||||
{subscription, none};
|
||||
"both" ->
|
||||
{subscription, both};
|
||||
"from" ->
|
||||
{subscription, from};
|
||||
"to" ->
|
||||
{subscription, to}
|
||||
end
|
||||
end,
|
||||
Action =
|
||||
case SAction of
|
||||
"a" -> allow;
|
||||
"d" -> deny
|
||||
end,
|
||||
Order = list_to_integer(SOrder),
|
||||
MatchAll = ejabberd_odbc:to_bool(SMatchAll),
|
||||
MatchIQ = ejabberd_odbc:to_bool(SMatchIQ),
|
||||
MatchMessage = ejabberd_odbc:to_bool(SMatchMessage),
|
||||
MatchPresenceIn = ejabberd_odbc:to_bool(SMatchPresenceIn),
|
||||
MatchPresenceOut = ejabberd_odbc:to_bool(SMatchPresenceOut),
|
||||
#listitem{type = Type,
|
||||
value = Value,
|
||||
action = Action,
|
||||
order = Order,
|
||||
match_all = MatchAll,
|
||||
match_iq = MatchIQ,
|
||||
match_message = MatchMessage,
|
||||
match_presence_in = MatchPresenceIn,
|
||||
match_presence_out = MatchPresenceOut
|
||||
}.
|
||||
|
||||
item_to_raw(#listitem{type = Type,
|
||||
value = Value,
|
||||
action = Action,
|
||||
order = Order,
|
||||
match_all = MatchAll,
|
||||
match_iq = MatchIQ,
|
||||
match_message = MatchMessage,
|
||||
match_presence_in = MatchPresenceIn,
|
||||
match_presence_out = MatchPresenceOut
|
||||
}) ->
|
||||
{SType, SValue} =
|
||||
case Type of
|
||||
none ->
|
||||
{"n", ""};
|
||||
jid ->
|
||||
{"j", ejabberd_odbc:escape(jlib:jid_to_string(Value))};
|
||||
group ->
|
||||
{"g", ejabberd_odbc:escape(Value)};
|
||||
subscription ->
|
||||
case Value of
|
||||
none ->
|
||||
{"s", "none"};
|
||||
both ->
|
||||
{"s", "both"};
|
||||
from ->
|
||||
{"s", "from"};
|
||||
to ->
|
||||
{"s", "to"}
|
||||
end
|
||||
end,
|
||||
SAction =
|
||||
case Action of
|
||||
allow -> "a";
|
||||
deny -> "d"
|
||||
end,
|
||||
SOrder = integer_to_list(Order),
|
||||
SMatchAll = if MatchAll -> "1"; true -> "0" end,
|
||||
SMatchIQ = if MatchIQ -> "1"; true -> "0" end,
|
||||
SMatchMessage = if MatchMessage -> "1"; true -> "0" end,
|
||||
SMatchPresenceIn = if MatchPresenceIn -> "1"; true -> "0" end,
|
||||
SMatchPresenceOut = if MatchPresenceOut -> "1"; true -> "0" end,
|
||||
[SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
|
||||
SMatchMessage, SMatchPresenceIn, SMatchPresenceOut].
|
||||
|
||||
sql_get_default_privacy_list(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:get_default_privacy_list(LServer, Username).
|
||||
|
||||
sql_get_default_privacy_list_t(LUser) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:get_default_privacy_list_t(Username).
|
||||
|
||||
sql_get_privacy_list_names(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:get_privacy_list_names(LServer, Username).
|
||||
|
||||
sql_get_privacy_list_names_t(LUser) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:get_privacy_list_names_t(Username).
|
||||
|
||||
sql_get_privacy_list_id(LUser, LServer, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:get_privacy_list_id(LServer, Username, SName).
|
||||
|
||||
sql_get_privacy_list_id_t(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:get_privacy_list_id_t(Username, SName).
|
||||
|
||||
sql_get_privacy_list_data(LUser, LServer, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:get_privacy_list_data(LServer, Username, SName).
|
||||
|
||||
sql_get_privacy_list_data_by_id(ID, LServer) ->
|
||||
odbc_queries:get_privacy_list_data_by_id(LServer, ID).
|
||||
|
||||
sql_get_privacy_list_data_by_id_t(ID) ->
|
||||
odbc_queries:get_privacy_list_data_by_id_t(ID).
|
||||
|
||||
sql_set_default_privacy_list(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:set_default_privacy_list(Username, SName).
|
||||
|
||||
sql_unset_default_privacy_list(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:unset_default_privacy_list(LServer, Username).
|
||||
|
||||
sql_remove_privacy_list(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:remove_privacy_list(Username, SName).
|
||||
|
||||
sql_add_privacy_list(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:add_privacy_list(Username, SName).
|
||||
|
||||
sql_set_privacy_list(ID, RItems) ->
|
||||
odbc_queries:set_privacy_list(ID, RItems).
|
||||
|
||||
sql_del_privacy_lists(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Server = ejabberd_odbc:escape(LServer),
|
||||
odbc_queries:del_privacy_lists(LServer, Server, Username).
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, privacy),
|
||||
|
@ -1,878 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_privacy_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : jabber:iq:privacy support
|
||||
%%% Created : 5 Oct 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_privacy_odbc).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1,
|
||||
process_iq/3,
|
||||
process_iq_set/4,
|
||||
process_iq_get/5,
|
||||
get_user_list/3,
|
||||
check_packet/6,
|
||||
remove_user/2,
|
||||
item_to_raw/1,
|
||||
raw_to_item/1,
|
||||
updated_list/3]).
|
||||
|
||||
%% For mod_blocking_odbc
|
||||
-export([sql_add_privacy_list/2,
|
||||
sql_get_default_privacy_list/2,
|
||||
sql_get_default_privacy_list_t/1,
|
||||
sql_get_privacy_list_data/3,
|
||||
sql_get_privacy_list_data_by_id_t/1,
|
||||
sql_get_privacy_list_id_t/2,
|
||||
sql_set_default_privacy_list/2,
|
||||
sql_set_privacy_list/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
ejabberd_hooks:add(privacy_iq_get, Host,
|
||||
?MODULE, process_iq_get, 50),
|
||||
ejabberd_hooks:add(privacy_iq_set, Host,
|
||||
?MODULE, process_iq_set, 50),
|
||||
ejabberd_hooks:add(privacy_get_user_list, Host,
|
||||
?MODULE, get_user_list, 50),
|
||||
ejabberd_hooks:add(privacy_check_packet, Host,
|
||||
?MODULE, check_packet, 50),
|
||||
ejabberd_hooks:add(privacy_updated_list, Host,
|
||||
?MODULE, updated_list, 50),
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY,
|
||||
?MODULE, process_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(privacy_iq_get, Host,
|
||||
?MODULE, process_iq_get, 50),
|
||||
ejabberd_hooks:delete(privacy_iq_set, Host,
|
||||
?MODULE, process_iq_set, 50),
|
||||
ejabberd_hooks:delete(privacy_get_user_list, Host,
|
||||
?MODULE, get_user_list, 50),
|
||||
ejabberd_hooks:delete(privacy_check_packet, Host,
|
||||
?MODULE, check_packet, 50),
|
||||
ejabberd_hooks:delete(privacy_updated_list, Host,
|
||||
?MODULE, updated_list, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY).
|
||||
|
||||
process_iq(_From, _To, IQ) ->
|
||||
SubEl = IQ#iq.sub_el,
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
|
||||
|
||||
process_iq_get(_, From, _To, #iq{sub_el = SubEl},
|
||||
#userlist{name = Active}) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
{xmlelement, _, _, Els} = SubEl,
|
||||
case xml:remove_cdata(Els) of
|
||||
[] ->
|
||||
process_lists_get(LUser, LServer, Active);
|
||||
[{xmlelement, Name, Attrs, _SubEls}] ->
|
||||
case Name of
|
||||
"list" ->
|
||||
ListName = xml:get_attr("name", Attrs),
|
||||
process_list_get(LUser, LServer, ListName);
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end.
|
||||
|
||||
|
||||
process_lists_get(LUser, LServer, Active) ->
|
||||
Default = case catch sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, ["name"], []} ->
|
||||
none;
|
||||
{selected, ["name"], [{DefName}]} ->
|
||||
DefName;
|
||||
_ ->
|
||||
none
|
||||
end,
|
||||
case catch sql_get_privacy_list_names(LUser, LServer) of
|
||||
{selected, ["name"], []} ->
|
||||
{result, [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}], []}]};
|
||||
{selected, ["name"], Names} ->
|
||||
LItems = lists:map(
|
||||
fun({N}) ->
|
||||
{xmlelement, "list",
|
||||
[{"name", N}], []}
|
||||
end, Names),
|
||||
DItems =
|
||||
case Default of
|
||||
none ->
|
||||
LItems;
|
||||
_ ->
|
||||
[{xmlelement, "default",
|
||||
[{"name", Default}], []} | LItems]
|
||||
end,
|
||||
ADItems =
|
||||
case Active of
|
||||
none ->
|
||||
DItems;
|
||||
_ ->
|
||||
[{xmlelement, "active",
|
||||
[{"name", Active}], []} | DItems]
|
||||
end,
|
||||
{result,
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
|
||||
ADItems}]};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
process_list_get(LUser, LServer, {value, Name}) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
|
||||
{selected, ["id"], []} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
{selected, ["id"], [{ID}]} ->
|
||||
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
RItems} ->
|
||||
Items = lists:map(fun raw_to_item/1, RItems),
|
||||
LItems = lists:map(fun item_to_xml/1, Items),
|
||||
{result,
|
||||
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
|
||||
[{xmlelement, "list",
|
||||
[{"name", Name}], LItems}]}]};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
|
||||
process_list_get(_LUser, _LServer, false) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
item_to_xml(Item) ->
|
||||
Attrs1 = [{"action", action_to_list(Item#listitem.action)},
|
||||
{"order", order_to_list(Item#listitem.order)}],
|
||||
Attrs2 = case Item#listitem.type of
|
||||
none ->
|
||||
Attrs1;
|
||||
Type ->
|
||||
[{"type", type_to_list(Item#listitem.type)},
|
||||
{"value", value_to_list(Type, Item#listitem.value)} |
|
||||
Attrs1]
|
||||
end,
|
||||
SubEls = case Item#listitem.match_all of
|
||||
true ->
|
||||
[];
|
||||
false ->
|
||||
SE1 = case Item#listitem.match_iq of
|
||||
true ->
|
||||
[{xmlelement, "iq", [], []}];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
SE2 = case Item#listitem.match_message of
|
||||
true ->
|
||||
[{xmlelement, "message", [], []} | SE1];
|
||||
false ->
|
||||
SE1
|
||||
end,
|
||||
SE3 = case Item#listitem.match_presence_in of
|
||||
true ->
|
||||
[{xmlelement, "presence-in", [], []} | SE2];
|
||||
false ->
|
||||
SE2
|
||||
end,
|
||||
SE4 = case Item#listitem.match_presence_out of
|
||||
true ->
|
||||
[{xmlelement, "presence-out", [], []} | SE3];
|
||||
false ->
|
||||
SE3
|
||||
end,
|
||||
SE4
|
||||
end,
|
||||
{xmlelement, "item", Attrs2, SubEls}.
|
||||
|
||||
|
||||
action_to_list(Action) ->
|
||||
case Action of
|
||||
allow -> "allow";
|
||||
deny -> "deny"
|
||||
end.
|
||||
|
||||
order_to_list(Order) ->
|
||||
integer_to_list(Order).
|
||||
|
||||
type_to_list(Type) ->
|
||||
case Type of
|
||||
jid -> "jid";
|
||||
group -> "group";
|
||||
subscription -> "subscription"
|
||||
end.
|
||||
|
||||
value_to_list(Type, Val) ->
|
||||
case Type of
|
||||
jid -> jlib:jid_to_string(Val);
|
||||
group -> Val;
|
||||
subscription ->
|
||||
case Val of
|
||||
both -> "both";
|
||||
to -> "to";
|
||||
from -> "from";
|
||||
none -> "none"
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
|
||||
list_to_action(S) ->
|
||||
case S of
|
||||
"allow" -> allow;
|
||||
"deny" -> deny
|
||||
end.
|
||||
|
||||
|
||||
|
||||
process_iq_set(_, From, _To, #iq{sub_el = SubEl}) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
{xmlelement, _, _, Els} = SubEl,
|
||||
case xml:remove_cdata(Els) of
|
||||
[{xmlelement, Name, Attrs, SubEls}] ->
|
||||
ListName = xml:get_attr("name", Attrs),
|
||||
case Name of
|
||||
"list" ->
|
||||
process_list_set(LUser, LServer, ListName,
|
||||
xml:remove_cdata(SubEls));
|
||||
"active" ->
|
||||
process_active_set(LUser, LServer, ListName);
|
||||
"default" ->
|
||||
process_default_set(LUser, LServer, ListName);
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end.
|
||||
|
||||
|
||||
process_default_set(LUser, LServer, {value, Name}) ->
|
||||
F = fun() ->
|
||||
case sql_get_privacy_list_names_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
{selected, ["name"], Names} ->
|
||||
case lists:member({Name}, Names) of
|
||||
true ->
|
||||
sql_set_default_privacy_list(LUser, Name),
|
||||
{result, []};
|
||||
false ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
end
|
||||
end
|
||||
end,
|
||||
case odbc_queries:sql_transaction(LServer, F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, {result, _} = Res} ->
|
||||
Res;
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
|
||||
process_default_set(LUser, LServer, false) ->
|
||||
case catch sql_unset_default_privacy_list(LUser, LServer) of
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
{error, _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
_ ->
|
||||
{result, []}
|
||||
end.
|
||||
|
||||
|
||||
process_active_set(LUser, LServer, {value, Name}) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
|
||||
{selected, ["id"], []} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
{selected, ["id"], [{ID}]} ->
|
||||
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
RItems} ->
|
||||
Items = lists:map(fun raw_to_item/1, RItems),
|
||||
NeedDb = is_list_needdb(Items),
|
||||
{result, [], #userlist{name = Name, list = Items, needdb = NeedDb}};
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
|
||||
process_active_set(_LUser, _LServer, false) ->
|
||||
{result, [], #userlist{}}.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
process_list_set(LUser, LServer, {value, Name}, Els) ->
|
||||
case parse_items(Els) of
|
||||
false ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
remove ->
|
||||
F =
|
||||
fun() ->
|
||||
case sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, ["name"], []} ->
|
||||
sql_remove_privacy_list(LUser, Name),
|
||||
{result, []};
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
%% TODO: check active
|
||||
if
|
||||
Name == Default ->
|
||||
{error, ?ERR_CONFLICT};
|
||||
true ->
|
||||
sql_remove_privacy_list(LUser, Name),
|
||||
{result, []}
|
||||
end
|
||||
end
|
||||
end,
|
||||
case odbc_queries:sql_transaction(LServer, F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, {result, _} = Res} ->
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
{xmlelement, "broadcast", [],
|
||||
[{privacy_list,
|
||||
#userlist{name = Name, list = []},
|
||||
Name}]}),
|
||||
Res;
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end;
|
||||
List ->
|
||||
RItems = lists:map(fun item_to_raw/1, List),
|
||||
F =
|
||||
fun() ->
|
||||
ID =
|
||||
case sql_get_privacy_list_id_t(LUser, Name) of
|
||||
{selected, ["id"], []} ->
|
||||
sql_add_privacy_list(LUser, Name),
|
||||
{selected, ["id"], [{I}]} =
|
||||
sql_get_privacy_list_id_t(LUser, Name),
|
||||
I;
|
||||
{selected, ["id"], [{I}]} ->
|
||||
I
|
||||
end,
|
||||
sql_set_privacy_list(ID, RItems),
|
||||
{result, []}
|
||||
end,
|
||||
case odbc_queries:sql_transaction(LServer, F) of
|
||||
{atomic, {error, _} = Error} ->
|
||||
Error;
|
||||
{atomic, {result, _} = Res} ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
ejabberd_router:route(
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
jlib:make_jid(LUser, LServer, ""),
|
||||
{xmlelement, "broadcast", [],
|
||||
[{privacy_list,
|
||||
#userlist{name = Name, list = List, needdb = NeedDb},
|
||||
Name}]}),
|
||||
Res;
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end
|
||||
end;
|
||||
|
||||
process_list_set(_LUser, _LServer, false, _Els) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
|
||||
parse_items([]) ->
|
||||
remove;
|
||||
parse_items(Els) ->
|
||||
parse_items(Els, []).
|
||||
|
||||
parse_items([], Res) ->
|
||||
%% Sort the items by their 'order' attribute
|
||||
lists:keysort(#listitem.order, Res);
|
||||
parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
|
||||
Type = xml:get_attr("type", Attrs),
|
||||
Value = xml:get_attr("value", Attrs),
|
||||
SAction = xml:get_attr("action", Attrs),
|
||||
SOrder = xml:get_attr("order", Attrs),
|
||||
Action = case catch list_to_action(element(2, SAction)) of
|
||||
{'EXIT', _} -> false;
|
||||
Val -> Val
|
||||
end,
|
||||
Order = case catch list_to_integer(element(2, SOrder)) of
|
||||
{'EXIT', _} ->
|
||||
false;
|
||||
IntVal ->
|
||||
if
|
||||
IntVal >= 0 ->
|
||||
IntVal;
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
if
|
||||
(Action /= false) and (Order /= false) ->
|
||||
I1 = #listitem{action = Action, order = Order},
|
||||
I2 = case {Type, Value} of
|
||||
{{value, T}, {value, V}} ->
|
||||
case T of
|
||||
"jid" ->
|
||||
case jlib:string_to_jid(V) of
|
||||
error ->
|
||||
false;
|
||||
JID ->
|
||||
I1#listitem{
|
||||
type = jid,
|
||||
value = jlib:jid_tolower(JID)}
|
||||
end;
|
||||
"group" ->
|
||||
I1#listitem{type = group,
|
||||
value = V};
|
||||
"subscription" ->
|
||||
case V of
|
||||
"none" ->
|
||||
I1#listitem{type = subscription,
|
||||
value = none};
|
||||
"both" ->
|
||||
I1#listitem{type = subscription,
|
||||
value = both};
|
||||
"from" ->
|
||||
I1#listitem{type = subscription,
|
||||
value = from};
|
||||
"to" ->
|
||||
I1#listitem{type = subscription,
|
||||
value = to};
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end;
|
||||
{{value, _}, false} ->
|
||||
false;
|
||||
_ ->
|
||||
I1
|
||||
end,
|
||||
case I2 of
|
||||
false ->
|
||||
false;
|
||||
_ ->
|
||||
case parse_matches(I2, xml:remove_cdata(SubEls)) of
|
||||
false ->
|
||||
false;
|
||||
I3 ->
|
||||
parse_items(Els, [I3 | Res])
|
||||
end
|
||||
end;
|
||||
true ->
|
||||
false
|
||||
end;
|
||||
|
||||
parse_items(_, _Res) ->
|
||||
false.
|
||||
|
||||
|
||||
parse_matches(Item, []) ->
|
||||
Item#listitem{match_all = true};
|
||||
parse_matches(Item, Els) ->
|
||||
parse_matches1(Item, Els).
|
||||
|
||||
parse_matches1(Item, []) ->
|
||||
Item;
|
||||
parse_matches1(Item, [{xmlelement, "message", _, _} | Els]) ->
|
||||
parse_matches1(Item#listitem{match_message = true}, Els);
|
||||
parse_matches1(Item, [{xmlelement, "iq", _, _} | Els]) ->
|
||||
parse_matches1(Item#listitem{match_iq = true}, Els);
|
||||
parse_matches1(Item, [{xmlelement, "presence-in", _, _} | Els]) ->
|
||||
parse_matches1(Item#listitem{match_presence_in = true}, Els);
|
||||
parse_matches1(Item, [{xmlelement, "presence-out", _, _} | Els]) ->
|
||||
parse_matches1(Item#listitem{match_presence_out = true}, Els);
|
||||
parse_matches1(_Item, [{xmlelement, _, _, _} | _Els]) ->
|
||||
false.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
is_list_needdb(Items) ->
|
||||
lists:any(
|
||||
fun(X) ->
|
||||
case X#listitem.type of
|
||||
subscription -> true;
|
||||
group -> true;
|
||||
_ -> false
|
||||
end
|
||||
end, Items).
|
||||
|
||||
get_user_list(_, User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
|
||||
case catch sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, ["name"], []} ->
|
||||
#userlist{};
|
||||
{selected, ["name"], [{Default}]} ->
|
||||
case catch sql_get_privacy_list_data(LUser, LServer, Default) of
|
||||
{selected, ["t", "value", "action", "ord", "match_all",
|
||||
"match_iq", "match_message",
|
||||
"match_presence_in", "match_presence_out"],
|
||||
RItems} ->
|
||||
Items = lists:map(fun raw_to_item/1, RItems),
|
||||
NeedDb = is_list_needdb(Items),
|
||||
#userlist{name = Default, list = Items, needdb = NeedDb};
|
||||
_ ->
|
||||
#userlist{}
|
||||
end;
|
||||
_ ->
|
||||
#userlist{}
|
||||
end.
|
||||
|
||||
|
||||
%% From is the sender, To is the destination.
|
||||
%% If Dir = out, User@Server is the sender account (From).
|
||||
%% If Dir = in, User@Server is the destination account (To).
|
||||
check_packet(_, _User, _Server,
|
||||
_UserList,
|
||||
{#jid{luser = "", lserver = Server} = _From,
|
||||
#jid{lserver = Server} = _To,
|
||||
_},
|
||||
in) ->
|
||||
allow;
|
||||
check_packet(_, _User, _Server,
|
||||
_UserList,
|
||||
{#jid{lserver = Server} = _From,
|
||||
#jid{luser = "", lserver = Server} = _To,
|
||||
_},
|
||||
out) ->
|
||||
allow;
|
||||
check_packet(_, _User, _Server,
|
||||
_UserList,
|
||||
{#jid{luser = User, lserver = Server} = _From,
|
||||
#jid{luser = User, lserver = Server} = _To,
|
||||
_},
|
||||
_Dir) ->
|
||||
allow;
|
||||
check_packet(_, User, Server,
|
||||
#userlist{list = List, needdb = NeedDb},
|
||||
{From, To, {xmlelement, PName, Attrs, _}},
|
||||
Dir) ->
|
||||
case List of
|
||||
[] ->
|
||||
allow;
|
||||
_ ->
|
||||
PType = case PName of
|
||||
"message" -> message;
|
||||
"iq" -> iq;
|
||||
"presence" ->
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
%% notification
|
||||
"" -> presence;
|
||||
"unavailable" -> presence;
|
||||
%% subscribe, subscribed, unsubscribe,
|
||||
%% unsubscribed, error, probe, or other
|
||||
_ -> other
|
||||
end
|
||||
end,
|
||||
PType2 = case {PType, Dir} of
|
||||
{message, in} -> message;
|
||||
{iq, in} -> iq;
|
||||
{presence, in} -> presence_in;
|
||||
{presence, out} -> presence_out;
|
||||
{_, _} -> other
|
||||
end,
|
||||
LJID = case Dir of
|
||||
in -> jlib:jid_tolower(From);
|
||||
out -> jlib:jid_tolower(To)
|
||||
end,
|
||||
{Subscription, Groups} =
|
||||
case NeedDb of
|
||||
true -> ejabberd_hooks:run_fold(roster_get_jid_info,
|
||||
jlib:nameprep(Server),
|
||||
{none, []},
|
||||
[User, Server, LJID]);
|
||||
false -> {[], []}
|
||||
end,
|
||||
check_packet_aux(List, PType2, LJID, Subscription, Groups)
|
||||
end.
|
||||
|
||||
check_packet_aux([], _PType, _JID, _Subscription, _Groups) ->
|
||||
allow;
|
||||
check_packet_aux([Item | List], PType, JID, Subscription, Groups) ->
|
||||
#listitem{type = Type, value = Value, action = Action} = Item,
|
||||
case is_ptype_match(Item, PType) of
|
||||
true ->
|
||||
case Type of
|
||||
none ->
|
||||
Action;
|
||||
_ ->
|
||||
case is_type_match(Type, Value,
|
||||
JID, Subscription, Groups) of
|
||||
true ->
|
||||
Action;
|
||||
false ->
|
||||
check_packet_aux(List, PType,
|
||||
JID, Subscription, Groups)
|
||||
end
|
||||
end;
|
||||
false ->
|
||||
check_packet_aux(List, PType, JID, Subscription, Groups)
|
||||
end.
|
||||
|
||||
|
||||
is_ptype_match(Item, PType) ->
|
||||
case Item#listitem.match_all of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
case PType of
|
||||
message ->
|
||||
Item#listitem.match_message;
|
||||
iq ->
|
||||
Item#listitem.match_iq;
|
||||
presence_in ->
|
||||
Item#listitem.match_presence_in;
|
||||
presence_out ->
|
||||
Item#listitem.match_presence_out;
|
||||
other ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
is_type_match(Type, Value, JID, Subscription, Groups) ->
|
||||
case Type of
|
||||
jid ->
|
||||
case Value of
|
||||
{"", Server, ""} ->
|
||||
case JID of
|
||||
{_, Server, _} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
{User, Server, ""} ->
|
||||
case JID of
|
||||
{User, Server, _} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
Value == JID
|
||||
end;
|
||||
subscription ->
|
||||
Value == Subscription;
|
||||
group ->
|
||||
lists:member(Value, Groups)
|
||||
end.
|
||||
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
sql_del_privacy_lists(LUser, LServer).
|
||||
|
||||
|
||||
updated_list(_,
|
||||
#userlist{name = OldName} = Old,
|
||||
#userlist{name = NewName} = New) ->
|
||||
if
|
||||
OldName == NewName ->
|
||||
New;
|
||||
true ->
|
||||
Old
|
||||
end.
|
||||
|
||||
|
||||
raw_to_item({SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
|
||||
SMatchMessage, SMatchPresenceIn, SMatchPresenceOut}) ->
|
||||
{Type, Value} =
|
||||
case SType of
|
||||
"n" ->
|
||||
{none, none};
|
||||
"j" ->
|
||||
case jlib:string_to_jid(SValue) of
|
||||
#jid{} = JID ->
|
||||
{jid, jlib:jid_tolower(JID)}
|
||||
end;
|
||||
"g" ->
|
||||
{group, SValue};
|
||||
"s" ->
|
||||
case SValue of
|
||||
"none" ->
|
||||
{subscription, none};
|
||||
"both" ->
|
||||
{subscription, both};
|
||||
"from" ->
|
||||
{subscription, from};
|
||||
"to" ->
|
||||
{subscription, to}
|
||||
end
|
||||
end,
|
||||
Action =
|
||||
case SAction of
|
||||
"a" -> allow;
|
||||
"d" -> deny
|
||||
end,
|
||||
Order = list_to_integer(SOrder),
|
||||
MatchAll = ejabberd_odbc:to_bool(SMatchAll),
|
||||
MatchIQ = ejabberd_odbc:to_bool(SMatchIQ),
|
||||
MatchMessage = ejabberd_odbc:to_bool(SMatchMessage),
|
||||
MatchPresenceIn = ejabberd_odbc:to_bool(SMatchPresenceIn),
|
||||
MatchPresenceOut = ejabberd_odbc:to_bool(SMatchPresenceOut),
|
||||
#listitem{type = Type,
|
||||
value = Value,
|
||||
action = Action,
|
||||
order = Order,
|
||||
match_all = MatchAll,
|
||||
match_iq = MatchIQ,
|
||||
match_message = MatchMessage,
|
||||
match_presence_in = MatchPresenceIn,
|
||||
match_presence_out = MatchPresenceOut
|
||||
}.
|
||||
|
||||
item_to_raw(#listitem{type = Type,
|
||||
value = Value,
|
||||
action = Action,
|
||||
order = Order,
|
||||
match_all = MatchAll,
|
||||
match_iq = MatchIQ,
|
||||
match_message = MatchMessage,
|
||||
match_presence_in = MatchPresenceIn,
|
||||
match_presence_out = MatchPresenceOut
|
||||
}) ->
|
||||
{SType, SValue} =
|
||||
case Type of
|
||||
none ->
|
||||
{"n", ""};
|
||||
jid ->
|
||||
{"j", ejabberd_odbc:escape(jlib:jid_to_string(Value))};
|
||||
group ->
|
||||
{"g", ejabberd_odbc:escape(Value)};
|
||||
subscription ->
|
||||
case Value of
|
||||
none ->
|
||||
{"s", "none"};
|
||||
both ->
|
||||
{"s", "both"};
|
||||
from ->
|
||||
{"s", "from"};
|
||||
to ->
|
||||
{"s", "to"}
|
||||
end
|
||||
end,
|
||||
SAction =
|
||||
case Action of
|
||||
allow -> "a";
|
||||
deny -> "d"
|
||||
end,
|
||||
SOrder = integer_to_list(Order),
|
||||
SMatchAll = if MatchAll -> "1"; true -> "0" end,
|
||||
SMatchIQ = if MatchIQ -> "1"; true -> "0" end,
|
||||
SMatchMessage = if MatchMessage -> "1"; true -> "0" end,
|
||||
SMatchPresenceIn = if MatchPresenceIn -> "1"; true -> "0" end,
|
||||
SMatchPresenceOut = if MatchPresenceOut -> "1"; true -> "0" end,
|
||||
[SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
|
||||
SMatchMessage, SMatchPresenceIn, SMatchPresenceOut].
|
||||
|
||||
sql_get_default_privacy_list(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:get_default_privacy_list(LServer, Username).
|
||||
|
||||
sql_get_default_privacy_list_t(LUser) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:get_default_privacy_list_t(Username).
|
||||
|
||||
sql_get_privacy_list_names(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:get_privacy_list_names(LServer, Username).
|
||||
|
||||
sql_get_privacy_list_names_t(LUser) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:get_privacy_list_names_t(Username).
|
||||
|
||||
sql_get_privacy_list_id(LUser, LServer, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:get_privacy_list_id(LServer, Username, SName).
|
||||
|
||||
sql_get_privacy_list_id_t(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:get_privacy_list_id_t(Username, SName).
|
||||
|
||||
sql_get_privacy_list_data(LUser, LServer, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:get_privacy_list_data(LServer, Username, SName).
|
||||
|
||||
sql_get_privacy_list_data_by_id(ID, LServer) ->
|
||||
odbc_queries:get_privacy_list_data_by_id(LServer, ID).
|
||||
|
||||
sql_get_privacy_list_data_by_id_t(ID) ->
|
||||
odbc_queries:get_privacy_list_data_by_id_t(ID).
|
||||
|
||||
sql_set_default_privacy_list(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:set_default_privacy_list(Username, SName).
|
||||
|
||||
sql_unset_default_privacy_list(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:unset_default_privacy_list(LServer, Username).
|
||||
|
||||
sql_remove_privacy_list(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:remove_privacy_list(Username, SName).
|
||||
|
||||
sql_add_privacy_list(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:add_privacy_list(Username, SName).
|
||||
|
||||
sql_set_privacy_list(ID, RItems) ->
|
||||
odbc_queries:set_privacy_list(ID, RItems).
|
||||
|
||||
sql_del_privacy_lists(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Server = ejabberd_odbc:escape(LServer),
|
||||
odbc_queries:del_privacy_lists(LServer, Server, Username).
|
@ -46,10 +46,16 @@
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
mnesia:create_table(private_storage,
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes, record_info(fields, private_storage)}]),
|
||||
update_table(),
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(private_storage,
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, private_storage)}]),
|
||||
update_table();
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE,
|
||||
@ -73,12 +79,21 @@ process_sm_iq(#jid{luser = LUser, lserver = LServer},
|
||||
sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]
|
||||
};
|
||||
Data ->
|
||||
mnesia:transaction(fun() ->
|
||||
lists:foreach(fun
|
||||
(Datum) ->
|
||||
set_data(LUser, LServer, Datum)
|
||||
end, Data)
|
||||
end),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun
|
||||
(Datum) ->
|
||||
set_data(LUser, LServer,
|
||||
Datum, DBType)
|
||||
end, Data)
|
||||
end,
|
||||
case DBType of
|
||||
odbc ->
|
||||
ejabberd_odbc:sql_transaction(LServer, F);
|
||||
mnesia ->
|
||||
mnesia:transaction(F)
|
||||
end,
|
||||
IQ#iq{type = result, sub_el = []}
|
||||
end;
|
||||
_ ->
|
||||
@ -132,30 +147,53 @@ filter_xmlels([_ | Xmlels], Data) ->
|
||||
filter_xmlels(Xmlels, Data).
|
||||
|
||||
|
||||
set_data(LUser, LServer, {XmlNS, Xmlel}) ->
|
||||
set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) ->
|
||||
mnesia:write(#private_storage{
|
||||
usns = {LUser, LServer, XmlNS},
|
||||
xml = Xmlel
|
||||
}).
|
||||
|
||||
usns = {LUser, LServer, XmlNS},
|
||||
xml = Xmlel});
|
||||
set_data(LUser, LServer, {XMLNS, El}, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LXMLNS = ejabberd_odbc:escape(XMLNS),
|
||||
SData = ejabberd_odbc:escape(
|
||||
xml:element_to_binary(El)),
|
||||
odbc_queries:set_private_data(LServer, Username, LXMLNS, SData).
|
||||
|
||||
get_data(LUser, LServer, Data) ->
|
||||
get_data(LUser, LServer, Data, []).
|
||||
get_data(LUser, LServer, gen_mod:db_type(LServer, ?MODULE), Data, []).
|
||||
|
||||
get_data(_LUser, _LServer, [], Storage_Xmlels) ->
|
||||
get_data(_LUser, _LServer, _DBType, [], Storage_Xmlels) ->
|
||||
lists:reverse(Storage_Xmlels);
|
||||
get_data(LUser, LServer, [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
|
||||
get_data(LUser, LServer, mnesia, [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
|
||||
case mnesia:dirty_read(private_storage, {LUser, LServer, XmlNS}) of
|
||||
[#private_storage{xml = Storage_Xmlel}] ->
|
||||
get_data(LUser, LServer, Data, [Storage_Xmlel | Storage_Xmlels]);
|
||||
get_data(LUser, LServer, mnesia, Data,
|
||||
[Storage_Xmlel | Storage_Xmlels]);
|
||||
_ ->
|
||||
get_data(LUser, LServer, Data, [Xmlel | Storage_Xmlels])
|
||||
get_data(LUser, LServer, mnesia, Data,
|
||||
[Xmlel | Storage_Xmlels])
|
||||
end;
|
||||
get_data(LUser, LServer, odbc, [{XMLNS, El} | Els], Res) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LXMLNS = ejabberd_odbc:escape(XMLNS),
|
||||
case catch odbc_queries:get_private_data(LServer, Username, LXMLNS) of
|
||||
{selected, ["data"], [{SData}]} ->
|
||||
case xml_stream:parse_element(SData) of
|
||||
Data when element(1, Data) == xmlelement ->
|
||||
get_data(LUser, LServer, odbc, Els, [Data | Res])
|
||||
end;
|
||||
%% MREMOND: I wonder when the query could return a vcard ?
|
||||
{selected, ["vcard"], []} ->
|
||||
get_data(LUser, LServer, odbc, Els, [El | Res]);
|
||||
_ ->
|
||||
get_data(LUser, LServer, odbc, Els, [El | Res])
|
||||
end.
|
||||
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
remove_user(LUser, LServer, gen_mod:db_type(Server, ?MODULE)).
|
||||
|
||||
remove_user(LUser, LServer, mnesia) ->
|
||||
F = fun() ->
|
||||
Namespaces = mnesia:select(
|
||||
private_storage,
|
||||
@ -169,8 +207,10 @@ remove_user(User, Server) ->
|
||||
{LUser, LServer, Namespace}})
|
||||
end, Namespaces)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_user_private_storage(LServer, Username).
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, private_storage),
|
||||
|
@ -1,136 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_private_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Private storage support
|
||||
%%% Created : 5 Oct 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_private_odbc).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_sm_iq/3,
|
||||
remove_user/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE,
|
||||
?MODULE, process_sm_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE).
|
||||
|
||||
|
||||
process_sm_iq(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true ->
|
||||
{xmlelement, Name, Attrs, Els} = SubEl,
|
||||
case Type of
|
||||
set ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun(El) ->
|
||||
set_data(LUser, LServer, El)
|
||||
end, Els)
|
||||
end,
|
||||
odbc_queries:sql_transaction(LServer, F),
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, Name, Attrs, []}]};
|
||||
get ->
|
||||
case catch get_data(LUser, LServer, Els) of
|
||||
{'EXIT', _Reason} ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl,
|
||||
?ERR_INTERNAL_SERVER_ERROR]};
|
||||
Res ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, Name, Attrs, Res}]}
|
||||
end
|
||||
end;
|
||||
false ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
|
||||
end.
|
||||
|
||||
set_data(LUser, LServer, El) ->
|
||||
case El of
|
||||
{xmlelement, _Name, Attrs, _Els} ->
|
||||
XMLNS = xml:get_attr_s("xmlns", Attrs),
|
||||
case XMLNS of
|
||||
"" ->
|
||||
ignore;
|
||||
_ ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LXMLNS = ejabberd_odbc:escape(XMLNS),
|
||||
SData = ejabberd_odbc:escape(
|
||||
xml:element_to_binary(El)),
|
||||
odbc_queries:set_private_data(LServer, Username, LXMLNS, SData)
|
||||
end;
|
||||
_ ->
|
||||
ignore
|
||||
end.
|
||||
|
||||
get_data(LUser, LServer, Els) ->
|
||||
get_data(LUser, LServer, Els, []).
|
||||
|
||||
get_data(_LUser, _LServer, [], Res) ->
|
||||
lists:reverse(Res);
|
||||
get_data(LUser, LServer, [El | Els], Res) ->
|
||||
case El of
|
||||
{xmlelement, _Name, Attrs, _} ->
|
||||
XMLNS = xml:get_attr_s("xmlns", Attrs),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LXMLNS = ejabberd_odbc:escape(XMLNS),
|
||||
case catch odbc_queries:get_private_data(LServer, Username, LXMLNS) of
|
||||
{selected, ["data"], [{SData}]} ->
|
||||
case xml_stream:parse_element(SData) of
|
||||
Data when element(1, Data) == xmlelement ->
|
||||
get_data(LUser, LServer, Els,
|
||||
[Data | Res])
|
||||
end;
|
||||
%% MREMOND: I wonder when the query could return a vcard ?
|
||||
{selected, ["vcard"], []} ->
|
||||
get_data(LUser, LServer, Els,
|
||||
[El | Res]);
|
||||
_ ->
|
||||
get_data(LUser, LServer, Els,[El | Res])
|
||||
end;
|
||||
_ ->
|
||||
get_data(LUser, LServer, Els, Res)
|
||||
end.
|
||||
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_user_private_storage(LServer, Username).
|
@ -65,14 +65,21 @@
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
mnesia:create_table(roster,[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, roster)}]),
|
||||
mnesia:create_table(roster_version, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, roster_version)}]),
|
||||
|
||||
update_table(),
|
||||
mnesia:add_table_index(roster, us),
|
||||
mnesia:add_table_index(roster_version, us),
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(roster,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, roster)}]),
|
||||
mnesia:create_table(roster_version,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, roster_version)}]),
|
||||
update_table(),
|
||||
mnesia:add_table_index(roster, us),
|
||||
mnesia:add_table_index(roster_version, us);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:add(roster_get, Host,
|
||||
?MODULE, get_user_roster, 50),
|
||||
ejabberd_hooks:add(roster_in_subscription, Host,
|
||||
@ -166,17 +173,69 @@ get_versioning_feature(Acc, Host) ->
|
||||
false -> []
|
||||
end.
|
||||
|
||||
roster_version(LServer ,LUser) ->
|
||||
US = {LUser, LServer},
|
||||
case roster_version_on_db(LServer) of
|
||||
true ->
|
||||
case mnesia:dirty_read(roster_version, US) of
|
||||
[#roster_version{version = V}] -> V;
|
||||
[] -> not_found
|
||||
end;
|
||||
false ->
|
||||
roster_hash(ejabberd_hooks:run_fold(roster_get, LServer, [], [US]))
|
||||
end.
|
||||
roster_version(LServer, LUser) ->
|
||||
US = {LUser, LServer},
|
||||
case roster_version_on_db(LServer) of
|
||||
true ->
|
||||
case read_roster_version(LUser, LServer) of
|
||||
error ->
|
||||
not_found;
|
||||
V ->
|
||||
V
|
||||
end;
|
||||
false ->
|
||||
roster_hash(ejabberd_hooks:run_fold(roster_get, LServer, [], [US]))
|
||||
end.
|
||||
|
||||
read_roster_version(LUser, LServer) ->
|
||||
read_roster_version(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
read_roster_version(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
case mnesia:dirty_read(roster_version, US) of
|
||||
[#roster_version{version = V}] -> V;
|
||||
[] -> error
|
||||
end;
|
||||
read_roster_version(LServer, LUser, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case odbc_queries:get_roster_version(LServer, Username) of
|
||||
{selected, ["version"], [{Version}]} ->
|
||||
Version;
|
||||
{selected, ["version"], []} ->
|
||||
error
|
||||
end.
|
||||
|
||||
write_roster_version(LUser, LServer) ->
|
||||
write_roster_version(LUser, LServer, false).
|
||||
|
||||
write_roster_version_t(LUser, LServer) ->
|
||||
write_roster_version(LUser, LServer, true).
|
||||
|
||||
write_roster_version(LUser, LServer, InTransaction) ->
|
||||
Ver = sha:sha(term_to_binary(now())),
|
||||
write_roster_version(LUser, LServer, InTransaction, Ver,
|
||||
gen_mod:db_type(LServer, ?MODULE)),
|
||||
Ver.
|
||||
|
||||
write_roster_version(LUser, LServer, InTransaction, Ver, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
if InTransaction ->
|
||||
mnesia:write(#roster_version{us = US, version = Ver});
|
||||
true ->
|
||||
mnesia:dirty_write(#roster_version{us = US, version = Ver})
|
||||
end;
|
||||
write_roster_version(LUser, LServer, InTransaction, Ver, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
EVer = ejabberd_odbc:escape(Ver),
|
||||
if InTransaction ->
|
||||
odbc_queries:set_roster_version(Username, EVer);
|
||||
true ->
|
||||
odbc_queries:sql_transaction(
|
||||
LServer,
|
||||
fun() ->
|
||||
odbc_queries:set_roster_version(Username, EVer)
|
||||
end)
|
||||
end.
|
||||
|
||||
%% Load roster from DB only if neccesary.
|
||||
%% It is neccesary if
|
||||
@ -189,60 +248,119 @@ process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) ->
|
||||
LServer = From#jid.lserver,
|
||||
US = {LUser, LServer},
|
||||
try
|
||||
{ItemsToSend, VersionToSend} =
|
||||
case {xml:get_tag_attr("ver", SubEl),
|
||||
roster_versioning_enabled(LServer),
|
||||
roster_version_on_db(LServer)} of
|
||||
{ItemsToSend, VersionToSend} =
|
||||
case {xml:get_tag_attr("ver", SubEl),
|
||||
roster_versioning_enabled(LServer),
|
||||
roster_version_on_db(LServer)} of
|
||||
{{value, RequestedVersion}, true, true} ->
|
||||
%% Retrieve version from DB. Only load entire roster
|
||||
%% when neccesary.
|
||||
case mnesia:dirty_read(roster_version, US) of
|
||||
[#roster_version{version = RequestedVersion}] ->
|
||||
{false, false};
|
||||
[#roster_version{version = NewVersion}] ->
|
||||
{lists:map(fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(roster_get, To#jid.lserver, [], [US])), NewVersion};
|
||||
[] ->
|
||||
RosterVersion = sha:sha(term_to_binary(now())),
|
||||
mnesia:dirty_write(#roster_version{us = US, version = RosterVersion}),
|
||||
{lists:map(fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(roster_get, To#jid.lserver, [], [US])), RosterVersion}
|
||||
end;
|
||||
|
||||
%% Retrieve version from DB. Only load entire roster
|
||||
%% when neccesary.
|
||||
case read_roster_version(LUser, LServer) of
|
||||
error ->
|
||||
RosterVersion = write_roster_version(LUser, LServer),
|
||||
{lists:map(
|
||||
fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get, To#jid.lserver, [], [US])),
|
||||
RosterVersion};
|
||||
RequestedVersion ->
|
||||
{false, false};
|
||||
NewVersion ->
|
||||
{lists:map(
|
||||
fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get, To#jid.lserver, [], [US])),
|
||||
NewVersion}
|
||||
end;
|
||||
{{value, RequestedVersion}, true, false} ->
|
||||
RosterItems = ejabberd_hooks:run_fold(roster_get, To#jid.lserver, [] , [US]),
|
||||
case roster_hash(RosterItems) of
|
||||
RequestedVersion ->
|
||||
{false, false};
|
||||
New ->
|
||||
{lists:map(fun item_to_xml/1, RosterItems), New}
|
||||
end;
|
||||
|
||||
RosterItems = ejabberd_hooks:run_fold(
|
||||
roster_get, To#jid.lserver, [] , [US]),
|
||||
case roster_hash(RosterItems) of
|
||||
RequestedVersion ->
|
||||
{false, false};
|
||||
New ->
|
||||
{lists:map(fun item_to_xml/1, RosterItems), New}
|
||||
end;
|
||||
_ ->
|
||||
{lists:map(fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(roster_get, To#jid.lserver, [], [US])), false}
|
||||
end,
|
||||
IQ#iq{type = result, sub_el = case {ItemsToSend, VersionToSend} of
|
||||
{false, false} -> [];
|
||||
{Items, false} -> [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}], Items}];
|
||||
{Items, Version} -> [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}, {"ver", Version}], Items}]
|
||||
end}
|
||||
catch
|
||||
{lists:map(
|
||||
fun item_to_xml/1,
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get, To#jid.lserver, [], [US])),
|
||||
false}
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = case {ItemsToSend, VersionToSend} of
|
||||
{false, false} ->
|
||||
[];
|
||||
{Items, false} ->
|
||||
[{xmlelement, "query",
|
||||
[{"xmlns", ?NS_ROSTER}], Items}];
|
||||
{Items, Version} ->
|
||||
[{xmlelement, "query",
|
||||
[{"xmlns", ?NS_ROSTER}, {"ver", Version}],
|
||||
Items}]
|
||||
end}
|
||||
catch
|
||||
_:_ ->
|
||||
IQ#iq{type =error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
||||
IQ#iq{type =error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
||||
end.
|
||||
|
||||
get_user_roster(Acc, {LUser, LServer}) ->
|
||||
Items = get_roster(LUser, LServer),
|
||||
lists:filter(fun(#roster{subscription = none, ask = in}) ->
|
||||
false;
|
||||
(_) ->
|
||||
true
|
||||
end, Items) ++ Acc.
|
||||
|
||||
get_user_roster(Acc, US) ->
|
||||
get_roster(LUser, LServer) ->
|
||||
get_roster(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_roster(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_index_read(roster, US, #roster.us) of
|
||||
Items when is_list(Items) ->
|
||||
lists:filter(fun(#roster{subscription = none, ask = in}) ->
|
||||
false;
|
||||
(_) ->
|
||||
true
|
||||
end, Items) ++ Acc;
|
||||
Items;
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
get_roster(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_roster(LServer, Username) of
|
||||
{selected, ["username", "jid", "nick", "subscription", "ask",
|
||||
"askmessage", "server", "subscribe", "type"],
|
||||
Items} when is_list(Items) ->
|
||||
JIDGroups = case catch odbc_queries:get_roster_jid_groups(LServer, Username) of
|
||||
{selected, ["jid","grp"], JGrps}
|
||||
when is_list(JGrps) ->
|
||||
JGrps;
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
GroupsDict =
|
||||
lists:foldl(
|
||||
fun({J, G}, Acc) ->
|
||||
dict:append(J, G, Acc)
|
||||
end, dict:new(), JIDGroups),
|
||||
RItems = lists:flatmap(
|
||||
fun(I) ->
|
||||
case raw_to_record(LServer, I) of
|
||||
%% Bad JID in database:
|
||||
error ->
|
||||
[];
|
||||
R ->
|
||||
SJID = jlib:jid_to_string(R#roster.jid),
|
||||
Groups =
|
||||
case dict:find(SJID, GroupsDict) of
|
||||
{ok, Gs} -> Gs;
|
||||
error -> []
|
||||
end,
|
||||
[R#roster{groups = Groups}]
|
||||
end
|
||||
end, Items),
|
||||
RItems;
|
||||
_ ->
|
||||
Acc
|
||||
[]
|
||||
end.
|
||||
|
||||
|
||||
@ -280,6 +398,50 @@ item_to_xml(Item) ->
|
||||
SubEls = SubEls1 ++ Item#roster.xs,
|
||||
{xmlelement, "item", Attrs4, SubEls}.
|
||||
|
||||
get_roster_by_jid_t(LUser, LServer, LJID) ->
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
get_roster_by_jid_t(LUser, LServer, LJID, DBType).
|
||||
|
||||
get_roster_by_jid_t(LUser, LServer, LJID, mnesia) ->
|
||||
case mnesia:read({roster, {LUser, LServer, LJID}}) of
|
||||
[] ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer},
|
||||
jid = LJID};
|
||||
[I] ->
|
||||
I#roster{jid = LJID,
|
||||
name = "",
|
||||
groups = [],
|
||||
xs = []}
|
||||
end;
|
||||
get_roster_by_jid_t(LUser, LServer, LJID, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
{selected,
|
||||
["username", "jid", "nick", "subscription",
|
||||
"ask", "askmessage", "server", "subscribe", "type"],
|
||||
Res} = odbc_queries:get_roster_by_jid(LServer, Username, SJID),
|
||||
case Res of
|
||||
[] ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer},
|
||||
jid = LJID};
|
||||
[I] ->
|
||||
R = raw_to_record(LServer, I),
|
||||
case R of
|
||||
%% Bad JID in database:
|
||||
error ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer},
|
||||
jid = LJID};
|
||||
_ ->
|
||||
R#roster{
|
||||
usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer},
|
||||
jid = LJID,
|
||||
name = ""}
|
||||
end
|
||||
end.
|
||||
|
||||
process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
|
||||
{xmlelement, _Name, _Attrs, Els} = SubEl,
|
||||
@ -293,40 +455,28 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
|
||||
error ->
|
||||
ok;
|
||||
_ ->
|
||||
JID = {JID1#jid.user, JID1#jid.server, JID1#jid.resource},
|
||||
LJID = jlib:jid_tolower(JID1),
|
||||
F = fun() ->
|
||||
Res = mnesia:read({roster, {LUser, LServer, LJID}}),
|
||||
Item = case Res of
|
||||
[] ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer},
|
||||
jid = JID};
|
||||
[I] ->
|
||||
I#roster{jid = JID,
|
||||
name = "",
|
||||
groups = [],
|
||||
xs = []}
|
||||
end,
|
||||
Item = get_roster_by_jid_t(LUser, LServer, LJID),
|
||||
Item1 = process_item_attrs(Item, Attrs),
|
||||
Item2 = process_item_els(Item1, Els),
|
||||
case Item2#roster.subscription of
|
||||
remove ->
|
||||
mnesia:delete({roster, {LUser, LServer, LJID}});
|
||||
del_roster_t(LUser, LServer, LJID);
|
||||
_ ->
|
||||
mnesia:write(Item2)
|
||||
update_roster_t(LUser, LServer, LJID, Item2)
|
||||
end,
|
||||
%% If the item exist in shared roster, take the
|
||||
%% subscription information from there:
|
||||
Item3 = ejabberd_hooks:run_fold(roster_process_item,
|
||||
LServer, Item2, [LServer]),
|
||||
case roster_version_on_db(LServer) of
|
||||
true -> mnesia:write(#roster_version{us = {LUser, LServer}, version = sha:sha(term_to_binary(now()))});
|
||||
false -> ok
|
||||
true -> write_roster_version_t(LUser, LServer);
|
||||
false -> ok
|
||||
end,
|
||||
{Item, Item3}
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
case transaction(LServer, F) of
|
||||
{atomic, {OldItem, Item}} ->
|
||||
push_item(User, LServer, To, Item),
|
||||
case Item#roster.subscription of
|
||||
@ -351,7 +501,7 @@ process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
|
||||
error ->
|
||||
process_item_attrs(Item, Attrs);
|
||||
JID1 ->
|
||||
JID = {JID1#jid.user, JID1#jid.server, JID1#jid.resource},
|
||||
JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
|
||||
process_item_attrs(Item#roster{jid = JID}, Attrs)
|
||||
end;
|
||||
"name" ->
|
||||
@ -435,30 +585,54 @@ push_item_version(Server, User, From, Item, RosterVersion) ->
|
||||
push_item(User, Server, Resource, From, Item, RosterVersion)
|
||||
end, ejabberd_sm:get_user_resources(User, Server)).
|
||||
|
||||
get_subscription_lists(_, User, Server) ->
|
||||
get_subscription_lists(Acc, User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
Items = get_subscription_lists(Acc, LUser, LServer, DBType),
|
||||
fill_subscription_lists(LServer, Items, [], []).
|
||||
|
||||
get_subscription_lists(_, LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
case mnesia:dirty_index_read(roster, US, #roster.us) of
|
||||
Items when is_list(Items) ->
|
||||
fill_subscription_lists(Items, [], []);
|
||||
Items;
|
||||
_ ->
|
||||
{[], []}
|
||||
end;
|
||||
get_subscription_lists(_, LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_roster(LServer, Username) of
|
||||
{selected, ["username", "jid", "nick", "subscription", "ask",
|
||||
"askmessage", "server", "subscribe", "type"],
|
||||
Items} when is_list(Items) ->
|
||||
Items;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
fill_subscription_lists([I | Is], F, T) ->
|
||||
fill_subscription_lists(LServer, [#roster{} = I | Is], F, T) ->
|
||||
J = element(3, I#roster.usj),
|
||||
case I#roster.subscription of
|
||||
both ->
|
||||
fill_subscription_lists(Is, [J | F], [J | T]);
|
||||
fill_subscription_lists(LServer, Is, [J | F], [J | T]);
|
||||
from ->
|
||||
fill_subscription_lists(Is, [J | F], T);
|
||||
fill_subscription_lists(LServer, Is, [J | F], T);
|
||||
to ->
|
||||
fill_subscription_lists(Is, F, [J | T]);
|
||||
fill_subscription_lists(LServer, Is, F, [J | T]);
|
||||
_ ->
|
||||
fill_subscription_lists(Is, F, T)
|
||||
fill_subscription_lists(LServer, Is, F, T)
|
||||
end;
|
||||
fill_subscription_lists([], F, T) ->
|
||||
fill_subscription_lists(LServer, [RawI | Is], F, T) ->
|
||||
I = raw_to_record(LServer, RawI),
|
||||
case I of
|
||||
%% Bad JID in database:
|
||||
error ->
|
||||
fill_subscription_lists(LServer, Is, F, T);
|
||||
_ ->
|
||||
fill_subscription_lists(LServer, [I | Is], F, T)
|
||||
end;
|
||||
fill_subscription_lists(_LServer, [], F, T) ->
|
||||
{F, T}.
|
||||
|
||||
ask_to_pending(subscribe) -> out;
|
||||
@ -466,6 +640,25 @@ ask_to_pending(unsubscribe) -> none;
|
||||
ask_to_pending(Ask) -> Ask.
|
||||
|
||||
|
||||
roster_subscribe_t(LUser, LServer, LJID, Item) ->
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
roster_subscribe_t(LUser, LServer, LJID, Item, DBType).
|
||||
|
||||
roster_subscribe_t(_LUser, _LServer, _LJID, Item, mnesia) ->
|
||||
mnesia:write(Item);
|
||||
roster_subscribe_t(LUser, LServer, LJID, Item, odbc) ->
|
||||
ItemVals = record_to_string(Item),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
odbc_queries:roster_subscribe(LServer, Username, SJID, ItemVals).
|
||||
|
||||
transaction(LServer, F) ->
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia ->
|
||||
mnesia:transaction(F);
|
||||
odbc ->
|
||||
ejabberd_odbc:sql_transaction(LServer, F)
|
||||
end.
|
||||
|
||||
in_subscription(_, User, Server, JID, Type, Reason) ->
|
||||
process_subscription(in, User, Server, JID, Type, Reason).
|
||||
@ -473,23 +666,54 @@ in_subscription(_, User, Server, JID, Type, Reason) ->
|
||||
out_subscription(User, Server, JID, Type) ->
|
||||
process_subscription(out, User, Server, JID, Type, []).
|
||||
|
||||
get_roster_by_jid_with_groups_t(LUser, LServer, LJID) ->
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
get_roster_by_jid_with_groups_t(LUser, LServer, LJID, DBType).
|
||||
|
||||
get_roster_by_jid_with_groups_t(LUser, LServer, LJID, mnesia) ->
|
||||
case mnesia:read({roster, {LUser, LServer, LJID}}) of
|
||||
[] ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer},
|
||||
jid = LJID};
|
||||
[I] ->
|
||||
I
|
||||
end;
|
||||
get_roster_by_jid_with_groups_t(LUser, LServer, LJID, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
case odbc_queries:get_roster_by_jid(LServer, Username, SJID) of
|
||||
{selected,
|
||||
["username", "jid", "nick", "subscription", "ask",
|
||||
"askmessage", "server", "subscribe", "type"],
|
||||
[I]} ->
|
||||
%% raw_to_record can return error, but
|
||||
%% jlib_to_string would fail before this point
|
||||
R = raw_to_record(LServer, I),
|
||||
Groups =
|
||||
case odbc_queries:get_roster_groups(LServer, Username, SJID) of
|
||||
{selected, ["grp"], JGrps} when is_list(JGrps) ->
|
||||
[JGrp || {JGrp} <- JGrps];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
R#roster{groups = Groups};
|
||||
{selected,
|
||||
["username", "jid", "nick", "subscription", "ask",
|
||||
"askmessage", "server", "subscribe", "type"],
|
||||
[]} ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer},
|
||||
jid = LJID}
|
||||
end.
|
||||
|
||||
process_subscription(Direction, User, Server, JID1, Type, Reason) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
LJID = jlib:jid_tolower(JID1),
|
||||
F = fun() ->
|
||||
Item = case mnesia:read({roster, {LUser, LServer, LJID}}) of
|
||||
[] ->
|
||||
JID = {JID1#jid.user,
|
||||
JID1#jid.server,
|
||||
JID1#jid.resource},
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = US,
|
||||
jid = JID};
|
||||
[I] ->
|
||||
I
|
||||
end,
|
||||
Item = get_roster_by_jid_with_groups_t(
|
||||
LUser, LServer, LJID),
|
||||
NewState = case Direction of
|
||||
out ->
|
||||
out_state_change(Item#roster.subscription,
|
||||
@ -518,21 +742,21 @@ process_subscription(Direction, User, Server, JID1, Type, Reason) ->
|
||||
{none, AutoReply};
|
||||
{none, none} when Item#roster.subscription == none,
|
||||
Item#roster.ask == in ->
|
||||
mnesia:delete({roster, {LUser, LServer, LJID}}),
|
||||
del_roster_t(LUser, LServer, LJID),
|
||||
{none, AutoReply};
|
||||
{Subscription, Pending} ->
|
||||
NewItem = Item#roster{subscription = Subscription,
|
||||
ask = Pending,
|
||||
askmessage = list_to_binary(AskMessage)},
|
||||
mnesia:write(NewItem),
|
||||
roster_subscribe_t(LUser, LServer, LJID, NewItem),
|
||||
case roster_version_on_db(LServer) of
|
||||
true -> mnesia:write(#roster_version{us = {LUser, LServer}, version = sha:sha(term_to_binary(now()))});
|
||||
false -> ok
|
||||
true -> write_roster_version_t(LUser, LServer);
|
||||
false -> ok
|
||||
end,
|
||||
{{push, NewItem}, AutoReply}
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
case transaction(LServer, F) of
|
||||
{atomic, {Push, AutoReply}} ->
|
||||
case AutoReply of
|
||||
none ->
|
||||
@ -663,6 +887,9 @@ in_auto_reply(_, _, _) -> none.
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
remove_user(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
send_unsubscription_to_rosteritems(LUser, LServer),
|
||||
F = fun() ->
|
||||
@ -671,7 +898,12 @@ remove_user(User, Server) ->
|
||||
end,
|
||||
mnesia:index_read(roster, US, #roster.us))
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
send_unsubscription_to_rosteritems(LUser, LServer),
|
||||
odbc_queries:del_user_roster_t(LServer, Username),
|
||||
ok.
|
||||
|
||||
%% For each contact with Subscription:
|
||||
%% Both or From, send a "unsubscribed" presence stanza;
|
||||
@ -725,11 +957,36 @@ set_items(User, Server, SubEl) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
F = fun() ->
|
||||
lists:foreach(fun(El) ->
|
||||
process_item_set_t(LUser, LServer, El)
|
||||
end, Els)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
lists:foreach(
|
||||
fun(El) ->
|
||||
process_item_set_t(LUser, LServer, El)
|
||||
end, Els)
|
||||
end,
|
||||
transaction(LServer, F).
|
||||
|
||||
update_roster_t(LUser, LServer, LJID, Item) ->
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
update_roster_t(LUser, LServer, LJID, Item, DBType).
|
||||
|
||||
update_roster_t(_LUser, _LServer,_LJID, Item, mnesia) ->
|
||||
mnesia:write(Item);
|
||||
update_roster_t(LUser, _LServer, LJID, Item, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
ItemVals = record_to_string(Item),
|
||||
ItemGroups = groups_to_string(Item),
|
||||
odbc_queries:update_roster_sql(Username, SJID, ItemVals, ItemGroups).
|
||||
|
||||
del_roster_t(LUser, LServer, LJID) ->
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
del_roster_t(LUser, LServer, LJID, DBType).
|
||||
|
||||
del_roster_t(LUser, LServer, LJID, mnesia) ->
|
||||
mnesia:delete({roster, {LUser, LServer, LJID}});
|
||||
del_roster_t(LUser, _LServer, LJID, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
odbc_queries:del_roster_sql(Username, SJID).
|
||||
|
||||
process_item_set_t(LUser, LServer, {xmlelement, _Name, Attrs, Els}) ->
|
||||
JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)),
|
||||
@ -744,12 +1001,12 @@ process_item_set_t(LUser, LServer, {xmlelement, _Name, Attrs, Els}) ->
|
||||
jid = JID},
|
||||
Item1 = process_item_attrs_ws(Item, Attrs),
|
||||
Item2 = process_item_els(Item1, Els),
|
||||
case Item2#roster.subscription of
|
||||
remove ->
|
||||
mnesia:delete({roster, {LUser, LServer, LJID}});
|
||||
_ ->
|
||||
mnesia:write(Item2)
|
||||
end
|
||||
case Item2#roster.subscription of
|
||||
remove ->
|
||||
del_roster_t(LUser, LServer, LJID);
|
||||
_ ->
|
||||
update_roster_t(LUser, LServer, LJID, Item2)
|
||||
end
|
||||
end;
|
||||
process_item_set_t(_LUser, _LServer, _) ->
|
||||
ok.
|
||||
@ -761,7 +1018,7 @@ process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
|
||||
error ->
|
||||
process_item_attrs_ws(Item, Attrs);
|
||||
JID1 ->
|
||||
JID = {JID1#jid.user, JID1#jid.server, JID1#jid.resource},
|
||||
JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
|
||||
process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
|
||||
end;
|
||||
"name" ->
|
||||
@ -795,6 +1052,11 @@ process_item_attrs_ws(Item, []) ->
|
||||
Item.
|
||||
|
||||
get_in_pending_subscriptions(Ls, User, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
get_in_pending_subscriptions(Ls, User, Server,
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_in_pending_subscriptions(Ls, User, Server, mnesia) ->
|
||||
JID = jlib:make_jid(User, Server, ""),
|
||||
US = {JID#jid.luser, JID#jid.lserver},
|
||||
case mnesia:dirty_index_read(roster, US, #roster.us) of
|
||||
@ -825,30 +1087,99 @@ get_in_pending_subscriptions(Ls, User, Server) ->
|
||||
Result));
|
||||
_ ->
|
||||
Ls
|
||||
end;
|
||||
get_in_pending_subscriptions(Ls, User, Server, odbc) ->
|
||||
JID = jlib:make_jid(User, Server, ""),
|
||||
LUser = JID#jid.luser,
|
||||
LServer = JID#jid.lserver,
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_roster(LServer, Username) of
|
||||
{selected, ["username", "jid", "nick", "subscription", "ask",
|
||||
"askmessage", "server", "subscribe", "type"],
|
||||
Items} when is_list(Items) ->
|
||||
Ls ++ lists:map(
|
||||
fun(R) ->
|
||||
Message = R#roster.askmessage,
|
||||
{xmlelement, "presence",
|
||||
[{"from", jlib:jid_to_string(R#roster.jid)},
|
||||
{"to", jlib:jid_to_string(JID)},
|
||||
{"type", "subscribe"}],
|
||||
[{xmlelement, "status", [],
|
||||
[{xmlcdata, Message}]}]}
|
||||
end,
|
||||
lists:flatmap(
|
||||
fun(I) ->
|
||||
case raw_to_record(LServer, I) of
|
||||
%% Bad JID in database:
|
||||
error ->
|
||||
[];
|
||||
R ->
|
||||
case R#roster.ask of
|
||||
in -> [R];
|
||||
both -> [R];
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
end,
|
||||
Items));
|
||||
_ ->
|
||||
Ls
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
get_jid_info(_, User, Server, JID) ->
|
||||
read_subscription_and_groups(User, Server, LJID) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
LServer = jlib:nameprep(Server),
|
||||
read_subscription_and_groups(LUser, LServer, LJID,
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
read_subscription_and_groups(LUser, LServer, LJID, mnesia) ->
|
||||
case catch mnesia:dirty_read(roster, {LUser, LServer, LJID}) of
|
||||
[#roster{subscription = Subscription, groups = Groups}] ->
|
||||
{Subscription, Groups};
|
||||
_ ->
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
read_subscription_and_groups(LUser, LServer, LJID, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
case catch odbc_queries:get_subscription(LServer, Username, SJID) of
|
||||
{selected, ["subscription"], [{SSubscription}]} ->
|
||||
Subscription = case SSubscription of
|
||||
"B" -> both;
|
||||
"T" -> to;
|
||||
"F" -> from;
|
||||
_ -> none
|
||||
end,
|
||||
Groups = case catch odbc_queries:get_rostergroup_by_jid(
|
||||
LServer, Username, SJID) of
|
||||
{selected, ["grp"], JGrps} when is_list(JGrps) ->
|
||||
[JGrp || {JGrp} <- JGrps];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
{Subscription, Groups};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
get_jid_info(_, User, Server, JID) ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
case read_subscription_and_groups(User, Server, LJID) of
|
||||
{Subscription, Groups} ->
|
||||
{Subscription, Groups};
|
||||
error ->
|
||||
LRJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
|
||||
if
|
||||
LRJID == LJID ->
|
||||
{none, []};
|
||||
true ->
|
||||
case catch mnesia:dirty_read(
|
||||
roster, {LUser, LServer, LRJID}) of
|
||||
[#roster{subscription = Subscription,
|
||||
groups = Groups}] ->
|
||||
case read_subscription_and_groups(
|
||||
User, Server, LRJID) of
|
||||
{Subscription, Groups} ->
|
||||
{Subscription, Groups};
|
||||
_ ->
|
||||
error ->
|
||||
{none, []}
|
||||
end
|
||||
end
|
||||
@ -856,6 +1187,75 @@ get_jid_info(_, User, Server, JID) ->
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
raw_to_record(LServer, {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
_SServer, _SSubscribe, _SType}) ->
|
||||
case jlib:string_to_jid(SJID) of
|
||||
error ->
|
||||
error;
|
||||
JID ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
Subscription = case SSubscription of
|
||||
"B" -> both;
|
||||
"T" -> to;
|
||||
"F" -> from;
|
||||
_ -> none
|
||||
end,
|
||||
Ask = case SAsk of
|
||||
"S" -> subscribe;
|
||||
"U" -> unsubscribe;
|
||||
"B" -> both;
|
||||
"O" -> out;
|
||||
"I" -> in;
|
||||
_ -> none
|
||||
end,
|
||||
#roster{usj = {User, LServer, LJID},
|
||||
us = {User, LServer},
|
||||
jid = LJID,
|
||||
name = Nick,
|
||||
subscription = Subscription,
|
||||
ask = Ask,
|
||||
askmessage = SAskMessage}
|
||||
end.
|
||||
|
||||
record_to_string(#roster{us = {User, _Server},
|
||||
jid = JID,
|
||||
name = Name,
|
||||
subscription = Subscription,
|
||||
ask = Ask,
|
||||
askmessage = AskMessage}) ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
|
||||
Nick = ejabberd_odbc:escape(Name),
|
||||
SSubscription = case Subscription of
|
||||
both -> "B";
|
||||
to -> "T";
|
||||
from -> "F";
|
||||
none -> "N"
|
||||
end,
|
||||
SAsk = case Ask of
|
||||
subscribe -> "S";
|
||||
unsubscribe -> "U";
|
||||
both -> "B";
|
||||
out -> "O";
|
||||
in -> "I";
|
||||
none -> "N"
|
||||
end,
|
||||
SAskMessage = ejabberd_odbc:escape(AskMessage),
|
||||
[Username, SJID, Nick, SSubscription, SAsk, SAskMessage, "N", "", "item"].
|
||||
|
||||
groups_to_string(#roster{us = {User, _Server},
|
||||
jid = JID,
|
||||
groups = Groups}) ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
|
||||
|
||||
%% Empty groups do not need to be converted to string to be inserted in
|
||||
%% the database
|
||||
lists:foldl(
|
||||
fun([], Acc) -> Acc;
|
||||
(Group, Acc) ->
|
||||
G = ejabberd_odbc:escape(Group),
|
||||
[[Username, SJID, G]|Acc] end, [], Groups).
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, roster),
|
||||
@ -927,10 +1327,12 @@ webadmin_page(_, Host,
|
||||
webadmin_page(Acc, _, _) -> Acc.
|
||||
|
||||
user_roster(User, Server, Query, Lang) ->
|
||||
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
|
||||
Items1 = mnesia:dirty_index_read(roster, US, #roster.us),
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
Items1 = get_roster(LUser, LServer),
|
||||
Res = user_roster_parse_query(User, Server, Items1, Query),
|
||||
Items = mnesia:dirty_index_read(roster, US, #roster.us),
|
||||
Items = get_roster(LUser, LServer),
|
||||
SItems = lists:sort(Items),
|
||||
FItems =
|
||||
case SItems of
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -63,15 +63,20 @@
|
||||
-record(sr_group, {group_host, opts}).
|
||||
-record(sr_user, {us, group_host}).
|
||||
|
||||
start(Host, _Opts) ->
|
||||
mnesia:create_table(sr_group,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, sr_group)}]),
|
||||
mnesia:create_table(sr_user,
|
||||
[{disc_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, sr_user)}]),
|
||||
mnesia:add_table_index(sr_user, group_host),
|
||||
start(Host, Opts) ->
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(sr_group,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, sr_group)}]),
|
||||
mnesia:create_table(sr_user,
|
||||
[{disc_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, sr_user)}]),
|
||||
mnesia:add_table_index(sr_user, group_host);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:add(webadmin_menu_host, Host,
|
||||
?MODULE, webadmin_menu, 70),
|
||||
ejabberd_hooks:add(webadmin_page_host, Host,
|
||||
@ -181,7 +186,7 @@ get_user_roster(Items, US) ->
|
||||
get_vcard_module(Server) ->
|
||||
Modules = gen_mod:loaded_modules(Server),
|
||||
[M || M <- Modules,
|
||||
(M == mod_vcard) or (M == mod_vcard_odbc) or (M == mod_vcard_ldap)].
|
||||
(M == mod_vcard) or (M == mod_vcard_ldap)].
|
||||
|
||||
get_rosteritem_name([], _, _) ->
|
||||
"";
|
||||
@ -237,15 +242,14 @@ process_item(RosterItem, Host) ->
|
||||
[] ->
|
||||
%% Remove pending subscription by setting it
|
||||
%% unsubscribed.
|
||||
Mod = get_roster_mod(ServerFrom),
|
||||
|
||||
%% Remove pending out subscription
|
||||
Mod:out_subscription(UserTo, ServerTo,
|
||||
mod_roster:out_subscription(UserTo, ServerTo,
|
||||
jlib:make_jid(UserFrom, ServerFrom, ""),
|
||||
unsubscribe),
|
||||
|
||||
%% Remove pending in subscription
|
||||
Mod:in_subscription(aaaa, UserFrom, ServerFrom,
|
||||
mod_roster:in_subscription(aaaa, UserFrom, ServerFrom,
|
||||
jlib:make_jid(UserTo, ServerTo, ""),
|
||||
unsubscribe, ""),
|
||||
|
||||
@ -274,8 +278,6 @@ build_roster_record(User1, Server1, User2, Server2, Name2, Groups) ->
|
||||
|
||||
set_new_rosteritems(UserFrom, ServerFrom,
|
||||
UserTo, ServerTo, ResourceTo, NameTo, GroupsFrom) ->
|
||||
Mod = get_roster_mod(ServerFrom),
|
||||
|
||||
RIFrom = build_roster_record(UserFrom, ServerFrom,
|
||||
UserTo, ServerTo, NameTo, GroupsFrom),
|
||||
set_item(UserFrom, ServerFrom, ResourceTo, RIFrom),
|
||||
@ -287,20 +289,20 @@ set_new_rosteritems(UserFrom, ServerFrom,
|
||||
set_item(UserTo, ServerTo, "", RITo),
|
||||
|
||||
%% From requests
|
||||
Mod:out_subscription(UserFrom, ServerFrom, JIDTo, subscribe),
|
||||
Mod:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribe, ""),
|
||||
mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo, subscribe),
|
||||
mod_roster:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribe, ""),
|
||||
|
||||
%% To accepts
|
||||
Mod:out_subscription(UserTo, ServerTo, JIDFrom, subscribed),
|
||||
Mod:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribed, ""),
|
||||
mod_roster:out_subscription(UserTo, ServerTo, JIDFrom, subscribed),
|
||||
mod_roster:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribed, ""),
|
||||
|
||||
%% To requests
|
||||
Mod:out_subscription(UserTo, ServerTo, JIDFrom, subscribe),
|
||||
Mod:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribe, ""),
|
||||
mod_roster:out_subscription(UserTo, ServerTo, JIDFrom, subscribe),
|
||||
mod_roster:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribe, ""),
|
||||
|
||||
%% From accepts
|
||||
Mod:out_subscription(UserFrom, ServerFrom, JIDTo, subscribed),
|
||||
Mod:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribed, ""),
|
||||
mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo, subscribed),
|
||||
mod_roster:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribed, ""),
|
||||
|
||||
RIFrom.
|
||||
|
||||
@ -361,15 +363,13 @@ in_subscription(Acc, User, Server, JID, Type, _Reason) ->
|
||||
process_subscription(in, User, Server, JID, Type, Acc).
|
||||
|
||||
out_subscription(UserFrom, ServerFrom, JIDTo, unsubscribed) ->
|
||||
Mod = get_roster_mod(ServerFrom),
|
||||
|
||||
%% Remove pending out subscription
|
||||
#jid{luser = UserTo, lserver = ServerTo} = JIDTo,
|
||||
JIDFrom = jlib:make_jid(UserFrom, UserTo, ""),
|
||||
Mod:out_subscription(UserTo, ServerTo, JIDFrom, unsubscribe),
|
||||
mod_roster:out_subscription(UserTo, ServerTo, JIDFrom, unsubscribe),
|
||||
|
||||
%% Remove pending in subscription
|
||||
Mod:in_subscription(aaaa, UserFrom, ServerFrom, JIDTo, unsubscribe, ""),
|
||||
mod_roster:in_subscription(aaaa, UserFrom, ServerFrom, JIDTo, unsubscribe, ""),
|
||||
|
||||
process_subscription(out, UserFrom, ServerFrom, JIDTo, unsubscribed, false);
|
||||
out_subscription(User, Server, JID, Type) ->
|
||||
@ -401,32 +401,70 @@ process_subscription(Direction, User, Server, JID, _Type, Acc) ->
|
||||
end.
|
||||
|
||||
list_groups(Host) ->
|
||||
list_groups(Host, gen_mod:db_type(Host, ?MODULE)).
|
||||
|
||||
list_groups(Host, mnesia) ->
|
||||
mnesia:dirty_select(
|
||||
sr_group,
|
||||
[{#sr_group{group_host = {'$1', '$2'},
|
||||
_ = '_'},
|
||||
[{'==', '$2', Host}],
|
||||
['$1']}]).
|
||||
['$1']}]);
|
||||
list_groups(Host, odbc) ->
|
||||
case ejabberd_odbc:sql_query(
|
||||
Host, ["select name from sr_group;"]) of
|
||||
{selected, ["name"], Rs} ->
|
||||
[G || {G} <- Rs];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
groups_with_opts(Host) ->
|
||||
groups_with_opts(Host, gen_mod:db_type(Host, ?MODULE)).
|
||||
|
||||
groups_with_opts(Host, mnesia) ->
|
||||
Gs = mnesia:dirty_select(
|
||||
sr_group,
|
||||
[{#sr_group{group_host={'$1', Host}, opts='$2', _='_'},
|
||||
[],
|
||||
[['$1','$2']] }]),
|
||||
lists:map(fun([G,O]) -> {G, O} end, Gs).
|
||||
lists:map(fun([G,O]) -> {G, O} end, Gs);
|
||||
groups_with_opts(Host, odbc) ->
|
||||
case ejabberd_odbc:sql_query(
|
||||
Host, ["select name, opts from sr_group;"]) of
|
||||
{selected, ["name", "opts"], Rs} ->
|
||||
[{G, ejabberd_odbc:decode_term(Opts)} || {G, Opts} <- Rs];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
create_group(Host, Group) ->
|
||||
create_group(Host, Group, []).
|
||||
|
||||
create_group(Host, Group, Opts) ->
|
||||
create_group(Host, Group, Opts, gen_mod:db_type(Host, ?MODULE)).
|
||||
|
||||
create_group(Host, Group, Opts, mnesia) ->
|
||||
R = #sr_group{group_host = {Group, Host}, opts = Opts},
|
||||
F = fun() ->
|
||||
mnesia:write(R)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
create_group(Host, Group, Opts, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
SOpts = ejabberd_odbc:encode_term(Opts),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t("sr_group",
|
||||
["name", "opts"],
|
||||
[SGroup, SOpts],
|
||||
["name='", SGroup, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(Host, F).
|
||||
|
||||
delete_group(Host, Group) ->
|
||||
delete_group(Host, Group, gen_mod:db_type(Host, ?MODULE)).
|
||||
|
||||
delete_group(Host, Group, mnesia) ->
|
||||
GroupHost = {Group, Host},
|
||||
F = fun() ->
|
||||
%% Delete the group ...
|
||||
@ -437,53 +475,102 @@ delete_group(Host, Group) ->
|
||||
mnesia:delete_object(UserEntry)
|
||||
end, Users)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
delete_group(Host, Group, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from sr_group where name='", SGroup, "';"]),
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from sr_user where grp='", SGroup, "';"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(Host, F).
|
||||
|
||||
get_group_opts(Host, Group) ->
|
||||
get_group_opts(Host, Group, gen_mod:db_type(Host, ?MODULE)).
|
||||
|
||||
get_group_opts(Host, Group, mnesia) ->
|
||||
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
|
||||
[#sr_group{opts = Opts}] ->
|
||||
Opts;
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
get_group_opts(Host, Group, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
Host, ["select opts from sr_group "
|
||||
"where name='", SGroup, "';"]) of
|
||||
{selected, ["opts"], [{SOpts}]} ->
|
||||
ejabberd_odbc:decode_term(SOpts);
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
set_group_opts(Host, Group, Opts) ->
|
||||
set_group_opts(Host, Group, Opts, gen_mod:db_type(Host, ?MODULE)).
|
||||
|
||||
set_group_opts(Host, Group, Opts, mnesia) ->
|
||||
R = #sr_group{group_host = {Group, Host}, opts = Opts},
|
||||
F = fun() ->
|
||||
mnesia:write(R)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
set_group_opts(Host, Group, Opts, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
SOpts = ejabberd_odbc:encode_term(Opts),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t("sr_group",
|
||||
["name", "opts"],
|
||||
[SGroup, SOpts],
|
||||
["name='", SGroup, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(Host, F).
|
||||
|
||||
get_user_groups(US) ->
|
||||
Host = element(2, US),
|
||||
DBType = gen_mod:db_type(Host, ?MODULE),
|
||||
get_user_groups(US, Host, DBType) ++ get_special_users_groups(Host).
|
||||
|
||||
get_user_groups(US, Host, mnesia) ->
|
||||
case catch mnesia:dirty_read(sr_user, US) of
|
||||
Rs when is_list(Rs) ->
|
||||
[Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
|
||||
_ ->
|
||||
[]
|
||||
end ++ get_special_users_groups(Host).
|
||||
end;
|
||||
get_user_groups(US, Host, odbc) ->
|
||||
SJID = make_jid_s(US),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
Host, ["select grp from sr_user "
|
||||
"where jid='", SJID, "';"]) of
|
||||
{selected, ["grp"], Rs} ->
|
||||
[G || {G} <- Rs];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
is_group_enabled(Host1, Group1) ->
|
||||
{Host, Group} = split_grouphost(Host1, Group1),
|
||||
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
|
||||
[#sr_group{opts = Opts}] ->
|
||||
not lists:member(disabled, Opts);
|
||||
_ ->
|
||||
false
|
||||
case get_group_opts(Host, Group) of
|
||||
error ->
|
||||
false;
|
||||
Opts ->
|
||||
not lists:member(disabled, Opts)
|
||||
end.
|
||||
|
||||
%% @spec (Host::string(), Group::string(), Opt::atom(), Default) -> OptValue | Default
|
||||
get_group_opt(Host, Group, Opt, Default) ->
|
||||
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
|
||||
[#sr_group{opts = Opts}] ->
|
||||
case get_group_opts(Host, Group) of
|
||||
error ->
|
||||
Default;
|
||||
Opts ->
|
||||
case lists:keysearch(Opt, 1, Opts) of
|
||||
{value, {_, Val}} ->
|
||||
Val;
|
||||
false ->
|
||||
Default
|
||||
end;
|
||||
_ ->
|
||||
Default
|
||||
end
|
||||
end.
|
||||
|
||||
get_online_users(Host) ->
|
||||
@ -522,6 +609,9 @@ get_group_users(Host, Group, GroupOpts) ->
|
||||
|
||||
%% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}]
|
||||
get_group_explicit_users(Host, Group) ->
|
||||
get_group_explicit_users(Host, Group, gen_mod:db_type(Host, ?MODULE)).
|
||||
|
||||
get_group_explicit_users(Host, Group, mnesia) ->
|
||||
Read = (catch mnesia:dirty_index_read(
|
||||
sr_user,
|
||||
{Group, Host},
|
||||
@ -531,6 +621,21 @@ get_group_explicit_users(Host, Group) ->
|
||||
[R#sr_user.us || R <- Rs];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
get_group_explicit_users(Host, Group, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
Host, ["select jid from sr_user "
|
||||
"where grp='", SGroup, "';"]) of
|
||||
{selected, ["jid"], Rs} ->
|
||||
lists:map(
|
||||
fun({JID}) ->
|
||||
{U, S, _} = jlib:jid_tolower(
|
||||
jlib:string_to_jid(JID)),
|
||||
{U, S}
|
||||
end, Rs);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_group_name(Host1, Group1) ->
|
||||
@ -581,15 +686,30 @@ get_special_displayed_groups(GroupsOpts) ->
|
||||
%% for the list of groups of that server that user is member
|
||||
%% get the list of groups displayed
|
||||
get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
|
||||
Groups = case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of
|
||||
Rs when is_list(Rs) ->
|
||||
[{Group, proplists:get_value(Group, GroupsOpts, [])} ||
|
||||
#sr_user{group_host = {Group, H}} <- Rs, H == LServer];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
Groups = get_user_displayed_groups(LUser, LServer, GroupsOpts,
|
||||
gen_mod:db_type(LServer, ?MODULE)),
|
||||
displayed_groups(GroupsOpts, Groups).
|
||||
|
||||
get_user_displayed_groups(LUser, LServer, GroupsOpts, mnesia) ->
|
||||
case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of
|
||||
Rs when is_list(Rs) ->
|
||||
[{Group, proplists:get_value(Group, GroupsOpts, [])} ||
|
||||
#sr_user{group_host = {Group, H}} <- Rs, H == LServer];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
get_user_displayed_groups(LUser, LServer, GroupsOpts, odbc) ->
|
||||
SJID = make_jid_s(LUser, LServer),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, ["select grp from sr_user "
|
||||
"where jid='", SJID, "';"]) of
|
||||
{selected, ["grp"], Rs} ->
|
||||
[{Group, proplists:get_value(Group, GroupsOpts, [])} ||
|
||||
{Group} <- Rs];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @doc Get the list of groups that are displayed to this user
|
||||
get_user_displayed_groups(US) ->
|
||||
Host = element(2, US),
|
||||
@ -607,13 +727,26 @@ get_user_displayed_groups(US) ->
|
||||
[Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)].
|
||||
|
||||
is_user_in_group(US, Group, Host) ->
|
||||
is_user_in_group(US, Group, Host, gen_mod:db_type(Host, ?MODULE)).
|
||||
|
||||
is_user_in_group(US, Group, Host, mnesia) ->
|
||||
case catch mnesia:dirty_match_object(
|
||||
#sr_user{us=US, group_host={Group, Host}}) of
|
||||
[] -> lists:member(US, get_group_users(Host, Group));
|
||||
_ -> true
|
||||
end;
|
||||
is_user_in_group(US, Group, Host, odbc) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
Host, ["select * from sr_user where "
|
||||
"jid='", SJID, "' and grp='", SGroup, "';"]) of
|
||||
{selected, _, []} ->
|
||||
lists:member(US, get_group_users(Host, Group));
|
||||
_ ->
|
||||
true
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok}
|
||||
add_user_to_group(Host, US, Group) ->
|
||||
{LUser, LServer} = US,
|
||||
@ -634,13 +767,27 @@ add_user_to_group(Host, US, Group) ->
|
||||
push_user_to_displayed(LUser, LServer, Group, Host, both),
|
||||
%% Push members of groups that are displayed to this group
|
||||
push_displayed_to_user(LUser, LServer, Group, Host, both),
|
||||
R = #sr_user{us = US, group_host = {Group, Host}},
|
||||
F = fun() ->
|
||||
mnesia:write(R)
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
add_user_to_group(Host, US, Group, gen_mod:db_type(Host, ?MODULE))
|
||||
end.
|
||||
|
||||
add_user_to_group(Host, US, Group, mnesia) ->
|
||||
R = #sr_user{us = US, group_host = {Group, Host}},
|
||||
F = fun() ->
|
||||
mnesia:write(R)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
add_user_to_group(Host, US, Group, odbc) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"sr_user",
|
||||
["jid", "grp"],
|
||||
[SJID, SGroup],
|
||||
["jid='", SJID, "' and grp='", SGroup, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(Host, F).
|
||||
|
||||
push_displayed_to_user(LUser, LServer, Group, Host, Subscription) ->
|
||||
GroupsOpts = groups_with_opts(LServer),
|
||||
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
|
||||
@ -648,7 +795,6 @@ push_displayed_to_user(LUser, LServer, Group, Host, Subscription) ->
|
||||
[push_members_to_user(LUser, LServer, DGroup, Host, Subscription) || DGroup <- DisplayedGroups].
|
||||
|
||||
remove_user_from_group(Host, US, Group) ->
|
||||
GroupHost = {Group, Host},
|
||||
{LUser, LServer} = US,
|
||||
case ejabberd_regexp:run(LUser, "^@.+@$") of
|
||||
match ->
|
||||
@ -662,11 +808,8 @@ remove_user_from_group(Host, US, Group) ->
|
||||
end,
|
||||
?MODULE:set_group_opts(Host, Group, NewGroupOpts);
|
||||
nomatch ->
|
||||
R = #sr_user{us = US, group_host = GroupHost},
|
||||
F = fun() ->
|
||||
mnesia:delete_object(R)
|
||||
end,
|
||||
Result = mnesia:transaction(F),
|
||||
Result = remove_user_from_group(Host, US, Group,
|
||||
gen_mod:db_type(Host, ?MODULE)),
|
||||
%% Push removal of the old user to members of groups where the group that this user was members was displayed
|
||||
push_user_to_displayed(LUser, LServer, Group, Host, remove),
|
||||
%% Push removal of members of groups that where displayed to the group which this user has left
|
||||
@ -674,6 +817,22 @@ remove_user_from_group(Host, US, Group) ->
|
||||
Result
|
||||
end.
|
||||
|
||||
remove_user_from_group(Host, US, Group, mnesia) ->
|
||||
R = #sr_user{us = US, group_host = {Group, Host}},
|
||||
F = fun() ->
|
||||
mnesia:delete_object(R)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
remove_user_from_group(Host, US, Group, odbc) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from sr_user where jid='",
|
||||
SJID, "' and grp='", SGroup, "';"]),
|
||||
ok
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(Host, F).
|
||||
|
||||
push_members_to_user(LUser, LServer, Group, Host, Subscription) ->
|
||||
GroupsOpts = groups_with_opts(LServer),
|
||||
@ -1099,14 +1258,6 @@ shared_roster_group_parse_query(Host, Group, Query) ->
|
||||
nothing
|
||||
end.
|
||||
|
||||
%% Get the roster module for Server.
|
||||
get_roster_mod(Server) ->
|
||||
case lists:member(mod_roster_odbc,
|
||||
gen_mod:loaded_modules(Server)) of
|
||||
true -> mod_roster_odbc;
|
||||
false -> mod_roster
|
||||
end.
|
||||
|
||||
get_opt(Opts, Opt, Default) ->
|
||||
case lists:keysearch(Opt, 1, Opts) of
|
||||
{value, {_, Val}} ->
|
||||
@ -1125,3 +1276,12 @@ split_grouphost(Host, Group) ->
|
||||
[_] ->
|
||||
{Host, Group}
|
||||
end.
|
||||
|
||||
make_jid_s(U, S) ->
|
||||
ejabberd_odbc:escape(
|
||||
jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:make_jid(U, S, "")))).
|
||||
|
||||
make_jid_s({U, S}) ->
|
||||
make_jid_s(U, S).
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_vcard.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Vcard management in Mnesia
|
||||
%%% Purpose : Vcard management
|
||||
%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@ -61,25 +61,32 @@
|
||||
-define(PROCNAME, ejabberd_mod_vcard).
|
||||
|
||||
start(Host, Opts) ->
|
||||
mnesia:create_table(vcard, [{disc_only_copies, [node()]},
|
||||
{attributes, record_info(fields, vcard)}]),
|
||||
mnesia:create_table(vcard_search,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, vcard_search)}]),
|
||||
update_tables(),
|
||||
mnesia:add_table_index(vcard_search, luser),
|
||||
mnesia:add_table_index(vcard_search, lfn),
|
||||
mnesia:add_table_index(vcard_search, lfamily),
|
||||
mnesia:add_table_index(vcard_search, lgiven),
|
||||
mnesia:add_table_index(vcard_search, lmiddle),
|
||||
mnesia:add_table_index(vcard_search, lnickname),
|
||||
mnesia:add_table_index(vcard_search, lbday),
|
||||
mnesia:add_table_index(vcard_search, lctry),
|
||||
mnesia:add_table_index(vcard_search, llocality),
|
||||
mnesia:add_table_index(vcard_search, lemail),
|
||||
mnesia:add_table_index(vcard_search, lorgname),
|
||||
mnesia:add_table_index(vcard_search, lorgunit),
|
||||
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(vcard,
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, vcard)}]),
|
||||
mnesia:create_table(vcard_search,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, vcard_search)}]),
|
||||
update_tables(),
|
||||
mnesia:add_table_index(vcard_search, luser),
|
||||
mnesia:add_table_index(vcard_search, lfn),
|
||||
mnesia:add_table_index(vcard_search, lfamily),
|
||||
mnesia:add_table_index(vcard_search, lgiven),
|
||||
mnesia:add_table_index(vcard_search, lmiddle),
|
||||
mnesia:add_table_index(vcard_search, lnickname),
|
||||
mnesia:add_table_index(vcard_search, lbday),
|
||||
mnesia:add_table_index(vcard_search, lctry),
|
||||
mnesia:add_table_index(vcard_search, llocality),
|
||||
mnesia:add_table_index(vcard_search, lemail),
|
||||
mnesia:add_table_index(vcard_search, lorgname),
|
||||
mnesia:add_table_index(vcard_search, lorgunit);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
@ -183,19 +190,45 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
|
||||
end;
|
||||
get ->
|
||||
#jid{luser = LUser, lserver = LServer} = To,
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:read({vcard, US})
|
||||
end,
|
||||
Els = case mnesia:transaction(F) of
|
||||
{atomic, Rs} ->
|
||||
lists:map(fun(R) ->
|
||||
R#vcard.vcard
|
||||
end, Rs);
|
||||
{aborted, _Reason} ->
|
||||
[]
|
||||
end,
|
||||
IQ#iq{type = result, sub_el = Els}
|
||||
case get_vcard(LUser, LServer) of
|
||||
error ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
|
||||
Els ->
|
||||
IQ#iq{type = result, sub_el = Els}
|
||||
end
|
||||
end.
|
||||
|
||||
get_vcard(LUser, LServer) ->
|
||||
get_vcard(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_vcard(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:read({vcard, US})
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Rs} ->
|
||||
lists:map(fun(R) ->
|
||||
R#vcard.vcard
|
||||
end, Rs);
|
||||
{aborted, _Reason} ->
|
||||
error
|
||||
end;
|
||||
get_vcard(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_vcard(LServer, Username) of
|
||||
{selected, ["vcard"], [{SVCARD}]} ->
|
||||
case xml_stream:parse_element(SVCARD) of
|
||||
{error, _Reason} ->
|
||||
error;
|
||||
VCARD ->
|
||||
[VCARD]
|
||||
end;
|
||||
{selected, ["vcard"], []} ->
|
||||
[];
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
set_vcard(User, LServer, VCARD) ->
|
||||
@ -231,8 +264,6 @@ set_vcard(User, LServer, VCARD) ->
|
||||
LOrgName = string2lower(OrgName),
|
||||
LOrgUnit = string2lower(OrgUnit),
|
||||
|
||||
US = {LUser, LServer},
|
||||
|
||||
if
|
||||
(LUser == error) or
|
||||
(LFN == error) or
|
||||
@ -248,26 +279,66 @@ set_vcard(User, LServer, VCARD) ->
|
||||
(LOrgUnit == error) ->
|
||||
{error, badarg};
|
||||
true ->
|
||||
F = fun() ->
|
||||
mnesia:write(#vcard{us = US, vcard = VCARD}),
|
||||
mnesia:write(
|
||||
#vcard_search{us = US,
|
||||
user = {User, LServer},
|
||||
luser = LUser,
|
||||
fn = FN, lfn = LFN,
|
||||
family = Family, lfamily = LFamily,
|
||||
given = Given, lgiven = LGiven,
|
||||
middle = Middle, lmiddle = LMiddle,
|
||||
nickname = Nickname, lnickname = LNickname,
|
||||
bday = BDay, lbday = LBDay,
|
||||
ctry = CTRY, lctry = LCTRY,
|
||||
locality = Locality, llocality = LLocality,
|
||||
email = EMail, lemail = LEMail,
|
||||
orgname = OrgName, lorgname = LOrgName,
|
||||
orgunit = OrgUnit, lorgunit = LOrgUnit
|
||||
})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:write(#vcard{us = US, vcard = VCARD}),
|
||||
mnesia:write(
|
||||
#vcard_search{us = US,
|
||||
user = {User, LServer},
|
||||
luser = LUser,
|
||||
fn = FN, lfn = LFN,
|
||||
family = Family, lfamily = LFamily,
|
||||
given = Given, lgiven = LGiven,
|
||||
middle = Middle, lmiddle = LMiddle,
|
||||
nickname = Nickname, lnickname = LNickname,
|
||||
bday = BDay, lbday = LBDay,
|
||||
ctry = CTRY, lctry = LCTRY,
|
||||
locality = Locality, llocality = LLocality,
|
||||
email = EMail, lemail = LEMail,
|
||||
orgname = OrgName, lorgname = LOrgName,
|
||||
orgunit = OrgUnit, lorgunit = LOrgUnit
|
||||
})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
odbc ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
LUsername = ejabberd_odbc:escape(LUser),
|
||||
SVCARD = ejabberd_odbc:escape(
|
||||
xml:element_to_binary(VCARD)),
|
||||
|
||||
SFN = ejabberd_odbc:escape(FN),
|
||||
SLFN = ejabberd_odbc:escape(LFN),
|
||||
SFamily = ejabberd_odbc:escape(Family),
|
||||
SLFamily = ejabberd_odbc:escape(LFamily),
|
||||
SGiven = ejabberd_odbc:escape(Given),
|
||||
SLGiven = ejabberd_odbc:escape(LGiven),
|
||||
SMiddle = ejabberd_odbc:escape(Middle),
|
||||
SLMiddle = ejabberd_odbc:escape(LMiddle),
|
||||
SNickname = ejabberd_odbc:escape(Nickname),
|
||||
SLNickname = ejabberd_odbc:escape(LNickname),
|
||||
SBDay = ejabberd_odbc:escape(BDay),
|
||||
SLBDay = ejabberd_odbc:escape(LBDay),
|
||||
SCTRY = ejabberd_odbc:escape(CTRY),
|
||||
SLCTRY = ejabberd_odbc:escape(LCTRY),
|
||||
SLocality = ejabberd_odbc:escape(Locality),
|
||||
SLLocality = ejabberd_odbc:escape(LLocality),
|
||||
SEMail = ejabberd_odbc:escape(EMail),
|
||||
SLEMail = ejabberd_odbc:escape(LEMail),
|
||||
SOrgName = ejabberd_odbc:escape(OrgName),
|
||||
SLOrgName = ejabberd_odbc:escape(LOrgName),
|
||||
SOrgUnit = ejabberd_odbc:escape(OrgUnit),
|
||||
SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
|
||||
|
||||
odbc_queries:set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail,
|
||||
SFN, SFamily, SGiven, SLBDay, SLCTRY,
|
||||
SLEMail, SLFN, SLFamily, SLGiven,
|
||||
SLLocality, SLMiddle, SLNickname,
|
||||
SLOrgName, SLOrgUnit, SLocality,
|
||||
SMiddle, SNickname, SOrgName,
|
||||
SOrgUnit, SVCARD, Username)
|
||||
end,
|
||||
ejabberd_hooks:run(vcard_set, LServer, [LUser, LServer, VCARD])
|
||||
end.
|
||||
|
||||
@ -481,14 +552,34 @@ search_result(Lang, JID, ServerHost, Data) ->
|
||||
?TLFIELD("text-single", "Email", "email"),
|
||||
?TLFIELD("text-single", "Organization Name", "orgname"),
|
||||
?TLFIELD("text-single", "Organization Unit", "orgunit")
|
||||
]}] ++ lists:map(fun record_to_item/1, search(ServerHost, Data)).
|
||||
]}] ++ lists:map(fun(R) -> record_to_item(ServerHost, R) end,
|
||||
search(ServerHost, Data)).
|
||||
|
||||
-define(FIELD(Var, Val),
|
||||
{xmlelement, "field", [{"var", Var}],
|
||||
[{xmlelement, "value", [],
|
||||
[{xmlcdata, Val}]}]}).
|
||||
|
||||
record_to_item(R) ->
|
||||
record_to_item(LServer, {Username, FN, Family, Given, Middle,
|
||||
Nickname, BDay, CTRY, Locality,
|
||||
EMail, OrgName, OrgUnit}) ->
|
||||
{xmlelement, "item", [],
|
||||
[
|
||||
?FIELD("jid", Username ++ "@" ++ LServer),
|
||||
?FIELD("fn", FN),
|
||||
?FIELD("last", Family),
|
||||
?FIELD("first", Given),
|
||||
?FIELD("middle", Middle),
|
||||
?FIELD("nick", Nickname),
|
||||
?FIELD("bday", BDay),
|
||||
?FIELD("ctry", CTRY),
|
||||
?FIELD("locality", Locality),
|
||||
?FIELD("email", EMail),
|
||||
?FIELD("orgname", OrgName),
|
||||
?FIELD("orgunit", OrgUnit)
|
||||
]
|
||||
};
|
||||
record_to_item(_LServer, #vcard_search{} = R) ->
|
||||
{User, Server} = R#vcard_search.user,
|
||||
{xmlelement, "item", [],
|
||||
[
|
||||
@ -509,9 +600,13 @@ record_to_item(R) ->
|
||||
|
||||
|
||||
search(LServer, Data) ->
|
||||
MatchSpec = make_matchspec(LServer, Data),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
MatchSpec = make_matchspec(LServer, Data, DBType),
|
||||
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
allow_return_all, false),
|
||||
search(LServer, MatchSpec, AllowReturnAll, DBType).
|
||||
|
||||
search(LServer, MatchSpec, AllowReturnAll, mnesia) ->
|
||||
if
|
||||
(MatchSpec == #vcard_search{_ = '_'}) and (not AllowReturnAll) ->
|
||||
[];
|
||||
@ -535,17 +630,58 @@ search(LServer, Data) ->
|
||||
lists:sublist(Rs, ?JUD_MATCHES)
|
||||
end
|
||||
end
|
||||
end;
|
||||
search(LServer, MatchSpec, AllowReturnAll, odbc) ->
|
||||
if
|
||||
(MatchSpec == "") and (not AllowReturnAll) ->
|
||||
[];
|
||||
true ->
|
||||
Limit = case gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
matches, ?JUD_MATCHES) of
|
||||
infinity ->
|
||||
"";
|
||||
Val when is_integer(Val) and (Val > 0) ->
|
||||
[" LIMIT ", integer_to_list(Val)];
|
||||
Val ->
|
||||
?ERROR_MSG("Illegal option value ~p. "
|
||||
"Default value ~p substituted.",
|
||||
[{matches, Val}, ?JUD_MATCHES]),
|
||||
[" LIMIT ", integer_to_list(?JUD_MATCHES)]
|
||||
end,
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select username, fn, family, given, middle, "
|
||||
" nickname, bday, ctry, locality, "
|
||||
" email, orgname, orgunit from vcard_search ",
|
||||
MatchSpec, Limit, ";"]) of
|
||||
{selected, ["username", "fn", "family", "given", "middle",
|
||||
"nickname", "bday", "ctry", "locality",
|
||||
"email", "orgname", "orgunit"],
|
||||
Rs} when is_list(Rs) ->
|
||||
Rs;
|
||||
Error ->
|
||||
?ERROR_MSG("~p", [Error]),
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
make_matchspec(LServer, Data) ->
|
||||
make_matchspec(LServer, Data, mnesia) ->
|
||||
GlobMatch = #vcard_search{_ = '_'},
|
||||
Match = filter_fields(Data, GlobMatch, LServer),
|
||||
Match.
|
||||
|
||||
filter_fields([], Match, _LServer) ->
|
||||
Match = filter_fields(Data, GlobMatch, LServer, mnesia),
|
||||
Match;
|
||||
filter_fields([{SVar, [Val]} | Ds], Match, LServer)
|
||||
make_matchspec(LServer, Data, odbc) ->
|
||||
filter_fields(Data, "", LServer, odbc).
|
||||
|
||||
filter_fields([], Match, _LServer, mnesia) ->
|
||||
Match;
|
||||
filter_fields([], Match, _LServer, odbc) ->
|
||||
case Match of
|
||||
"" ->
|
||||
"";
|
||||
_ ->
|
||||
[" where ", Match]
|
||||
end;
|
||||
filter_fields([{SVar, [Val]} | Ds], Match, LServer, mnesia)
|
||||
when is_list(Val) and (Val /= "") ->
|
||||
LVal = string2lower(Val),
|
||||
NewMatch = case SVar of
|
||||
@ -571,9 +707,46 @@ filter_fields([{SVar, [Val]} | Ds], Match, LServer)
|
||||
"orgunit" -> Match#vcard_search{lorgunit = make_val(LVal)};
|
||||
_ -> Match
|
||||
end,
|
||||
filter_fields(Ds, NewMatch, LServer);
|
||||
filter_fields([_ | Ds], Match, LServer) ->
|
||||
filter_fields(Ds, Match, LServer).
|
||||
filter_fields(Ds, NewMatch, LServer, mnesia);
|
||||
filter_fields([{SVar, [Val]} | Ds], Match, LServer, odbc)
|
||||
when is_list(Val) and (Val /= "") ->
|
||||
LVal = string2lower(Val),
|
||||
NewMatch = case SVar of
|
||||
"user" -> make_val(Match, "lusername", LVal);
|
||||
"fn" -> make_val(Match, "lfn", LVal);
|
||||
"last" -> make_val(Match, "lfamily", LVal);
|
||||
"first" -> make_val(Match, "lgiven", LVal);
|
||||
"middle" -> make_val(Match, "lmiddle", LVal);
|
||||
"nick" -> make_val(Match, "lnickname", LVal);
|
||||
"bday" -> make_val(Match, "lbday", LVal);
|
||||
"ctry" -> make_val(Match, "lctry", LVal);
|
||||
"locality" -> make_val(Match, "llocality", LVal);
|
||||
"email" -> make_val(Match, "lemail", LVal);
|
||||
"orgname" -> make_val(Match, "lorgname", LVal);
|
||||
"orgunit" -> make_val(Match, "lorgunit", LVal);
|
||||
_ -> Match
|
||||
end,
|
||||
filter_fields(Ds, NewMatch, LServer, odbc);
|
||||
filter_fields([_ | Ds], Match, LServer, DBType) ->
|
||||
filter_fields(Ds, Match, LServer, DBType).
|
||||
|
||||
make_val(Match, Field, Val) ->
|
||||
Condition =
|
||||
case lists:suffix("*", Val) of
|
||||
true ->
|
||||
Val1 = lists:sublist(Val, length(Val) - 1),
|
||||
SVal = ejabberd_odbc:escape_like(Val1) ++ "%",
|
||||
[Field, " LIKE '", SVal, "'"];
|
||||
_ ->
|
||||
SVal = ejabberd_odbc:escape(Val),
|
||||
[Field, " = '", SVal, "'"]
|
||||
end,
|
||||
case Match of
|
||||
"" ->
|
||||
Condition;
|
||||
_ ->
|
||||
[Match, " and ", Condition]
|
||||
end.
|
||||
|
||||
make_val(Val) ->
|
||||
case lists:suffix("*", Val) of
|
||||
@ -679,13 +852,21 @@ reindex_vcards() ->
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
remove_user(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({vcard, US}),
|
||||
mnesia:delete({vcard_search, US})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
ejabberd_odbc:sql_transaction(
|
||||
LServer,
|
||||
[["delete from vcard where username='", Username, "';"],
|
||||
["delete from vcard_search where lusername='", Username, "';"]]).
|
||||
|
||||
update_tables() ->
|
||||
update_vcard_table(),
|
||||
|
@ -1,659 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_vcard.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : vCard support via ODBC
|
||||
%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_vcard_odbc).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, init/3, stop/1,
|
||||
get_sm_features/5,
|
||||
process_local_iq/3,
|
||||
process_sm_iq/3,
|
||||
%reindex_vcards/0,
|
||||
remove_user/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
|
||||
-define(JUD_MATCHES, 30).
|
||||
-define(PROCNAME, ejabberd_mod_vcard).
|
||||
|
||||
start(Host, Opts) ->
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||
?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD,
|
||||
?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "vjud.@HOST@"),
|
||||
Search = gen_mod:get_opt(search, Opts, true),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MyHost, Host, Search])).
|
||||
|
||||
|
||||
init(Host, ServerHost, Search) ->
|
||||
case Search of
|
||||
false ->
|
||||
loop(Host, ServerHost);
|
||||
_ ->
|
||||
ejabberd_router:register_route(Host),
|
||||
loop(Host, ServerHost)
|
||||
end.
|
||||
|
||||
loop(Host, ServerHost) ->
|
||||
receive
|
||||
{route, From, To, Packet} ->
|
||||
case catch do_route(ServerHost, From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
loop(Host, ServerHost);
|
||||
stop ->
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ok;
|
||||
_ ->
|
||||
loop(Host, ServerHost)
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(remove_user, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
Proc ! stop,
|
||||
{wait, Proc}.
|
||||
|
||||
get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc;
|
||||
|
||||
get_sm_features(Acc, _From, _To, Node, _Lang) ->
|
||||
case Node of
|
||||
[] ->
|
||||
case Acc of
|
||||
{result, Features} ->
|
||||
{result, [?NS_VCARD | Features]};
|
||||
empty ->
|
||||
{result, [?NS_VCARD]}
|
||||
end;
|
||||
_ ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "vCard",
|
||||
[{"xmlns", ?NS_VCARD}],
|
||||
[{xmlelement, "FN", [],
|
||||
[{xmlcdata, "ejabberd"}]},
|
||||
{xmlelement, "URL", [],
|
||||
[{xmlcdata, ?EJABBERD_URI}]},
|
||||
{xmlelement, "DESC", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(
|
||||
Lang,
|
||||
"Erlang Jabber Server") ++
|
||||
"\nCopyright (c) 2002-2012 ProcessOne"}]},
|
||||
{xmlelement, "BDAY", [],
|
||||
[{xmlcdata, "2002-11-16"}]}
|
||||
]}]}
|
||||
end.
|
||||
|
||||
|
||||
process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
#jid{user = User, lserver = LServer} = From,
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true ->
|
||||
set_vcard(User, LServer, SubEl),
|
||||
IQ#iq{type = result, sub_el = []};
|
||||
false ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
|
||||
end;
|
||||
get ->
|
||||
#jid{luser = LUser, lserver = LServer} = To,
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_vcard(LServer, Username) of
|
||||
{selected, ["vcard"], [{SVCARD}]} ->
|
||||
case xml_stream:parse_element(SVCARD) of
|
||||
{error, _Reason} ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
|
||||
VCARD ->
|
||||
IQ#iq{type = result, sub_el = [VCARD]}
|
||||
end;
|
||||
{selected, ["vcard"], []} ->
|
||||
IQ#iq{type = result, sub_el = []};
|
||||
_ ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
||||
end
|
||||
end.
|
||||
|
||||
set_vcard(User, LServer, VCARD) ->
|
||||
FN = xml:get_path_s(VCARD, [{elem, "FN"}, cdata]),
|
||||
Family = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "FAMILY"}, cdata]),
|
||||
Given = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "GIVEN"}, cdata]),
|
||||
Middle = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "MIDDLE"}, cdata]),
|
||||
Nickname = xml:get_path_s(VCARD, [{elem, "NICKNAME"}, cdata]),
|
||||
BDay = xml:get_path_s(VCARD, [{elem, "BDAY"}, cdata]),
|
||||
CTRY = xml:get_path_s(VCARD, [{elem, "ADR"}, {elem, "CTRY"}, cdata]),
|
||||
Locality = xml:get_path_s(VCARD, [{elem, "ADR"}, {elem, "LOCALITY"},cdata]),
|
||||
EMail1 = xml:get_path_s(VCARD, [{elem, "EMAIL"}, {elem, "USERID"},cdata]),
|
||||
EMail2 = xml:get_path_s(VCARD, [{elem, "EMAIL"}, cdata]),
|
||||
OrgName = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGNAME"}, cdata]),
|
||||
OrgUnit = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGUNIT"}, cdata]),
|
||||
EMail = case EMail1 of
|
||||
"" ->
|
||||
EMail2;
|
||||
_ ->
|
||||
EMail1
|
||||
end,
|
||||
|
||||
LUser = jlib:nodeprep(User),
|
||||
LFN = string2lower(FN),
|
||||
LFamily = string2lower(Family),
|
||||
LGiven = string2lower(Given),
|
||||
LMiddle = string2lower(Middle),
|
||||
LNickname = string2lower(Nickname),
|
||||
LBDay = string2lower(BDay),
|
||||
LCTRY = string2lower(CTRY),
|
||||
LLocality = string2lower(Locality),
|
||||
LEMail = string2lower(EMail),
|
||||
LOrgName = string2lower(OrgName),
|
||||
LOrgUnit = string2lower(OrgUnit),
|
||||
|
||||
if
|
||||
(LUser == error) or
|
||||
(LFN == error) or
|
||||
(LFamily == error) or
|
||||
(LGiven == error) or
|
||||
(LMiddle == error) or
|
||||
(LNickname == error) or
|
||||
(LBDay == error) or
|
||||
(LCTRY == error) or
|
||||
(LLocality == error) or
|
||||
(LEMail == error) or
|
||||
(LOrgName == error) or
|
||||
(LOrgUnit == error) ->
|
||||
{error, badarg};
|
||||
true ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
LUsername = ejabberd_odbc:escape(LUser),
|
||||
SVCARD = ejabberd_odbc:escape(
|
||||
xml:element_to_binary(VCARD)),
|
||||
|
||||
SFN = ejabberd_odbc:escape(FN),
|
||||
SLFN = ejabberd_odbc:escape(LFN),
|
||||
SFamily = ejabberd_odbc:escape(Family),
|
||||
SLFamily = ejabberd_odbc:escape(LFamily),
|
||||
SGiven = ejabberd_odbc:escape(Given),
|
||||
SLGiven = ejabberd_odbc:escape(LGiven),
|
||||
SMiddle = ejabberd_odbc:escape(Middle),
|
||||
SLMiddle = ejabberd_odbc:escape(LMiddle),
|
||||
SNickname = ejabberd_odbc:escape(Nickname),
|
||||
SLNickname = ejabberd_odbc:escape(LNickname),
|
||||
SBDay = ejabberd_odbc:escape(BDay),
|
||||
SLBDay = ejabberd_odbc:escape(LBDay),
|
||||
SCTRY = ejabberd_odbc:escape(CTRY),
|
||||
SLCTRY = ejabberd_odbc:escape(LCTRY),
|
||||
SLocality = ejabberd_odbc:escape(Locality),
|
||||
SLLocality = ejabberd_odbc:escape(LLocality),
|
||||
SEMail = ejabberd_odbc:escape(EMail),
|
||||
SLEMail = ejabberd_odbc:escape(LEMail),
|
||||
SOrgName = ejabberd_odbc:escape(OrgName),
|
||||
SLOrgName = ejabberd_odbc:escape(LOrgName),
|
||||
SOrgUnit = ejabberd_odbc:escape(OrgUnit),
|
||||
SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
|
||||
|
||||
odbc_queries:set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail,
|
||||
SFN, SFamily, SGiven, SLBDay, SLCTRY,
|
||||
SLEMail, SLFN, SLFamily, SLGiven,
|
||||
SLLocality, SLMiddle, SLNickname,
|
||||
SLOrgName, SLOrgUnit, SLocality,
|
||||
SMiddle, SNickname, SOrgName,
|
||||
SOrgUnit, SVCARD, Username),
|
||||
|
||||
ejabberd_hooks:run(vcard_set, LServer, [LUser, LServer, VCARD])
|
||||
end.
|
||||
|
||||
string2lower(String) ->
|
||||
case stringprep:tolower(String) of
|
||||
Lower when is_list(Lower) -> Lower;
|
||||
error -> string:to_lower(String)
|
||||
end.
|
||||
|
||||
-define(TLFIELD(Type, Label, Var),
|
||||
{xmlelement, "field", [{"type", Type},
|
||||
{"label", translate:translate(Lang, Label)},
|
||||
{"var", Var}], []}).
|
||||
|
||||
|
||||
-define(FORM(JID),
|
||||
[{xmlelement, "instructions", [],
|
||||
[{xmlcdata, translate:translate(Lang, "You need an x:data capable client to search")}]},
|
||||
{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[{xmlelement, "title", [],
|
||||
[{xmlcdata, translate:translate(Lang, "Search users in ") ++
|
||||
jlib:jid_to_string(JID)}]},
|
||||
{xmlelement, "instructions", [],
|
||||
[{xmlcdata, translate:translate(Lang, "Fill in the form to search "
|
||||
"for any matching Jabber User "
|
||||
"(Add * to the end of field to "
|
||||
"match substring)")}]},
|
||||
?TLFIELD("text-single", "User", "user"),
|
||||
?TLFIELD("text-single", "Full Name", "fn"),
|
||||
?TLFIELD("text-single", "Name", "first"),
|
||||
?TLFIELD("text-single", "Middle Name", "middle"),
|
||||
?TLFIELD("text-single", "Family Name", "last"),
|
||||
?TLFIELD("text-single", "Nickname", "nick"),
|
||||
?TLFIELD("text-single", "Birthday", "bday"),
|
||||
?TLFIELD("text-single", "Country", "ctry"),
|
||||
?TLFIELD("text-single", "City", "locality"),
|
||||
?TLFIELD("text-single", "Email", "email"),
|
||||
?TLFIELD("text-single", "Organization Name", "orgname"),
|
||||
?TLFIELD("text-single", "Organization Unit", "orgunit")
|
||||
]}]).
|
||||
|
||||
do_route(ServerHost, From, To, Packet) ->
|
||||
#jid{user = User, resource = Resource} = To,
|
||||
if
|
||||
(User /= "") or (Resource /= "") ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
true ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang, sub_el = SubEl} ->
|
||||
case Type of
|
||||
set ->
|
||||
XDataEl = find_xdata_el(SubEl),
|
||||
case XDataEl of
|
||||
false ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
XData = jlib:parse_xdata_submit(XDataEl),
|
||||
case XData of
|
||||
invalid ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From,
|
||||
Err);
|
||||
_ ->
|
||||
ResIQ =
|
||||
IQ#iq{
|
||||
type = result,
|
||||
sub_el =
|
||||
[{xmlelement,
|
||||
"query",
|
||||
[{"xmlns", ?NS_SEARCH}],
|
||||
[{xmlelement, "x",
|
||||
[{"xmlns", ?NS_XDATA},
|
||||
{"type", "result"}],
|
||||
search_result(Lang, To, ServerHost, XData)
|
||||
}]}]},
|
||||
ejabberd_router:route(
|
||||
To, From, jlib:iq_to_xml(ResIQ))
|
||||
end
|
||||
end;
|
||||
get ->
|
||||
ResIQ = IQ#iq{type = result,
|
||||
sub_el = [{xmlelement,
|
||||
"query",
|
||||
[{"xmlns", ?NS_SEARCH}],
|
||||
?FORM(To)
|
||||
}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(ResIQ))
|
||||
end;
|
||||
#iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
|
||||
case Type of
|
||||
set ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_NOT_ALLOWED),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
get ->
|
||||
Info = ejabberd_hooks:run_fold(
|
||||
disco_info, ServerHost, [],
|
||||
[ServerHost, ?MODULE, "", ""]),
|
||||
ResIQ =
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement,
|
||||
"query",
|
||||
[{"xmlns", ?NS_DISCO_INFO}],
|
||||
[{xmlelement, "identity",
|
||||
[{"category", "directory"},
|
||||
{"type", "user"},
|
||||
{"name",
|
||||
translate:translate(Lang, "vCard User Search")}],
|
||||
[]},
|
||||
{xmlelement, "feature",
|
||||
[{"var", ?NS_SEARCH}], []},
|
||||
{xmlelement, "feature",
|
||||
[{"var", ?NS_VCARD}], []}
|
||||
] ++ Info
|
||||
}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(ResIQ))
|
||||
end;
|
||||
#iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
|
||||
case Type of
|
||||
set ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_NOT_ALLOWED),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
get ->
|
||||
ResIQ =
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement,
|
||||
"query",
|
||||
[{"xmlns", ?NS_DISCO_ITEMS}],
|
||||
[]}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(ResIQ))
|
||||
end;
|
||||
#iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
|
||||
ResIQ =
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement,
|
||||
"vCard",
|
||||
[{"xmlns", ?NS_VCARD}],
|
||||
iq_get_vcard(Lang)}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(ResIQ));
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end.
|
||||
|
||||
iq_get_vcard(Lang) ->
|
||||
[{xmlelement, "FN", [],
|
||||
[{xmlcdata, "ejabberd/mod_vcard"}]},
|
||||
{xmlelement, "URL", [],
|
||||
[{xmlcdata, ?EJABBERD_URI}]},
|
||||
{xmlelement, "DESC", [],
|
||||
[{xmlcdata, translate:translate(
|
||||
Lang,
|
||||
"ejabberd vCard module") ++
|
||||
"\nCopyright (c) 2003-2012 ProcessOne"}]}].
|
||||
|
||||
find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
|
||||
find_xdata_el1(SubEls).
|
||||
|
||||
find_xdata_el1([]) ->
|
||||
false;
|
||||
find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_XDATA ->
|
||||
{xmlelement, Name, Attrs, SubEls};
|
||||
_ ->
|
||||
find_xdata_el1(Els)
|
||||
end;
|
||||
find_xdata_el1([_ | Els]) ->
|
||||
find_xdata_el1(Els).
|
||||
|
||||
-define(LFIELD(Label, Var),
|
||||
{xmlelement, "field", [{"label", translate:translate(Lang, Label)},
|
||||
{"var", Var}], []}).
|
||||
|
||||
search_result(Lang, JID, ServerHost, Data) ->
|
||||
[{xmlelement, "title", [],
|
||||
[{xmlcdata, translate:translate(Lang, "Search Results for ") ++
|
||||
jlib:jid_to_string(JID)}]},
|
||||
{xmlelement, "reported", [],
|
||||
[?TLFIELD("text-single", "Jabber ID", "jid"),
|
||||
?TLFIELD("text-single", "Full Name", "fn"),
|
||||
?TLFIELD("text-single", "Name", "first"),
|
||||
?TLFIELD("text-single", "Middle Name", "middle"),
|
||||
?TLFIELD("text-single", "Family Name", "last"),
|
||||
?TLFIELD("text-single", "Nickname", "nick"),
|
||||
?TLFIELD("text-single", "Birthday", "bday"),
|
||||
?TLFIELD("text-single", "Country", "ctry"),
|
||||
?TLFIELD("text-single", "City", "locality"),
|
||||
?TLFIELD("text-single", "Email", "email"),
|
||||
?TLFIELD("text-single", "Organization Name", "orgname"),
|
||||
?TLFIELD("text-single", "Organization Unit", "orgunit")
|
||||
]}] ++ lists:map(fun(R) -> record_to_item(ServerHost, R) end,
|
||||
search(ServerHost, Data)).
|
||||
|
||||
-define(FIELD(Var, Val),
|
||||
{xmlelement, "field", [{"var", Var}],
|
||||
[{xmlelement, "value", [],
|
||||
[{xmlcdata, Val}]}]}).
|
||||
|
||||
|
||||
record_to_item(LServer, {Username, FN, Family, Given, Middle,
|
||||
Nickname, BDay, CTRY, Locality,
|
||||
EMail, OrgName, OrgUnit}) ->
|
||||
{xmlelement, "item", [],
|
||||
[
|
||||
?FIELD("jid", Username ++ "@" ++ LServer),
|
||||
?FIELD("fn", FN),
|
||||
?FIELD("last", Family),
|
||||
?FIELD("first", Given),
|
||||
?FIELD("middle", Middle),
|
||||
?FIELD("nick", Nickname),
|
||||
?FIELD("bday", BDay),
|
||||
?FIELD("ctry", CTRY),
|
||||
?FIELD("locality", Locality),
|
||||
?FIELD("email", EMail),
|
||||
?FIELD("orgname", OrgName),
|
||||
?FIELD("orgunit", OrgUnit)
|
||||
]
|
||||
}.
|
||||
|
||||
|
||||
search(LServer, Data) ->
|
||||
MatchSpec = make_matchspec(LServer, Data),
|
||||
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
allow_return_all, false),
|
||||
if
|
||||
(MatchSpec == "") and (not AllowReturnAll) ->
|
||||
[];
|
||||
true ->
|
||||
Limit = case gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
matches, ?JUD_MATCHES) of
|
||||
infinity ->
|
||||
"";
|
||||
Val when is_integer(Val) and (Val > 0) ->
|
||||
[" LIMIT ", integer_to_list(Val)];
|
||||
Val ->
|
||||
?ERROR_MSG("Illegal option value ~p. "
|
||||
"Default value ~p substituted.",
|
||||
[{matches, Val}, ?JUD_MATCHES]),
|
||||
[" LIMIT ", integer_to_list(?JUD_MATCHES)]
|
||||
end,
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
["select username, fn, family, given, middle, "
|
||||
" nickname, bday, ctry, locality, "
|
||||
" email, orgname, orgunit from vcard_search ",
|
||||
MatchSpec, Limit, ";"]) of
|
||||
{selected, ["username", "fn", "family", "given", "middle",
|
||||
"nickname", "bday", "ctry", "locality",
|
||||
"email", "orgname", "orgunit"],
|
||||
Rs} when is_list(Rs) ->
|
||||
Rs;
|
||||
Error ->
|
||||
?ERROR_MSG("~p", [Error]),
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
make_matchspec(LServer, Data) ->
|
||||
filter_fields(Data, "", LServer).
|
||||
|
||||
filter_fields([], Match, _LServer) ->
|
||||
case Match of
|
||||
"" ->
|
||||
"";
|
||||
_ ->
|
||||
[" where ", Match]
|
||||
end;
|
||||
filter_fields([{SVar, [Val]} | Ds], Match, LServer)
|
||||
when is_list(Val) and (Val /= "") ->
|
||||
LVal = string2lower(Val),
|
||||
NewMatch = case SVar of
|
||||
"user" -> make_val(Match, "lusername", LVal);
|
||||
"fn" -> make_val(Match, "lfn", LVal);
|
||||
"last" -> make_val(Match, "lfamily", LVal);
|
||||
"first" -> make_val(Match, "lgiven", LVal);
|
||||
"middle" -> make_val(Match, "lmiddle", LVal);
|
||||
"nick" -> make_val(Match, "lnickname", LVal);
|
||||
"bday" -> make_val(Match, "lbday", LVal);
|
||||
"ctry" -> make_val(Match, "lctry", LVal);
|
||||
"locality" -> make_val(Match, "llocality", LVal);
|
||||
"email" -> make_val(Match, "lemail", LVal);
|
||||
"orgname" -> make_val(Match, "lorgname", LVal);
|
||||
"orgunit" -> make_val(Match, "lorgunit", LVal);
|
||||
_ -> Match
|
||||
end,
|
||||
filter_fields(Ds, NewMatch, LServer);
|
||||
filter_fields([_ | Ds], Match, LServer) ->
|
||||
filter_fields(Ds, Match, LServer).
|
||||
|
||||
make_val(Match, Field, Val) ->
|
||||
Condition =
|
||||
case lists:suffix("*", Val) of
|
||||
true ->
|
||||
Val1 = lists:sublist(Val, length(Val) - 1),
|
||||
SVal = ejabberd_odbc:escape_like(Val1) ++ "%",
|
||||
[Field, " LIKE '", SVal, "'"];
|
||||
_ ->
|
||||
SVal = ejabberd_odbc:escape(Val),
|
||||
[Field, " = '", SVal, "'"]
|
||||
end,
|
||||
case Match of
|
||||
"" ->
|
||||
Condition;
|
||||
_ ->
|
||||
[Match, " and ", Condition]
|
||||
end.
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%set_vcard_t(R, _) ->
|
||||
% US = R#vcard.us,
|
||||
% User = US,
|
||||
% VCARD = R#vcard.vcard,
|
||||
%
|
||||
% FN = xml:get_path_s(VCARD, [{elem, "FN"}, cdata]),
|
||||
% Family = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "FAMILY"}, cdata]),
|
||||
% Given = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "GIVEN"}, cdata]),
|
||||
% Middle = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "MIDDLE"}, cdata]),
|
||||
% Nickname = xml:get_path_s(VCARD, [{elem, "NICKNAME"}, cdata]),
|
||||
% BDay = xml:get_path_s(VCARD, [{elem, "BDAY"}, cdata]),
|
||||
% CTRY = xml:get_path_s(VCARD, [{elem, "ADR"}, {elem, "CTRY"}, cdata]),
|
||||
% Locality = xml:get_path_s(VCARD, [{elem, "ADR"}, {elem, "LOCALITY"},cdata]),
|
||||
% EMail = xml:get_path_s(VCARD, [{elem, "EMAIL"}, cdata]),
|
||||
% OrgName = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGNAME"}, cdata]),
|
||||
% OrgUnit = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGUNIT"}, cdata]),
|
||||
%
|
||||
% {LUser, _LServer} = US,
|
||||
% LFN = stringprep:tolower(FN),
|
||||
% LFamily = stringprep:tolower(Family),
|
||||
% LGiven = stringprep:tolower(Given),
|
||||
% LMiddle = stringprep:tolower(Middle),
|
||||
% LNickname = stringprep:tolower(Nickname),
|
||||
% LBDay = stringprep:tolower(BDay),
|
||||
% LCTRY = stringprep:tolower(CTRY),
|
||||
% LLocality = stringprep:tolower(Locality),
|
||||
% LEMail = stringprep:tolower(EMail),
|
||||
% LOrgName = stringprep:tolower(OrgName),
|
||||
% LOrgUnit = stringprep:tolower(OrgUnit),
|
||||
%
|
||||
% if
|
||||
% (LUser == error) or
|
||||
% (LFN == error) or
|
||||
% (LFamily == error) or
|
||||
% (LGiven == error) or
|
||||
% (LMiddle == error) or
|
||||
% (LNickname == error) or
|
||||
% (LBDay == error) or
|
||||
% (LCTRY == error) or
|
||||
% (LLocality == error) or
|
||||
% (LEMail == error) or
|
||||
% (LOrgName == error) or
|
||||
% (LOrgUnit == error) ->
|
||||
% {error, badarg};
|
||||
% true ->
|
||||
% mnesia:write(
|
||||
% #vcard_search{us = US,
|
||||
% user = User, luser = LUser,
|
||||
% fn = FN, lfn = LFN,
|
||||
% family = Family, lfamily = LFamily,
|
||||
% given = Given, lgiven = LGiven,
|
||||
% middle = Middle, lmiddle = LMiddle,
|
||||
% nickname = Nickname, lnickname = LNickname,
|
||||
% bday = BDay, lbday = LBDay,
|
||||
% ctry = CTRY, lctry = LCTRY,
|
||||
% locality = Locality, llocality = LLocality,
|
||||
% email = EMail, lemail = LEMail,
|
||||
% orgname = OrgName, lorgname = LOrgName,
|
||||
% orgunit = OrgUnit, lorgunit = LOrgUnit
|
||||
% })
|
||||
% end.
|
||||
%
|
||||
%
|
||||
%reindex_vcards() ->
|
||||
% F = fun() ->
|
||||
% mnesia:foldl(fun set_vcard_t/2, [], vcard)
|
||||
% end,
|
||||
% mnesia:transaction(F).
|
||||
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
ejabberd_odbc:sql_transaction(
|
||||
LServer,
|
||||
[["delete from vcard where username='", Username, "';"],
|
||||
["delete from vcard_search where lusername='", Username, "';"]]).
|
@ -26,10 +26,16 @@
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
|
||||
start(Host, _Opts) ->
|
||||
mnesia:create_table(vcard_xupdate,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, vcard_xupdate)}]),
|
||||
start(Host, Opts) ->
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(vcard_xupdate,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, vcard_xupdate)}]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:add(c2s_update_presence, Host,
|
||||
?MODULE, update_presence, 100),
|
||||
ejabberd_hooks:add(vcard_set, Host,
|
||||
@ -68,28 +74,66 @@ vcard_set(LUser, LServer, VCARD) ->
|
||||
ejabberd_sm:force_update_presence(US).
|
||||
|
||||
%%====================================================================
|
||||
%% Mnesia storage
|
||||
%% Storage
|
||||
%%====================================================================
|
||||
|
||||
add_xupdate(LUser, LServer, Hash) ->
|
||||
add_xupdate(LUser, LServer, Hash, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
add_xupdate(LUser, LServer, Hash, mnesia) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#vcard_xupdate{us = {LUser, LServer}, hash = Hash})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
add_xupdate(LUser, LServer, Hash, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SHash = ejabberd_odbc:escape(Hash),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"vcard_xupdate",
|
||||
["username", "hash"],
|
||||
[Username, SHash],
|
||||
["username='", Username, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
get_xupdate(LUser, LServer) ->
|
||||
get_xupdate(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_xupdate(LUser, LServer, mnesia) ->
|
||||
case mnesia:dirty_read(vcard_xupdate, {LUser, LServer}) of
|
||||
[#vcard_xupdate{hash = Hash}] ->
|
||||
Hash;
|
||||
_ ->
|
||||
undefined
|
||||
end;
|
||||
get_xupdate(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case ejabberd_odbc:sql_query(
|
||||
LServer, ["select hash from vcard_xupdate "
|
||||
"where username='", Username, "';"]) of
|
||||
{selected, ["hash"], [{Hash}]} ->
|
||||
Hash;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
remove_xupdate(LUser, LServer) ->
|
||||
remove_xupdate(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
remove_xupdate(LUser, LServer, mnesia) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({vcard_xupdate, {LUser, LServer}})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:transaction(F);
|
||||
remove_xupdate(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from vcard_xupdate where "
|
||||
"username='", Username, "';"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Presence stanza rebuilding
|
||||
|
@ -1,128 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_vcard_xupdate_odbc.erl
|
||||
%%% Author : Igor Goryachev <igor@goryachev.org>
|
||||
%%% Purpose : Add avatar hash in presence on behalf of client (XEP-0153)
|
||||
%%% Created : 9 Mar 2007 by Igor Goryachev <igor@goryachev.org>
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_vcard_xupdate_odbc).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% gen_mod callbacks
|
||||
-export([start/2,
|
||||
stop/1]).
|
||||
|
||||
%% hooks
|
||||
-export([update_presence/3,
|
||||
vcard_set/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(c2s_update_presence, Host,
|
||||
?MODULE, update_presence, 100),
|
||||
ejabberd_hooks:add(vcard_set, Host,
|
||||
?MODULE, vcard_set, 100),
|
||||
ok.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(c2s_update_presence, Host,
|
||||
?MODULE, update_presence, 100),
|
||||
ejabberd_hooks:delete(vcard_set, Host,
|
||||
?MODULE, vcard_set, 100),
|
||||
ok.
|
||||
|
||||
%%====================================================================
|
||||
%% Hooks
|
||||
%%====================================================================
|
||||
|
||||
update_presence({xmlelement, "presence", Attrs, _Els} = Packet, User, Host) ->
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
[] ->
|
||||
presence_with_xupdate(Packet, User, Host);
|
||||
_ ->
|
||||
Packet
|
||||
end;
|
||||
update_presence(Packet, _User, _Host) ->
|
||||
Packet.
|
||||
|
||||
vcard_set(LUser, LServer, VCARD) ->
|
||||
US = {LUser, LServer},
|
||||
case xml:get_path_s(VCARD, [{elem, "PHOTO"}, {elem, "BINVAL"}, cdata]) of
|
||||
[] ->
|
||||
remove_xupdate(LUser, LServer);
|
||||
BinVal ->
|
||||
add_xupdate(LUser, LServer, sha:sha(jlib:decode_base64(BinVal)))
|
||||
end,
|
||||
ejabberd_sm:force_update_presence(US).
|
||||
|
||||
%%====================================================================
|
||||
%% ODBC storage
|
||||
%%====================================================================
|
||||
|
||||
add_xupdate(LUser, LServer, Hash) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SHash = ejabberd_odbc:escape(Hash),
|
||||
F = fun() ->
|
||||
odbc_queries:update_t(
|
||||
"vcard_xupdate",
|
||||
["username", "hash"],
|
||||
[Username, SHash],
|
||||
["username='", Username, "'"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
get_xupdate(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case ejabberd_odbc:sql_query(
|
||||
LServer, ["select hash from vcard_xupdate "
|
||||
"where username='", Username, "';"]) of
|
||||
{selected, ["hash"], [{Hash}]} ->
|
||||
Hash;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
remove_xupdate(LUser, LServer) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["delete from vcard_xupdate where "
|
||||
"username='", Username, "';"])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Presence stanza rebuilding
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
presence_with_xupdate({xmlelement, "presence", Attrs, Els}, User, Host) ->
|
||||
XPhotoEl = build_xphotoel(User, Host),
|
||||
Els2 = presence_with_xupdate2(Els, [], XPhotoEl),
|
||||
{xmlelement, "presence", Attrs, Els2}.
|
||||
|
||||
presence_with_xupdate2([], Els2, XPhotoEl) ->
|
||||
lists:reverse([XPhotoEl | Els2]);
|
||||
%% This clause assumes that the x element contains only the XMLNS attribute:
|
||||
presence_with_xupdate2([{xmlelement, "x", [{"xmlns", ?NS_VCARD_UPDATE}], _}
|
||||
| Els], Els2, XPhotoEl) ->
|
||||
presence_with_xupdate2(Els, Els2, XPhotoEl);
|
||||
presence_with_xupdate2([El | Els], Els2, XPhotoEl) ->
|
||||
presence_with_xupdate2(Els, [El | Els2], XPhotoEl).
|
||||
|
||||
build_xphotoel(User, Host) ->
|
||||
Hash = get_xupdate(User, Host),
|
||||
PhotoSubEls = case Hash of
|
||||
Hash when is_list(Hash) ->
|
||||
[{xmlcdata, Hash}];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
PhotoEl = [{xmlelement, "photo", [], PhotoSubEls}],
|
||||
{xmlelement, "x", [{"xmlns", ?NS_VCARD_UPDATE}], PhotoEl}.
|
@ -1564,7 +1564,6 @@ list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
|
||||
[list_given_users(Host, Sub, "../../", Lang, URLFunc)].
|
||||
|
||||
list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
|
||||
ModLast = get_lastactivity_module(Host),
|
||||
ModOffline = get_offlinemsg_module(Host),
|
||||
?XE("table",
|
||||
[?XE("thead",
|
||||
@ -1583,7 +1582,7 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
|
||||
FLast =
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
case ModLast:get_last_info(User, Server) of
|
||||
case mod_last:get_last_info(User, Server) of
|
||||
not_found ->
|
||||
?T("Never");
|
||||
{ok, Shift, _Status} ->
|
||||
@ -1618,22 +1617,17 @@ get_offlinemsg_length(ModOffline, User, Server) ->
|
||||
end.
|
||||
|
||||
get_offlinemsg_module(Server) ->
|
||||
case [mod_offline, mod_offline_odbc] -- gen_mod:loaded_modules(Server) of
|
||||
[mod_offline, mod_offline_odbc] -> none;
|
||||
[mod_offline_odbc] -> mod_offline;
|
||||
[mod_offline] -> mod_offline_odbc
|
||||
end.
|
||||
|
||||
get_lastactivity_module(Server) ->
|
||||
case lists:member(mod_last, gen_mod:loaded_modules(Server)) of
|
||||
true -> mod_last;
|
||||
_ -> mod_last_odbc
|
||||
case gen_mod:is_loaded(Server, mod_offline) of
|
||||
true ->
|
||||
mod_offline;
|
||||
false ->
|
||||
none
|
||||
end.
|
||||
|
||||
get_lastactivity_menuitem_list(Server) ->
|
||||
case get_lastactivity_module(Server) of
|
||||
mod_last -> [{"last-activity", "Last Activity"}];
|
||||
mod_last_odbc -> []
|
||||
case gen_mod:db_type(Server, mod_last) of
|
||||
mnesia -> [{"last-activity", "Last Activity"}];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
us_to_list({User, Server}) ->
|
||||
@ -1735,10 +1729,9 @@ user_info(User, Server, Query, Lang) ->
|
||||
UserItems = ejabberd_hooks:run_fold(webadmin_user, LServer, [],
|
||||
[User, Server, Lang]),
|
||||
%% Code copied from list_given_users/5:
|
||||
ModLast = get_lastactivity_module(Server),
|
||||
LastActivity = case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
case ModLast:get_last_info(User, Server) of
|
||||
case mod_last:get_last_info(User, Server) of
|
||||
not_found ->
|
||||
?T("Never");
|
||||
{ok, Shift, _Status} ->
|
||||
|
Loading…
Reference in New Issue
Block a user