%%%---------------------------------------------------------------------- %%% File : mod_muc.erl %%% Author : Alexey Shchepin %%% Purpose : MUC support (XEP-0045) %%% Created : 19 Mar 2003 by Alexey Shchepin %%% %%% %%% ejabberd, Copyright (C) 2002-2016 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., %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- -module(mod_muc). -author('alexey@process-one.net'). -protocol({xep, 45, '1.25'}). -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, shutdown_rooms/1, process_disco_info/1, process_disco_items/1, process_vcard/1, process_register/1, process_muc_unique/1, process_mucsub/1, broadcast_service_message/2, export/1, import/1, import/3, opts_to_binary/1, can_use_nick/4]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). -include("xmpp.hrl"). -include("mod_muc.hrl"). -record(state, {host = <<"">> :: binary(), server_host = <<"">> :: binary(), access = {none, none, none, none} :: {atom(), atom(), atom(), atom()}, history_size = 20 :: non_neg_integer(), default_room_opts = [] :: list(), room_shaper = none :: shaper:shaper()}). -define(PROCNAME, ejabberd_mod_muc). -define(MAX_ROOMS_DISCOITEMS, 100). -type muc_room_opts() :: [{atom(), any()}]. -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), #muc_room{} | #muc_registered{}) -> ok | pass. -callback store_room(binary(), binary(), binary(), list()) -> {atomic, any()}. -callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error. -callback forget_room(binary(), binary(), binary()) -> {atomic, any()}. -callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean(). -callback get_rooms(binary(), binary()) -> [#muc_room{}]. -callback get_nick(binary(), binary(), jid()) -> binary() | error. -callback set_nick(binary(), binary(), jid(), binary()) -> {atomic, ok | false}. %%==================================================================== %% API %%==================================================================== start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). start(Host, Opts) -> 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) -> Rooms = shutdown_rooms(Host), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), gen_server:call(Proc, stop), supervisor:delete_child(ejabberd_sup, Proc), {wait, Rooms}. depends(_Host, _Opts) -> [{mod_mam, soft}]. shutdown_rooms(Host) -> MyHost = gen_mod:get_module_opt_host(Host, mod_muc, <<"conference.@HOST@">>), Rooms = mnesia:dirty_select(muc_online_room, [{#muc_online_room{name_host = '$1', pid = '$2'}, [{'==', {element, 2, '$1'}, MyHost}, {'==', {node, '$2'}, node()}], ['$2']}]), [Pid ! shutdown || Pid <- Rooms], Rooms. %% 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) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:store_room(LServer, Host, Name, Opts). restore_room(ServerHost, Host, Name) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:restore_room(LServer, Host, Name). forget_room(ServerHost, Host, Name) -> LServer = jid:nameprep(ServerHost), ejabberd_hooks:run(remove_room, LServer, [LServer, Name, Host]), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:forget_room(LServer, Host, Name). can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false; can_use_nick(ServerHost, Host, JID, Nick) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:can_use_nick(LServer, Host, JID, Nick). %%==================================================================== %% gen_server callbacks %%==================================================================== init([Host, Opts]) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), MyHost = gen_mod:get_opt_host(Host, Opts, <<"conference.@HOST@">>), Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod:init(Host, [{host, MyHost}|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}]), clean_table_from_bad_node(node(), MyHost), mnesia:subscribe(system), Access = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1, all), AccessCreate = gen_mod:get_opt(access_create, Opts, fun acl:access_rules_validator/1, all), AccessAdmin = gen_mod:get_opt(access_admin, Opts, fun acl:access_rules_validator/1, none), AccessPersistent = gen_mod:get_opt(access_persistent, Opts, fun acl:access_rules_validator/1, all), HistorySize = gen_mod:get_opt(history_size, Opts, fun(I) when is_integer(I), I>=0 -> I end, 20), DefRoomOpts1 = gen_mod:get_opt(default_room_options, Opts, fun(L) when is_list(L) -> L end, []), DefRoomOpts = lists:flatmap( fun({Opt, Val}) -> Bool = fun(B) when is_boolean(B) -> B end, VFun = case Opt of allow_change_subj -> Bool; allow_private_messages -> Bool; allow_query_users -> Bool; allow_user_invites -> Bool; allow_visitor_nickchange -> Bool; allow_visitor_status -> Bool; anonymous -> Bool; captcha_protected -> Bool; logging -> Bool; members_by_default -> Bool; members_only -> Bool; moderated -> Bool; password_protected -> Bool; persistent -> Bool; public -> Bool; public_list -> Bool; mam -> Bool; password -> fun iolist_to_binary/1; title -> fun iolist_to_binary/1; allow_private_messages_from_visitors -> fun(anyone) -> anyone; (moderators) -> moderators; (nobody) -> nobody end; max_users -> fun(I) when is_integer(I), I > 0 -> I end; presence_broadcast -> fun(L) -> lists:map( fun(moderator) -> moderator; (participant) -> participant; (visitor) -> visitor end, L) end; _ -> ?ERROR_MSG("unknown option ~p with value ~p", [Opt, Val]), fun(_) -> undefined end end, case gen_mod:get_opt(Opt, [{Opt, Val}], VFun) of undefined -> []; NewVal -> [{Opt, NewVal}] end end, DefRoomOpts1), RoomShaper = gen_mod:get_opt(room_shaper, Opts, fun(A) when is_atom(A) -> A end, none), gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER, ?MODULE, process_register, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB, ?MODULE, process_mucsub, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE, ?MODULE, process_muc_unique, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, ?MODULE, process_disco_info, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, ?MODULE, process_disco_items, IQDisc), ejabberd_router:register_route(MyHost, Host), 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}}. 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}. handle_cast(_Msg, State) -> {noreply, State}. 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}. terminate(_Reason, #state{host = MyHost}) -> ejabberd_router:unregister_route(MyHost), gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER), gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB), gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE), gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO), gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- 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); deny -> Lang = xmpp:get_lang(Packet), ErrText = <<"Access denied by service policy">>, Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(To, From, Packet, Err) end. do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper, From, #jid{luser = <<"">>, lresource = <<"">>} = To, #iq{} = IQ, _DefRoomOpts) -> ejabberd_local:process_iq(From, To, IQ); do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper, From, #jid{luser = <<"">>, lresource = <<"">>} = To, #message{lang = Lang, body = Body, type = Type} = Packet, _) -> {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = Access, if Type == error -> ok; true -> case acl:match_rule(ServerHost, AccessAdmin, From) of allow -> Msg = xmpp:get_text(Body), broadcast_service_message(Host, Msg); deny -> ErrText = <<"Only service administrators are allowed " "to send service messages">>, Err = xmpp:make_error( Packet, xmpp:err_forbidden(ErrText, Lang)), ejabberd_router:route(To, From, Err) end end; do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper, From, #jid{luser = <<"">>} = To, Packet, _DefRoomOpts) -> Err = xmpp:err_item_not_found(), ejabberd_router:route_error(To, From, Packet, Err); do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts) -> {_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access, {Room, _, Nick} = jid:tolower(To), case mnesia:dirty_read(muc_online_room, {Room, Host}) of [] -> case Packet of #presence{type = available, lang = Lang} -> case check_user_can_create_room( ServerHost, AccessCreate, From, Room) and check_create_roomid(ServerHost, 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 -> ErrText = <<"Room creation is denied by service policy">>, Err = xmpp:make_error( Packet, xmpp:err_forbidden(ErrText, Lang)), ejabberd_router:route(To, From, Err) end; _ -> Lang = xmpp:get_lang(Packet), ErrText = <<"Conference room does not exist">>, Err = xmpp:err_item_not_found(ErrText, Lang), ejabberd_router:route_error(To, From, Packet, 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. -spec process_vcard(iq()) -> iq(). process_vcard(#iq{type = get, lang = Lang} = IQ) -> Desc = translate:translate(Lang, <<"ejabberd MUC module">>), Copyright = <<"Copyright (c) 2003-2016 ProcessOne">>, xmpp:make_iq_result( IQ, #vcard_temp{fn = <<"ejabberd/mod_muc">>, url = ?EJABBERD_URI, desc = <>}); process_vcard(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). -spec process_register(iq()) -> iq(). process_register(#iq{type = get, from = From, to = To, lang = Lang} = IQ) -> Host = To#jid.lserver, ServerHost = ejabberd_router:host_of_route(Host), xmpp:make_iq_result(IQ, iq_get_register_info(ServerHost, Host, From, Lang)); process_register(#iq{type = set, from = From, to = To, lang = Lang, sub_els = [El]} = IQ) -> Host = To#jid.lserver, ServerHost = ejabberd_router:host_of_route(Host), case process_iq_register_set(ServerHost, Host, From, El, Lang) of {result, Result} -> xmpp:make_iq_result(IQ, Result); {error, Err} -> xmpp:make_error(IQ, Err) end. -spec process_disco_info(iq()) -> iq(). process_disco_info(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_info(#iq{type = get, to = To, lang = Lang, sub_els = [#disco_info{node = undefined}]} = IQ) -> ServerHost = ejabberd_router:host_of_route(To#jid.lserver), X = ejabberd_hooks:run_fold(disco_info, ServerHost, [], [ServerHost, ?MODULE, undefined, Lang]), MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1]; false -> [] end, Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_REGISTER, ?NS_MUC, ?NS_RSM, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures], Identity = #identity{category = <<"conference">>, type = <<"text">>, name = translate:translate(Lang, <<"Chatrooms">>)}, xmpp:make_iq_result( IQ, #disco_info{features = Features, identities = [Identity], xdata = X}); process_disco_info(#iq{type = get, lang = Lang} = IQ) -> xmpp:make_error(IQ, xmpp:err_item_not_found(<<"No info available">>, Lang)). -spec process_disco_items(iq()) -> iq(). process_disco_items(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_items(#iq{type = get, from = From, to = To, lang = Lang, sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) -> Host = To#jid.lserver, xmpp:make_iq_result( IQ, #disco_items{node = Node, items = iq_disco_items(Host, From, Lang, Node, RSM)}). -spec process_muc_unique(iq()) -> iq(). process_muc_unique(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_muc_unique(#iq{from = From, type = get} = IQ) -> Name = p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(), randoms:get_string()])), xmpp:make_iq_result(IQ, #muc_unique{name = Name}). -spec process_mucsub(iq()) -> iq(). process_mucsub(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_mucsub(#iq{type = get, from = From, to = To} = IQ) -> Host = To#jid.lserver, ServerHost = ejabberd_router:host_of_route(Host), RoomJIDs = get_subscribed_rooms(ServerHost, Host, From), xmpp:make_iq_result(IQ, #muc_subscriptions{list = RoomJIDs}). check_user_can_create_room(ServerHost, AccessCreate, From, _RoomID) -> case acl:match_rule(ServerHost, AccessCreate, From) of allow -> true; _ -> false end. check_create_roomid(ServerHost, RoomID) -> Max = gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id, fun(infinity) -> infinity; (I) when is_integer(I), I>0 -> I end, infinity), Regexp = gen_mod:get_module_opt(ServerHost, ?MODULE, regexp_room_id, fun iolist_to_binary/1, ""), (byte_size(RoomID) =< Max) and (re:run(RoomID, Regexp, [unicode, {capture, none}]) == match). get_rooms(ServerHost, Host) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_rooms(LServer, Host). 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 restore_room(ServerHost, Host, Room) of error -> ?DEBUG("MUC: open new room '~s'~n", [Room]), mod_muc_room:start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, From, Nick, DefRoomOpts); Opts -> ?DEBUG("MUC: restore room '~s'~n", [Room]), mod_muc_room:start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) 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_items(Host, From, Lang, undefined, undefined) -> Rooms = get_vh_rooms(Host), case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of true -> iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}); false -> iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined) end; iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined) -> Empty = #disco_item{jid = jid:make(<<"conference.localhost">>), node = <<"emptyrooms">>, name = translate:translate(Lang, <<"Empty Rooms">>)}, Query = {get_disco_item, only_non_empty, From, Lang}, [Empty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)]; iq_disco_items(Host, From, Lang, <<"emptyrooms">>, undefined) -> iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang}); iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) -> {Rooms, RsmO} = get_vh_rooms(Host, Rsm), RsmOut = jlib:rsm_encode(RsmO), iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut. iq_disco_items_list(Host, Rooms, Query) -> lists:zf( fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) -> case catch gen_fsm:sync_send_all_state_event(Pid, Query, 100) of {item, Desc} -> flush(), {true, #disco_item{jid = jid:make(Name, Host), name = Desc}}; _ -> false end end, Rooms). get_vh_rooms(_, _) -> todo. %% 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. get_subscribed_rooms(ServerHost, Host, From) -> Rooms = get_rooms(ServerHost, Host), lists:flatmap( fun(#muc_room{name_host = {Name, _}, opts = Opts}) -> Subscribers = proplists:get_value(subscribers, Opts, []), case lists:keymember(From, 1, Subscribers) of true -> [jid:make(Name, Host, <<>>)]; false -> [] end; (_) -> [] end, Rooms). %% @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. get_nick(ServerHost, Host, From) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_nick(LServer, Host, From). iq_get_register_info(ServerHost, Host, From, Lang) -> {Nick, Registered} = case get_nick(ServerHost, Host, From) of error -> {<<"">>, false}; N -> {N, true} end, Title = <<(translate:translate( Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>, Inst = translate:translate(Lang, <<"Enter nickname you want to register">>), Field = #xdata_field{type = 'text-single', label = translate:translate(Lang, <<"Nickname">>), var = <<"nick">>, values = [Nick]}, X = #xdata{type = form, title = Title, instructions = [Inst], fields = [Field]}, #register{nick = Nick, registered = Registered, instructions = translate:translate( Lang, <<"You need a client that supports x:data " "to register the nickname">>), xdata = X}. set_nick(ServerHost, Host, From, Nick) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:set_nick(LServer, Host, From, Nick). iq_set_register_info(ServerHost, Host, From, Nick, Lang) -> case set_nick(ServerHost, Host, From, Nick) of {atomic, ok} -> {result, undefined}; {atomic, false} -> ErrText = <<"That nickname is registered by another " "person">>, {error, xmpp:err_conflict(ErrText, Lang)}; _ -> Txt = <<"Database failure">>, {error, xmpp:err_internal_server_error(Txt, Lang)} end. process_iq_register_set(ServerHost, Host, From, #register{remove = true}, Lang) -> iq_set_register_info(ServerHost, Host, From, <<"">>, Lang); process_iq_register_set(_ServerHost, _Host, _From, #register{xdata = #xdata{type = cancel}}, _Lang) -> {result, undefined}; process_iq_register_set(ServerHost, Host, From, #register{nick = Nick, xdata = XData}, Lang) -> case XData of #xdata{type = submit, fields = Fs} -> case lists:keyfind(<<"nick">>, #xdata_field.var, Fs) of #xdata_field{values = [N]} -> iq_set_register_info(ServerHost, Host, From, N, Lang); _ -> ErrText = <<"You must fill in field \"Nickname\" in the form">>, {error, xmpp:err_not_acceptable(ErrText, Lang)} end; #xdata{} -> Txt = <<"Incorrect data form">>, {error, xmpp:err_bad_request(Txt, Lang)}; _ when is_binary(Nick), Nick /= <<"">> -> iq_set_register_info(ServerHost, Host, From, Nick, Lang); _ -> ErrText = <<"You must fill in field \"Nickname\" in the form">>, {error, xmpp:err_not_acceptable(ErrText, Lang)} end. 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). opts_to_binary(Opts) -> lists:map( fun({title, Title}) -> {title, iolist_to_binary(Title)}; ({description, Desc}) -> {description, iolist_to_binary(Desc)}; ({password, Pass}) -> {password, iolist_to_binary(Pass)}; ({subject, Subj}) -> {subject, iolist_to_binary(Subj)}; ({subject_author, Author}) -> {subject_author, iolist_to_binary(Author)}; ({affiliations, Affs}) -> {affiliations, lists:map( fun({{U, S, R}, Aff}) -> NewAff = case Aff of {A, Reason} -> {A, iolist_to_binary(Reason)}; _ -> Aff end, {{iolist_to_binary(U), iolist_to_binary(S), iolist_to_binary(R)}, NewAff} end, Affs)}; ({captcha_whitelist, CWList}) -> {captcha_whitelist, lists:map( fun({U, S, R}) -> {iolist_to_binary(U), iolist_to_binary(S), iolist_to_binary(R)} end, CWList)}; (Opt) -> Opt end, Opts). export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). import(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:import(LServer). import(LServer, DBType, Data) -> Mod = gen_mod:db_mod(DBType, ?MODULE), Mod:import(LServer, Data). mod_opt_type(access) -> fun acl:access_rules_validator/1; mod_opt_type(access_admin) -> fun acl:access_rules_validator/1; mod_opt_type(access_create) -> fun acl:access_rules_validator/1; mod_opt_type(access_persistent) -> fun acl:access_rules_validator/1; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(default_room_options) -> fun (L) when is_list(L) -> L end; mod_opt_type(history_size) -> fun (I) when is_integer(I), I >= 0 -> I end; mod_opt_type(host) -> fun iolist_to_binary/1; mod_opt_type(max_room_desc) -> fun (infinity) -> infinity; (I) when is_integer(I), I > 0 -> I end; mod_opt_type(max_room_id) -> fun (infinity) -> infinity; (I) when is_integer(I), I > 0 -> I end; mod_opt_type(regexp_room_id) -> fun iolist_to_binary/1; mod_opt_type(max_room_name) -> fun (infinity) -> infinity; (I) when is_integer(I), I > 0 -> I end; mod_opt_type(max_user_conferences) -> fun (I) when is_integer(I), I > 0 -> I end; mod_opt_type(max_users) -> fun (I) when is_integer(I), I > 0 -> I end; mod_opt_type(max_users_admin_threshold) -> fun (I) when is_integer(I), I > 0 -> I end; mod_opt_type(max_users_presence) -> fun (MUP) when is_integer(MUP) -> MUP end; mod_opt_type(min_message_interval) -> fun (MMI) when is_number(MMI) -> MMI end; mod_opt_type(min_presence_interval) -> fun (I) when is_number(I), I >= 0 -> I end; mod_opt_type(room_shaper) -> fun (A) when is_atom(A) -> A end; mod_opt_type(user_message_shaper) -> fun (A) when is_atom(A) -> A end; mod_opt_type(user_presence_shaper) -> fun (A) when is_atom(A) -> A end; mod_opt_type(_) -> [access, access_admin, access_create, access_persistent, db_type, default_room_options, history_size, host, max_room_desc, max_room_id, max_room_name, regexp_room_id, max_user_conferences, max_users, max_users_admin_threshold, max_users_presence, min_message_interval, min_presence_interval, room_shaper, user_message_shaper, user_presence_shaper].