24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-26 22:35:31 +02:00

Merge SQL and Mnesia code into one module (EJAB-1560)

This commit is contained in:
Evgeniy Khramtsov 2012-04-27 19:52:05 +10:00
parent 453e249de3
commit 437f68a9f3
38 changed files with 2855 additions and 9070 deletions

View File

@ -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.

View File

@ -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,

View File

@ -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

View File

@ -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) ->

View File

@ -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).

View File

@ -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

View File

@ -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 "

View File

@ -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}},

View File

@ -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),

View File

@ -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

View File

@ -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).

View File

@ -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.

View File

@ -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.

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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),

View File

@ -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).

View File

@ -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

View File

@ -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) ->

View File

@ -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).

View File

@ -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 ->

View File

@ -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.

View File

@ -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.

View File

@ -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),

View File

@ -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).

View File

@ -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),

View File

@ -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).

View File

@ -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

View File

@ -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

View File

@ -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(),

View File

@ -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, "';"]]).

View File

@ -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

View File

@ -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}.

View File

@ -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} ->