%%%---------------------------------------------------------------------- %%% File : mod_muc.erl %%% Author : Alexey Shchepin %%% Purpose : MUC support (XEP-0045) %%% Created : 19 Mar 2003 by Alexey Shchepin %%% %%% %%% ejabberd, Copyright (C) 2002-2011 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 %%% %%%---------------------------------------------------------------------- %%% Database schema (version / storage / table) %%% %%% 2.1.x / mnesia / muc_online_room %%% name_host = {Name::string(), Host::string()} %%% pid = pid() %%% 2.1.x / mnesia / muc_registered %%% us_host = {{Username::string, Hostname::string()}, Host::string()}} %%% nick = string() %%% 2.1.x / mnesia / muc_room %%% name_host = {Name::string(), Host::string()} %%% opts = [{Option::atom(), Value::any()} %%% %%% 3.0.0-alpha / mnesia / muc_online_room %%% name_host = {Name::binary(), Host::binary()} %%% pid = pid() %%% 3.0.0-alpha / mnesia / muc_room_opt %%% name_host = {Name::binary(), Host::binary()} %%% opt = atom() %%% val = atom() | string() %%% 3.0.0-alpha / mnesia / muc_room_affiliation %%% name_host = {Name::binary(), Host::binary()} %%% jid = jid() %%% affiliation = owner | %%% reason = string() %%% 3.0.0-alpha / mnesia / muc_registered %%% user_host = {User::jid(), MucHost::binary()}} %%% nick = binary() %%% %%% 3.0.0-alpha / odbc / muc_online_room %%% name = text %%% host = text %%% pid = text %%% 3.0.0-alpha / odbc / muc_room_opt %%% name = text %%% host = text %%% opt = text %%% 3.0.0-alpha / mnesia / muc_room_affiliation %%% name = text %%% host = text %%% jid = text %%% affiliation = text %%% reason = text %%% 3.0.0-alpha / mnesia / muc_registered %%% user = text %%% host = text %%% nick = text %%% -module(mod_muc). -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/3, forget_room/2, create_room/5, process_iq_disco_items/4, broadcast_service_message/2, register_room/3, migrate/1, get_vh_rooms/1, remove_host/1, can_use_nick/3]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include_lib("exmpp/include/exmpp.hrl"). -include("ejabberd.hrl"). -include("jlib.hrl"). -record(muc_room_opt, {name_host, opt, val}). -record(muc_room_affiliation, {name_host, jid, affiliation, reason}). -record(muc_online_room, {name_host, pid}). -record(muc_registered, {user_host, nick}). -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) when is_list(Host) -> start(list_to_binary(Host), Opts); start(HostB, Opts) -> start_supervisor(HostB), Proc = gen_mod:get_module_proc(HostB, ?PROCNAME), ChildSpec = {Proc, {?MODULE, start_link, [HostB, 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) when is_binary(Host), is_binary(Room) -> catch gen_mod:get_module_proc_existing(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_existing(Host, ?PROCNAME), RoomHost = gen_mod:get_module_opt_host(Host, ?MODULE, "conference.@HOST@"), Node = ejabberd_cluster:get_node({Name, RoomHost}), gen_server:call({Proc, Node}, {create, Name, From, Nick, Opts}). store_room(Host, Name, Opts) when is_binary(Host), is_binary(Name) -> F = fun() -> gen_storage:delete(Host, {muc_room_opt, {Name, Host}}), gen_storage:delete(Host, {muc_room_affiliation, {Name, Host}}), lists:foreach( fun({affiliations, Affiliations}) -> lists:foreach( fun({JID, Affiliation1}) -> {Affiliation, Reason} = case Affiliation1 of {_, _} = A -> A; A when is_atom(A) -> {A, ""} end, {Username, Server, Resource} = JID, gen_storage:write( Host, #muc_room_affiliation{name_host = {Name, Host}, jid = exmpp_jid:make(Username, Server, Resource), affiliation = Affiliation, reason = Reason}) end, Affiliations); ({Opt, Val}) -> ValS = if is_integer(Val) -> integer_to_list(Val); is_binary(Val) -> binary_to_list(Val); is_list(Val) -> Val; is_atom(Val) -> Val end, gen_storage:write(Host, #muc_room_opt{name_host = {Name, Host}, opt = Opt, val = ValS}) end, Opts) end, {atomic, ok} = gen_storage:transaction(Host, muc_room_opt, F). restore_room_internal(Host, Name) -> RoomOpts = gen_storage:read(Host, {muc_room_opt, {Name, Host}}), Opts = lists:map( fun(#muc_room_opt{opt = Opt, val = Val}) -> Val2 = if is_list(Val) andalso (Opt =:= allow_change_subj orelse Opt =:= allow_query_users orelse Opt =:= allow_private_messages orelse Opt =:= allow_visitor_status orelse Opt =:= allow_visitor_nickchange orelse Opt =:= public orelse Opt =:= public_list orelse Opt =:= persistent orelse Opt =:= moderated orelse Opt =:= members_by_default orelse Opt =:= members_only orelse Opt =:= allow_user_invites orelse Opt =:= password_protected orelse Opt =:= anonymous orelse Opt =:= logging) -> list_to_atom(Val); is_list(Val) andalso Opt =:= max_users -> list_to_integer(Val); true -> Val end, {Opt, Val2} end, RoomOpts), RoomAffiliations = gen_storage:read(Host, {muc_room_affiliation, {Name, Host}}), Affiliations = lists:map(fun(#muc_room_affiliation{jid = JID, affiliation = Affiliation, reason = Reason}) -> A = case Reason of "" -> Affiliation; _ -> {Affiliation, Reason} end, {jlib:short_prepd_jid(JID), A} end, RoomAffiliations), [{affiliations, Affiliations} | Opts]. forget_room(Host, Name) when is_binary(Host), is_binary(Name) -> F = fun() -> gen_storage:delete(Host, {muc_room_opt, {Name, Host}}), gen_storage:delete(Host, {muc_room_affiliation, {Name, Host}}) end, gen_storage:transaction(Host, muc_room_opt, F). process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) -> Rsm = jlib:rsm_decode(IQ), Res = exmpp_iq:result(IQ, #xmlel{ns = ?NS_DISCO_ITEMS, name = 'query', children = iq_disco_items(Host, From, Lang, Rsm)}), ejabberd_router:route(To, From, exmpp_iq:iq_to_xmlel(Res)). can_use_nick(_Host, _JID, <<>>) -> false; can_use_nick(Host, JID, Nick) when is_binary(Host), is_binary(Nick) -> case catch gen_storage:dirty_select( Host, muc_registered, [{'=', user_host, {'_', Host}}, {'=', nick, Nick}]) of {'EXIT', _Reason} -> true; [] -> true; [#muc_registered{user_host = {U, _Host}}] -> U == exmpp_jid:bare(JID) end. migrate(After) -> Rs = mnesia:dirty_select( muc_online_room, [{#muc_online_room{name_host = '$1', pid = '$2', _ = '_'}, [], ['$$']}]), lists:foreach( fun([NameHost, Pid]) -> case ejabberd_cluster:get_node_new(NameHost) of Node when Node /= node() -> mod_muc_room:migrate(Pid, Node, After); _ -> ok end end, Rs). remove_host(MyHostB) when is_binary(MyHostB) -> Host = gen_mod:get_module_opt_host(binary_to_list(MyHostB), ?MODULE, "conference.@HOST@"), ?INFO_MSG("Removing rooms of MUC service ~p", [Host]), lists:foreach( fun(#muc_online_room{name_host = {NameB, HostB}, pid = Pid}) -> gen_fsm:send_all_state_event(Pid, destroy), forget_room(HostB, NameB) end, get_vh_rooms(list_to_binary(Host))). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- init([Host, Opts]) -> MyHostStr = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"), MyHost = l2b(MyHostStr), Backend = gen_mod:get_opt(backend, Opts, mnesia), gen_storage:create_table(Backend, MyHost, muc_room_opt, [{disc_copies, [node()]}, {odbc_host, Host}, {attributes, record_info(fields, muc_room_opt)}, {type, bag}, {types, [{name_host, {binary, binary}}, {opt, atom}]}]), gen_storage:create_table(Backend, MyHost, muc_room_affiliation, [{disc_copies, [node()]}, {odbc_host, Host}, {attributes, record_info(fields, muc_room_affiliation)}, {type, bag}, {types, [{name_host, {binary, binary}}, {affiliation, atom}, {jid, jid}]}]), gen_storage:create_table(Backend, MyHost, muc_registered, [{disc_copies, [node()]}, {odbc_host, Host}, {attributes, record_info(fields, muc_registered)}, {types, [{user_host, {jid, text}}, {nick, binary}]}]), gen_storage:create_table(Backend, MyHost, muc_online_room, [{ram_copies, [node()]}, {odbc_host, Host}, {attributes, record_info(fields, muc_online_room)}, {types, [{name_host, {binary, binary}}, {pid, pid}]}]), %% If ejabberd stops abruptly, ODBC table keeps obsolete data. Let's clean: gen_storage:dirty_delete_where(MyHost, muc_online_room, [{'=', name_host, {'_', MyHost}}]), gen_storage:add_table_copy(MyHost, muc_online_room, node(), ram_copies), catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]), gen_storage:add_table_index(MyHost, muc_registered, nick), update_tables(MyHost, Backend), 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(MyHostStr), ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100), ejabberd_hooks:add(remove_host, Host, ?MODULE, remove_host, 50), 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), 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{server_host = ServerHost, access = Access, default_room_opts = DefRoomOpts, history_size = HistorySize, room_shaper = RoomShaper} = State) -> Host = exmpp_jid:domain(To), US = {exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To)}, case ejabberd_cluster:get_node(US) of Node when Node == node() -> case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts) of {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); _ -> ok end; Node -> Proc = gen_mod:get_module_proc_existing(ServerHost, ?PROCNAME), {Proc, Node} ! {route, From, To, Packet} end, {noreply, State}; handle_info({room_destroyed, {_, Host} = RoomHost, Pid}, State) -> F = fun() -> gen_storage:delete_object(Host, #muc_online_room{name_host = RoomHost, pid = Pid}) end, gen_storage:sync_dirty(Host, muc_online_room, F), {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_hooks:delete(node_hash_update, ?MODULE, migrate, 100), ejabberd_hooks:delete(remove_host, State#state.server_host, ?MODULE, remove_host, 50), ejabberd_router:unregister_route(binary_to_list(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); _ -> Lang = exmpp_stanza:get_lang(Packet), ErrText = "Access denied by service policy", Err = exmpp_iq:error(Packet,exmpp_stanza:error(Packet#xmlel.ns, 'forbidden', {Lang,ErrText})), ejabberd_router:route_error(To, From, Err, Packet) end. l2b(global) -> global; l2b(String) when is_list(String) -> list_to_binary(String); l2b(Other) -> Other. do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts) -> {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access, Room = exmpp_jid:prep_node(To), Nick = exmpp_jid:prep_resource(To), #xmlel{name = Name} = Packet, case Room of 'undefined' -> case Nick of 'undefined' -> case Name of 'iq' -> case exmpp_iq:xmlel_to_iq(Packet) of #iq{type = get, ns = ?NS_DISCO_INFO = XMLNS, payload = _SubEl, lang = Lang} = IQ -> ServerHostB = l2b(ServerHost), Info = ejabberd_hooks:run_fold( disco_info, ServerHostB, [], [ServerHost, ?MODULE, <<>>, ""]), ResPayload = #xmlel{ns = XMLNS, name = 'query', children = iq_disco_info(Lang)++Info}, Res = exmpp_iq:result(IQ, ResPayload), ejabberd_router:route(To, From, exmpp_iq:iq_to_xmlel(Res)); #iq{type = get, ns = ?NS_DISCO_ITEMS} = IQ -> spawn(?MODULE, process_iq_disco_items, [Host, From, To, IQ]); #iq{type = get, ns = ?NS_INBAND_REGISTER = XMLNS, lang = Lang} = IQ -> ResPayload = #xmlel{ns = XMLNS, name = 'query', children = iq_get_register_info(Host, From, Lang)}, Res = exmpp_iq:result(IQ,ResPayload), ejabberd_router:route(To, From, exmpp_iq:iq_to_xmlel(Res)); #iq{type = set, ns = ?NS_INBAND_REGISTER , lang = Lang, payload = SubEl} = IQ -> case process_iq_register_set(Host, From, SubEl, Lang) of ok -> Res = exmpp_iq:result(IQ), ejabberd_router:route( To, From, exmpp_iq:iq_to_xmlel(Res)); {error, Error} -> Err = exmpp_iq:error(IQ,Error), ejabberd_router:route( To, From, exmpp_iq:iq_to_xmlel(Err)) end; #iq{type = get, ns = ?NS_VCARD, lang = Lang} = IQ -> Res = exmpp_iq:result(IQ,iq_get_vcard(Lang)), ejabberd_router:route(To, From, exmpp_iq:iq_to_xmlel(Res)); #iq{type = get, ns = ?NS_MUC_UNIQUE} = IQ -> Res = exmpp_iq:result(IQ, iq_get_unique_el(From)), ejabberd_router:route(To, From, exmpp_iq:iq_to_xmlel(Res)); #iq{} = IQ -> Err = exmpp_iq:error(IQ,'feature-not-implemented'), ejabberd_router:route(To, From, Err) end; 'message' -> case exmpp_xml:get_attribute_as_list(Packet,<<"type">>, "chat") of "error" -> ok; _ -> case acl:match_rule(ServerHost, AccessAdmin, From) of allow -> Msg = exmpp_xml:get_path(Packet, [{element,'body'},cdata]), broadcast_service_message(Host, Msg); _ -> Lang = exmpp_stanza:get_lang(Packet), ErrText = "Only service administrators " "are allowed to send service messages", Err = exmpp_iq:error(Packet,exmpp_stanza:error(Packet#xmlel.ns, 'forbidden', {Lang,ErrText})), ejabberd_router:route( To, From, Err) end end; 'presence' -> ok end; _ -> case exmpp_stanza:get_type(Packet) of <<"error">> -> ok; <<"result">> -> ok; _ -> Err = exmpp_iq:error(Packet,'item-not-found'), ejabberd_router:route(To, From, Err) end end; _ -> case gen_storage:dirty_read(Host, muc_online_room, {Room, Host}) of [] -> Type = exmpp_stanza:get_type(Packet), case {Name, Type} of {'presence', 'undefined'} -> 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 = exmpp_stanza:get_lang(Packet), ErrText = "Room creation is denied by service policy", Err = exmpp_stanza:reply_with_error(Packet,exmpp_stanza:error(Packet#xmlel.ns, 'forbidden', {Lang,ErrText})), ejabberd_router:route(To, From, Err) end; _ -> Lang = exmpp_stanza:get_lang(Packet), ErrText = "Room does not exist", Err = exmpp_stanza:reply_with_error(Packet, exmpp_stanza:error(Packet#xmlel.ns, '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 -> (size(RoomID) =< gen_mod:get_module_opt(ServerHost, mod_muc, max_room_id, infinite)); _ -> false end. load_permanent_rooms({global, Prefix}, global, Access, HistorySize, RoomShaper) -> timer:sleep(2000), %% Wait for ejabberd_hosts to start lists:foreach( fun(ServerHost) -> Host = list_to_binary(Prefix++"."++ServerHost), load_permanent_rooms(Host, global, Access, HistorySize, RoomShaper) end, ?MYHOSTS); load_permanent_rooms(Host, ServerHost1, Access, HistorySize, RoomShaper) -> F = fun() -> Rs = gen_storage:select(Host, muc_room_opt, [{'=', opt, persistent}, {'=', name_host, {'_', Host}}]), Names = lists:foldl( fun(#muc_room_opt{name_host = {Room, _}}, Names) -> sets:add_element(Room, Names) end, sets:new(), Rs), lists:foreach( fun(Room) -> case ejabberd_cluster:get_node({Room, Host}) of Node when Node == node() -> case gen_storage:read(Host, {muc_online_room, {Room, Host}}) of [] -> Opts = restore_room_internal(Host, Room), {ok, Pid} = mod_muc_room:start( Host, ServerHost1, Access, Room, HistorySize, RoomShaper, Opts), register_room_internal(Host, Room, Pid); _ -> ok end; _ -> ok end end, sets:to_list(Names)) end, {atomic, ok} = gen_storage:transaction(Host, muc_room_opt, F). start_new_room(Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, Nick, DefRoomOpts) -> case gen_storage:dirty_read(Host, {muc_room_opt, {Room, Host}}) of [] -> ?DEBUG("MUC: open new room '~s'~n", [Room]), mod_muc_room:start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, Nick, DefRoomOpts); Opts when is_list(Opts) -> ?DEBUG("MUC: restore room '~s'~n", [Room]), mod_muc_room:start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) end. register_room_internal(Host, Room, Pid) -> gen_storage:write(Host, #muc_online_room{name_host = {Room, Host}, pid = Pid}). register_room(Host, Room, Pid) -> F = fun() -> register_room_internal(Host, Room, Pid) end, gen_storage:sync_dirty(Host, muc_online_room, F). iq_disco_info(Lang) -> [#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', attrs = [?XMLATTR(<<"category">>, <<"conference">>), ?XMLATTR(<<"type">>, <<"text">>), ?XMLATTR(<<"name">>, translate:translate(Lang, "Rooms"))]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR(<<"var">>, ?NS_DISCO_INFO_s)]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR(<<"var">>, ?NS_DISCO_ITEMS_s)]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR(<<"var">>, ?NS_MUC_s)]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR(<<"var">>, ?NS_MUC_UNIQUE_s)]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR(<<"var">>, ?NS_INBAND_REGISTER_s)]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR(<<"var">>, ?NS_RSM_s)]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR(<<"var">>, ?NS_VCARD_s)]}]. iq_disco_items(Host, From, Lang, none) when is_binary(Host) -> 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", exmpp_jid:to_list(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, #xmlel{name = 'item', attrs = [?XMLATTR(<<"jid">>, exmpp_jid:to_binary(exmpp_jid:make(Name, Host))), ?XMLATTR(<<"name">>, Desc)]}}; _ -> false end end, Rooms) ++ RsmOut. get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})-> AllRooms = get_vh_rooms_all_nodes(Host), Count = erlang:length(AllRooms), L = get_vh_rooms_direction(Direction, I, Index, AllRooms), 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. get_vh_rooms_direction(_Direction, _I, Index, AllRooms) when Index =/= undefined -> AllRooms; get_vh_rooms_direction(aft, I, _Index, AllRooms) -> {_Before, After} = lists:splitwith( fun(#muc_online_room{name_host = {Na, _}}) -> Na < I end, AllRooms), case After of [] -> []; [#muc_online_room{name_host = {I, _Host}} | AfterTail] -> AfterTail; _ -> After end; get_vh_rooms_direction(before, I, _Index, AllRooms) when I =/= []-> {Before, _} = lists:splitwith( fun(#muc_online_room{name_host = {Na, _}}) -> Na < I end, AllRooms), Before; get_vh_rooms_direction(_Direction, _I, _Index, AllRooms) -> AllRooms. %% @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), #xmlel{name = "field", attrs = [?XMLATTR(<<"type">>, Type), ?XMLATTR(<<"label">>, translate:translate(Lang, Label)), ?XMLATTR(<<"var">>, Var)], children = [#xmlel{name = 'value', children = [#xmlcdata{cdata = Val}]}]}). iq_get_register_info(Host, From, Lang) -> FromBare = exmpp_jid:bare(From), {Nick, Registered} = case catch gen_storage:dirty_read(Host, muc_registered, {FromBare, Host}) of {'EXIT', _Reason} -> {"", []}; [] -> {"", []}; [#muc_registered{nick = N}] -> {N, [#xmlel{name = 'registered'}]} end, Registered ++ [#xmlel{name = 'instructions' , children = [#xmlcdata{cdata = list_to_binary(translate:translate(Lang, "You need an x:data capable client to register nickname"))}]}, #xmlel{ns = ?NS_DATA_FORMS, name = 'x', children = [ #xmlel{ns = ?NS_DATA_FORMS, name = 'title', children = [#xmlcdata{cdata = list_to_binary(translate:translate(Lang, "Nickname Registration at ") ++ Host)}]}, #xmlel{ns = ?NS_DATA_FORMS, name = 'instructions', children = [#xmlcdata{cdata = translate:translate(Lang, "Enter nickname you want to register")}]}, ?XFIELD(<<"text-single">>, "Nickname", <<"nick">>, Nick)]}]. iq_set_register_info(Host, From, Nick, Lang) when is_binary(Host), is_binary(Nick) -> FromBare = exmpp_jid:bare(From), F = fun() -> case Nick of <<>> -> gen_storage:delete(Host, {muc_registered, {FromBare, Host}}), ok; _ -> Allow = case gen_storage:select( Host, muc_registered, [{'=', user_host, {'_', Host}}, {'=', nick, Nick}]) of [] -> true; [#muc_registered{user_host = {U, _Host}}] -> U == FromBare end, if Allow -> gen_storage:write( Host, #muc_registered{user_host = {FromBare, Host}, nick = Nick}), ok; true -> false end end end, case gen_storage:transaction(Host, muc_registered, F) of {atomic, ok} -> ok; {atomic, false} -> ErrText = "That nickname is registered by another person", %%TODO: Always in the jabber:client namespace? {error,exmpp_stanza:error(?NS_JABBER_CLIENT, 'conflict', {Lang, ErrText})}; _ -> {error, 'internal-server-error'} end. process_iq_register_set(Host, From, SubEl, Lang) -> % {xmlelement, _Name, _Attrs, Els} = SubEl, case exmpp_xml:get_element(SubEl,'remove') of undefined -> case exmpp_xml:get_child_elements(SubEl) of [#xmlel{ns= NS, name = 'x'} = XEl] -> case {NS, exmpp_stanza:get_type(XEl)} of {?NS_DATA_FORMS, <<"cancel">>} -> ok; {?NS_DATA_FORMS, <<"submit">>} -> XData = jlib:parse_xdata_submit(XEl), case XData of invalid -> {error, 'bad-request'}; _ -> case lists:keysearch("nick", 1, XData) of {value, {_, [Nick]}} -> iq_set_register_info(Host, From, list_to_binary(Nick), Lang); _ -> ErrText = "You must fill in field \"Nickname\" in the form", Err = exmpp_stanza:error(SubEl#xmlel.ns, 'not-acceptable', {Lang, translate:translate(Lang,ErrText)}), {error, Err} end end; _ -> {error, 'bad-request'} end; _ -> {error, 'bad-request'} end; _ -> iq_set_register_info(Host, From, <<>>, Lang) end. iq_get_vcard(Lang) -> #xmlel{ns = ?NS_VCARD, name = 'vCard', children = [#xmlel{ns = ?NS_VCARD, name = 'FN', children = [#xmlcdata{cdata = <<"ejabberd/mod_muc">>}]}, #xmlel{ns = ?NS_VCARD, name = 'URL', children = [#xmlcdata{cdata = list_to_binary(?EJABBERD_URI)}]}, #xmlel{ns = ?NS_VCARD, name = 'DESC', children = [#xmlcdata{cdata = list_to_binary(translate:translate(Lang, "ejabberd MUC module") ++ "\nCopyright (c) 2003-2011 ProcessOne")}]}]}. iq_get_unique_el(From) -> #xmlel{ns = ?NS_MUC_UNIQUE, name = 'unique', children = [#xmlcdata{cdata = list_to_binary(iq_get_unique_name(From))}]}. %% @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_name(From) -> sha:sha(term_to_binary([From, now(), randoms:get_string()])). 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_all_nodes(Host)). get_vh_rooms_all_nodes(Host) -> Rooms = lists:foldl( fun(Node, Acc) when Node == node() -> get_vh_rooms(Host) ++ Acc; (Node, Acc) -> case catch rpc:call(Node, ?MODULE, get_vh_rooms, [Host], 5000) of Res when is_list(Res) -> Res ++ Acc; _ -> Acc end end, [], ejabberd_cluster:get_nodes()), lists:ukeysort(#muc_online_room.name_host, Rooms). get_vh_rooms(Host) when is_binary(Host) -> gen_storage:dirty_select(Host, muc_online_room, [{'=', name_host, {'_', Host}}]). %%%%% %%%%% Database migration from ejabberd 2.1.x %%%%% update_tables(global, Storage) -> [update_tables(HostB, Storage) || HostB <- ejabberd_hosts:get_hosts(ejabberd)]; update_tables(HostB, mnesia) -> gen_storage_migration:migrate_mnesia( HostB, muc_registered, [{muc_registered, [us_host, nick], fun({muc_registered, {{UserS, ServerS}, HostS}, NickS}) -> #muc_registered{user_host = {exmpp_jid:make(UserS, ServerS), list_to_binary(HostS)}, nick = list_to_binary(NickS)} end}]), gen_storage_migration:migrate_mnesia( HostB, muc_room_opt, [{muc_room, [name_host, opts], fun({muc_room, {NameString, HostString}, Options}) -> NameHost = {list_to_binary(NameString), list_to_binary(HostString)}, lists:foreach( fun({affiliations, Affiliations}) -> lists:foreach( fun({{Username, Hostname, Resource}, AffiliationIni}) -> UserJid = exmpp_jid:make(Username, Hostname, Resource), {Affiliation, Reason} = case AffiliationIni of Aff when is_atom(Aff) -> {Aff, ""}; {Aff, Reas} -> {Aff, Reas} end, gen_storage:write(HostB, #muc_room_affiliation{name_host = NameHost, jid = UserJid, affiliation = Affiliation, reason = Reason}) end, Affiliations); ({Opt, Val}) -> gen_storage:write(HostB, #muc_room_opt{name_host = NameHost, opt = Opt, val = Val}) end, Options) end}]); update_tables(_HostB, odbc) -> ok.