From 6b3875814c47ba3ea4d76c329f9d63b0d96510e7 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sat, 31 Mar 2012 21:41:54 +1000 Subject: [PATCH] ODBC support for mod_muc --- src/ejd2odbc.erl | 44 +- src/mod_muc/mod_muc.erl | 27 +- src/mod_muc/mod_muc_odbc.erl | 907 +++++++++++++++++++++++++++++++++++ src/mod_muc/mod_muc_room.erl | 98 ++-- src/mod_muc/mod_muc_room.hrl | 1 + src/odbc/mysql.sql | 19 + src/odbc/pg.sql | 19 + 7 files changed, 1060 insertions(+), 55 deletions(-) create mode 100644 src/mod_muc/mod_muc_odbc.erl diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl index 8daea9c6c..5f7b9d4da 100644 --- a/src/ejd2odbc.erl +++ b/src/ejd2odbc.erl @@ -34,7 +34,9 @@ export_last/2, export_vcard/2, export_vcard_search/2, - export_private_storage/2]). + export_private_storage/2, + export_muc_room/2, + export_muc_registered/2]). -include("ejabberd.hrl"). -include("jlib.hrl"). @@ -58,6 +60,8 @@ orgunit, lorgunit }). -record(private_storage, {usns, xml}). +-record(muc_room, {name_host, opts}). +-record(muc_registered, {us_host, nick}). -define(MAX_RECORDS_PER_TRANSACTION, 1000). @@ -265,6 +269,44 @@ export_private_storage(Server, Output) -> [] end). +export_muc_room(Server, Output) -> + export_common( + Server, muc_room, Output, + fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) -> + case lists:suffix(Host, RoomHost) of + true -> + SName = ejabberd_odbc:escape(Name), + SRoomHost = ejabberd_odbc:escape(RoomHost), + SOpts = mod_muc_odbc:encode_opts(Opts), + ["delete from muc_room where name='", SName, + "' and host='", SRoomHost, "';", + "insert into muc_room(name, host, opts) values (", + "'", SName, "', '", SRoomHost, "', '", SOpts, "');"]; + false -> + [] + end + end). + +export_muc_registered(Server, Output) -> + export_common( + Server, muc_registered, Output, + fun(Host, #muc_registered{us_host = {{U, S}, RoomHost}, nick = Nick}) -> + case lists:suffix(Host, RoomHost) of + true -> + SJID = ejabberd_odbc:escape( + jlib:jid_to_string( + jlib:make_jid(U, S, ""))), + SNick = ejabberd_odbc:escape(Nick), + SRoomHost = ejabberd_odbc:escape(RoomHost), + ["delete from muc_registered where jid='", SJID, + "' and host='", SRoomHost, "';" + "insert into muc_registered(jid, host, nick) values (" + "'", SJID, "', '", SRoomHost, "', '", SNick, "');"]; + false -> + [] + end + end). + %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl index b5f3c1c7d..5ae7e82ec 100644 --- a/src/mod_muc/mod_muc.erl +++ b/src/mod_muc/mod_muc.erl @@ -35,13 +35,13 @@ start/2, stop/1, room_destroyed/4, - store_room/3, - restore_room/2, - forget_room/2, + store_room/4, + restore_room/3, + forget_room/3, create_room/5, process_iq_disco_items/4, broadcast_service_message/2, - can_use_nick/3]). + can_use_nick/4]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -111,14 +111,14 @@ 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(Host, Name, Opts) -> +store_room(_ServerHost, Host, Name, Opts) -> F = fun() -> mnesia:write(#muc_room{name_host = {Name, Host}, opts = Opts}) end, mnesia:transaction(F). -restore_room(Host, Name) -> +restore_room(_ServerHost, Host, Name) -> case catch mnesia:dirty_read(muc_room, {Name, Host}) of [#muc_room{opts = Opts}] -> Opts; @@ -126,7 +126,7 @@ restore_room(Host, Name) -> error end. -forget_room(Host, Name) -> +forget_room(_ServerHost, Host, Name) -> F = fun() -> mnesia:delete({muc_room, {Name, Host}}) end, @@ -142,9 +142,9 @@ process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) -> From, jlib:iq_to_xml(Res)). -can_use_nick(_Host, _JID, "") -> +can_use_nick(_ServerHost, _Host, _JID, "") -> false; -can_use_nick(Host, JID, Nick) -> +can_use_nick(_ServerHost, Host, JID, Nick) -> {LUser, LServer, _} = jlib:jid_tolower(JID), LUS = {LUser, LServer}, case catch mnesia:dirty_select( @@ -238,7 +238,7 @@ handle_call({create, Room, From, Nick, Opts}, Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, - Nick, NewOpts), + Nick, NewOpts, ?MODULE), register_room(Host, Room, Pid), {reply, ok, State}. @@ -541,7 +541,8 @@ load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) -> Room, HistorySize, RoomShaper, - R#muc_room.opts), + R#muc_room.opts, + ?MODULE), register_room(Host, Room, Pid); _ -> ok @@ -558,12 +559,12 @@ start_new_room(Host, ServerHost, Access, Room, mod_muc_room:start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, - Nick, DefRoomOpts); + Nick, DefRoomOpts, ?MODULE); [#muc_room{opts = Opts}|_] -> ?DEBUG("MUC: restore room '~s'~n", [Room]), mod_muc_room:start(Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Opts) + RoomShaper, Opts, ?MODULE) end. register_room(Host, Room, Pid) -> diff --git a/src/mod_muc/mod_muc_odbc.erl b/src/mod_muc/mod_muc_odbc.erl new file mode 100644 index 000000000..de0a6ae74 --- /dev/null +++ b/src/mod_muc/mod_muc_odbc.erl @@ -0,0 +1,907 @@ +%%%---------------------------------------------------------------------- +%%% 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, + encode_opts/1, + decode_opts/1, + 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:stop was called, and each room is being terminated +%% In this case, the mod_muc 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), + F = fun() -> + update_t( + "muc_room", + ["name", "host", "opts"], + [SName, SHost, encode_opts(Opts)], + ["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}]} -> + decode_opts(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, mod_muc, + 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, + decode_opts(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, decode_opts(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 -> + 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). + +encode_opts(Opts) -> + ejabberd_odbc:escape(erl_prettypr:format(erl_syntax:abstract(Opts))). + +decode_opts(Str) -> + {ok, Tokens, _} = erl_scan:string(Str ++ "."), + {ok, Opts} = erl_parse:parse_term(Tokens), + Opts. + +%% Almost a copy of string:join/2. +%% We use this version because string:join/2 is relatively +%% new function (introduced in R12B-0). +join([], _Sep) -> + []; +join([H|T], Sep) -> + [H, [[Sep, X] || X <- T]]. + +%% Safe atomic update. +update_t(Table, Fields, Vals, Where) -> + UPairs = lists:zipwith(fun(A, B) -> A ++ "='" ++ B ++ "'" end, + Fields, Vals), + case ejabberd_odbc:sql_query_t( + ["update ", Table, " set ", + join(UPairs, ", "), + " where ", Where, ";"]) of + {updated, 1} -> + ok; + _ -> + ejabberd_odbc:sql_query_t( + ["insert into ", Table, "(", join(Fields, ", "), + ") values ('", join(Vals, "', '"), "');"]) + end. diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index 940b807b1..13390b3b7 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/9, - start_link/7, - start/9, - start/7, +-export([start_link/10, + start_link/8, + start/10, + start/8, 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], + RoomShaper, Creator, Nick, DefRoomOpts, Mod], ?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])). + Creator, Nick, DefRoomOpts, Mod])). -endif. %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Creator, Nick, DefRoomOpts) -> + Creator, Nick, DefRoomOpts, Mod) -> ?SUPERVISOR_START. -start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) -> +start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, Mod) -> Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup), supervisor:start_child( Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Opts]). + Opts, Mod]). start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Creator, Nick, DefRoomOpts) -> + Creator, Nick, DefRoomOpts, Mod) -> gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Creator, Nick, DefRoomOpts], + RoomShaper, Creator, Nick, DefRoomOpts, Mod], ?FSMOPTS). -start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) -> +start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, Mod) -> gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Opts], + RoomShaper, Opts, Mod], ?FSMOPTS). %%%---------------------------------------------------------------------- @@ -110,12 +110,14 @@ start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- -init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, DefRoomOpts]) -> +init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, + DefRoomOpts, Mod]) -> 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), @@ -128,11 +130,12 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, D 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]) -> +init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, Mod]) -> 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), @@ -161,7 +164,8 @@ normal_state({route, From, "", MinMessageInterval = trunc(gen_mod:get_module_opt( StateData#state.server_host, - mod_muc, min_message_interval, 0) * 1000000), + StateData#state.mod, + min_message_interval, 0) * 1000000), Size = element_size(Packet), {MessageShaper, MessageShaperInterval} = shaper:update(Activity#activity.message_shaper, Size), @@ -282,7 +286,8 @@ normal_state({route, From, "", StateData), case (NSD#state.config)#config.persistent of true -> - mod_muc:store_room( + (NSD#state.mod):store_room( + NSD#state.server_host, NSD#state.host, NSD#state.room, make_opts(NSD)); @@ -480,7 +485,7 @@ normal_state({route, From, Nick, MinPresenceInterval = trunc(gen_mod:get_module_opt( StateData#state.server_host, - mod_muc, min_presence_interval, 0) * 1000000), + StateData#state.mod, min_presence_interval, 0) * 1000000), if (Now >= Activity#activity.presence_time + MinPresenceInterval) and (Activity#activity.presence == undefined) -> @@ -850,8 +855,9 @@ terminate(Reason, _StateName, StateData) -> tab_remove_online_user(LJID, StateData) end, [], StateData#state.users), add_to_log(room_existence, stopped, StateData), - mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(), - StateData#state.server_host), + (StateData#state.mod):room_destroyed( + StateData#state.host, StateData#state.room, self(), + StateData#state.server_host), ok. %%%---------------------------------------------------------------------- @@ -886,7 +892,8 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet, FromNick}, case (NSD#state.config)#config.persistent of true -> - mod_muc:store_room( + (NSD#state.mod):store_room( + NSD#state.server_host, NSD#state.host, NSD#state.room, make_opts(NSD)); @@ -1024,8 +1031,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), - mod_muc:can_use_nick( - StateData#state.host, From, Nick), + (StateData#state.mod):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}} -> @@ -1420,11 +1428,11 @@ get_max_users(StateData) -> get_service_max_users(StateData) -> gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users, ?MAX_USERS_DEFAULT). + StateData#state.mod, max_users, ?MAX_USERS_DEFAULT). get_max_users_admin_threshold(StateData) -> gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users_admin_threshold, 5). + StateData#state.mod, max_users_admin_threshold, 5). get_user_activity(JID, StateData) -> case treap:lookup(jlib:jid_tolower(JID), @@ -1434,11 +1442,11 @@ get_user_activity(JID, StateData) -> MessageShaper = shaper:new(gen_mod:get_module_opt( StateData#state.server_host, - mod_muc, user_message_shaper, none)), + StateData#state.mod, user_message_shaper, none)), PresenceShaper = shaper:new(gen_mod:get_module_opt( StateData#state.server_host, - mod_muc, user_presence_shaper, none)), + StateData#state.mod, user_presence_shaper, none)), #activity{message_shaper = MessageShaper, presence_shaper = PresenceShaper} end. @@ -1447,11 +1455,11 @@ store_user_activity(JID, UserActivity, StateData) -> MinMessageInterval = gen_mod:get_module_opt( StateData#state.server_host, - mod_muc, min_message_interval, 0), + StateData#state.mod, min_message_interval, 0), MinPresenceInterval = gen_mod:get_module_opt( StateData#state.server_host, - mod_muc, min_presence_interval, 0), + StateData#state.mod, min_presence_interval, 0), Key = jlib:jid_tolower(JID), Now = now_to_usec(now()), Activity1 = clean_treap(StateData#state.activity, {1, -Now}), @@ -1732,7 +1740,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, - mod_muc, max_user_conferences, 10), + StateData#state.mod, max_user_conferences, 10), Collision = nick_collision(From, Nick, StateData), case {(ServiceAffiliation == owner orelse ((Affiliation == admin orelse Affiliation == owner) andalso @@ -1740,7 +1748,8 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> NUsers < MaxUsers) andalso NConferences < MaxConferences, Collision, - mod_muc:can_use_nick(StateData#state.host, From, Nick), + (StateData#state.mod):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 @@ -2580,8 +2589,9 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> end, StateData, lists:flatten(Res)), case (NSD#state.config)#config.persistent of true -> - mod_muc:store_room(NSD#state.host, NSD#state.room, - make_opts(NSD)); + (NSD#state.mod):store_room(NSD#state.server_host, + NSD#state.host, NSD#state.room, + make_opts(NSD)); _ -> ok end, @@ -3081,8 +3091,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, - mod_muc, max_room_name, - infinite); + StateData#state.mod, + max_room_name, infinite); _ -> true end, @@ -3091,8 +3101,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, - mod_muc, max_room_desc, - infinite); + StateData#state.mod, + max_room_desc, infinite); _ -> true end, @@ -3163,7 +3173,9 @@ is_password_settings_correct(XEl, StateData) -> || JID <- JIDList]}). get_default_room_maxusers(RoomState) -> - DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, mod_muc, default_room_options, []), + DefRoomOpts = gen_mod:get_module_opt( + RoomState#state.server_host, + RoomState#state.mod, default_room_options, []), RoomState2 = set_opts(DefRoomOpts, RoomState), (RoomState2#state.config)#config.max_users. @@ -3475,12 +3487,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_muc:store_room(NSD#state.host, NSD#state.room, make_opts(NSD)); + Mod:store_room(NSD#state.server_host, NSD#state.host, + NSD#state.room, make_opts(NSD)); {true, false} -> - mod_muc:forget_room(NSD#state.host, NSD#state.room); + Mod:forget_room(NSD#state.server_host, NSD#state.host, NSD#state.room); {false, false} -> ok end, @@ -3611,7 +3625,9 @@ destroy_room(DEl, StateData) -> end, ?DICT:to_list(StateData#state.users)), case (StateData#state.config)#config.persistent of true -> - mod_muc:forget_room(StateData#state.host, StateData#state.room); + (StateData#state.mod):forget_room( + StateData#state.server_host, + StateData#state.host, StateData#state.room); false -> ok end, diff --git a/src/mod_muc/mod_muc_room.hrl b/src/mod_muc/mod_muc_room.hrl index b66d4e7db..a7a6ca0b2 100644 --- a/src/mod_muc/mod_muc_room.hrl +++ b/src/mod_muc/mod_muc_room.hrl @@ -67,6 +67,7 @@ -record(state, {room, host, server_host, + mod, access, jid, config = #config{}, diff --git a/src/odbc/mysql.sql b/src/odbc/mysql.sql index e66152c3b..d67ca6c88 100644 --- a/src/odbc/mysql.sql +++ b/src/odbc/mysql.sql @@ -219,3 +219,22 @@ CREATE TABLE pubsub_subscription_opt ( opt_value text ); CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt(subid(32), opt_name(32)); + +CREATE TABLE muc_room ( + name text NOT NULL, + host text NOT NULL, + opts text NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +) CHARACTER SET utf8; + +CREATE UNIQUE INDEX i_muc_room_name_host USING BTREE ON muc_room(name(75), host(75)); + +CREATE TABLE muc_registered ( + jid text NOT NULL, + host text NOT NULL, + nick text NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +) CHARACTER SET utf8; + +CREATE INDEX i_muc_registered_nick USING BTREE ON muc_registered(nick(75)); +CREATE UNIQUE INDEX i_muc_registered_jid_host USING BTREE ON muc_registered(jid(75), host(75)); diff --git a/src/odbc/pg.sql b/src/odbc/pg.sql index 06fcc47c1..aae472d6b 100644 --- a/src/odbc/pg.sql +++ b/src/odbc/pg.sql @@ -219,3 +219,22 @@ CREATE TABLE pubsub_subscription_opt ( opt_value text ); CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt USING btree (subid, opt_name); + +CREATE TABLE muc_room ( + name text NOT NULL, + host text NOT NULL, + opts text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); + +CREATE UNIQUE INDEX i_muc_room_name_host ON muc_room USING btree (name, host); + +CREATE TABLE muc_registered ( + jid text NOT NULL, + host text NOT NULL, + nick text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); + +CREATE INDEX i_muc_registered_nick ON muc_registered USING btree (nick); +CREATE UNIQUE INDEX i_muc_registered_jid_host ON muc_registered USING btree (jid, host);