From 437f68a9f320a3cafd063ae03313ce83db96668e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 27 Apr 2012 19:52:05 +1000 Subject: [PATCH] Merge SQL and Mnesia code into one module (EJAB-1560) --- src/acl.erl | 10 +- src/ejabberd.app | 6 - src/ejabberd_admin.erl | 13 +- src/ejabberd_auth_external.erl | 21 +- src/ejabberd_config.erl | 29 + src/ejabberd_piefxis.erl | 70 +- src/ejd2odbc.erl | 2 +- src/gen_mod.erl | 14 + src/jd2ejd.erl | 46 +- src/mod_announce.erl | 180 ++++- src/mod_announce_odbc.erl | 885 ----------------------- src/mod_blocking.erl | 292 +++++--- src/mod_blocking_odbc.erl | 365 ---------- src/mod_configure.erl | 12 +- src/mod_irc/Makefile.win32 | 5 +- src/mod_irc/mod_irc.erl | 206 ++++-- src/mod_irc/mod_irc_odbc.erl | 1033 --------------------------- src/mod_last.erl | 53 +- src/mod_last_odbc.erl | 204 ------ src/mod_muc/Makefile.win32 | 5 +- src/mod_muc/mod_muc.erl | 279 ++++++-- src/mod_muc/mod_muc_odbc.erl | 875 ----------------------- src/mod_muc/mod_muc_room.erl | 100 ++- src/mod_offline.erl | 435 +++++++++--- src/mod_offline_odbc.erl | 548 --------------- src/mod_privacy.erl | 602 ++++++++++++---- src/mod_privacy_odbc.erl | 878 ----------------------- src/mod_private.erl | 86 ++- src/mod_private_odbc.erl | 136 ---- src/mod_roster.erl | 666 ++++++++++++++---- src/mod_roster_odbc.erl | 1211 -------------------------------- src/mod_shared_roster.erl | 300 ++++++-- src/mod_shared_roster_odbc.erl | 1165 ------------------------------ src/mod_vcard.erl | 321 +++++++-- src/mod_vcard_odbc.erl | 659 ----------------- src/mod_vcard_xupdate.erl | 58 +- src/mod_vcard_xupdate_odbc.erl | 128 ---- src/web/ejabberd_web_admin.erl | 27 +- 38 files changed, 2855 insertions(+), 9070 deletions(-) delete mode 100644 src/mod_announce_odbc.erl delete mode 100644 src/mod_blocking_odbc.erl delete mode 100644 src/mod_irc/mod_irc_odbc.erl delete mode 100644 src/mod_last_odbc.erl delete mode 100644 src/mod_muc/mod_muc_odbc.erl delete mode 100644 src/mod_offline_odbc.erl delete mode 100644 src/mod_privacy_odbc.erl delete mode 100644 src/mod_private_odbc.erl delete mode 100644 src/mod_roster_odbc.erl delete mode 100644 src/mod_shared_roster_odbc.erl delete mode 100644 src/mod_vcard_odbc.erl delete mode 100644 src/mod_vcard_xupdate_odbc.erl diff --git a/src/acl.erl b/src/acl.erl index dd7c441de..6ce11f99a 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -241,9 +241,9 @@ is_glob_match(String, Glob) -> is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)). loaded_shared_roster_module(Host) -> - case {gen_mod:is_loaded(Host, mod_shared_roster_odbc), - gen_mod:is_loaded(Host, mod_shared_roster_ldap)} of - {true, _} -> mod_shared_roster_odbc; - {_, true} -> mod_shared_roster_ldap; - _ -> mod_shared_roster + case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of + true -> + mod_shared_roster_ldap; + false -> + mod_shared_roster end. diff --git a/src/ejabberd.app b/src/ejabberd.app index 2f08601a8..1af67fbae 100644 --- a/src/ejabberd.app +++ b/src/ejabberd.app @@ -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, diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index b7fbbd00c..40c8b8dca 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -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 diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 06ec5548f..ba89534d1 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -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) -> diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 503537e76..f83d85671 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -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). diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index 0ed1f6c0c..b667301a5 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -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 diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl index a2331137e..7d96aac34 100644 --- a/src/ejd2odbc.erl +++ b/src/ejd2odbc.erl @@ -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 " diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 941717f64..4f5c06e7a 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -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}}, diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl index 0770e0794..15a467b1e 100644 --- a/src/jd2ejd.erl +++ b/src/jd2ejd.erl @@ -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), diff --git a/src/mod_announce.erl b/src/mod_announce.erl index d398c2b36..d270a6e4e 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -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 diff --git a/src/mod_announce_odbc.erl b/src/mod_announce_odbc.erl deleted file mode 100644 index 101f8aae2..000000000 --- a/src/mod_announce_odbc.erl +++ /dev/null @@ -1,885 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_announce_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : Manage announce messages -%%% Created : 11 Aug 2003 by Alexey Shchepin -%%% -%%% -%%% 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 - %% 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 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). diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index e099291b9..7cb0f5c82 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -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. diff --git a/src/mod_blocking_odbc.erl b/src/mod_blocking_odbc.erl deleted file mode 100644 index 016e7945e..000000000 --- a/src/mod_blocking_odbc.erl +++ /dev/null @@ -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 -%%% -%%% -%%% 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. diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 29b7276ca..44fee1e93 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -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, diff --git a/src/mod_irc/Makefile.win32 b/src/mod_irc/Makefile.win32 index 70b71e7d6..fb0671104 100644 --- a/src/mod_irc/Makefile.win32 +++ b/src/mod_irc/Makefile.win32 @@ -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 diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl index f616a92fd..12c291944 100644 --- a/src/mod_irc/mod_irc.erl +++ b/src/mod_irc/mod_irc.erl @@ -98,11 +98,17 @@ stop(Host) -> %%-------------------------------------------------------------------- init([Host, Opts]) -> iconv:start(), - mnesia:create_table(irc_custom, - [{disc_copies, [node()]}, - {attributes, record_info(fields, irc_custom)}]), MyHost = gen_mod:get_opt_host(Host, Opts, "irc.@HOST@"), - update_table(MyHost), + case gen_mod:db_type(Opts) of + mnesia -> + mnesia:create_table(irc_custom, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, irc_custom)}]), + update_table(MyHost); + _ -> + ok + end, Access = gen_mod:get_opt(access, Opts, all), catch ets:new(irc_connection, [named_table, public, @@ -218,7 +224,7 @@ do_route1(Host, ServerHost, From, To, Packet) -> Info = ejabberd_hooks:run_fold( disco_info, ServerHost, [], [ServerHost, ?MODULE, "", ""]), - case iq_disco(Node, Lang) of + case iq_disco(ServerHost, Node, Lang) of [] -> Res = IQ#iq{type = result, sub_el = [{xmlelement, "query", @@ -263,7 +269,8 @@ do_route1(Host, ServerHost, From, To, Packet) -> sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}, {"node", Node}], - command_items(Host, Lang)}]}, + command_items(ServerHost, + Host, Lang)}]}, Res = jlib:iq_to_xml(ResIQ); _ -> Res = jlib:make_error_reply( @@ -273,7 +280,7 @@ do_route1(Host, ServerHost, From, To, Packet) -> From, Res); #iq{xmlns = ?NS_REGISTER} = IQ -> - process_register(Host, From, To, IQ); + process_register(ServerHost, Host, From, To, IQ); #iq{type = get, xmlns = ?NS_VCARD = XMLNS, lang = Lang} = IQ -> Res = IQ#iq{type = result, @@ -287,7 +294,8 @@ do_route1(Host, ServerHost, From, To, Packet) -> #iq{type = set, xmlns = ?NS_COMMANDS, lang = _Lang, sub_el = SubEl} = IQ -> Request = adhoc:parse_request(IQ), - case lists:keysearch(Request#adhoc_request.node, 1, commands()) of + case lists:keysearch(Request#adhoc_request.node, + 1, commands(ServerHost)) of {value, {_, _, Function}} -> case catch Function(From, To, Request) of {'EXIT', Reason} -> @@ -394,7 +402,7 @@ closed_connection(Host, From, Server) -> ets:delete(irc_connection, {From, Server, Host}). -iq_disco([], Lang) -> +iq_disco(_ServerHost, [], Lang) -> [{xmlelement, "identity", [{"category", "conference"}, {"type", "irc"}, @@ -404,8 +412,8 @@ iq_disco([], Lang) -> {xmlelement, "feature", [{"var", ?NS_REGISTER}], []}, {xmlelement, "feature", [{"var", ?NS_VCARD}], []}, {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}]; -iq_disco(Node, Lang) -> - case lists:keysearch(Node, 1, commands()) of +iq_disco(ServerHost, Node, Lang) -> + case lists:keysearch(Node, 1, commands(ServerHost)) of {value, {_, Name, _}} -> [{xmlelement, "identity", [{"category", "automation"}, @@ -428,20 +436,23 @@ iq_get_vcard(Lang) -> [{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++ "\nCopyright (c) 2003-2012 ProcessOne"}]}]. -command_items(Host, Lang) -> +command_items(ServerHost, Host, Lang) -> lists:map(fun({Node, Name, _Function}) -> {xmlelement, "item", [{"jid", Host}, {"node", Node}, {"name", translate:translate(Lang, Name)}], []} - end, commands()). + end, commands(ServerHost)). -commands() -> +commands(ServerHost) -> [{"join", "Join channel", fun adhoc_join/3}, - {"register", "Configure username, encoding, port and password", fun adhoc_register/3}]. + {"register", "Configure username, encoding, port and password", + fun(From, To, Request) -> + adhoc_register(ServerHost, From, To, Request) + end}]. -process_register(Host, From, To, #iq{} = IQ) -> - case catch process_irc_register(Host, From, To, IQ) of +process_register(ServerHost, Host, From, To, #iq{} = IQ) -> + case catch process_irc_register(ServerHost, Host, From, To, IQ) of {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); ResIQ -> @@ -471,7 +482,7 @@ find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) -> find_xdata_el1([_ | Els]) -> find_xdata_el1(Els). -process_irc_register(Host, From, _To, +process_irc_register(ServerHost, Host, From, _To, #iq{type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ) -> case Type of @@ -497,7 +508,8 @@ process_irc_register(Host, From, _To, xml:get_tag_attr_s("node", SubEl), "/"), case set_form( - Host, From, Node, Lang, XData) of + ServerHost, Host, From, + Node, Lang, XData) of {result, Res} -> IQ#iq{type = result, sub_el = [{xmlelement, "query", @@ -517,7 +529,7 @@ process_irc_register(Host, From, _To, get -> Node = string:tokens(xml:get_tag_attr_s("node", SubEl), "/"), - case get_form(Host, From, Node, Lang) of + case get_form(ServerHost, Host, From, Node, Lang) of {result, Res} -> IQ#iq{type = result, sub_el = [{xmlelement, "query", @@ -530,23 +542,52 @@ process_irc_register(Host, From, _To, end end. +get_data(ServerHost, Host, From) -> + LServer = jlib:nameprep(ServerHost), + get_data(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)). - -get_form(Host, From, [], Lang) -> - #jid{user = User, server = Server, - luser = LUser, lserver = LServer} = From, +get_data(_LServer, Host, From, mnesia) -> + #jid{luser = LUser, lserver = LServer} = From, US = {LUser, LServer}, + case catch mnesia:dirty_read({irc_custom, {US, Host}}) of + {'EXIT', _Reason} -> + error; + [] -> + empty; + [#irc_custom{data = Data}] -> + Data + end; +get_data(LServer, Host, From, odbc) -> + SJID = ejabberd_odbc:escape( + jlib:jid_to_string( + jlib:jid_tolower( + jlib:jid_remove_resource(From)))), + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query( + LServer, + ["select data from irc_custom where " + "jid='", SJID, "' and host='", SHost, "';"]) of + {selected, ["data"], [{SData}]} -> + ejabberd_odbc:decode_term(SData); + {'EXIT', _} -> + error; + {selected, _, _} -> + empty + end. + +get_form(ServerHost, Host, From, [], Lang) -> + #jid{user = User, server = Server} = From, DefaultEncoding = get_default_encoding(Host), Customs = - case catch mnesia:dirty_read({irc_custom, {US, Host}}) of - {'EXIT', _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - [] -> - {User, []}; - [#irc_custom{data = Data}] -> - {xml:get_attr_s(username, Data), - xml:get_attr_s(connections_params, Data)} - end, + case get_data(ServerHost, Host, From) of + error -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + empty -> + {User, []}; + Data -> + {xml:get_attr_s(username, Data), + xml:get_attr_s(connections_params, Data)} + end, case Customs of {error, _Error} -> Customs; @@ -614,15 +655,41 @@ get_form(Host, From, [], Lang) -> ]}]} end; -get_form(_Host, _, _, _Lang) -> +get_form(_ServerHost, _Host, _, _, _Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. +set_data(ServerHost, Host, From, Data) -> + LServer = jlib:nameprep(ServerHost), + set_data(LServer, Host, From, Data, gen_mod:db_type(LServer, ?MODULE)). - -set_form(Host, From, [], _Lang, XData) -> +set_data(_LServer, Host, From, Data, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(From), US = {LUser, LServer}, + F = fun() -> + mnesia:write(#irc_custom{us_host = {US, Host}, data = Data}) + end, + mnesia:transaction(F); +set_data(LServer, Host, From, Data, odbc) -> + SJID = ejabberd_odbc:escape( + jlib:jid_to_string( + jlib:jid_tolower( + jlib:jid_remove_resource(From)))), + SHost = ejabberd_odbc:escape(Host), + SData = ejabberd_odbc:encode_term(Data), + F = fun() -> + odbc_queries:update_t( + "irc_custom", + ["jid", "host", "data"], + [SJID, SHost, SData], + ["jid='", SJID, + "' and host='", + SHost, "'"]), + ok + end, + ejabberd_odbc:sql_transaction(LServer, F). + +set_form(ServerHost, Host, From, [], _Lang, XData) -> case {lists:keysearch("username", 1, XData), lists:keysearch("connections_params", 1, XData)} of {{value, {_, [Username]}}, {value, {_, Strings}}} -> @@ -633,17 +700,11 @@ set_form(Host, From, [], _Lang, XData) -> {ok, Tokens, _} -> case erl_parse:parse_term(Tokens) of {ok, ConnectionsParams} -> - case mnesia:transaction( - fun() -> - mnesia:write( - #irc_custom{us_host = - {US, Host}, - data = - [{username, - Username}, - {connections_params, - ConnectionsParams}]}) - end) of + case set_data(ServerHost, Host, From, + [{username, + Username}, + {connections_params, + ConnectionsParams}]) of {atomic, _} -> {result, []}; _ -> @@ -660,7 +721,7 @@ set_form(Host, From, [], _Lang, XData) -> end; -set_form(_Host, _, _, _Lang, _XData) -> +set_form(_ServerHost, _Host, _, _, _Lang, _XData) -> {error, ?ERR_SERVICE_UNAVAILABLE}. @@ -679,16 +740,14 @@ get_default_encoding(ServerHost) -> Result. get_connection_params(Host, ServerHost, From, IRCServer) -> - #jid{user = User, server = _Server, - luser = LUser, lserver = LServer} = From, - US = {LUser, LServer}, + #jid{user = User, server = _Server} = From, DefaultEncoding = get_default_encoding(ServerHost), - case catch mnesia:dirty_read({irc_custom, {US, Host}}) of - {'EXIT', _Reason} -> + case get_data(ServerHost, Host, From) of + error -> {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}; - [] -> + empty -> {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}; - [#irc_custom{data = Data}] -> + Data -> Username = xml:get_attr_s(username, Data), {NewUsername, NewEncoding, NewPort, NewPassword} = case lists:keysearch(IRCServer, 1, xml:get_attr_s(connections_params, Data)) of @@ -785,28 +844,27 @@ adhoc_join(From, To, #adhoc_request{lang = Lang, end end. -adhoc_register(_From, _To, #adhoc_request{action = "cancel"} = Request) -> +adhoc_register(_ServerHost, _From, _To, #adhoc_request{action = "cancel"} = Request) -> adhoc:produce_response(Request, #adhoc_response{status = canceled}); -adhoc_register(From, To, #adhoc_request{lang = Lang, - node = _Node, - xdata = XData, - action = Action} = Request) -> - #jid{user = User, luser = LUser, lserver = LServer} = From, +adhoc_register(ServerHost, From, To, #adhoc_request{lang = Lang, + node = _Node, + xdata = XData, + action = Action} = Request) -> + #jid{user = User} = From, #jid{lserver = Host} = To, - US = {LUser, LServer}, %% Generate form for setting username and encodings. If the user %% hasn't begun to fill out the form, generate an initial form %% based on current values. if XData == false -> - case catch mnesia:dirty_read({irc_custom, {US, Host}}) of - {'EXIT', _Reason} -> + case get_data(ServerHost, Host, From) of + error -> Username = User, ConnectionsParams = []; - [] -> + empty -> Username = User, ConnectionsParams = []; - [#irc_custom{data = Data}] -> + Data -> Username = xml:get_attr_s(username, Data), ConnectionsParams = xml:get_attr_s(connections_params, Data) end, @@ -832,17 +890,11 @@ adhoc_register(From, To, #adhoc_request{lang = Lang, if Error /= false -> Error; Action == "complete" -> - case mnesia:transaction( - fun () -> - mnesia:write( - #irc_custom{us_host = - {US, Host}, - data = - [{username, - Username}, - {connections_params, - ConnectionsParams}]}) - end) of + case set_data(ServerHost, Host, From, + [{username, + Username}, + {connections_params, + ConnectionsParams}]) of {atomic, _} -> adhoc:produce_response(Request, #adhoc_response{status = completed}); _ -> diff --git a/src/mod_irc/mod_irc_odbc.erl b/src/mod_irc/mod_irc_odbc.erl deleted file mode 100644 index ab0fafe14..000000000 --- a/src/mod_irc/mod_irc_odbc.erl +++ /dev/null @@ -1,1033 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_irc_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : IRC transport -%%% Created : 15 Feb 2003 by Alexey Shchepin -%%% -%%% -%%% 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_irc_odbc). --author('alexey@process-one.net'). - --behaviour(gen_server). --behaviour(gen_mod). - -%% API --export([start_link/2, - start/2, - stop/1, - closed_connection/3, - get_connection_params/3]). - -%% 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"). --include("adhoc.hrl"). - --define(DEFAULT_IRC_ENCODING, "iso8859-1"). --define(DEFAULT_IRC_PORT, 6667). --define(POSSIBLE_ENCODINGS, ["koi8-r", "iso8859-1", "iso8859-2", "utf-8", "utf-8+latin-1"]). - --record(irc_connection, {jid_server_host, pid}). - --record(state, {host, server_host, access}). - --define(PROCNAME, ejabberd_mod_irc). - -%%==================================================================== -%% 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). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Host, Opts]) -> - iconv:start(), - MyHost = gen_mod:get_opt_host(Host, Opts, "irc.@HOST@"), - Access = gen_mod:get_opt(access, Opts, all), - catch ets:new(irc_connection, [named_table, - public, - {keypos, #irc_connection.jid_server_host}]), - ejabberd_router:register_route(MyHost), - {ok, #state{host = MyHost, - server_host = Host, - access = Access}}. - -%%-------------------------------------------------------------------- -%% 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}. - -%%-------------------------------------------------------------------- -%% 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} = State) -> - case catch do_route(Host, ServerHost, Access, From, To, Packet) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - _ -> - ok - end, - {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_irc_sup), - ChildSpec = - {Proc, - {ejabberd_tmp_sup, start_link, - [Proc, mod_irc_connection]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, - supervisor:start_child(ejabberd_sup, ChildSpec). - -stop_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, ejabberd_mod_irc_sup), - supervisor:terminate_child(ejabberd_sup, Proc), - supervisor:delete_child(ejabberd_sup, Proc). - -do_route(Host, ServerHost, Access, From, To, Packet) -> - case acl:match_rule(ServerHost, Access, From) of - allow -> - do_route1(Host, ServerHost, From, To, Packet); - _ -> - {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(To, From, Err) - end. - -do_route1(Host, ServerHost, From, To, Packet) -> - #jid{user = ChanServ, resource = Resource} = To, - {xmlelement, _Name, _Attrs, _Els} = Packet, - case ChanServ of - "" -> - case Resource of - "" -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, - sub_el = SubEl, lang = Lang} = IQ -> - Node = xml:get_tag_attr_s("node", SubEl), - Info = ejabberd_hooks:run_fold( - disco_info, ServerHost, [], - [ServerHost, ?MODULE, "", ""]), - case iq_disco(ServerHost, Node, Lang) of - [] -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)); - DiscoInfo -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - DiscoInfo ++ Info}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)) - end; - #iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS, - sub_el = SubEl, lang = Lang} = IQ -> - Node = xml:get_tag_attr_s("node", SubEl), - case Node of - [] -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - "join" -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - "register" -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - ?NS_COMMANDS -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}, - {"node", Node}], - command_items(ServerHost, - Host, Lang)}]}, - Res = jlib:iq_to_xml(ResIQ); - _ -> - Res = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND) - end, - ejabberd_router:route(To, - From, - Res); - #iq{xmlns = ?NS_REGISTER} = IQ -> - process_register(ServerHost, Host, From, To, IQ); - #iq{type = get, xmlns = ?NS_VCARD = XMLNS, - lang = Lang} = 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 = set, xmlns = ?NS_COMMANDS, - lang = _Lang, sub_el = SubEl} = IQ -> - Request = adhoc:parse_request(IQ), - case lists:keysearch(Request#adhoc_request.node, - 1, commands(ServerHost)) of - {value, {_, _, Function}} -> - case catch Function(From, To, Request) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p~nfor ad-hoc handler of ~p", - [Reason, {From, To, IQ}]), - Res = IQ#iq{type = error, sub_el = [SubEl, - ?ERR_INTERNAL_SERVER_ERROR]}; - ignore -> - Res = ignore; - {error, Error} -> - Res = IQ#iq{type = error, sub_el = [SubEl, Error]}; - Command -> - Res = IQ#iq{type = result, sub_el = [Command]} - end, - if Res /= ignore -> - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - true -> - ok - end; - _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) - end; - #iq{} = _IQ -> - Err = jlib:make_error_reply( - Packet, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> - ok - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err) - end; - _ -> - case string:tokens(ChanServ, "%") of - [[_ | _] = Channel, [_ | _] = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of - [] -> - ?DEBUG("open new connection~n", []), - {Username, Encoding, Port, Password} = get_connection_params( - Host, ServerHost, From, Server), - ConnectionUsername = - case Packet of - %% If the user tries to join a - %% chatroom, the packet for sure - %% contains the desired username. - {xmlelement, "presence", _, _} -> - Resource; - %% Otherwise, there is no firm - %% conclusion from the packet. - %% Better to use the configured - %% username (which defaults to the - %% username part of the JID). - _ -> - Username - end, - {ok, Pid} = mod_irc_connection:start( - From, Host, ServerHost, Server, - ConnectionUsername, Encoding, Port, - Password, ?MODULE), - ets:insert( - irc_connection, - #irc_connection{jid_server_host = {From, Server, Host}, - pid = Pid}), - mod_irc_connection:route_chan( - Pid, Channel, Resource, Packet), - ok; - [R] -> - Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", - [Pid]), - mod_irc_connection:route_chan( - Pid, Channel, Resource, Packet), - ok - end; - _ -> - case string:tokens(ChanServ, "!") of - [[_ | _] = Nick, [_ | _] = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of - [] -> - Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err); - [R] -> - Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", - [Pid]), - mod_irc_connection:route_nick( - Pid, Nick, Packet), - ok - end; - _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err) - end - end - end. - - -closed_connection(Host, From, Server) -> - ets:delete(irc_connection, {From, Server, Host}). - - -iq_disco(_ServerHost, [], Lang) -> - [{xmlelement, "identity", - [{"category", "conference"}, - {"type", "irc"}, - {"name", translate:translate(Lang, "IRC Transport")}], []}, - {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []}, - {xmlelement, "feature", [{"var", ?NS_MUC}], []}, - {xmlelement, "feature", [{"var", ?NS_REGISTER}], []}, - {xmlelement, "feature", [{"var", ?NS_VCARD}], []}, - {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}]; -iq_disco(ServerHost, Node, Lang) -> - case lists:keysearch(Node, 1, commands(ServerHost)) of - {value, {_, Name, _}} -> - [{xmlelement, "identity", - [{"category", "automation"}, - {"type", "command-node"}, - {"name", translate:translate(Lang, Name)}], []}, - {xmlelement, "feature", - [{"var", ?NS_COMMANDS}], []}, - {xmlelement, "feature", - [{"var", ?NS_XDATA}], []}]; - _ -> - [] - end. - -iq_get_vcard(Lang) -> - [{xmlelement, "FN", [], - [{xmlcdata, "ejabberd/mod_irc"}]}, - {xmlelement, "URL", [], - [{xmlcdata, ?EJABBERD_URI}]}, - {xmlelement, "DESC", [], - [{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++ - "\nCopyright (c) 2003-2012 ProcessOne"}]}]. - -command_items(ServerHost, Host, Lang) -> - lists:map(fun({Node, Name, _Function}) - -> {xmlelement, "item", - [{"jid", Host}, - {"node", Node}, - {"name", translate:translate(Lang, Name)}], []} - end, commands(ServerHost)). - -commands(ServerHost) -> - [{"join", "Join channel", fun adhoc_join/3}, - {"register", "Configure username, encoding, port and password", - fun(From, To, Request) -> - adhoc_register(ServerHost, From, To, Request) - end}]. - -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 -> - if - ResIQ /= ignore -> - ejabberd_router:route(To, From, - jlib:iq_to_xml(ResIQ)); - true -> - ok - end - end. - -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). - -process_irc_register(ServerHost, Host, From, _To, - #iq{type = Type, xmlns = XMLNS, - lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; - {xmlelement, _Name, Attrs, _SubEls} -> - case xml:get_attr_s("type", Attrs) of - "cancel" -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], []}]}; - "submit" -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - _ -> - Node = string:tokens( - xml:get_tag_attr_s("node", SubEl), - "/"), - case set_form( - ServerHost, Host, From, - Node, Lang, XData) of - {result, Res} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end; - _ -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]} - end - end; - get -> - Node = - string:tokens(xml:get_tag_attr_s("node", SubEl), "/"), - case get_form(ServerHost, Host, From, Node, Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end. - - - -get_form(ServerHost, Host, From, [], Lang) -> - #jid{user = User, server = Server} = From, - LServer = jlib:nameprep(ServerHost), - SJID = ejabberd_odbc:escape( - jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(From)))), - SHost = ejabberd_odbc:escape(Host), - DefaultEncoding = get_default_encoding(Host), - Customs = - case catch ejabberd_odbc:sql_query( - LServer, - ["select data from irc_custom where " - "jid='", SJID, "' and host='", SHost, "';"]) of - {selected, ["data"], [{SData}]} -> - Data = ejabberd_odbc:decode_term(SData), - {xml:get_attr_s(username, Data), - xml:get_attr_s(connections_params, Data)}; - {'EXIT', _} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {selected, _, _} -> - {User, []} - end, - case Customs of - {error, _Error} -> - Customs; - {Username, ConnectionsParams} -> - {result, - [{xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "You need an x:data capable client " - "to configure mod_irc settings")}]}, - {xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, - "Registration in mod_irc for ") ++ User ++ "@" ++ Server}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "Enter username, encodings, ports and passwords you wish to use for " - "connecting to IRC servers")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate( - Lang, "IRC Username")}, - {"var", "username"}], - [{xmlelement, "value", [], [{xmlcdata, Username}]}]}, - {xmlelement, "field", [{"type", "fixed"}], - [{xmlelement, "value", [], - [{xmlcdata, - lists:flatten( - io_lib:format( - translate:translate( - Lang, - "If you want to specify different ports, " - "passwords, encodings for IRC servers, fill " - "this list with values in format " - "'{\"irc server\", \"encoding\", port, \"password\"}'. " - "By default this service use \"~s\" encoding, port ~p, " - "empty password."), - [DefaultEncoding, ?DEFAULT_IRC_PORT]))}]}]}, - {xmlelement, "field", [{"type", "fixed"}], - [{xmlelement, "value", [], - [{xmlcdata, - translate:translate( - Lang, - "Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, " - "{\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]." - )}]}]}, - {xmlelement, "field", [{"type", "text-multi"}, - {"label", - translate:translate(Lang, "Connections parameters")}, - {"var", "connections_params"}], - lists:map( - fun(S) -> - {xmlelement, "value", [], [{xmlcdata, S}]} - end, - string:tokens( - lists:flatten( - io_lib:format("~p.", [ConnectionsParams])), - "\n")) - } - ]}]} - end; - -get_form(_ServerHost, _Host, _, _, _Lang) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. - - - - -set_form(ServerHost, Host, From, [], _Lang, XData) -> - LServer = jlib:nameprep(ServerHost), - SJID = ejabberd_odbc:escape( - jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(From)))), - SHost = ejabberd_odbc:escape(Host), - case {lists:keysearch("username", 1, XData), - lists:keysearch("connections_params", 1, XData)} of - {{value, {_, [Username]}}, {value, {_, Strings}}} -> - EncString = lists:foldl(fun(S, Res) -> - Res ++ S ++ "\n" - end, "", Strings), - case erl_scan:string(EncString) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ConnectionsParams} -> - SData = ejabberd_odbc:encode_term( - [{username, - Username}, - {connections_params, - ConnectionsParams}]), - case ejabberd_odbc:sql_transaction( - LServer, - fun() -> - odbc_queries:update_t( - "irc_custom", - ["jid", "host", "data"], - [SJID, SHost, SData], - ["jid='", SJID, - "' and host='", - SHost, "'"]), - ok - end) of - {atomic, _} -> - {result, []}; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - - -set_form(_ServerHost, _Host, _, _, _Lang, _XData) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. - - -%% Host = "irc.example.com" -%% ServerHost = "example.com" -get_connection_params(Host, From, IRCServer) -> - [_ | HostTail] = string:tokens(Host, "."), - ServerHost = string:join(HostTail, "."), - get_connection_params(Host, ServerHost, From, IRCServer). - -get_default_encoding(ServerHost) -> - Result = gen_mod:get_module_opt( - ServerHost, ?MODULE, default_encoding, - ?DEFAULT_IRC_ENCODING), - ?INFO_MSG("The default_encoding configured for host ~p is: ~p~n", [ServerHost, Result]), - Result. - -get_connection_params(Host, ServerHost, From, IRCServer) -> - #jid{user = User, server = _Server} = From, - LServer = jlib:nameprep(ServerHost), - SJID = ejabberd_odbc:escape( - jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(From)))), - SHost = ejabberd_odbc:escape(Host), - DefaultEncoding = get_default_encoding(ServerHost), - case catch ejabberd_odbc:sql_query( - LServer, - ["select data from irc_custom where " - "jid='", SJID, "' and host='", SHost, "';"]) of - {'EXIT', _Reason} -> - {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}; - {selected, ["data"], []} -> - {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}; - {selected, ["data"], [{SData}]} -> - Data = ejabberd_odbc:decode_term(SData), - Username = xml:get_attr_s(username, Data), - {NewUsername, NewEncoding, NewPort, NewPassword} = - case lists:keysearch( - IRCServer, 1, - xml:get_attr_s(connections_params, Data)) of - {value, {_, Encoding, Port, Password}} -> - {Username, Encoding, Port, Password}; - {value, {_, Encoding, Port}} -> - {Username, Encoding, Port, ""}; - {value, {_, Encoding}} -> - {Username, Encoding, ?DEFAULT_IRC_PORT, ""}; - _ -> - {Username, DefaultEncoding, ?DEFAULT_IRC_PORT, ""} - end, - {NewUsername, - NewEncoding, - if - NewPort >= 0 andalso NewPort =< 65535 -> - NewPort; - true -> - ?DEFAULT_IRC_PORT - end, - NewPassword} - end. - -adhoc_join(_From, _To, #adhoc_request{action = "cancel"} = Request) -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); -adhoc_join(From, To, #adhoc_request{lang = Lang, - node = _Node, - action = _Action, - xdata = XData} = Request) -> - %% Access control has already been taken care of in do_route. - if XData == false -> - Form = - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - [{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "Join IRC channel")}]}, - {xmlelement, "field", - [{"var", "channel"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC channel (don't put the first #)")}], - [{xmlelement, "required", [], []}]}, - {xmlelement, "field", - [{"var", "server"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC server")}], - [{xmlelement, "required", [], []}]}]}, - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form]}); - true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERR_BAD_REQUEST}; - Fields -> - Channel = case lists:keysearch("channel", 1, Fields) of - {value, {"channel", C}} -> - C; - _ -> - false - end, - Server = case lists:keysearch("server", 1, Fields) of - {value, {"server", S}} -> - S; - _ -> - false - end, - if Channel /= false, - Server /= false -> - RoomJID = Channel ++ "%" ++ Server ++ "@" ++ To#jid.server, - Invite = {xmlelement, "message", [], - [{xmlelement, "x", - [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "invite", - [{"from", jlib:jid_to_string(From)}], - [{xmlelement, "reason", [], - [{xmlcdata, - translate:translate(Lang, - "Join the IRC channel here.")}]}]}]}, - {xmlelement, "x", - [{"xmlns", ?NS_XCONFERENCE}], - [{xmlcdata, translate:translate(Lang, - "Join the IRC channel here.")}]}, - {xmlelement, "body", [], - [{xmlcdata, io_lib:format( - translate:translate(Lang, - "Join the IRC channel in this Jabber ID: ~s"), - [RoomJID])}]}]}, - ejabberd_router:route(jlib:string_to_jid(RoomJID), From, Invite), - adhoc:produce_response(Request, #adhoc_response{status = completed}); - true -> - {error, ?ERR_BAD_REQUEST} - end - end - end. - -adhoc_register(_ServerHost, _From, _To, #adhoc_request{action = "cancel"} = Request) -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); -adhoc_register(ServerHost, From, To, #adhoc_request{lang = Lang, - node = _Node, - xdata = XData, - action = Action} = Request) -> - #jid{user = User} = From, - #jid{lserver = Host} = To, - LServer = jlib:nameprep(ServerHost), - SHost = ejabberd_odbc:escape(Host), - SJID = ejabberd_odbc:escape( - jlib:jid_to_string( - jlib:jid_tolower( - jlib:jid_remove_resource(From)))), - %% 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 ejabberd_odbc:sql_query( - LServer, - ["select data from irc_custom where " - "jid='", SJID, "' and host='", SHost, "';"]) of - {'EXIT', _Reason} -> - Username = User, - ConnectionsParams = []; - {selected, ["data"], []} -> - Username = User, - ConnectionsParams = []; - {selected, ["data"], [{Data1}]} -> - Data = ejabberd_odbc:decode_term(Data1), - Username = xml:get_attr_s(username, Data), - ConnectionsParams = xml:get_attr_s(connections_params, Data) - end, - Error = false; - true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - Error = {error, ?ERR_BAD_REQUEST}, - Username = false, - ConnectionsParams = false; - Fields -> - Username = case lists:keysearch("username", 1, Fields) of - {value, {"username", U}} -> - U; - _ -> - User - end, - ConnectionsParams = parse_connections_params(Fields), - Error = false - end - end, - - if Error /= false -> - Error; - Action == "complete" -> - SData = ejabberd_odbc:encode_term( - [{username, Username}, - {connections_params, ConnectionsParams}]), - case catch ejabberd_odbc:sql_transaction( - LServer, - fun() -> - odbc_queries:update_t( - "irc_custom", - ["jid", "host", "data"], - [SJID, SHost, SData], - ["jid='", SJID, - "' and host='", SHost, "'"]), - ok - end) of - {atomic, ok} -> - adhoc:produce_response(Request, #adhoc_response{status = completed}); - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} - end; - true -> - Form = generate_adhoc_register_form(Lang, Username, ConnectionsParams), - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form], - actions = ["next", "complete"]}) - end. - -generate_adhoc_register_form(Lang, Username, ConnectionsParams) -> - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - [{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "IRC settings")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "Enter username and encodings you wish to use for " - "connecting to IRC servers. Press 'Next' to get more fields " - "to fill in. Press 'Complete' to save settings.")}]}, - {xmlelement, "field", - [{"var", "username"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC username")}], - [{xmlelement, "required", [], []}, - {xmlelement, "value", [], [{xmlcdata, Username}]}]}] ++ - generate_connection_params_fields(Lang, ConnectionsParams, 1, [])}. - -generate_connection_params_fields(Lang, [], Number, Acc) -> - Field = generate_connection_params_field(Lang, "", "", -1, "", Number), - lists:reverse(Field ++ Acc); - -generate_connection_params_fields(Lang, [ConnectionParams | ConnectionsParams], Number, Acc) -> - case ConnectionParams of - {Server, Encoding, Port, Password} -> - Field = generate_connection_params_field(Lang, Server, Encoding, Port, Password, Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - {Server, Encoding, Port} -> - Field = generate_connection_params_field(Lang, Server, Encoding, Port, [], Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - {Server, Encoding} -> - Field = generate_connection_params_field(Lang, Server, Encoding, [], [], Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - _ -> - [] - end. - -generate_connection_params_field(Lang, Server, Encoding, Port, Password, Number) -> - EncodingUsed = case Encoding of - [] -> - get_default_encoding(Server); - _ -> - Encoding - end, - PortUsedInt = if - Port >= 0 andalso Port =< 65535 -> - Port; - true -> - ?DEFAULT_IRC_PORT - end, - PortUsed = integer_to_list(PortUsedInt), - PasswordUsed = case Password of - [] -> - ""; - _ -> - Password - end, - NumberString = integer_to_list(Number), - %% Fields are in reverse order, as they will be reversed again later. - [{xmlelement, "field", - [{"var", "password" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Password ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, PasswordUsed}]}]}, - {xmlelement, "field", - [{"var", "port" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Port ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, PortUsed}]}]}, - {xmlelement, "field", - [{"var", "encoding" ++ NumberString}, - {"type", "list-single"}, - {"label", io_lib:format(translate:translate(Lang, "Encoding for server ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, EncodingUsed}]} | - lists:map(fun(E) -> - {xmlelement, "option", [{"label", E}], - [{xmlelement, "value", [], [{xmlcdata, E}]}]} - end, ?POSSIBLE_ENCODINGS)]}, - {xmlelement, "field", - [{"var", "server" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Server ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, Server}]}]}]. - -parse_connections_params(Fields) -> - %% Find all fields staring with serverN, encodingN, portN and passwordN for any values - %% of N, and generate lists of {"N", Value}. - Servers = lists:sort( - [{lists:nthtail(6, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("server", Var)]), - Encodings = lists:sort( - [{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("encoding", Var)]), - - Ports = lists:sort( - [{lists:nthtail(4, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("port", Var)]), - - Passwords = lists:sort( - [{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("password", Var)]), - - %% Now sort the lists, and find the corresponding pairs. - parse_connections_params(Servers, Encodings, Ports, Passwords). - -retrieve_connections_params(ConnectionParams, ServerN) -> - case ConnectionParams of - [{ConnectionParamN, ConnectionParam} | ConnectionParamsTail] -> - if - ServerN == ConnectionParamN -> - {ConnectionParam, ConnectionParamsTail}; - ServerN < ConnectionParamN -> - {[], [{ConnectionParamN, ConnectionParam} | ConnectionParamsTail]}; - ServerN > ConnectionParamN -> - {[], ConnectionParamsTail} - end; - _ -> - {[], []} - end. - -parse_connections_params([], _, _, _) -> - []; -parse_connections_params(_, [], [], []) -> - []; - -parse_connections_params([{ServerN, Server} | Servers], Encodings, Ports, Passwords) -> - %% Try to match matches of servers, ports, passwords and encodings, no matter what fields - %% the client might have left out. - {NewEncoding, NewEncodings} = retrieve_connections_params(Encodings, ServerN), - {NewPort, NewPorts} = retrieve_connections_params(Ports, ServerN), - {NewPassword, NewPasswords} = retrieve_connections_params(Passwords, ServerN), - [{Server, NewEncoding, NewPort, NewPassword} | parse_connections_params(Servers, NewEncodings, NewPorts, NewPasswords)]. diff --git a/src/mod_last.erl b/src/mod_last.erl index 31876d47c..4cc0c5cc3 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -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), diff --git a/src/mod_last_odbc.erl b/src/mod_last_odbc.erl deleted file mode 100644 index 4466909d4..000000000 --- a/src/mod_last_odbc.erl +++ /dev/null @@ -1,204 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_last_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : jabber:iq:last support (XEP-0012) -%%% Created : 24 Oct 2003 by Alexey Shchepin -%%% -%%% -%%% 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). diff --git a/src/mod_muc/Makefile.win32 b/src/mod_muc/Makefile.win32 index 27b81925e..5107b1069 100644 --- a/src/mod_muc/Makefile.win32 +++ b/src/mod_muc/Makefile.win32 @@ -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 diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl index 81573d1a1..811e3c068 100644 --- a/src/mod_muc/mod_muc.erl +++ b/src/mod_muc/mod_muc.erl @@ -111,26 +111,70 @@ create_room(Host, Name, From, Nick, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), gen_server:call(Proc, {create, Name, From, Nick, Opts}). -store_room(_ServerHost, Host, Name, Opts) -> +store_room(ServerHost, Host, Name, Opts) -> + LServer = jlib:nameprep(ServerHost), + store_room(LServer, Host, Name, Opts, gen_mod:db_type(LServer, ?MODULE)). + +store_room(_LServer, Host, Name, Opts, mnesia) -> F = fun() -> mnesia:write(#muc_room{name_host = {Name, Host}, opts = Opts}) end, - mnesia:transaction(F). + mnesia:transaction(F); +store_room(LServer, Host, Name, Opts, odbc) -> + SName = ejabberd_odbc:escape(Name), + SHost = ejabberd_odbc:escape(Host), + SOpts = ejabberd_odbc:encode_term(Opts), + F = fun() -> + odbc_queries:update_t( + "muc_room", + ["name", "host", "opts"], + [SName, SHost, SOpts], + ["name='", SName, "' and host='", SHost, "'"]) + end, + ejabberd_odbc:sql_transaction(LServer, F). -restore_room(_ServerHost, Host, Name) -> +restore_room(ServerHost, Host, Name) -> + LServer = jlib:nameprep(ServerHost), + restore_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)). + +restore_room(_LServer, Host, Name, mnesia) -> case catch mnesia:dirty_read(muc_room, {Name, Host}) of [#muc_room{opts = Opts}] -> Opts; _ -> error + end; +restore_room(LServer, Host, Name, odbc) -> + SName = ejabberd_odbc:escape(Name), + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query( + LServer, ["select opts from muc_room where name='", + SName, "' and host='", SHost, "';"]) of + {selected, ["opts"], [{Opts}]} -> + ejabberd_odbc:decode_term(Opts); + _ -> + error end. -forget_room(_ServerHost, Host, Name) -> +forget_room(ServerHost, Host, Name) -> + LServer = jlib:nameprep(ServerHost), + forget_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)). + +forget_room(_LServer, Host, Name, mnesia) -> F = fun() -> mnesia:delete({muc_room, {Name, Host}}) end, - mnesia:transaction(F). + mnesia:transaction(F); +forget_room(LServer, Host, Name, odbc) -> + SName = ejabberd_odbc:escape(Name), + SHost = ejabberd_odbc:escape(Host), + F = fun() -> + ejabberd_odbc:sql_query_t( + ["delete from muc_room where name='", + SName, "' and host='", SHost, "';"]) + end, + ejabberd_odbc:sql_transaction(LServer, F). process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) -> Rsm = jlib:rsm_decode(IQ), @@ -144,7 +188,11 @@ process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) -> can_use_nick(_ServerHost, _Host, _JID, "") -> false; -can_use_nick(_ServerHost, Host, JID, Nick) -> +can_use_nick(ServerHost, Host, JID, Nick) -> + LServer = jlib:nameprep(ServerHost), + can_use_nick(LServer, Host, JID, Nick, gen_mod:db_type(LServer, ?MODULE)). + +can_use_nick(_LServer, Host, JID, Nick, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(JID), LUS = {LUser, LServer}, case catch mnesia:dirty_select( @@ -160,6 +208,21 @@ can_use_nick(_ServerHost, Host, JID, Nick) -> true; [#muc_registered{us_host = {U, _Host}}] -> U == LUS + end; +can_use_nick(LServer, Host, JID, Nick, odbc) -> + SJID = jlib:jid_to_string( + jlib:jid_tolower( + jlib:jid_remove_resource(JID))), + SNick = ejabberd_odbc:escape(Nick), + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query( + LServer, ["select jid from muc_registered ", + "where nick='", SNick, "' and host='", + SHost, "';"]) of + {selected, ["jid"], [{SJID1}]} -> + SJID == SJID1; + _ -> + true end. %%==================================================================== @@ -174,21 +237,28 @@ can_use_nick(_ServerHost, Host, JID, Nick) -> %% Description: Initiates the server %%-------------------------------------------------------------------- init([Host, Opts]) -> - mnesia:create_table(muc_room, - [{disc_copies, [node()]}, - {attributes, record_info(fields, muc_room)}]), - mnesia:create_table(muc_registered, - [{disc_copies, [node()]}, - {attributes, record_info(fields, muc_registered)}]), + MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"), + case gen_mod:db_type(Opts) of + mnesia -> + update_tables(MyHost), + mnesia:create_table(muc_room, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, muc_room)}]), + mnesia:create_table(muc_registered, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, muc_registered)}]), + mnesia:add_table_index(muc_registered, nick); + _ -> + ok + end, mnesia:create_table(muc_online_room, [{ram_copies, [node()]}, {attributes, record_info(fields, muc_online_room)}]), mnesia:add_table_copy(muc_online_room, node(), ram_copies), catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]), - MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"), - update_tables(MyHost), clean_table_from_bad_node(node(), MyHost), - mnesia:add_table_index(muc_registered, nick), mnesia:subscribe(system), Access = gen_mod:get_opt(access, Opts, all), AccessCreate = gen_mod:get_opt(access_create, Opts, all), @@ -238,7 +308,7 @@ handle_call({create, Room, From, Nick, Opts}, Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, - Nick, NewOpts, ?MODULE), + Nick, NewOpts), register_room(Host, Room, Pid), {reply, ok, State}. @@ -379,7 +449,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, [{xmlelement, "query", [{"xmlns", XMLNS}], iq_get_register_info( - Host, From, Lang)}]}, + ServerHost, Host, From, Lang)}]}, ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); @@ -387,7 +457,8 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, xmlns = ?NS_REGISTER = XMLNS, lang = Lang, sub_el = SubEl} = IQ -> - case process_iq_register_set(Host, From, SubEl, Lang) of + case process_iq_register_set( + ServerHost, Host, From, SubEl, Lang) of {result, IQRes} -> Res = IQ#iq{type = result, sub_el = @@ -519,52 +590,72 @@ check_user_can_create_room(ServerHost, AccessCreate, From, RoomID) -> false end. +get_rooms(ServerHost, Host) -> + LServer = jlib:nameprep(ServerHost), + get_rooms(LServer, Host, gen_mod:db_type(LServer, ?MODULE)). -load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) -> +get_rooms(_LServer, Host, mnesia) -> case catch mnesia:dirty_select( muc_room, [{#muc_room{name_host = {'_', Host}, _ = '_'}, [], ['$_']}]) of {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), - ok; + []; Rs -> - lists:foreach( - fun(R) -> - {Room, Host} = R#muc_room.name_host, - case mnesia:dirty_read(muc_online_room, {Room, Host}) of - [] -> - {ok, Pid} = mod_muc_room:start( - Host, - ServerHost, - Access, - Room, - HistorySize, - RoomShaper, - R#muc_room.opts, - ?MODULE), - register_room(Host, Room, Pid); - _ -> - ok - end - end, Rs) + Rs + end; +get_rooms(LServer, Host, odbc) -> + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query( + LServer, ["select name, opts from muc_room ", + "where host='", SHost, "';"]) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p", [Reason]), + []; + {selected, ["name", "opts"], RoomOpts} -> + lists:map( + fun({Room, Opts}) -> + #muc_room{name_host = {Room, Host}, + opts = ejabberd_odbc:decode_term(Opts)} + end, RoomOpts) end. +load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) -> + lists:foreach( + fun(R) -> + {Room, Host} = R#muc_room.name_host, + case mnesia:dirty_read(muc_online_room, {Room, Host}) of + [] -> + {ok, Pid} = mod_muc_room:start( + Host, + ServerHost, + Access, + Room, + HistorySize, + RoomShaper, + R#muc_room.opts), + register_room(Host, Room, Pid); + _ -> + ok + end + end, get_rooms(ServerHost, Host)). + start_new_room(Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, Nick, DefRoomOpts) -> - case mnesia:dirty_read(muc_room, {Room, Host}) of - [] -> + case restore_room(ServerHost, Room, Host) of + error -> ?DEBUG("MUC: open new room '~s'~n", [Room]), mod_muc_room:start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, - Nick, DefRoomOpts, ?MODULE); - [#muc_room{opts = Opts}|_] -> + Nick, DefRoomOpts); + Opts -> ?DEBUG("MUC: restore room '~s'~n", [Room]), mod_muc_room:start(Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Opts, ?MODULE) + RoomShaper, Opts) end. register_room(Host, Room, Pid) -> @@ -693,18 +784,44 @@ flush() -> iq_get_unique(From) -> {xmlcdata, sha:sha(term_to_binary([From, now(), randoms:get_string()]))}. -iq_get_register_info(Host, From, Lang) -> +get_nick(ServerHost, Host, From) -> + LServer = jlib:nameprep(ServerHost), + get_nick(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)). + +get_nick(_LServer, Host, From, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(From), LUS = {LUser, LServer}, + case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of + {'EXIT', _Reason} -> + error; + [] -> + error; + [#muc_registered{nick = Nick}] -> + Nick + end; +get_nick(LServer, Host, From, odbc) -> + SJID = ejabberd_odbc:escape( + jlib:jid_to_string( + jlib:jid_tolower( + jlib:jid_remove_resource(From)))), + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query( + LServer, ["select nick from muc_registered where " + "jid='", SJID, "' and host='", SHost, "';"]) of + {selected, ["nick"], [{Nick}]} -> + Nick; + _ -> + error + end. + +iq_get_register_info(ServerHost, Host, From, Lang) -> {Nick, Registered} = - case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of - {'EXIT', _Reason} -> - {"", []}; - [] -> - {"", []}; - [#muc_registered{nick = N}] -> - {N, [{xmlelement, "registered", [], []}]} - end, + case get_nick(ServerHost, Host, From) of + error -> + {"", []}; + N -> + {N, [{xmlelement, "registered", [], []}]} + end, Registered ++ [{xmlelement, "instructions", [], [{xmlcdata, @@ -722,7 +839,11 @@ iq_get_register_info(Host, From, Lang) -> Lang, "Enter nickname you want to register")}]}, ?XFIELD("text-single", "Nickname", "nick", Nick)]}]. -iq_set_register_info(Host, From, Nick, Lang) -> +set_nick(ServerHost, Host, From, Nick) -> + LServer = jlib:nameprep(ServerHost), + set_nick(LServer, Host, From, Nick, gen_mod:db_type(LServer, ?MODULE)). + +set_nick(_LServer, Host, From, Nick, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(From), LUS = {LUser, LServer}, F = fun() -> @@ -755,7 +876,48 @@ iq_set_register_info(Host, From, Nick, Lang) -> end end end, - case mnesia:transaction(F) of + mnesia:transaction(F); +set_nick(LServer, Host, From, Nick, odbc) -> + JID = jlib:jid_to_string( + jlib:jid_tolower( + jlib:jid_remove_resource(From))), + SJID = ejabberd_odbc:escape(JID), + SNick = ejabberd_odbc:escape(Nick), + SHost = ejabberd_odbc:escape(Host), + F = fun() -> + case Nick of + "" -> + ejabberd_odbc:sql_query_t( + ["delete from muc_registered where ", + "jid='", SJID, "' and host='", Host, "';"]), + ok; + _ -> + Allow = + case ejabberd_odbc:sql_query_t( + ["select jid from muc_registered ", + "where nick='", SNick, "' and host='", + SHost, "';"]) of + {selected, ["jid"], [{J}]} -> + J == JID; + _ -> + true + end, + if Allow -> + odbc_queries:update_t( + "muc_registered", + ["jid", "host", "nick"], + [SJID, SHost, SNick], + ["jid='", SJID, "' and host='", SHost, "'"]), + ok; + true -> + false + end + end + end, + ejabberd_odbc:sql_transaction(LServer, F). + +iq_set_register_info(ServerHost, Host, From, Nick, Lang) -> + case set_nick(ServerHost, Host, From, Nick) of {atomic, ok} -> {result, []}; {atomic, false} -> @@ -765,7 +927,7 @@ iq_set_register_info(Host, From, Nick, Lang) -> {error, ?ERR_INTERNAL_SERVER_ERROR} end. -process_iq_register_set(Host, From, SubEl, Lang) -> +process_iq_register_set(ServerHost, Host, From, SubEl, Lang) -> {xmlelement, _Name, _Attrs, Els} = SubEl, case xml:get_subtag(SubEl, "remove") of false -> @@ -783,7 +945,8 @@ process_iq_register_set(Host, From, SubEl, Lang) -> _ -> case lists:keysearch("nick", 1, XData) of {value, {_, [Nick]}} when Nick /= "" -> - iq_set_register_info(Host, From, Nick, Lang); + iq_set_register_info(ServerHost, Host, + From, Nick, Lang); _ -> ErrText = "You must fill in field \"Nickname\" in the form", {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)} @@ -796,7 +959,7 @@ process_iq_register_set(Host, From, SubEl, Lang) -> {error, ?ERR_BAD_REQUEST} end; _ -> - iq_set_register_info(Host, From, "", Lang) + iq_set_register_info(ServerHost, Host, From, "", Lang) end. iq_get_vcard(Lang) -> diff --git a/src/mod_muc/mod_muc_odbc.erl b/src/mod_muc/mod_muc_odbc.erl deleted file mode 100644 index 47dc4c9ef..000000000 --- a/src/mod_muc/mod_muc_odbc.erl +++ /dev/null @@ -1,875 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_muc_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : MUC support (XEP-0045) -%%% Created : 19 Mar 2003 by Alexey Shchepin -%%% -%%% -%%% 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). diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index a5845175c..670460be7 100644 --- a/src/mod_muc/mod_muc_room.erl +++ b/src/mod_muc/mod_muc_room.erl @@ -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 -> diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 20874c833..75b1966b2 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -1,7 +1,7 @@ %%%---------------------------------------------------------------------- %%% File : mod_offline.erl %%% Author : Alexey Shchepin -%%% Purpose : Store and manage offline messages in Mnesia database. +%%% Purpose : Store and manage offline messages. %%% Created : 5 Jan 2003 by Alexey Shchepin %%% %%% @@ -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. diff --git a/src/mod_offline_odbc.erl b/src/mod_offline_odbc.erl deleted file mode 100644 index 0d30d5745..000000000 --- a/src/mod_offline_odbc.erl +++ /dev/null @@ -1,548 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_offline_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : Store and manage offline messages in relational database. -%%% Created : 5 Jan 2003 by Alexey Shchepin -%%% -%%% -%%% 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. diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index 665b8866a..42809ef8b 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -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), diff --git a/src/mod_privacy_odbc.erl b/src/mod_privacy_odbc.erl deleted file mode 100644 index 87303b57a..000000000 --- a/src/mod_privacy_odbc.erl +++ /dev/null @@ -1,878 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_privacy_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : jabber:iq:privacy support -%%% Created : 5 Oct 2006 by Alexey Shchepin -%%% -%%% -%%% 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). diff --git a/src/mod_private.erl b/src/mod_private.erl index ba5c00a8e..5ca6204f3 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -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), diff --git a/src/mod_private_odbc.erl b/src/mod_private_odbc.erl deleted file mode 100644 index 45fee4cb2..000000000 --- a/src/mod_private_odbc.erl +++ /dev/null @@ -1,136 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_private_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : Private storage support -%%% Created : 5 Oct 2006 by Alexey Shchepin -%%% -%%% -%%% 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). diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 3383148fc..5e6b4fd52 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -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 diff --git a/src/mod_roster_odbc.erl b/src/mod_roster_odbc.erl deleted file mode 100644 index b4162ca50..000000000 --- a/src/mod_roster_odbc.erl +++ /dev/null @@ -1,1211 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_roster_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : Roster management -%%% Created : 15 Dec 2004 by Alexey Shchepin -%%% -%%% -%%% 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 -%%% -%%%---------------------------------------------------------------------- - -%%% @doc Roster management (Mnesia storage). -%%% -%%% Includes support for XEP-0237: Roster Versioning. -%%% The roster versioning follows an all-or-nothing strategy: -%%% - If the version supplied by the client is the latest, return an empty response. -%%% - If not, return the entire new roster (with updated version string). -%%% Roster version is a hash digest of the entire roster. -%%% No additional data is stored in DB. - --module(mod_roster_odbc). --author('alexey@process-one.net'). - --behaviour(gen_mod). - --export([start/2, stop/1, - process_iq/3, - process_local_iq/3, - get_user_roster/2, - get_subscription_lists/3, - get_in_pending_subscriptions/3, - in_subscription/6, - out_subscription/4, - set_items/3, - remove_user/2, - get_jid_info/4, - webadmin_page/3, - webadmin_user/4, - get_versioning_feature/2, - roster_versioning_enabled/1]). - --include("ejabberd.hrl"). --include("jlib.hrl"). --include("mod_roster.hrl"). --include("web/ejabberd_http.hrl"). --include("web/ejabberd_web_admin.hrl"). - - -start(Host, Opts) -> - IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - ejabberd_hooks:add(roster_get, Host, - ?MODULE, get_user_roster, 50), - ejabberd_hooks:add(roster_in_subscription, Host, - ?MODULE, in_subscription, 50), - ejabberd_hooks:add(roster_out_subscription, Host, - ?MODULE, out_subscription, 50), - ejabberd_hooks:add(roster_get_subscription_lists, Host, - ?MODULE, get_subscription_lists, 50), - ejabberd_hooks:add(roster_get_jid_info, Host, - ?MODULE, get_jid_info, 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(resend_subscription_requests_hook, Host, - ?MODULE, get_in_pending_subscriptions, 50), - ejabberd_hooks:add(roster_get_versioning_feature, Host, - ?MODULE, get_versioning_feature, 50), - ejabberd_hooks:add(webadmin_page_host, Host, - ?MODULE, webadmin_page, 50), - ejabberd_hooks:add(webadmin_user, Host, - ?MODULE, webadmin_user, 50), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ROSTER, - ?MODULE, process_iq, IQDisc). - -stop(Host) -> - ejabberd_hooks:delete(roster_get, Host, - ?MODULE, get_user_roster, 50), - ejabberd_hooks:delete(roster_in_subscription, Host, - ?MODULE, in_subscription, 50), - ejabberd_hooks:delete(roster_out_subscription, Host, - ?MODULE, out_subscription, 50), - ejabberd_hooks:delete(roster_get_subscription_lists, Host, - ?MODULE, get_subscription_lists, 50), - ejabberd_hooks:delete(roster_get_jid_info, Host, - ?MODULE, get_jid_info, 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(resend_subscription_requests_hook, Host, - ?MODULE, get_in_pending_subscriptions, 50), - ejabberd_hooks:delete(roster_get_versioning_feature, Host, - ?MODULE, get_versioning_feature, 50), - ejabberd_hooks:delete(webadmin_page_host, Host, - ?MODULE, webadmin_page, 50), - ejabberd_hooks:delete(webadmin_user, Host, - ?MODULE, webadmin_user, 50), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ROSTER). - - -process_iq(From, To, IQ) -> - #iq{sub_el = SubEl} = IQ, - #jid{lserver = LServer} = From, - case lists:member(LServer, ?MYHOSTS) of - true -> - process_local_iq(From, To, IQ); - _ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]} - end. - -process_local_iq(From, To, #iq{type = Type} = IQ) -> - case Type of - set -> - process_iq_set(From, To, IQ); - get -> - process_iq_get(From, To, IQ) - end. - - -roster_hash(Items) -> - sha:sha(term_to_binary( - lists:sort( - [R#roster{groups = lists:sort(Grs)} || - R = #roster{groups = Grs} <- Items]))). - -roster_versioning_enabled(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, versioning, false). - -roster_version_on_db(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, store_current_id, false). - -%% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled. -get_versioning_feature(Acc, Host) -> - case roster_versioning_enabled(Host) of - true -> - Feature = {xmlelement, - "ver", - [{"xmlns", ?NS_ROSTER_VER}], - [{xmlelement, "optional", [], []}]}, - [Feature | Acc]; - false -> [] - end. - -roster_version(LServer ,LUser) -> - US = {LUser, LServer}, - case roster_version_on_db(LServer) of - true -> - case odbc_queries:get_roster_version(ejabberd_odbc:escape(LServer), ejabberd_odbc:escape(LUser)) of - {selected, ["version"], [{Version}]} -> Version; - {selected, ["version"], []} -> - %% If for some reason we don't had it on DB. Create a version Id and store it. - %% (we did the same on process_iq_get, that is called when client get roster, - %% not sure why it can still not be on DB at this point) - RosterVersion = sha:sha(term_to_binary(now())), - {atomic, {updated,1}} = odbc_queries:sql_transaction(LServer, fun() -> - odbc_queries:set_roster_version(ejabberd_odbc:escape(LUser), RosterVersion) - end), - RosterVersion - end; - false -> - roster_hash(ejabberd_hooks:run_fold(roster_get, LServer, [], [US])) - end. - -%% Load roster from DB only if neccesary. -%% It is neccesary if -%% - roster versioning is disabled in server OR -%% - roster versioning is not used by the client OR -%% - roster versioning is used by server and client, BUT the server isn't storing versions on db OR -%% - the roster version from client don't match current version. -process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) -> - LUser = From#jid.luser, - 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 - {{value, RequestedVersion}, true, true} -> - %% Retrieve version from DB. Only load entire roster - %% when neccesary. - case odbc_queries:get_roster_version(ejabberd_odbc:escape(LServer), ejabberd_odbc:escape(LUser)) of - {selected, ["version"], [{RequestedVersion}]} -> - {false, false}; - {selected, ["version"], [{NewVersion}]} -> - {lists:map(fun item_to_xml/1, - ejabberd_hooks:run_fold(roster_get, To#jid.lserver, [], [US])), NewVersion}; - {selected, ["version"], []} -> - RosterVersion = sha:sha(term_to_binary(now())), - {atomic, {updated,1}} = odbc_queries:sql_transaction(LServer, fun() -> - odbc_queries:set_roster_version(ejabberd_odbc:escape(LUser), RosterVersion) - end), - - {lists:map(fun item_to_xml/1, - ejabberd_hooks:run_fold(roster_get, To#jid.lserver, [], [US])), RosterVersion} - 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; - - _ -> - {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]} - 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_roster(LUser, 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) -> - 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; - _ -> - [] - end. - - -item_to_xml(Item) -> - Attrs1 = [{"jid", jlib:jid_to_string(Item#roster.jid)}], - Attrs2 = case Item#roster.name of - "" -> - Attrs1; - Name -> - [{"name", Name} | Attrs1] - end, - Attrs3 = case Item#roster.subscription of - none -> - [{"subscription", "none"} | Attrs2]; - from -> - [{"subscription", "from"} | Attrs2]; - to -> - [{"subscription", "to"} | Attrs2]; - both -> - [{"subscription", "both"} | Attrs2]; - remove -> - [{"subscription", "remove"} | Attrs2] - end, - Attrs = case ask_to_pending(Item#roster.ask) of - out -> - [{"ask", "subscribe"} | Attrs3]; - both -> - [{"ask", "subscribe"} | Attrs3]; - _ -> - Attrs3 - end, - SubEls = lists:map(fun(G) -> - {xmlelement, "group", [], [{xmlcdata, G}]} - end, Item#roster.groups), - {xmlelement, "item", Attrs, SubEls}. - - -process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) -> - {xmlelement, _Name, _Attrs, Els} = SubEl, - lists:foreach(fun(El) -> process_item_set(From, To, El) end, Els), - IQ#iq{type = result, sub_el = []}. - -process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) -> - JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)), - #jid{user = User, luser = LUser, lserver = LServer} = From, - case JID1 of - error -> - ok; - _ -> - LJID = jlib:jid_tolower(JID1), - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)), - F = fun() -> - {selected, - ["username", "jid", "nick", "subscription", - "ask", "askmessage", "server", "subscribe", "type"], - Res} = odbc_queries:get_roster_by_jid(LServer, Username, SJID), - Item = 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, - Item1 = process_item_attrs(Item, Attrs), - Item2 = process_item_els(Item1, Els), - case Item2#roster.subscription of - remove -> - odbc_queries:del_roster(LServer, Username, SJID); - _ -> - ItemVals = record_to_string(Item2), - ItemGroups = groups_to_string(Item2), - odbc_queries:update_roster(LServer, Username, SJID, ItemVals, ItemGroups) - 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 -> odbc_queries:set_roster_version(ejabberd_odbc:escape(LUser), sha:sha(term_to_binary(now()))); - false -> ok - end, - {Item, Item3} - end, - case odbc_queries:sql_transaction(LServer, F) of - {atomic, {OldItem, Item}} -> - push_item(User, LServer, To, Item), - case Item#roster.subscription of - remove -> - send_unsubscribing_presence(From, OldItem), - ok; - _ -> - ok - end; - E -> - ?DEBUG("ROSTER: roster item set error: ~p~n", [E]), - ok - end - end; -process_item_set(_From, _To, _) -> - ok. - -process_item_attrs(Item, [{Attr, Val} | Attrs]) -> - case Attr of - "jid" -> - case jlib:string_to_jid(Val) of - error -> - process_item_attrs(Item, Attrs); - JID1 -> - JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource}, - process_item_attrs(Item#roster{jid = JID}, Attrs) - end; - "name" -> - process_item_attrs(Item#roster{name = Val}, Attrs); - "subscription" -> - case Val of - "remove" -> - process_item_attrs(Item#roster{subscription = remove}, - Attrs); - _ -> - process_item_attrs(Item, Attrs) - end; - "ask" -> - process_item_attrs(Item, Attrs); - _ -> - process_item_attrs(Item, Attrs) - end; -process_item_attrs(Item, []) -> - Item. - - -process_item_els(Item, [{xmlelement, Name, _Attrs, SEls} | Els]) -> - case Name of - "group" -> - Groups = [xml:get_cdata(SEls) | Item#roster.groups], - process_item_els(Item#roster{groups = Groups}, Els); - _ -> - process_item_els(Item, Els) - end; -process_item_els(Item, [{xmlcdata, _} | Els]) -> - process_item_els(Item, Els); -process_item_els(Item, []) -> - Item. - - -push_item(User, Server, From, Item) -> - ejabberd_sm:route(jlib:make_jid("", "", ""), - jlib:make_jid(User, Server, ""), - {xmlelement, "broadcast", [], - [{item, - Item#roster.jid, - Item#roster.subscription}]}), - case roster_versioning_enabled(Server) of - true -> - push_item_version(Server, User, From, Item, roster_version(Server, User)); - false -> - lists:foreach(fun(Resource) -> - push_item(User, Server, Resource, From, Item) - end, ejabberd_sm:get_user_resources(User, Server)) - end. - -% TODO: don't push to those who not load roster -push_item(User, Server, Resource, From, Item) -> - ResIQ = #iq{type = set, xmlns = ?NS_ROSTER, - id = "push" ++ randoms:get_string(), - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_ROSTER}], - [item_to_xml(Item)]}]}, - ejabberd_router:route( - From, - jlib:make_jid(User, Server, Resource), - jlib:iq_to_xml(ResIQ)). - -%% @doc Roster push, calculate and include the version attribute. -%% TODO: don't push to those who didn't load roster -push_item_version(Server, User, From, Item, RosterVersion) -> - lists:foreach(fun(Resource) -> - push_item_version(User, Server, Resource, From, Item, RosterVersion) - end, ejabberd_sm:get_user_resources(User, Server)). - -push_item_version(User, Server, Resource, From, Item, RosterVersion) -> - IQPush = #iq{type = 'set', xmlns = ?NS_ROSTER, - id = "push" ++ randoms:get_string(), - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_ROSTER}, - {"ver", RosterVersion}], - [item_to_xml(Item)]}]}, - ejabberd_router:route( - From, - jlib:make_jid(User, Server, Resource), - jlib:iq_to_xml(IQPush)). - -get_subscription_lists(_, User, Server) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - 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) -> - fill_subscription_lists(LServer, Items, [], []); - _ -> - {[], []} - end. - -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); - _ -> - J = I#roster.jid, - case I#roster.subscription of - both -> - fill_subscription_lists(LServer, Is, [J | F], [J | T]); - from -> - fill_subscription_lists(LServer, Is, [J | F], T); - to -> - fill_subscription_lists(LServer, Is, F, [J | T]); - _ -> - fill_subscription_lists(LServer, Is, F, T) - end - end; -fill_subscription_lists(_LServer, [], F, T) -> - {F, T}. - -ask_to_pending(subscribe) -> out; -ask_to_pending(unsubscribe) -> none; -ask_to_pending(Ask) -> Ask. - - - -in_subscription(_, User, Server, JID, Type, Reason) -> - process_subscription(in, User, Server, JID, Type, Reason). - -out_subscription(User, Server, JID, Type) -> - process_subscription(out, User, Server, JID, Type, []). - -process_subscription(Direction, User, Server, JID1, Type, Reason) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - LJID = jlib:jid_tolower(JID1), - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)), - F = fun() -> - Item = - 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, - NewState = case Direction of - out -> - out_state_change(Item#roster.subscription, - Item#roster.ask, - Type); - in -> - in_state_change(Item#roster.subscription, - Item#roster.ask, - Type) - end, - AutoReply = case Direction of - out -> - none; - in -> - in_auto_reply(Item#roster.subscription, - Item#roster.ask, - Type) - end, - AskMessage = case NewState of - {_, both} -> Reason; - {_, in} -> Reason; - _ -> "" - end, - case NewState of - none -> - {none, AutoReply}; - {none, none} when Item#roster.subscription == none, - Item#roster.ask == in -> - odbc_queries:del_roster(LServer, Username, SJID), - {none, AutoReply}; - {Subscription, Pending} -> - NewItem = Item#roster{subscription = Subscription, - ask = Pending, - askmessage = AskMessage}, - ItemVals = record_to_string(NewItem), - odbc_queries:roster_subscribe(LServer, Username, SJID, ItemVals), - case roster_version_on_db(LServer) of - true -> odbc_queries:set_roster_version(ejabberd_odbc:escape(LUser), sha:sha(term_to_binary(now()))); - false -> ok - end, - {{push, NewItem}, AutoReply} - end - end, - case odbc_queries:sql_transaction(LServer, F) of - {atomic, {Push, AutoReply}} -> - case AutoReply of - none -> - ok; - _ -> - T = case AutoReply of - subscribed -> "subscribed"; - unsubscribed -> "unsubscribed" - end, - ejabberd_router:route( - jlib:make_jid(User, Server, ""), JID1, - {xmlelement, "presence", [{"type", T}], []}) - end, - case Push of - {push, Item} -> - if - Item#roster.subscription == none, - Item#roster.ask == in -> - ok; - true -> - push_item(User, Server, - jlib:make_jid(User, Server, ""), Item) - end, - true; - none -> - false - end; - _ -> - false - end. - - -%% in_state_change(Subscription, Pending, Type) -> NewState -%% NewState = none | {NewSubscription, NewPending} --ifdef(ROSTER_GATEWAY_WORKAROUND). --define(NNSD, {to, none}). --define(NISD, {to, in}). --else. --define(NNSD, none). --define(NISD, none). --endif. - -in_state_change(none, none, subscribe) -> {none, in}; -in_state_change(none, none, subscribed) -> ?NNSD; -in_state_change(none, none, unsubscribe) -> none; -in_state_change(none, none, unsubscribed) -> none; -in_state_change(none, out, subscribe) -> {none, both}; -in_state_change(none, out, subscribed) -> {to, none}; -in_state_change(none, out, unsubscribe) -> none; -in_state_change(none, out, unsubscribed) -> {none, none}; -in_state_change(none, in, subscribe) -> none; -in_state_change(none, in, subscribed) -> ?NISD; -in_state_change(none, in, unsubscribe) -> {none, none}; -in_state_change(none, in, unsubscribed) -> none; -in_state_change(none, both, subscribe) -> none; -in_state_change(none, both, subscribed) -> {to, in}; -in_state_change(none, both, unsubscribe) -> {none, out}; -in_state_change(none, both, unsubscribed) -> {none, in}; -in_state_change(to, none, subscribe) -> {to, in}; -in_state_change(to, none, subscribed) -> none; -in_state_change(to, none, unsubscribe) -> none; -in_state_change(to, none, unsubscribed) -> {none, none}; -in_state_change(to, in, subscribe) -> none; -in_state_change(to, in, subscribed) -> none; -in_state_change(to, in, unsubscribe) -> {to, none}; -in_state_change(to, in, unsubscribed) -> {none, in}; -in_state_change(from, none, subscribe) -> none; -in_state_change(from, none, subscribed) -> {both, none}; -in_state_change(from, none, unsubscribe) -> {none, none}; -in_state_change(from, none, unsubscribed) -> none; -in_state_change(from, out, subscribe) -> none; -in_state_change(from, out, subscribed) -> {both, none}; -in_state_change(from, out, unsubscribe) -> {none, out}; -in_state_change(from, out, unsubscribed) -> {from, none}; -in_state_change(both, none, subscribe) -> none; -in_state_change(both, none, subscribed) -> none; -in_state_change(both, none, unsubscribe) -> {to, none}; -in_state_change(both, none, unsubscribed) -> {from, none}. - -out_state_change(none, none, subscribe) -> {none, out}; -out_state_change(none, none, subscribed) -> none; -out_state_change(none, none, unsubscribe) -> none; -out_state_change(none, none, unsubscribed) -> none; -out_state_change(none, out, subscribe) -> {none, out}; %% We need to resend query (RFC3921, section 9.2) -out_state_change(none, out, subscribed) -> none; -out_state_change(none, out, unsubscribe) -> {none, none}; -out_state_change(none, out, unsubscribed) -> none; -out_state_change(none, in, subscribe) -> {none, both}; -out_state_change(none, in, subscribed) -> {from, none}; -out_state_change(none, in, unsubscribe) -> none; -out_state_change(none, in, unsubscribed) -> {none, none}; -out_state_change(none, both, subscribe) -> none; -out_state_change(none, both, subscribed) -> {from, out}; -out_state_change(none, both, unsubscribe) -> {none, in}; -out_state_change(none, both, unsubscribed) -> {none, out}; -out_state_change(to, none, subscribe) -> none; -out_state_change(to, none, subscribed) -> {both, none}; -out_state_change(to, none, unsubscribe) -> {none, none}; -out_state_change(to, none, unsubscribed) -> none; -out_state_change(to, in, subscribe) -> none; -out_state_change(to, in, subscribed) -> {both, none}; -out_state_change(to, in, unsubscribe) -> {none, in}; -out_state_change(to, in, unsubscribed) -> {to, none}; -out_state_change(from, none, subscribe) -> {from, out}; -out_state_change(from, none, subscribed) -> none; -out_state_change(from, none, unsubscribe) -> none; -out_state_change(from, none, unsubscribed) -> {none, none}; -out_state_change(from, out, subscribe) -> none; -out_state_change(from, out, subscribed) -> none; -out_state_change(from, out, unsubscribe) -> {from, none}; -out_state_change(from, out, unsubscribed) -> {none, out}; -out_state_change(both, none, subscribe) -> none; -out_state_change(both, none, subscribed) -> none; -out_state_change(both, none, unsubscribe) -> {from, none}; -out_state_change(both, none, unsubscribed) -> {to, none}. - -in_auto_reply(from, none, subscribe) -> subscribed; -in_auto_reply(from, out, subscribe) -> subscribed; -in_auto_reply(both, none, subscribe) -> subscribed; -in_auto_reply(none, in, unsubscribe) -> unsubscribed; -in_auto_reply(none, both, unsubscribe) -> unsubscribed; -in_auto_reply(to, in, unsubscribe) -> unsubscribed; -in_auto_reply(from, none, unsubscribe) -> unsubscribed; -in_auto_reply(from, out, unsubscribe) -> unsubscribed; -in_auto_reply(both, none, unsubscribe) -> unsubscribed; -in_auto_reply(_, _, _) -> none. - - -remove_user(User, Server) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - 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; -%% Both or To, send a "unsubscribe" presence stanza. -send_unsubscription_to_rosteritems(LUser, LServer) -> - RosterItems = get_user_roster([], {LUser, LServer}), - From = jlib:make_jid({LUser, LServer, ""}), - lists:foreach(fun(RosterItem) -> - send_unsubscribing_presence(From, RosterItem) - end, - RosterItems). - -%% @spec (From::jid(), Item::roster()) -> ok -send_unsubscribing_presence(From, Item) -> - IsTo = case Item#roster.subscription of - both -> true; - to -> true; - _ -> false - end, - IsFrom = case Item#roster.subscription of - both -> true; - from -> true; - _ -> false - end, - if IsTo -> - send_presence_type( - jlib:jid_remove_resource(From), - jlib:make_jid(Item#roster.jid), "unsubscribe"); - true -> ok - end, - if IsFrom -> - send_presence_type( - jlib:jid_remove_resource(From), - jlib:make_jid(Item#roster.jid), "unsubscribed"); - true -> ok - end, - ok. - -send_presence_type(From, To, Type) -> - ejabberd_router:route( - From, To, - {xmlelement, "presence", - [{"type", Type}], - []}). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -set_items(User, Server, SubEl) -> - {xmlelement, _Name, _Attrs, Els} = SubEl, - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - catch odbc_queries:sql_transaction( - LServer, - lists:flatmap(fun(El) -> - process_item_set_t(LUser, LServer, El) - end, Els)). - -process_item_set_t(LUser, LServer, {xmlelement, _Name, Attrs, Els}) -> - JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)), - case JID1 of - error -> - []; - _ -> - LJID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource}, - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)), - Item = #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, - jid = LJID}, - Item1 = process_item_attrs_ws(Item, Attrs), - Item2 = process_item_els(Item1, Els), - case Item2#roster.subscription of - remove -> - odbc_queries:del_roster_sql(Username, SJID); - _ -> - ItemVals = record_to_string(Item1), - ItemGroups = groups_to_string(Item2), - odbc_queries:update_roster_sql(Username, SJID, ItemVals, ItemGroups) - end - end; -process_item_set_t(_LUser, _LServer, _) -> - []. - -process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) -> - case Attr of - "jid" -> - case jlib:string_to_jid(Val) of - error -> - process_item_attrs_ws(Item, Attrs); - JID1 -> - JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource}, - process_item_attrs_ws(Item#roster{jid = JID}, Attrs) - end; - "name" -> - process_item_attrs_ws(Item#roster{name = Val}, Attrs); - "subscription" -> - case Val of - "remove" -> - process_item_attrs_ws(Item#roster{subscription = remove}, - Attrs); - "none" -> - process_item_attrs_ws(Item#roster{subscription = none}, - Attrs); - "both" -> - process_item_attrs_ws(Item#roster{subscription = both}, - Attrs); - "from" -> - process_item_attrs_ws(Item#roster{subscription = from}, - Attrs); - "to" -> - process_item_attrs_ws(Item#roster{subscription = to}, - Attrs); - _ -> - process_item_attrs_ws(Item, Attrs) - end; - "ask" -> - process_item_attrs_ws(Item, Attrs); - _ -> - process_item_attrs_ws(Item, Attrs) - end; -process_item_attrs_ws(Item, []) -> - Item. - -get_in_pending_subscriptions(Ls, User, Server) -> - 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) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - LJID = jlib:jid_tolower(JID), - 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}; - _ -> - LRJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)), - if - LRJID == LJID -> - {none, []}; - true -> - SRJID = ejabberd_odbc:escape(jlib:jid_to_string(LRJID)), - case catch odbc_queries:get_subscription(LServer, Username, SRJID) 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, SRJID) of - {selected, ["grp"], JGrps} when is_list(JGrps) -> - [JGrp || {JGrp} <- JGrps]; - _ -> - [] - end, - {Subscription, Groups}; - _ -> - {none, []} - end - end - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -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). - -webadmin_page(_, Host, - #request{us = _US, - path = ["user", U, "roster"], - q = Query, - lang = Lang} = _Request) -> - Res = user_roster(U, Host, Query, Lang), - {stop, Res}; - -webadmin_page(Acc, _, _) -> Acc. - -user_roster(User, Server, Query, Lang) -> - 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 = get_roster(LUser, LServer), - SItems = lists:sort(Items), - FItems = - case SItems of - [] -> - [?CT("None")]; - _ -> - [?XE("table", - [?XE("thead", - [?XE("tr", - [?XCT("td", "Jabber ID"), - ?XCT("td", "Nickname"), - ?XCT("td", "Subscription"), - ?XCT("td", "Pending"), - ?XCT("td", "Groups") - ])]), - ?XE("tbody", - lists:map( - fun(R) -> - Groups = - lists:flatmap( - fun(Group) -> - [?C(Group), ?BR] - end, R#roster.groups), - Pending = ask_to_pending(R#roster.ask), - TDJID = build_contact_jid_td(R#roster.jid), - ?XE("tr", - [TDJID, - ?XAC("td", [{"class", "valign"}], - R#roster.name), - ?XAC("td", [{"class", "valign"}], - atom_to_list(R#roster.subscription)), - ?XAC("td", [{"class", "valign"}], - atom_to_list(Pending)), - ?XAE("td", [{"class", "valign"}], Groups), - if - Pending == in -> - ?XAE("td", [{"class", "valign"}], - [?INPUTT("submit", - "validate" ++ - ejabberd_web_admin:term_to_id(R#roster.jid), - "Validate")]); - true -> - ?X("td") - end, - ?XAE("td", [{"class", "valign"}], - [?INPUTT("submit", - "remove" ++ - ejabberd_web_admin:term_to_id(R#roster.jid), - "Remove")])]) - end, SItems))])] - end, - [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - FItems ++ - [?P, - ?INPUT("text", "newjid", ""), ?C(" "), - ?INPUTT("submit", "addjid", "Add Jabber ID") - ])]. - -build_contact_jid_td(RosterJID) -> - %% Convert {U, S, R} into {jid, U, S, R, U, S, R}: - ContactJID = jlib:make_jid(RosterJID), - JIDURI = case {ContactJID#jid.luser, ContactJID#jid.lserver} of - {"", _} -> ""; - {CUser, CServer} -> - case lists:member(CServer, ?MYHOSTS) of - false -> ""; - true -> "/admin/server/" ++ CServer ++ "/user/" ++ CUser ++ "/" - end - end, - case JIDURI of - [] -> - ?XAC("td", [{"class", "valign"}], jlib:jid_to_string(RosterJID)); - URI when is_list(URI) -> - ?XAE("td", [{"class", "valign"}], [?AC(JIDURI, jlib:jid_to_string(RosterJID))]) - end. - -user_roster_parse_query(User, Server, Items, Query) -> - case lists:keysearch("addjid", 1, Query) of - {value, _} -> - case lists:keysearch("newjid", 1, Query) of - {value, {_, undefined}} -> - error; - {value, {_, SJID}} -> - case jlib:string_to_jid(SJID) of - JID when is_record(JID, jid) -> - user_roster_subscribe_jid(User, Server, JID), - ok; - error -> - error - end; - false -> - error - end; - false -> - case catch user_roster_item_parse_query( - User, Server, Items, Query) of - submitted -> - ok; - {'EXIT', _Reason} -> - error; - _ -> - nothing - end - end. - - -user_roster_subscribe_jid(User, Server, JID) -> - out_subscription(User, Server, JID, subscribe), - UJID = jlib:make_jid(User, Server, ""), - ejabberd_router:route( - UJID, JID, {xmlelement, "presence", [{"type", "subscribe"}], []}). - -user_roster_item_parse_query(User, Server, Items, Query) -> - lists:foreach( - fun(R) -> - JID = R#roster.jid, - case lists:keysearch( - "validate" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of - {value, _} -> - JID1 = jlib:make_jid(JID), - out_subscription( - User, Server, JID1, subscribed), - UJID = jlib:make_jid(User, Server, ""), - ejabberd_router:route( - UJID, JID1, {xmlelement, "presence", - [{"type", "subscribed"}], []}), - throw(submitted); - false -> - case lists:keysearch( - "remove" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of - {value, _} -> - UJID = jlib:make_jid(User, Server, ""), - process_iq( - UJID, UJID, - #iq{type = set, - sub_el = {xmlelement, "query", - [{"xmlns", ?NS_ROSTER}], - [{xmlelement, "item", - [{"jid", jlib:jid_to_string(JID)}, - {"subscription", "remove"}], - []}]}}), - throw(submitted); - false -> - ok - end - - end - end, Items), - nothing. - -us_to_list({User, Server}) -> - jlib:jid_to_string({User, Server, ""}). - -webadmin_user(Acc, _User, _Server, Lang) -> - Acc ++ [?XE("h3", [?ACT("roster/", "Roster")])]. diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index a8dd977eb..d5fa1e3b2 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -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). diff --git a/src/mod_shared_roster_odbc.erl b/src/mod_shared_roster_odbc.erl deleted file mode 100644 index 570e50947..000000000 --- a/src/mod_shared_roster_odbc.erl +++ /dev/null @@ -1,1165 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_shared_roster_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : Shared roster management -%%% Created : 5 Mar 2005 by Alexey Shchepin -%%% -%%% -%%% 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_shared_roster_odbc). --author('alexey@process-one.net'). - --behaviour(gen_mod). - --export([start/2, stop/1, - item_to_xml/1, - webadmin_menu/3, webadmin_page/3, - get_user_roster/2, - get_subscription_lists/3, - get_jid_info/4, - process_item/2, - in_subscription/6, - out_subscription/4, - user_available/1, - unset_presence/4, - register_user/2, - remove_user/2, - list_groups/1, - create_group/2, - create_group/3, - delete_group/2, - get_group_opts/2, - set_group_opts/3, - get_group_users/2, - get_group_explicit_users/2, - is_user_in_group/3, - add_user_to_group/3, - remove_user_from_group/3]). - --include("ejabberd.hrl"). --include("jlib.hrl"). --include("mod_roster.hrl"). --include("web/ejabberd_http.hrl"). --include("web/ejabberd_web_admin.hrl"). - -start(Host, _Opts) -> - ejabberd_hooks:add(webadmin_menu_host, Host, - ?MODULE, webadmin_menu, 70), - ejabberd_hooks:add(webadmin_page_host, Host, - ?MODULE, webadmin_page, 50), - ejabberd_hooks:add(roster_get, Host, - ?MODULE, get_user_roster, 70), - ejabberd_hooks:add(roster_in_subscription, Host, - ?MODULE, in_subscription, 30), - ejabberd_hooks:add(roster_out_subscription, Host, - ?MODULE, out_subscription, 30), - ejabberd_hooks:add(roster_get_subscription_lists, Host, - ?MODULE, get_subscription_lists, 70), - ejabberd_hooks:add(roster_get_jid_info, Host, - ?MODULE, get_jid_info, 70), - ejabberd_hooks:add(roster_process_item, Host, - ?MODULE, process_item, 50), - ejabberd_hooks:add(user_available_hook, Host, - ?MODULE, user_available, 50), - ejabberd_hooks:add(unset_presence_hook, Host, - ?MODULE, unset_presence, 50), - ejabberd_hooks:add(register_user, Host, - ?MODULE, register_user, 50), - ejabberd_hooks:add(anonymous_purge_hook, Host, - ?MODULE, remove_user, 50), - ejabberd_hooks:add(remove_user, Host, - ?MODULE, remove_user, 50). -%%ejabberd_hooks:add(remove_user, Host, -%% ?MODULE, remove_user, 50), - -stop(Host) -> - ejabberd_hooks:delete(webadmin_menu_host, Host, - ?MODULE, webadmin_menu, 70), - ejabberd_hooks:delete(webadmin_page_host, Host, - ?MODULE, webadmin_page, 50), - ejabberd_hooks:delete(roster_get, Host, - ?MODULE, get_user_roster, 70), - ejabberd_hooks:delete(roster_in_subscription, Host, - ?MODULE, in_subscription, 30), - ejabberd_hooks:delete(roster_out_subscription, Host, - ?MODULE, out_subscription, 30), - ejabberd_hooks:delete(roster_get_subscription_lists, Host, - ?MODULE, get_subscription_lists, 70), - ejabberd_hooks:delete(roster_get_jid_info, Host, - ?MODULE, get_jid_info, 70), - ejabberd_hooks:delete(roster_process_item, Host, - ?MODULE, process_item, 50), - ejabberd_hooks:delete(user_available_hook, Host, - ?MODULE, user_available, 50), - ejabberd_hooks:delete(unset_presence_hook, Host, - ?MODULE, unset_presence, 50), - ejabberd_hooks:delete(register_user, Host, - ?MODULE, register_user, 50), - ejabberd_hooks:delete(anonymous_purge_hook, Host, - ?MODULE, remove_user, 50), - ejabberd_hooks:delete(remove_user, Host, - ?MODULE, remove_user, 50). -%%ejabberd_hooks:delete(remove_user, Host, -%% ?MODULE, remove_user, 50), - - -get_user_roster(Items, US) -> - {U, S} = US, - DisplayedGroups = get_user_displayed_groups(US), - %% Get shared roster users in all groups and remove self: - SRUsers = - lists:foldl( - fun(Group, Acc1) -> - GroupName = get_group_name(S, Group), - lists:foldl( - fun(User, Acc2) -> - if User == US -> Acc2; - true -> dict:append(User, - GroupName, - Acc2) - end - end, Acc1, get_group_users(S, Group)) - end, dict:new(), DisplayedGroups), - - %% If partially subscribed users are also in shared roster, show them as - %% totally subscribed: - {NewItems1, SRUsersRest} = - lists:mapfoldl( - fun(Item, SRUsers1) -> - {_, _, {U1, S1, _}} = Item#roster.usj, - US1 = {U1, S1}, - case dict:find(US1, SRUsers1) of - {ok, _GroupNames} -> - {Item#roster{subscription = both, ask = none}, - dict:erase(US1, SRUsers1)}; - error -> - {Item, SRUsers1} - end - end, SRUsers, Items), - - %% Export items in roster format: - ModVcard = get_vcard_module(S), - SRItems = [#roster{usj = {U, S, {U1, S1, ""}}, - us = US, - jid = {U1, S1, ""}, - name = get_rosteritem_name(ModVcard, U1, S1), - subscription = both, - ask = none, - groups = GroupNames} || - {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)], - SRItems ++ NewItems1. - -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)]. - -get_rosteritem_name([], _, _) -> - ""; -get_rosteritem_name([ModVcard], U, S) -> - From = jlib:make_jid("", S, ?MODULE), - To = jlib:make_jid(U, S, ""), - IQ = {iq,"",get,"vcard-temp","", - {xmlelement,"vCard",[{"xmlns","vcard-temp"}],[]}}, - IQ_Vcard = ModVcard:process_sm_iq(From, To, IQ), - try get_rosteritem_name_vcard(IQ_Vcard#iq.sub_el) - catch E1:E2 -> - ?ERROR_MSG("Error ~p found when trying to get the vCard of ~s@~s " - "in ~p:~n ~p", [E1, U, S, ModVcard, E2]), - "" - end. - -get_rosteritem_name_vcard([]) -> - ""; -get_rosteritem_name_vcard([Vcard]) -> - case xml:get_path_s(Vcard, [{elem, "NICKNAME"}, cdata]) of - "" -> xml:get_path_s(Vcard, [{elem, "FN"}, cdata]); - Nickname -> Nickname - end. - -%% This function rewrites the roster entries when moving or renaming -%% them in the user contact list. -process_item(RosterItem, Host) -> - USFrom = {UserFrom, ServerFrom} = RosterItem#roster.us, - {UserTo, ServerTo, ResourceTo} = RosterItem#roster.jid, - NameTo = RosterItem#roster.name, - USTo = {UserTo, ServerTo}, - DisplayedGroups = get_user_displayed_groups(USFrom), - CommonGroups = lists:filter(fun(Group) -> - is_user_in_group(USTo, Group, Host) - end, DisplayedGroups), - case CommonGroups of - [] -> RosterItem; - %% Roster item cannot be removed: We simply reset the original groups: - _ when RosterItem#roster.subscription == remove -> - GroupNames = lists:map(fun(Group) -> - get_group_name(Host, Group) - end, CommonGroups), - RosterItem#roster{subscription = both, ask = none, - groups=[GroupNames]}; - %% Both users have at least a common shared group, - %% So each user can see the other - _ -> - %% Check if the list of groups of the new roster item - %% include at least a new one - case lists:subtract(RosterItem#roster.groups, CommonGroups) of - %% If it doesn't, then remove this user from any - %% existing roster groups. - [] -> - %% Remove pending subscription by setting it - %% unsubscribed. - Mod = get_roster_mod(ServerFrom), - - %% Remove pending out subscription - Mod:out_subscription(UserTo, ServerTo, - jlib:make_jid(UserFrom, ServerFrom, ""), - unsubscribe), - - %% Remove pending in subscription - Mod:in_subscription(aaaa, UserFrom, ServerFrom, - jlib:make_jid(UserTo, ServerTo, ""), - unsubscribe, ""), - - %% But we're still subscribed, so respond as such. - RosterItem#roster{subscription = both, ask = none}; - %% If so, it means the user wants to add that contact - %% to his personal roster - PersonalGroups -> - %% Store roster items in From and To rosters - set_new_rosteritems(UserFrom, ServerFrom, - UserTo, ServerTo, ResourceTo, NameTo, - PersonalGroups) - end - end. - -build_roster_record(User1, Server1, User2, Server2, Name2, Groups) -> - USR2 = {User2, Server2, ""}, - #roster{usj = {User1, Server1, USR2}, - us = {User1, Server1}, - jid = USR2, - name = Name2, - subscription = both, - ask = none, - groups = 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), - JIDTo = jlib:make_jid(UserTo, ServerTo, ""), - - JIDFrom = jlib:make_jid(UserFrom, ServerFrom, ""), - RITo = build_roster_record(UserTo, ServerTo, - UserFrom, ServerFrom, UserFrom,[]), - set_item(UserTo, ServerTo, "", RITo), - - %% From requests - Mod:out_subscription(UserFrom, ServerFrom, JIDTo, subscribe), - Mod:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribe, ""), - - %% To accepts - Mod:out_subscription(UserTo, ServerTo, JIDFrom, subscribed), - Mod:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribed, ""), - - %% To requests - Mod:out_subscription(UserTo, ServerTo, JIDFrom, subscribe), - Mod:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribe, ""), - - %% From accepts - Mod:out_subscription(UserFrom, ServerFrom, JIDTo, subscribed), - Mod:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribed, ""), - - RIFrom. - -set_item(User, Server, Resource, Item) -> - ResIQ = #iq{type = set, xmlns = ?NS_ROSTER, - id = "push" ++ randoms:get_string(), - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_ROSTER}], - [mod_roster:item_to_xml(Item)]}]}, - ejabberd_router:route( - jlib:make_jid(User, Server, Resource), - jlib:make_jid("", Server, ""), - jlib:iq_to_xml(ResIQ)). - - -get_subscription_lists({F, T}, User, Server) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - US = {LUser, LServer}, - DisplayedGroups = get_user_displayed_groups(US), - SRUsers = - lists:usort( - lists:flatmap( - fun(Group) -> - get_group_users(LServer, Group) - end, DisplayedGroups)), - SRJIDs = [{U1, S1, ""} || {U1, S1} <- SRUsers], - {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}. - -get_jid_info({Subscription, Groups}, User, Server, JID) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - US = {LUser, LServer}, - {U1, S1, _} = jlib:jid_tolower(JID), - US1 = {U1, S1}, - DisplayedGroups = get_user_displayed_groups(US), - SRUsers = - lists:foldl( - fun(Group, Acc1) -> - lists:foldl( - fun(User1, Acc2) -> - dict:append( - User1, get_group_name(LServer, Group), Acc2) - end, Acc1, get_group_users(LServer, Group)) - end, dict:new(), DisplayedGroups), - case dict:find(US1, SRUsers) of - {ok, GroupNames} -> - NewGroups = if - Groups == [] -> GroupNames; - true -> Groups - end, - {both, NewGroups}; - error -> - {Subscription, Groups} - end. - -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), - - %% Remove pending in subscription - Mod:in_subscription(aaaa, UserFrom, ServerFrom, JIDTo, unsubscribe, ""), - - process_subscription(out, UserFrom, ServerFrom, JIDTo, unsubscribed, false); -out_subscription(User, Server, JID, Type) -> - process_subscription(out, User, Server, JID, Type, false). - -process_subscription(Direction, User, Server, JID, _Type, Acc) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - US = {LUser, LServer}, - {U1, S1, _} = jlib:jid_tolower(jlib:jid_remove_resource(JID)), - US1 = {U1, S1}, - DisplayedGroups = get_user_displayed_groups(US), - SRUsers = - lists:usort( - lists:flatmap( - fun(Group) -> - get_group_users(LServer, Group) - end, DisplayedGroups)), - case lists:member(US1, SRUsers) of - true -> - case Direction of - in -> - {stop, false}; - out -> - stop - end; - false -> - Acc - end. - -list_groups(Host) -> - case ejabberd_odbc:sql_query( - Host, ["select name from sr_group;"]) of - {selected, ["name"], Rs} -> - [G || {G} <- Rs]; - _ -> - [] - end. - -groups_with_opts(Host) -> - 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) -> - 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) -> - 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) -> - 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) -> - 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), - 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 ++ get_special_users_groups(Host). - -is_group_enabled(Host1, Group1) -> - {Host, Group} = split_grouphost(Host1, Group1), - SGroup = ejabberd_odbc:escape(Group), - case catch ejabberd_odbc:sql_query( - Host, ["select opts from sr_group " - "where name='", SGroup, "';"]) of - {selected, ["opts"], [{SOpts}]} -> - Opts = ejabberd_odbc:decode_term(SOpts), - not lists:member(disabled, Opts); - _ -> - false - end. - -%% @spec (Host::string(), Group::string(), Opt::atom(), Default) -> OptValue | Default -get_group_opt(Host, Group, Opt, Default) -> - SGroup = ejabberd_odbc:escape(Group), - case catch ejabberd_odbc:sql_query( - Host, ["select opts from sr_group " - "where name='", SGroup, "';"]) of - {selected, ["opts"], [{SOpts}]} -> - Opts = ejabberd_odbc:decode_term(SOpts), - case lists:keysearch(Opt, 1, Opts) of - {value, {_, Val}} -> - Val; - false -> - Default - end; - _ -> - Default - end. - -get_online_users(Host) -> - lists:usort([{U, S} || {U, S, _} <- ejabberd_sm:get_vh_session_list(Host)]). - -get_group_users(Host1, Group1) -> - {Host, Group} = split_grouphost(Host1, Group1), - case get_group_opt(Host, Group, all_users, false) of - true -> - ejabberd_auth:get_vh_registered_users(Host); - false -> - [] - end ++ - case get_group_opt(Host, Group, online_users, false) of - true -> - get_online_users(Host); - false -> - [] - end ++ - get_group_explicit_users(Host, Group). - -get_group_users(Host, Group, GroupOpts) -> - case proplists:get_value(all_users, GroupOpts, false) of - true -> - ejabberd_auth:get_vh_registered_users(Host); - false -> - [] - end ++ - case proplists:get_value(online_users, GroupOpts, false) of - true -> - get_online_users(Host); - false -> - [] - end ++ - get_group_explicit_users(Host, Group). - -%% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}] -get_group_explicit_users(Host, Group) -> - 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) -> - {Host, Group} = split_grouphost(Host1, Group1), - get_group_opt(Host, Group, name, Group). - -%% Get list of names of groups that have @all@/@online@/etc in the memberlist -get_special_users_groups(Host) -> - lists:filter( - fun(Group) -> - get_group_opt(Host, Group, all_users, false) - orelse get_group_opt(Host, Group, online_users, false) - end, - list_groups(Host)). - -%% Get list of names of groups that have @online@ in the memberlist -get_special_users_groups_online(Host) -> - lists:filter( - fun(Group) -> - get_group_opt(Host, Group, online_users, false) - end, - list_groups(Host)). - -%% Given two lists of groupnames and their options, -%% return the list of displayed groups to the second list -displayed_groups(GroupsOpts, SelectedGroupsOpts) -> - DisplayedGroups = - lists:usort( - lists:flatmap( - fun({_Group, Opts}) -> - [G || G <- proplists:get_value(displayed_groups, Opts, []), - not lists:member(disabled, Opts)] - end, SelectedGroupsOpts)), - [G || G <- DisplayedGroups, - not lists:member(disabled, proplists:get_value(G, GroupsOpts, []))]. - -%% Given a list of group names with options, -%% for those that have @all@ in memberlist, -%% get the list of groups displayed -get_special_displayed_groups(GroupsOpts) -> - Groups = lists:filter( - fun({_Group, Opts}) -> - proplists:get_value(all_users, Opts, false) - end, GroupsOpts), - displayed_groups(GroupsOpts, Groups). - -%% Given a username and server, and a list of group names with options, -%% 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) -> - SJID = make_jid_s(LUser, LServer), - Groups = 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, - displayed_groups(GroupsOpts, Groups). - -%% @doc Get the list of groups that are displayed to this user -get_user_displayed_groups(US) -> - Host = element(2, US), - DisplayedGroups1 = - lists:usort( - lists:flatmap( - fun(Group) -> - case is_group_enabled(Host, Group) of - true -> - get_group_opt(Host, Group, displayed_groups, []); - false -> - [] - end - end, get_user_groups(US))), - [Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)]. - -is_user_in_group(US, Group, Host) -> - 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, - case ejabberd_regexp:run(LUser, "^@.+@$") of - match -> - GroupOpts = ?MODULE:get_group_opts(Host, Group), - MoreGroupOpts = - case LUser of - "@all@" -> [{all_users, true}]; - "@online@" -> [{online_users, true}]; - _ -> [] - end, - ?MODULE:set_group_opts( - Host, Group, - GroupOpts ++ MoreGroupOpts); - nomatch -> - %% Push this new user to members of groups where this group is displayed - 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), - 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) - end. - -push_displayed_to_user(LUser, LServer, Group, Host, Subscription) -> - GroupsOpts = groups_with_opts(LServer), - GroupOpts = proplists:get_value(Group, GroupsOpts, []), - DisplayedGroups = proplists:get_value(displayed_groups, GroupOpts, []), - [push_members_to_user(LUser, LServer, DGroup, Host, Subscription) || DGroup <- DisplayedGroups]. - -remove_user_from_group(Host, US, Group) -> - {LUser, LServer} = US, - case ejabberd_regexp:run(LUser, "^@.+@$") of - match -> - GroupOpts = ?MODULE:get_group_opts(Host, Group), - NewGroupOpts = - case LUser of - "@all@" -> - lists:filter(fun(X) -> X/={all_users,true} end, GroupOpts); - "@online@" -> - lists:filter(fun(X) -> X/={online_users,true} end, GroupOpts) - end, - ?MODULE:set_group_opts(Host, Group, NewGroupOpts); - nomatch -> - 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, - Result = ejabberd_odbc:sql_transaction(Host, F), - %% 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 - push_displayed_to_user(LUser, LServer, Group, Host, remove), - Result - end. - - -push_members_to_user(LUser, LServer, Group, Host, Subscription) -> - GroupsOpts = groups_with_opts(LServer), - GroupOpts = proplists:get_value(Group, GroupsOpts, []), - GroupName = proplists:get_value(name, GroupOpts, Group), - Members = get_group_users(Host, Group), - lists:foreach( - fun({U, S}) -> - push_roster_item(LUser, LServer, U, S, GroupName, Subscription) - end, Members). - -register_user(User, Server) -> - %% Get list of groups where this user is member - Groups = get_user_groups({User, Server}), - %% Push this user to members of groups where is displayed a group which this user is member - [push_user_to_displayed(User, Server, Group, Server, both) || Group <- Groups]. - -remove_user(User, Server) -> - push_user_to_members(User, Server, remove). - -push_user_to_members(User, Server, Subscription) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - GroupsOpts = groups_with_opts(LServer), - SpecialGroups = get_special_displayed_groups(GroupsOpts), - UserGroups = get_user_displayed_groups(LUser, LServer, GroupsOpts), - lists:foreach( - fun(Group) -> - remove_user_from_group(LServer, {LUser, LServer}, Group), - GroupOpts = proplists:get_value(Group, GroupsOpts, []), - GroupName = proplists:get_value(name, GroupOpts, Group), - lists:foreach( - fun({U, S}) -> - push_roster_item(U, S, LUser, LServer, GroupName, Subscription) - end, get_group_users(LServer, Group, GroupOpts)) - end, lists:usort(SpecialGroups++UserGroups)). - -push_user_to_displayed(LUser, LServer, Group, Host, Subscription) -> - GroupsOpts = groups_with_opts(Host), - GroupOpts = proplists:get_value(Group, GroupsOpts, []), - GroupName = proplists:get_value(name, GroupOpts, Group), - DisplayedToGroupsOpts = displayed_to_groups(Group, Host), - [push_user_to_group(LUser, LServer, GroupD, Host, GroupName, Subscription) || {GroupD, _Opts} <- DisplayedToGroupsOpts]. - -push_user_to_group(LUser, LServer, Group, Host, GroupName, Subscription) -> - lists:foreach( - fun({U, S}) when (U == LUser) and (S == LServer) -> ok; - ({U, S}) -> - push_roster_item(U, S, LUser, LServer, GroupName, Subscription) - end, get_group_users(Host, Group)). - -%% Get list of groups to which this group is displayed -displayed_to_groups(GroupName, LServer) -> - GroupsOpts = groups_with_opts(LServer), - lists:filter( - fun({_Group, Opts}) -> - lists:member(GroupName, proplists:get_value(displayed_groups, Opts, [])) - end, GroupsOpts). - -push_item(User, Server, From, Item) -> - %% It was - %% ejabberd_sm:route(jlib:make_jid("", "", ""), - %% jlib:make_jid(User, Server, "") - %% why? - ejabberd_sm:route(From, jlib:make_jid(User, Server, ""), - {xmlelement, "broadcast", [], - [{item, - Item#roster.jid, - Item#roster.subscription}]}), - Stanza = jlib:iq_to_xml( - #iq{type = set, xmlns = ?NS_ROSTER, - id = "push" ++ randoms:get_string(), - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_ROSTER}], - [item_to_xml(Item)]}]}), - lists:foreach( - fun(Resource) -> - JID = jlib:make_jid(User, Server, Resource), - ejabberd_router:route(JID, JID, Stanza) - end, ejabberd_sm:get_user_resources(User, Server)). - -push_roster_item(User, Server, ContactU, ContactS, GroupName, Subscription) -> - Item = #roster{usj = {User, Server, {ContactU, ContactS, ""}}, - us = {User, Server}, - jid = {ContactU, ContactS, ""}, - name = "", - subscription = Subscription, - ask = none, - groups = [GroupName]}, - push_item(User, Server, jlib:make_jid("", Server, ""), Item). - -item_to_xml(Item) -> - Attrs1 = [{"jid", jlib:jid_to_string(Item#roster.jid)}], - Attrs2 = case Item#roster.name of - "" -> - Attrs1; - Name -> - [{"name", Name} | Attrs1] - end, - Attrs3 = case Item#roster.subscription of - none -> - [{"subscription", "none"} | Attrs2]; - from -> - [{"subscription", "from"} | Attrs2]; - to -> - [{"subscription", "to"} | Attrs2]; - both -> - [{"subscription", "both"} | Attrs2]; - remove -> - [{"subscription", "remove"} | Attrs2] - end, - Attrs4 = case ask_to_pending(Item#roster.ask) of - out -> - [{"ask", "subscribe"} | Attrs3]; - both -> - [{"ask", "subscribe"} | Attrs3]; - _ -> - Attrs3 - end, - SubEls1 = lists:map(fun(G) -> - {xmlelement, "group", [], [{xmlcdata, G}]} - end, Item#roster.groups), - SubEls = SubEls1 ++ Item#roster.xs, - {xmlelement, "item", Attrs4, SubEls}. - -ask_to_pending(subscribe) -> out; -ask_to_pending(unsubscribe) -> none; -ask_to_pending(Ask) -> Ask. - -user_available(New) -> - LUser = New#jid.luser, - LServer = New#jid.lserver, - Resources = ejabberd_sm:get_user_resources(LUser, LServer), - ?DEBUG("user_available for ~p @ ~p (~p resources)", - [LUser, LServer, length(Resources)]), - case length(Resources) of - %% first session for this user - 1 -> - %% This is a simplification - we ignore he 'display' - %% property - @online@ is always reflective. - OnlineGroups = get_special_users_groups_online(LServer), - lists:foreach( - fun(OG) -> - ?DEBUG("user_available: pushing ~p @ ~p grp ~p", - [LUser, LServer, OG ]), - push_user_to_displayed(LUser, LServer, OG, LServer, both) - end, OnlineGroups); - _ -> - ok - end. - -unset_presence(LUser, LServer, Resource, Status) -> - Resources = ejabberd_sm:get_user_resources(LUser, LServer), - ?DEBUG("unset_presence for ~p @ ~p / ~p -> ~p (~p resources)", - [LUser, LServer, Resource, Status, length(Resources)]), - %% if user has no resources left... - case length(Resources) of - 0 -> - %% This is a simplification - we ignore he 'display' - %% property - @online@ is always reflective. - OnlineGroups = get_special_users_groups_online(LServer), - %% for each of these groups... - lists:foreach( - fun(OG) -> - %% Push removal of the old user to members of groups - %% where the group that this uwas members was displayed - push_user_to_displayed(LUser, LServer, OG, LServer, remove), - %% Push removal of members of groups that where - %% displayed to the group which thiuser has left - push_displayed_to_user(LUser, LServer, OG, LServer,remove) - end, OnlineGroups); - _ -> - ok - end. - -%%--------------------- -%% Web Admin -%%--------------------- - -webadmin_menu(Acc, _Host, Lang) -> - [{"shared-roster", ?T("Shared Roster Groups")} | Acc]. - -webadmin_page(_, Host, - #request{us = _US, - path = ["shared-roster"], - q = Query, - lang = Lang} = _Request) -> - Res = list_shared_roster_groups(Host, Query, Lang), - {stop, Res}; - -webadmin_page(_, Host, - #request{us = _US, - path = ["shared-roster", Group], - q = Query, - lang = Lang} = _Request) -> - Res = shared_roster_group(Host, Group, Query, Lang), - {stop, Res}; - -webadmin_page(Acc, _, _) -> Acc. - -list_shared_roster_groups(Host, Query, Lang) -> - Res = list_sr_groups_parse_query(Host, Query), - SRGroups = ?MODULE:list_groups(Host), - FGroups = - ?XAE("table", [], - [?XE("tbody", - lists:map( - fun(Group) -> - ?XE("tr", - [?XE("td", [?INPUT("checkbox", "selected", - Group)]), - ?XE("td", [?AC(Group ++ "/", Group)]) - ] - ) - end, lists:sort(SRGroups)) ++ - [?XE("tr", - [?X("td"), - ?XE("td", [?INPUT("text", "namenew", "")]), - ?XE("td", [?INPUTT("submit", "addnew", "Add New")]) - ] - )] - )]), - ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [FGroups, - ?BR, - ?INPUTT("submit", "delete", "Delete Selected") - ]) - ]. - -list_sr_groups_parse_query(Host, Query) -> - case lists:keysearch("addnew", 1, Query) of - {value, _} -> - list_sr_groups_parse_addnew(Host, Query); - _ -> - case lists:keysearch("delete", 1, Query) of - {value, _} -> - list_sr_groups_parse_delete(Host, Query); - _ -> - nothing - end - end. - -list_sr_groups_parse_addnew(Host, Query) -> - case lists:keysearch("namenew", 1, Query) of - {value, {_, Group}} when Group /= "" -> - ?MODULE:create_group(Host, Group), - ok; - _ -> - error - end. - -list_sr_groups_parse_delete(Host, Query) -> - SRGroups = ?MODULE:list_groups(Host), - lists:foreach( - fun(Group) -> - case lists:member({"selected", Group}, Query) of - true -> - ?MODULE:delete_group(Host, Group); - _ -> - ok - end - end, SRGroups), - ok. - - -shared_roster_group(Host, Group, Query, Lang) -> - Res = shared_roster_group_parse_query(Host, Group, Query), - GroupOpts = ?MODULE:get_group_opts(Host, Group), - Name = get_opt(GroupOpts, name, ""), - Description = get_opt(GroupOpts, description, ""), - AllUsers = get_opt(GroupOpts, all_users, false), - OnlineUsers = get_opt(GroupOpts, online_users, false), - %%Disabled = false, - DisplayedGroups = get_opt(GroupOpts, displayed_groups, []), - Members = ?MODULE:get_group_explicit_users(Host, Group), - FMembers = - if - AllUsers -> - "@all@\n"; - true -> - [] - end ++ - if - OnlineUsers -> - "@online@\n"; - true -> - [] - end ++ - [[us_to_list(Member), $\n] || Member <- Members], - FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups], - DescNL = length(ejabberd_regexp:split(Description, "\n")), - FGroup = - ?XAE("table", [{"class", "withtextareas"}], - [?XE("tbody", - [?XE("tr", - [?XCT("td", "Name:"), - ?XE("td", [?INPUT("text", "name", Name)]) - ] - ), - ?XE("tr", - [?XCT("td", "Description:"), - ?XE("td", [ - ?TEXTAREA("description", integer_to_list(lists:max([3, DescNL])), "20", Description) - ] - ) - ] - ), - ?XE("tr", - [?XCT("td", "Members:"), - ?XE("td", [ - ?TEXTAREA("members", integer_to_list(lists:max([3, length(FMembers)])), "20", FMembers) - ] - ) - ] - ), - ?XE("tr", - [?XCT("td", "Displayed Groups:"), - ?XE("td", [ - ?TEXTAREA("dispgroups", integer_to_list(lists:max([3, length(FDisplayedGroups)])), "20", FDisplayedGroups) - ] - ) - ] - )] - )]), - ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++ - [?XC("h2", ?T("Group ") ++ Group)] ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [FGroup, - ?BR, - ?INPUTT("submit", "submit", "Submit") - ]) - ]. - -shared_roster_group_parse_query(Host, Group, Query) -> - case lists:keysearch("submit", 1, Query) of - {value, _} -> - {value, {_, Name}} = lists:keysearch("name", 1, Query), - {value, {_, Description}} = lists:keysearch("description", 1, Query), - {value, {_, SMembers}} = lists:keysearch("members", 1, Query), - {value, {_, SDispGroups}} = lists:keysearch("dispgroups", 1, Query), - NameOpt = - if - Name == "" -> []; - true -> [{name, Name}] - end, - DescriptionOpt = - if - Description == "" -> []; - true -> [{description, Description}] - end, - DispGroups = string:tokens(SDispGroups, "\r\n"), - DispGroupsOpt = - if - DispGroups == [] -> []; - true -> [{displayed_groups, DispGroups}] - end, - - OldMembers = ?MODULE:get_group_explicit_users( - Host, Group), - SJIDs = string:tokens(SMembers, ", \r\n"), - NewMembers = - lists:foldl( - fun(_SJID, error) -> error; - (SJID, USs) -> - case SJID of - "@all@" -> - USs; - "@online@" -> - USs; - _ -> - case jlib:string_to_jid(SJID) of - JID when is_record(JID, jid) -> - [{JID#jid.luser, JID#jid.lserver} | USs]; - error -> - error - end - end - end, [], SJIDs), - AllUsersOpt = - case lists:member("@all@", SJIDs) of - true -> [{all_users, true}]; - false -> [] - end, - OnlineUsersOpt = - case lists:member("@online@", SJIDs) of - true -> [{online_users, true}]; - false -> [] - end, - - ?MODULE:set_group_opts( - Host, Group, - NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt ++ OnlineUsersOpt), - - if - NewMembers == error -> error; - true -> - AddedMembers = NewMembers -- OldMembers, - RemovedMembers = OldMembers -- NewMembers, - lists:foreach( - fun(US) -> - ?MODULE:remove_user_from_group( - Host, US, Group) - end, RemovedMembers), - lists:foreach( - fun(US) -> - ?MODULE:add_user_to_group( - Host, US, Group) - end, AddedMembers), - ok - end; - _ -> - 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}} -> - Val; - false -> - Default - end. - -us_to_list({User, Server}) -> - jlib:jid_to_string({User, Server, ""}). - -split_grouphost(Host, Group) -> - case string:tokens(Group, "@") of - [GroupName, HostName] -> - {HostName, GroupName}; - [_] -> - {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). diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index b796375f5..6381ec55c 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -1,7 +1,7 @@ %%%---------------------------------------------------------------------- %%% File : mod_vcard.erl %%% Author : Alexey Shchepin -%%% Purpose : Vcard management in Mnesia +%%% Purpose : Vcard management %%% Created : 2 Jan 2003 by Alexey Shchepin %%% %%% @@ -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(), diff --git a/src/mod_vcard_odbc.erl b/src/mod_vcard_odbc.erl deleted file mode 100644 index f472dcb78..000000000 --- a/src/mod_vcard_odbc.erl +++ /dev/null @@ -1,659 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_vcard.erl -%%% Author : Alexey Shchepin -%%% Purpose : vCard support via ODBC -%%% Created : 2 Jan 2003 by Alexey Shchepin -%%% -%%% -%%% 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, "';"]]). diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index 38503c4c3..3ee632aaa 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -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 diff --git a/src/mod_vcard_xupdate_odbc.erl b/src/mod_vcard_xupdate_odbc.erl deleted file mode 100644 index b6a5adfb4..000000000 --- a/src/mod_vcard_xupdate_odbc.erl +++ /dev/null @@ -1,128 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_vcard_xupdate_odbc.erl -%%% Author : Igor Goryachev -%%% Purpose : Add avatar hash in presence on behalf of client (XEP-0153) -%%% Created : 9 Mar 2007 by Igor Goryachev -%%%---------------------------------------------------------------------- - --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}. diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl index 447926e26..59e9b0313 100644 --- a/src/web/ejabberd_web_admin.erl +++ b/src/web/ejabberd_web_admin.erl @@ -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} ->