From 437f68a9f320a3cafd063ae03313ce83db96668e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 27 Apr 2012 19:52:05 +1000 Subject: [PATCH 1/5] 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} -> From 8b13226d00502579960fc196e5b829897f571e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Fri, 27 Apr 2012 13:19:49 +0200 Subject: [PATCH 2/5] Do not trigger item-not-found errors in mod_http_bind (part of EJABS-1827) This changes what happens to request received with out of order rid, previously response to such request was send immediately, and client was free to submit another request, which triggered item-not-found if it was delivered before request with missing rid. This change make us wait for sending response to out of order request until request with missing rid arrives. It also queues all outgoing data before that condition is meet. --- src/web/ejabberd_http_bind.erl | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl index b28587fce..0108d4aa1 100644 --- a/src/web/ejabberd_http_bind.erl +++ b/src/web/ejabberd_http_bind.erl @@ -66,6 +66,7 @@ last_receiver, last_poll, http_receiver, + out_of_order_receiver = false, wait_timer, ctime = 0, timer, @@ -372,6 +373,11 @@ handle_sync_event({send_xml, Packet}, _From, StateName, Output = [Packet | StateData#state.output], Reply = ok, {reply, Reply, StateName, StateData#state{output = Output}}; +handle_sync_event({send_xml, Packet}, _From, StateName, + #state{out_of_order_receiver = true} = StateData) -> + Output = [Packet | StateData#state.output], + Reply = ok, + {reply, Reply, StateName, StateData#state{output = Output}}; handle_sync_event({send_xml, Packet}, _From, StateName, StateData) -> Output = [Packet | StateData#state.output], cancel_timer(StateData#state.timer), @@ -440,9 +446,9 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> TNow = tnow(), if (Hold > 0) and - (StateData#state.output == []) and + ((StateData#state.output == []) or (StateData#state.rid < Rid)) and ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and - (StateData#state.rid == Rid) and + (StateData#state.rid =< Rid) and (StateData#state.input /= cancel) and (StateData#state.pause == 0) -> WaitTimer = erlang:start_timer(Wait * 1000, self(), []), @@ -450,6 +456,7 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> cancel_timer(StateData#state.timer), {next_state, StateName, StateData#state{ http_receiver = From, + out_of_order_receiver = StateData#state.rid < Rid, wait_timer = WaitTimer, timer = undefined}}; (StateData#state.input == cancel) -> @@ -587,10 +594,11 @@ handle_http_put_event(#http_put{rid = Rid, attrs = Attrs, UnprocessedReqList = [Request | Requests], cancel_timer(StateData#state.timer), Timer = set_inactivity_timer(0, StateData#state.max_inactivity), - {reply, buffered, StateName, - StateData#state{unprocessed_req_list = UnprocessedReqList, - req_list = ReqList, - timer = Timer}}; + {reply, ok, StateName, + StateData#state{unprocessed_req_list = UnprocessedReqList, + req_list = ReqList, + timer = Timer}, hibernate}; + _ -> %% Request is in sequence: process_http_put(Request, StateName, StateData, RidAllow) @@ -794,8 +802,6 @@ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) -> % {"type", "error"}], []})}; handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP); - {buffered, _Sess} -> - {200, ?HEADER, ""}; {ok, Sess} -> prepare_response(Sess, Rid, [], StreamStart) end. From 6719d9669b6dc420652c02f7af44e9605ede1c07 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 29 Apr 2012 19:34:57 +1000 Subject: [PATCH 3/5] Fix get_subscription_lists/4 --- src/mod_roster.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 5e6b4fd52..65d5c0965 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -598,7 +598,7 @@ get_subscription_lists(_, LUser, LServer, mnesia) -> Items when is_list(Items) -> Items; _ -> - {[], []} + [] end; get_subscription_lists(_, LUser, LServer, odbc) -> Username = ejabberd_odbc:escape(LUser), From e8921d79bab5bc725faf5f5d5933e80d4f13c6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Wed, 2 May 2012 20:59:09 +0200 Subject: [PATCH 4/5] Receiving missing http-bind request shouldn't close waiting out-ouf-order request --- src/web/ejabberd_http_bind.erl | 36 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl index 0108d4aa1..0024cb17d 100644 --- a/src/web/ejabberd_http_bind.erl +++ b/src/web/ejabberd_http_bind.erl @@ -441,8 +441,6 @@ handle_sync_event(#http_put{payload_size = PayloadSize} = Request, %% HTTP GET: send packets to the client handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> %% setup timer - send_receiver_reply(StateData#state.http_receiver, {ok, empty}), - cancel_timer(StateData#state.wait_timer), TNow = tnow(), if (Hold > 0) and @@ -450,7 +448,9 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and (StateData#state.rid =< Rid) and (StateData#state.input /= cancel) and - (StateData#state.pause == 0) -> + (StateData#state.pause == 0) -> + send_receiver_reply(StateData#state.http_receiver, {ok, empty}), + cancel_timer(StateData#state.wait_timer), WaitTimer = erlang:start_timer(Wait * 1000, self(), []), %% MR: Not sure we should cancel the state timer here. cancel_timer(StateData#state.timer), @@ -460,6 +460,8 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> wait_timer = WaitTimer, timer = undefined}}; (StateData#state.input == cancel) -> + send_receiver_reply(StateData#state.http_receiver, {ok, empty}), + cancel_timer(StateData#state.wait_timer), cancel_timer(StateData#state.timer), Timer = set_inactivity_timer(StateData#state.pause, StateData#state.max_inactivity), @@ -471,8 +473,6 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> timer = Timer}}; true -> cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), Reply = {ok, StateData#state.output}, %% save request ReqList = [#hbr{rid = Rid, @@ -482,12 +482,26 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> [El || El <- StateData#state.req_list, El#hbr.rid /= Rid ] ], - {reply, Reply, StateName, StateData#state{ - output = [], - http_receiver = undefined, - wait_timer = undefined, - timer = Timer, - req_list = ReqList}} + if + (StateData#state.http_receiver /= undefined) and + StateData#state.out_of_order_receiver -> + {reply, Reply, StateName, StateData#state{ + output = [], + timer = undefined, + req_list = ReqList, + out_of_order_receiver = false}}; + true -> + send_receiver_reply(StateData#state.http_receiver, {ok, empty}), + cancel_timer(StateData#state.wait_timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + {reply, Reply, StateName, + StateData#state{output = [], + http_receiver = undefined, + wait_timer = undefined, + timer = Timer, + req_list = ReqList}} + end end; handle_sync_event(peername, _From, StateName, StateData) -> From 6c94d040faf968ada5312b3dead7d9a79f38e399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Wed, 2 May 2012 22:38:19 +0200 Subject: [PATCH 5/5] Repeated http-bind request should abort only requests with same rid Before this change, when request with repeat rid was received any waiting request was aborted (but only after next request was delivered). With this change, only request with identical rid are aborted and this is done immediately --- src/web/ejabberd_http_bind.erl | 37 +++++++++++++--------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl index 0024cb17d..02b8d27b0 100644 --- a/src/web/ejabberd_http_bind.erl +++ b/src/web/ejabberd_http_bind.erl @@ -447,7 +447,6 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> ((StateData#state.output == []) or (StateData#state.rid < Rid)) and ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and (StateData#state.rid =< Rid) and - (StateData#state.input /= cancel) and (StateData#state.pause == 0) -> send_receiver_reply(StateData#state.http_receiver, {ok, empty}), cancel_timer(StateData#state.wait_timer), @@ -459,18 +458,6 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> out_of_order_receiver = StateData#state.rid < Rid, wait_timer = WaitTimer, timer = undefined}}; - (StateData#state.input == cancel) -> - send_receiver_reply(StateData#state.http_receiver, {ok, empty}), - cancel_timer(StateData#state.wait_timer), - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - Reply = {ok, cancel}, - {reply, Reply, StateName, StateData#state{ - input = queue:new(), - http_receiver = undefined, - wait_timer = undefined, - timer = Timer}}; true -> cancel_timer(StateData#state.timer), Reply = {ok, StateData#state.output}, @@ -668,16 +655,20 @@ process_http_put(#http_put{rid = Rid, attrs = Attrs, payload = Payload, {reply, Reply, StateName, StateData}; repeat -> ?DEBUG("REPEATING ~p", [Rid]), - Reply = case [El#hbr.out || - El <- StateData#state.req_list, - El#hbr.rid == Rid] of - [] -> - {error, not_exists}; - [Out | _XS] -> - {repeat, lists:reverse(Out)} - end, - {reply, Reply, StateName, StateData#state{input = cancel, - last_poll = LastPoll}}; + case [El#hbr.out || + El <- StateData#state.req_list, + El#hbr.rid == Rid] of + [] -> + {error, not_exists}; + [Out | _XS] -> + if (Rid == StateData#state.rid) and + (StateData#state.http_receiver /= undefined) -> + {reply, ok, StateName, StateData}; + true -> + Reply = {repeat, lists:reverse(Out)}, + {reply, Reply, StateName, StateData#state{last_poll = LastPoll}} + end + end; {true, Pause} -> SaveKey = if NewKey == "" ->