diff options
Diffstat (limited to 'src/mod_muc')
-rw-r--r-- | src/mod_muc/Makefile.in | 42 | ||||
-rw-r--r-- | src/mod_muc/Makefile.win32 | 21 | ||||
-rw-r--r-- | src/mod_muc/mod_muc.erl | 1127 | ||||
-rw-r--r-- | src/mod_muc/mod_muc_log.erl | 1236 | ||||
-rw-r--r-- | src/mod_muc/mod_muc_room.erl | 4460 | ||||
-rw-r--r-- | src/mod_muc/mod_muc_room.hrl | 116 |
6 files changed, 0 insertions, 7002 deletions
diff --git a/src/mod_muc/Makefile.in b/src/mod_muc/Makefile.in deleted file mode 100644 index 5ede5e521..000000000 --- a/src/mod_muc/Makefile.in +++ /dev/null @@ -1,42 +0,0 @@ -# $Id$ - -CC = @CC@ -CFLAGS = @CFLAGS@ -CPPFLAGS = @CPPFLAGS@ -LDFLAGS = @LDFLAGS@ -LIBS = @LIBS@ - -ERLANG_CFLAGS = @ERLANG_CFLAGS@ -ERLANG_LIBS = @ERLANG_LIBS@ - -EFLAGS += -I .. -EFLAGS += -pz .. - -# make debug=true to compile Erlang module with debug informations. -ifdef debug - EFLAGS+=+debug_info -endif - -ifeq (@transient_supervisors@, false) - EFLAGS+=-DNO_TRANSIENT_SUPERVISORS -endif - -OUTDIR = .. -SOURCES = $(wildcard *.erl) -BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam)) - - -all: $(BEAMS) - -$(OUTDIR)/%.beam: %.erl - @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $< - -clean: - rm -f $(BEAMS) - -distclean: clean - rm -f Makefile - -TAGS: - etags *.erl - diff --git a/src/mod_muc/Makefile.win32 b/src/mod_muc/Makefile.win32 deleted file mode 100644 index 5107b1069..000000000 --- a/src/mod_muc/Makefile.win32 +++ /dev/null @@ -1,21 +0,0 @@ - -include ..\Makefile.inc - -EFLAGS = -I .. -pz .. - -OUTDIR = .. -BEAMS = ..\mod_muc.beam ..\mod_muc_log.beam ..\mod_muc_room.beam - -ALL : $(BEAMS) - -CLEAN : - -@erase $(BEAMS) - -$(OUTDIR)\mod_muc.beam : mod_muc.erl - erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc.erl - -$(OUTDIR)\mod_muc_log.beam : mod_muc_log.erl - erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc_log.erl - -$(OUTDIR)\mod_muc_room.beam : mod_muc_room.erl - erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc_room.erl diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl deleted file mode 100644 index 6587cc5d0..000000000 --- a/src/mod_muc/mod_muc.erl +++ /dev/null @@ -1,1127 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_muc.erl -%%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : MUC support (XEP-0045) -%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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). - --author('alexey@process-one.net'). - --behaviour(gen_server). - --behaviour(gen_mod). - -%% API --export([start_link/2, - start/2, - stop/1, - room_destroyed/4, - store_room/4, - restore_room/3, - forget_room/3, - create_room/5, - process_iq_disco_items/4, - broadcast_service_message/2, - can_use_nick/4]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --record(muc_room, {name_host = {<<"">>, <<"">>} :: {binary(), binary()} | - {'_', binary()}, - opts = [] :: list() | '_'}). - --record(muc_online_room, - {name_host = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', - pid = self() :: pid() | '$2' | '_'}). - --record(muc_registered, - {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1', - nick = <<"">> :: binary()}). - --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). - -%%==================================================================== -%% 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) -> - LServer = jlib:nameprep(ServerHost), - store_room(LServer, Host, Name, Opts, - gen_mod:db_type(LServer, ?MODULE)). - -store_room(_LServer, Host, Name, Opts, mnesia) -> - F = fun () -> - mnesia:write(#muc_room{name_host = {Name, Host}, - opts = Opts}) - end, - mnesia:transaction(F); -store_room(LServer, Host, Name, Opts, odbc) -> - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), - SOpts = ejabberd_odbc:encode_term(Opts), - F = fun () -> - odbc_queries:update_t(<<"muc_room">>, - [<<"name">>, <<"host">>, <<"opts">>], - [SName, SHost, SOpts], - [<<"name='">>, SName, <<"' and host='">>, - SHost, <<"'">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F). - -restore_room(ServerHost, Host, Name) -> - LServer = jlib:nameprep(ServerHost), - restore_room(LServer, Host, Name, - gen_mod:db_type(LServer, ?MODULE)). - -restore_room(_LServer, Host, Name, mnesia) -> - case catch mnesia:dirty_read(muc_room, {Name, Host}) of - [#muc_room{opts = Opts}] -> Opts; - _ -> error - end; -restore_room(LServer, Host, Name, odbc) -> - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select opts from muc_room where name='">>, - SName, <<"' and host='">>, SHost, - <<"';">>]) - of - {selected, [<<"opts">>], [[Opts]]} -> - opts_to_binary(ejabberd_odbc:decode_term(Opts)); - _ -> error - end. - -forget_room(ServerHost, Host, Name) -> - LServer = jlib:nameprep(ServerHost), - forget_room(LServer, Host, Name, - gen_mod:db_type(LServer, ?MODULE)). - -forget_room(_LServer, Host, Name, mnesia) -> - F = fun () -> mnesia:delete({muc_room, {Name, Host}}) - end, - mnesia:transaction(F); -forget_room(LServer, Host, Name, odbc) -> - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), - F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>, - SName, <<"' and host='">>, SHost, - <<"';">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F). - -process_iq_disco_items(Host, From, To, - #iq{lang = Lang} = IQ) -> - Rsm = jlib:rsm_decode(IQ), - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}], - children = 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) -> - LServer = jlib:nameprep(ServerHost), - can_use_nick(LServer, Host, JID, Nick, - gen_mod:db_type(LServer, ?MODULE)). - -can_use_nick(_LServer, Host, JID, Nick, mnesia) -> - {LUser, LServer, _} = jlib:jid_tolower(JID), - LUS = {LUser, LServer}, - case catch mnesia:dirty_select(muc_registered, - [{#muc_registered{us_host = '$1', - nick = Nick, _ = '_'}, - [{'==', {element, 2, '$1'}, Host}], - ['$_']}]) - of - {'EXIT', _Reason} -> true; - [] -> true; - [#muc_registered{us_host = {U, _Host}}] -> U == LUS - end; -can_use_nick(LServer, Host, JID, Nick, odbc) -> - SJID = - jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(JID))), - SNick = ejabberd_odbc:escape(Nick), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select jid from muc_registered ">>, - <<"where nick='">>, SNick, - <<"' and host='">>, SHost, <<"';">>]) - of - {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1; - _ -> true - end. - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Host, Opts]) -> - MyHost = gen_mod:get_opt_host(Host, Opts, - <<"conference.@HOST@">>), - case gen_mod:db_type(Opts) of - mnesia -> - mnesia:create_table(muc_room, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, muc_room)}]), - mnesia:create_table(muc_registered, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, muc_registered)}]), - update_tables(MyHost), - mnesia:add_table_index(muc_registered, nick); - _ -> - ok - end, - mnesia:create_table(muc_online_room, - [{ram_copies, [node()]}, - {attributes, record_info(fields, muc_online_room)}]), - mnesia:add_table_copy(muc_online_room, node(), ram_copies), - catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]), - clean_table_from_bad_node(node(), MyHost), - mnesia:subscribe(system), - Access = gen_mod:get_opt(access, Opts, fun(A) -> A end, all), - AccessCreate = gen_mod:get_opt(access_create, Opts, fun(A) -> A end, all), - AccessAdmin = gen_mod:get_opt(access_admin, Opts, fun(A) -> A end, none), - AccessPersistent = gen_mod:get_opt(access_persistent, Opts, fun(A) -> A end, all), - HistorySize = gen_mod:get_opt(history_size, Opts, fun(A) -> A end, 20), - DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, fun(A) -> A end, []), - RoomShaper = gen_mod:get_opt(room_shaper, Opts, fun(A) -> A end, 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), - 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); - _ -> - #xmlel{attrs = Attrs} = 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), - #xmlel{name = Name, attrs = Attrs} = 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 = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - 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 = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - 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 = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = 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 = - [#xmlel{name = <<"vCard">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - 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 = - [#xmlel{name = <<"unique">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_UNIQUE}], - children = - [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 -> - byte_size(RoomID) =< - gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id, - fun(infinity) -> infinity; - (I) when is_integer(I), I>0 -> I - end, infinity); - _ -> false - end. - -get_rooms(ServerHost, Host) -> - LServer = jlib:nameprep(ServerHost), - get_rooms(LServer, Host, - gen_mod:db_type(LServer, ?MODULE)). - -get_rooms(_LServer, Host, mnesia) -> - case catch mnesia:dirty_select(muc_room, - [{#muc_room{name_host = {'_', Host}, - _ = '_'}, - [], ['$_']}]) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), []; - Rs -> Rs - end; -get_rooms(LServer, Host, odbc) -> - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select name, opts from muc_room ">>, - <<"where host='">>, SHost, <<"';">>]) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), []; - {selected, [<<"name">>, <<"opts">>], RoomOpts} -> - lists:map(fun ([Room, Opts]) -> - #muc_room{name_host = {Room, Host}, - opts = opts_to_binary( - ejabberd_odbc:decode_term(Opts))} - end, - RoomOpts) - end. - -load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) -> - lists:foreach( - fun(R) -> - {Room, Host} = R#muc_room.name_host, - case mnesia:dirty_read(muc_online_room, {Room, Host}) of - [] -> - {ok, Pid} = mod_muc_room:start( - Host, - ServerHost, - Access, - Room, - HistorySize, - RoomShaper, - R#muc_room.opts), - register_room(Host, Room, Pid); - _ -> - ok - end - end, get_rooms(ServerHost, Host)). - -start_new_room(Host, ServerHost, Access, Room, - HistorySize, RoomShaper, From, - Nick, DefRoomOpts) -> - case restore_room(ServerHost, Room, Host) of - error -> - ?DEBUG("MUC: open new room '~s'~n", [Room]), - mod_muc_room:start(Host, ServerHost, Access, - Room, HistorySize, - RoomShaper, From, - Nick, DefRoomOpts); - 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_info(Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, - translate:translate(Lang, <<"Chatrooms">>)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_REGISTER}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_RSM}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}]. - -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, - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string({Name, Host, - <<"">>})}, - {<<"name">>, Desc}], - children = []}}; - _ -> 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 = - [{<<"jid">>, - jlib:jid_to_string({Name, Host, - <<"">>})}, - {<<"name">>, Desc}], - children = []}}; - _ -> 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), -%% @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 - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - -iq_get_unique(From) -> - {xmlcdata, - sha:sha(term_to_binary([From, now(), - randoms:get_string()]))}. - -get_nick(ServerHost, Host, From) -> - LServer = jlib:nameprep(ServerHost), - get_nick(LServer, Host, From, - gen_mod:db_type(LServer, ?MODULE)). - -get_nick(_LServer, Host, From, mnesia) -> - {LUser, LServer, _} = jlib:jid_tolower(From), - LUS = {LUser, LServer}, - case catch mnesia:dirty_read(muc_registered, - {LUS, Host}) - of - {'EXIT', _Reason} -> error; - [] -> error; - [#muc_registered{nick = Nick}] -> Nick - end; -get_nick(LServer, Host, From, odbc) -> - SJID = - ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select nick from muc_registered where " - "jid='">>, - SJID, <<"' and host='">>, SHost, - <<"';">>]) - of - {selected, [<<"nick">>], [[Nick]]} -> Nick; - _ -> error - end. - -iq_get_register_info(ServerHost, Host, From, Lang) -> - {Nick, Registered} = case get_nick(ServerHost, Host, - From) - of - error -> {<<"">>, []}; - N -> - {N, - [#xmlel{name = <<"registered">>, attrs = [], - children = []}]} - end, - Registered ++ - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need a client that supports x:data " - "to register the nickname">>)}]}, - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Nickname Registration at ">>))/binary, - Host/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Enter nickname you want to register">>)}]}, - ?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>, - Nick)]}]. - -set_nick(ServerHost, Host, From, Nick) -> - LServer = jlib:nameprep(ServerHost), - set_nick(LServer, Host, From, Nick, - gen_mod:db_type(LServer, ?MODULE)). - -set_nick(_LServer, Host, From, Nick, mnesia) -> - {LUser, LServer, _} = jlib:jid_tolower(From), - LUS = {LUser, LServer}, - F = fun () -> - case Nick of - <<"">> -> - mnesia:delete({muc_registered, {LUS, Host}}), ok; - _ -> - Allow = case mnesia:select(muc_registered, - [{#muc_registered{us_host = - '$1', - nick = Nick, - _ = '_'}, - [{'==', {element, 2, '$1'}, - Host}], - ['$_']}]) - of - [] -> true; - [#muc_registered{us_host = {U, _Host}}] -> - U == LUS - end, - if Allow -> - mnesia:write(#muc_registered{us_host = {LUS, Host}, - nick = Nick}), - ok; - true -> false - end - end - end, - mnesia:transaction(F); -set_nick(LServer, Host, From, Nick, odbc) -> - JID = - jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From))), - SJID = ejabberd_odbc:escape(JID), - SNick = ejabberd_odbc:escape(Nick), - SHost = ejabberd_odbc:escape(Host), - F = fun () -> - case Nick of - <<"">> -> - ejabberd_odbc:sql_query_t([<<"delete from muc_registered where ">>, - <<"jid='">>, SJID, - <<"' and host='">>, Host, - <<"';">>]), - ok; - _ -> - Allow = case - ejabberd_odbc:sql_query_t([<<"select jid from muc_registered ">>, - <<"where nick='">>, - SNick, - <<"' and host='">>, - SHost, <<"';">>]) - of - {selected, [<<"jid">>], [[J]]} -> J == JID; - _ -> true - end, - if Allow -> - odbc_queries:update_t(<<"muc_registered">>, - [<<"jid">>, <<"host">>, - <<"nick">>], - [SJID, SHost, SNick], - [<<"jid='">>, SJID, - <<"' and host='">>, SHost, - <<"'">>]), - ok; - true -> false - end - end - end, - ejabberd_odbc:sql_transaction(LServer, F). - -iq_set_register_info(ServerHost, Host, From, Nick, - Lang) -> - case set_nick(ServerHost, Host, From, Nick) of - {atomic, ok} -> {result, []}; - {atomic, false} -> - 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) -> - #xmlel{children = Els} = SubEl, - case xml:get_subtag(SubEl, <<"remove">>) of - false -> - case xml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = 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) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_muc">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd MUC module">>))/binary, - "\nCopyright (c) 2003-2013 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). - -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). - -update_tables(Host) -> - update_muc_room_table(Host), - update_muc_registered_table(Host). - -update_muc_room_table(Host) -> - Fields = record_info(fields, muc_room), - case mnesia:table_info(muc_room, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - muc_room, Fields, set, - fun(#muc_room{name_host = {N, _}}) -> N end, - fun(#muc_room{name_host = {N, H}, - opts = Opts} = R) -> - R#muc_room{name_host = {iolist_to_binary(N), - iolist_to_binary(H)}, - opts = opts_to_binary(Opts)} - end); - _ -> - ?INFO_MSG("Recreating muc_room table", []), - mnesia:transform_table(muc_room, ignore, Fields) - end. - -update_muc_registered_table(Host) -> - Fields = record_info(fields, muc_registered), - case mnesia:table_info(muc_registered, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - muc_registered, Fields, set, - fun(#muc_registered{us_host = {_, H}}) -> H end, - fun(#muc_registered{us_host = {{U, S}, H}, - nick = Nick} = R) -> - R#muc_registered{us_host = {{iolist_to_binary(U), - iolist_to_binary(S)}, - iolist_to_binary(H)}, - nick = iolist_to_binary(Nick)} - end); - _ -> - ?INFO_MSG("Recreating muc_registered table", []), - mnesia:transform_table(muc_registered, ignore, Fields) - end. diff --git a/src/mod_muc/mod_muc_log.erl b/src/mod_muc/mod_muc_log.erl deleted file mode 100644 index ec62c85fd..000000000 --- a/src/mod_muc/mod_muc_log.erl +++ /dev/null @@ -1,1236 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_muc_log.erl -%%% Author : Badlop@process-one.net -%%% Purpose : MUC room logging -%%% Created : 12 Mar 2006 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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_log). - --author('badlop@process-one.net'). - --behaviour(gen_server). - --behaviour(gen_mod). - -%% API --export([start_link/2, start/2, stop/1, - check_access_log/2, add_to_log/5]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("mod_muc_room.hrl"). - -%% Copied from mod_muc/mod_muc.erl --record(muc_online_room, {name_host = {<<>>, <<>>} :: {binary(), binary()}, - pid = self() :: pid()}). - --define(T(Text), translate:translate(Lang, Text)). --define(PROCNAME, ejabberd_mod_muc_log). --record(room, {jid, title, subject, subject_author, config}). - --define(PLAINTEXT_CO, <<"ZZCZZ">>). --define(PLAINTEXT_IN, <<"ZZIZZ">>). --define(PLAINTEXT_OUT, <<"ZZOZZ">>). - --record(logstate, {host, - out_dir, - dir_type, - dir_name, - file_format, - file_permissions, - css_file, - access, - lang, - timezone, - spam_prevention, - top_link}). - -%%==================================================================== -%% 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) -> - 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) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:call(Proc, stop), - supervisor:delete_child(ejabberd_sup, Proc). - -add_to_log(Host, Type, Data, Room, Opts) -> - gen_server:cast(get_proc_name(Host), - {add_to_log, Type, Data, Room, Opts}). - -check_access_log(Host, From) -> - case catch gen_server:call(get_proc_name(Host), - {check_access_log, Host, From}) - of - {'EXIT', _Error} -> deny; - Res -> Res - end. - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Host, Opts]) -> - OutDir = gen_mod:get_opt(outdir, Opts, - fun iolist_to_binary/1, - <<"www/muc">>), - DirType = gen_mod:get_opt(dirtype, Opts, - fun(subdirs) -> subdirs; - (plain) -> plain - end, subdirs), - DirName = gen_mod:get_opt(dirname, Opts, - fun(room_jid) -> room_jid; - (room_name) -> room_name - end, room_jid), - FileFormat = gen_mod:get_opt(file_format, Opts, - fun(html) -> html; - (plaintext) -> plaintext - end, html), - FilePermissions = gen_mod:get_opt(file_permissions, Opts, - fun({A, B}) -> {A, B} - end, {644, 33}), - CSSFile = gen_mod:get_opt(cssfile, Opts, - fun iolist_to_binary/1, - false), - AccessLog = gen_mod:get_opt(access_log, Opts, - fun(A) when is_atom(A) -> A end, - muc_admin), - Timezone = gen_mod:get_opt(timezone, Opts, - fun(local) -> local; - (universal) -> universal - end, local), - Top_link = gen_mod:get_opt(top_link, Opts, - fun({S1, S2}) -> - {iolist_to_binary(S1), - iolist_to_binary(S2)} - end, {<<"/">>, <<"Home">>}), - NoFollow = gen_mod:get_opt(spam_prevention, Opts, - fun(B) when is_boolean(B) -> B end, - true), - Lang = ejabberd_config:get_local_option( - {language, Host}, - fun iolist_to_binary/1, - ?MYLANG), - {ok, - #logstate{host = Host, out_dir = OutDir, - dir_type = DirType, dir_name = DirName, - file_format = FileFormat, file_permissions = FilePermissions, css_file = CSSFile, - access = AccessLog, lang = Lang, timezone = Timezone, - spam_prevention = NoFollow, top_link = Top_link}}. - -%%-------------------------------------------------------------------- -%% 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({check_access_log, ServerHost, FromJID}, _From, State) -> - Reply = acl:match_rule(ServerHost, State#logstate.access, FromJID), - {reply, Reply, State}; -handle_call(stop, _From, State) -> - {stop, normal, ok, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast({add_to_log, Type, Data, Room, Opts}, State) -> - case catch add_to_log2(Type, Data, Room, Opts, State) of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - _ -> ok - end, - {noreply, State}; -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(_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) -> 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 -%%-------------------------------------------------------------------- -add_to_log2(text, {Nick, Packet}, Room, Opts, State) -> - case {xml:get_subtag(Packet, <<"subject">>), - xml:get_subtag(Packet, <<"body">>)} - of - {false, false} -> ok; - {false, SubEl} -> - Message = {body, xml:get_tag_cdata(SubEl)}, - add_message_to_log(Nick, Message, Room, Opts, State); - {SubEl, _} -> - Message = {subject, xml:get_tag_cdata(SubEl)}, - add_message_to_log(Nick, Message, Room, Opts, State) - end; -add_to_log2(roomconfig_change, _Occupants, Room, Opts, - State) -> - add_message_to_log(<<"">>, roomconfig_change, Room, - Opts, State); -add_to_log2(roomconfig_change_enabledlogging, Occupants, - Room, Opts, State) -> - add_message_to_log(<<"">>, - {roomconfig_change, Occupants}, Room, Opts, State); -add_to_log2(room_existence, NewStatus, Room, Opts, - State) -> - add_message_to_log(<<"">>, {room_existence, NewStatus}, - Room, Opts, State); -add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, - State) -> - add_message_to_log(NewNick, {nickchange, OldNick}, Room, - Opts, State); -add_to_log2(join, Nick, Room, Opts, State) -> - add_message_to_log(Nick, join, Room, Opts, State); -add_to_log2(leave, {Nick, Reason}, Room, Opts, State) -> - case Reason of - <<"">> -> - add_message_to_log(Nick, leave, Room, Opts, State); - _ -> - add_message_to_log(Nick, {leave, Reason}, Room, Opts, - State) - end; -add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, - State) -> - add_message_to_log(Nick, {kickban, Code, Reason}, Room, - Opts, State). - -%%---------------------------------------------------------------------- -%% Core - -build_filename_string(TimeStamp, OutDir, RoomJID, - DirType, DirName, FileFormat) -> - {{Year, Month, Day}, _Time} = TimeStamp, - {Dir, Filename, Rel} = case DirType of - subdirs -> - SYear = - iolist_to_binary(io_lib:format("~4..0w", - [Year])), - SMonth = - iolist_to_binary(io_lib:format("~2..0w", - [Month])), - SDay = iolist_to_binary(io_lib:format("~2..0w", - [Day])), - {fjoin([SYear, SMonth]), SDay, - <<"../..">>}; - plain -> - Date = - iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w", - [Year, - Month, - Day])), - {<<"">>, Date, <<".">>} - end, - RoomString = case DirName of - room_jid -> RoomJID; - room_name -> get_room_name(RoomJID) - end, - Extension = case FileFormat of - html -> <<".html">>; - plaintext -> <<".txt">> - end, - Fd = fjoin([OutDir, RoomString, Dir]), - Fn = fjoin([Fd, <<Filename/binary, Extension/binary>>]), - Fnrel = fjoin([Rel, Dir, <<Filename/binary, Extension/binary>>]), - {Fd, Fn, Fnrel}. - -get_room_name(RoomJID) -> - JID = jlib:string_to_jid(RoomJID), JID#jid.user. - -%% calculate day before -get_timestamp_daydiff(TimeStamp, Daydiff) -> - {Date1, HMS} = TimeStamp, - Date2 = - calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(Date1) - + Daydiff), - {Date2, HMS}. - -%% Try to close the previous day log, if it exists -close_previous_log(Fn, Images_dir, FileFormat) -> - case file:read_file_info(Fn) of - {ok, _} -> - {ok, F} = file:open(Fn, [append]), - write_last_lines(F, Images_dir, FileFormat), - file:close(F); - _ -> ok - end. - -write_last_lines(_, _, plaintext) -> ok; -write_last_lines(F, Images_dir, _FileFormat) -> -%% list_to_integer/2 was introduced in OTP R14 - fw(F, <<"<div class=\"legend\">">>), - fw(F, - <<" <a href=\"http://www.ejabberd.im\"><img " - "style=\"border:0\" src=\"~s/powered-by-ejabbe" - "rd.png\" alt=\"Powered by ejabberd\"/></a>">>, - [Images_dir]), - fw(F, - <<" <a href=\"http://www.erlang.org/\"><img " - "style=\"border:0\" src=\"~s/powered-by-erlang" - ".png\" alt=\"Powered by Erlang\"/></a>">>, - [Images_dir]), - fw(F, <<"<span class=\"w3c\">">>), - fw(F, - <<" <a href=\"http://validator.w3.org/check?uri" - "=referer\"><img style=\"border:0;width:88px;h" - "eight:31px\" src=\"~s/valid-xhtml10.png\" " - "alt=\"Valid XHTML 1.0 Transitional\" " - "/></a>">>, - [Images_dir]), - fw(F, - <<" <a href=\"http://jigsaw.w3.org/css-validato" - "r/\"><img style=\"border:0;width:88px;height:" - "31px\" src=\"~s/vcss.png\" alt=\"Valid " - "CSS!\"/></a>">>, - [Images_dir]), - fw(F, <<"</span></div></body></html>">>). - -set_filemode(Fn, {FileMode, FileGroup}) -> - ok = file:change_mode(Fn, list_to_integer(integer_to_list(FileMode), 8)), - ok = file:change_group(Fn, FileGroup). - -add_message_to_log(Nick1, Message, RoomJID, Opts, - State) -> - #logstate{out_dir = OutDir, dir_type = DirType, - dir_name = DirName, file_format = FileFormat, - file_permissions = FilePermissions, - css_file = CSSFile, lang = Lang, timezone = Timezone, - spam_prevention = NoFollow, top_link = TopLink} = - State, - Room = get_room_info(RoomJID, Opts), - Nick = htmlize(Nick1, FileFormat), - Nick2 = htmlize(<<"<", Nick1/binary, ">">>, FileFormat), - Now = now(), - TimeStamp = case Timezone of - local -> calendar:now_to_local_time(Now); - universal -> calendar:now_to_universal_time(Now) - end, - {Fd, Fn, _Dir} = build_filename_string(TimeStamp, - OutDir, Room#room.jid, DirType, - DirName, FileFormat), - {Date, Time} = TimeStamp, - case file:read_file_info(Fn) of - {ok, _} -> {ok, F} = file:open(Fn, [append]); - {error, enoent} -> - make_dir_rec(Fd), - {ok, F} = file:open(Fn, [append]), - catch set_filemode(Fn, FilePermissions), - Datestring = get_dateweek(Date, Lang), - TimeStampYesterday = get_timestamp_daydiff(TimeStamp, - -1), - {_FdYesterday, FnYesterday, DatePrev} = - build_filename_string(TimeStampYesterday, OutDir, - Room#room.jid, DirType, DirName, - FileFormat), - TimeStampTomorrow = get_timestamp_daydiff(TimeStamp, 1), - {_FdTomorrow, _FnTomorrow, DateNext} = - build_filename_string(TimeStampTomorrow, OutDir, - Room#room.jid, DirType, DirName, - FileFormat), - HourOffset = calc_hour_offset(TimeStamp), - put_header(F, Room, Datestring, CSSFile, Lang, - HourOffset, DatePrev, DateNext, TopLink, FileFormat), - Images_dir = fjoin([OutDir, <<"images">>]), - file:make_dir(Images_dir), - create_image_files(Images_dir), - Images_url = case DirType of - subdirs -> <<"../../../images">>; - plain -> <<"../images">> - end, - close_previous_log(FnYesterday, Images_url, FileFormat) - end, - Text = case Message of - roomconfig_change -> - RoomConfig = roomconfig_to_string(Room#room.config, - Lang, FileFormat), - put_room_config(F, RoomConfig, Lang, FileFormat), - io_lib:format("<font class=\"mrcm\">~s</font><br/>", - [?T(<<"Chatroom configuration modified">>)]); - {roomconfig_change, Occupants} -> - RoomConfig = roomconfig_to_string(Room#room.config, - Lang, FileFormat), - put_room_config(F, RoomConfig, Lang, FileFormat), - RoomOccupants = roomoccupants_to_string(Occupants, - FileFormat), - put_room_occupants(F, RoomOccupants, Lang, FileFormat), - io_lib:format("<font class=\"mrcm\">~s</font><br/>", - [?T(<<"Chatroom configuration modified">>)]); - join -> - io_lib:format("<font class=\"mj\">~s ~s</font><br/>", - [Nick, ?T(<<"joins the room">>)]); - leave -> - io_lib:format("<font class=\"ml\">~s ~s</font><br/>", - [Nick, ?T(<<"leaves the room">>)]); - {leave, Reason} -> - io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>", - [Nick, ?T(<<"leaves the room">>), - htmlize(Reason, NoFollow, FileFormat)]); - {kickban, <<"301">>, <<"">>} -> - io_lib:format("<font class=\"mb\">~s ~s</font><br/>", - [Nick, ?T(<<"has been banned">>)]); - {kickban, <<"301">>, Reason} -> - io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>", - [Nick, ?T(<<"has been banned">>), - htmlize(Reason, FileFormat)]); - {kickban, <<"307">>, <<"">>} -> - io_lib:format("<font class=\"mk\">~s ~s</font><br/>", - [Nick, ?T(<<"has been kicked">>)]); - {kickban, <<"307">>, Reason} -> - io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>", - [Nick, ?T(<<"has been kicked">>), - htmlize(Reason, FileFormat)]); - {kickban, <<"321">>, <<"">>} -> - io_lib:format("<font class=\"mk\">~s ~s</font><br/>", - [Nick, - ?T(<<"has been kicked because of an affiliation " - "change">>)]); - {kickban, <<"322">>, <<"">>} -> - io_lib:format("<font class=\"mk\">~s ~s</font><br/>", - [Nick, - ?T(<<"has been kicked because the room has " - "been changed to members-only">>)]); - {kickban, <<"332">>, <<"">>} -> - io_lib:format("<font class=\"mk\">~s ~s</font><br/>", - [Nick, - ?T(<<"has been kicked because of a system " - "shutdown">>)]); - {nickchange, OldNick} -> - io_lib:format("<font class=\"mnc\">~s ~s ~s</font><br/>", - [htmlize(OldNick, FileFormat), - ?T(<<"is now known as">>), Nick]); - {subject, T} -> - io_lib:format("<font class=\"msc\">~s~s~s</font><br/>", - [Nick, ?T(<<" has set the subject to: ">>), - htmlize(T, NoFollow, FileFormat)]); - {body, T} -> - case {ejabberd_regexp:run(T, <<"^/me ">>), Nick} of - {_, <<"">>} -> - io_lib:format("<font class=\"msm\">~s</font><br/>", - [htmlize(T, NoFollow, FileFormat)]); - {match, _} -> - io_lib:format("<font class=\"mne\">~s ~s</font><br/>", - [Nick, - str:substr(htmlize(T, FileFormat), 5)]); - {nomatch, _} -> - io_lib:format("<font class=\"mn\">~s</font> ~s<br/>", - [Nick2, htmlize(T, NoFollow, FileFormat)]) - end; - {room_existence, RoomNewExistence} -> - io_lib:format("<font class=\"mrcm\">~s</font><br/>", - [get_room_existence_string(RoomNewExistence, - Lang)]) - end, - {Hour, Minute, Second} = Time, - STime = io_lib:format("~2..0w:~2..0w:~2..0w", - [Hour, Minute, Second]), - {_, _, Microsecs} = Now, - STimeUnique = io_lib:format("~s.~w", - [STime, Microsecs]), - catch fw(F, - list_to_binary( - io_lib:format("<a id=\"~s\" name=\"~s\" href=\"#~s\" " - "class=\"ts\">[~s]</a> ", - [STimeUnique, STimeUnique, STimeUnique, STime]) - ++ Text), - FileFormat), - file:close(F), - ok. - -%%---------------------------------------------------------------------- -%% Utilities - -get_room_existence_string(created, Lang) -> - ?T(<<"Chatroom is created">>); -get_room_existence_string(destroyed, Lang) -> - ?T(<<"Chatroom is destroyed">>); -get_room_existence_string(started, Lang) -> - ?T(<<"Chatroom is started">>); -get_room_existence_string(stopped, Lang) -> - ?T(<<"Chatroom is stopped">>). - -get_dateweek(Date, Lang) -> - Weekday = case calendar:day_of_the_week(Date) of - 1 -> ?T(<<"Monday">>); - 2 -> ?T(<<"Tuesday">>); - 3 -> ?T(<<"Wednesday">>); - 4 -> ?T(<<"Thursday">>); - 5 -> ?T(<<"Friday">>); - 6 -> ?T(<<"Saturday">>); - 7 -> ?T(<<"Sunday">>) - end, - {Y, M, D} = Date, - Month = case M of - 1 -> ?T(<<"January">>); - 2 -> ?T(<<"February">>); - 3 -> ?T(<<"March">>); - 4 -> ?T(<<"April">>); - 5 -> ?T(<<"May">>); - 6 -> ?T(<<"June">>); - 7 -> ?T(<<"July">>); - 8 -> ?T(<<"August">>); - 9 -> ?T(<<"September">>); - 10 -> ?T(<<"October">>); - 11 -> ?T(<<"November">>); - 12 -> ?T(<<"December">>) - end, - list_to_binary( - case Lang of - <<"en">> -> - io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]); - <<"es">> -> - io_lib:format("~s ~w de ~s de ~w", - [Weekday, D, Month, Y]); - _ -> - io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y]) - end). - -make_dir_rec(Dir) -> - DirS = binary_to_list(Dir), - case file:read_file_info(DirS) of - {ok, _} -> ok; - {error, enoent} -> - DirL = [list_to_binary(F) || F <- filename:split(DirS)], - DirR = lists:sublist(DirL, length(DirL) - 1), - make_dir_rec(fjoin(DirR)), - file:make_dir(DirS), - file:change_mode(DirS, 8#00755) % -rwxr-xr-x - end. - -%% {ok, F1}=file:open("valid-xhtml10.png", [read]). -%% {ok, F1b}=file:read(F1, 1000000). -%% c("../../ejabberd/src/jlib.erl"). -%% jlib:encode_base64(F1b). - -image_base64(<<"powered-by-erlang.png">>) -> - <<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoA" - "AADN0lEQVRo3u1aP0waURz+rjGRRQ+nUyRCYmJyDPTapD" - "ARaSIbTUjt1gVSh8ZW69aBAR0cWLSxCXWp59LR1jbdqKn" - "GxoQuRZZrSYyHEVM6iZMbHewROA7u3fHvkr5vOn737vcu" - "33ffu9/vcQz+gef5Cij6CkmSGABgFEH29r5SVvqIsTEOH" - "o8HkiQxDBXEOjg9PcHc3BxuUSqsI8jR0REAUFGsCCoKFY" - "WCBAN6AxyO0Z7cyMXFb6oGqSgAsIrJut9hMQlvdNbUhKW" - "shLd3HtTF4jihShgVpRaBxKKmIGX5HL920/hz/BM2+zAm" - "pn2YioQaxnECj0BiEYcrG0Tzzc8/rfudSm02jaVSm9Vr1" - "MdG8rSKKXlJ7lHrfjouCut2IrC82BDPbe/gc+xlXez7Kx" - "Ez63H4lmIN473Rh8Si1BKhRY6aEJI8pLmbjSPN0xOnBBI" - "Lmg5RC6Lg28preKOzsNmHG8R1Bf0o7GdMucUslDy1pJLG" - "2sndVVG0lq3c9vum4zmBR1kuwiYMN5ybmCYXxQg57ThFO" - "TYznzpPO+IQi+IK+jXjg/YhuIJ+cIIHg+wQJoJ+2N3jYN" - "3Olvk4ge/IU98spne+FfGtlslm16nna8fduntfDscoVjG" - "JqUgIjz686ViFUdjP4N39x9Xq638viZVtlq2tLXKncLf5" - "ticuZSWU5XOUshJKxxKtfdtdvs4OyNb/68urKvlluYizg" - "wwu5SLK8jllu1t9ihYOlzdwdpBBKSvh+vKKzHkCj1JW3y" - "1m+hSj13WjqOiJKK0qpXKhSFxJAYBvKYaZ9TjWRu4SiWi" - "2LyDtb6wghGmn5HfTml16ILGA/G5al2DW7URYTFYrOU7g" - "icQ020sYqYDM9CbdgqFd4vzHL03JfvLjk6ZgADAVCSEsJ" - "vHsdL+utNYrm2ufZDVZSkzPKaQkW8kthpyS297BvRdRzR" - "6DdTurJbPy9Ov1K6xr3HBPQuIMowR3asegUyDuU9SuUG+" - "dmIGyZ0b7FBN9St3WunyC5yMsrVv7uXzRP58s/qKn6C4q" - "lQoVxVIvd4YBwzBUFKs6ZaD27U9hEdcAN98Sx2IxykafI" - "YrizbfESoB+dd9/KF/d/wX3cJvREzl1vAAAAABJRU5Erk" - "Jggg==">>; -image_base64(<<"valid-xhtml10.png">>) -> - <<"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAAEjEcpEA" - "AACiFBMVEUAAADe5+fOezmtra3ejEKlhELvvWO9WlrehE" - "LOe3vepaWclHvetVLGc3PerVKcCAj3vVqUjHOUe1JjlL0" - "xOUpjjL2UAAC91ueMrc7vrVKlvdbW3u+EpcbO3ufO1ucY" - "WpSMKQi9SiF7e3taWkoQEAiMczkQSoxaUkpzc3O1lEoIC" - "ACEazEhGAgIAACEYzFra2utjELWcznGnEr/7+9jY2POaz" - "HOYzGta2NShLVrlL05OUqctdacCADGa2ucAADGpVqUtc6" - "1ORg5OTmlUikYGAiUezl7YzEYEAiUczkxMTG9nEqtIRDe" - "3t4AMXu9lEoQCACMazEAKXspKSmljFrW1ta1jELOzs7n7" - "/fGxsa9pVqEOSkpY5xznL29tZxahLXOpVr/99ZrY1L/79" - "ZjUiljSikAOYTvxmMAMYScezmchFqUczGtlFp7c2utjFq" - "UlJStxt73///39/9Ce61CSkq9xsZznMbW5+9Cc62MjIxC" - "Qkrv9/fv7/fOzsbnlErWjIz/3mtCORhza1IpIRBzWjH/1" - "mtCMRhzY1L/zmvnvVpSQiHOpVJrUinntVr3zmOEc1L3xm" - "NaWlq1nFo5QkrGWim1lFoISpRSUlK1zt4hWpwASoz////" - "///8xa6WUaykAQoxKe61KSkp7nMbWtWPe5+9jWlL39/f3" - "9/fWrWNCQkLera3nvWPv7+85MRjntWPetVp7c1IxKRCUl" - "HtKORh7a1IxIRCUjHtaSiHWrVIpIQhzWinvvVpaQiH/1m" - "PWpVKMe1L/zmP/xmNrUiGErc4YGBj/73PG1ucQWpT/53O" - "9nFoQUpS1SiEQEBC9zt69vb05c6UISoxSUko5a6UICAhS" - "SkohUpS1tbXetWMAQoSUgD+kAAAA2HRSTlP/////////i" - "P9sSf//dP////////////////////////////////////" - "////////////8M////////////ef/////////////////" - "/////////////////////////////////////////////" - "//////////////////////9d/////////////////////" - "///////////////AP//////////////CP//RP////////" - "/////////////////////////////////////////////" - "///////9xPp1gAAAFvUlEQVR42pVWi18URRwfy7vsYUba" - "iqBRBFmICUQGVKcZckQeaRJQUCLeycMSfKGH0uo5NELpI" - "vGQGzokvTTA85VHKTpbRoeJnPno/p1+M7t3txj20e/Nzu" - "7Ofve7v/k9Zg4Vc+wRQMW0eyLx1ZSANeBDxVmxZZSwEUY" - "kGAewm1eIBOMRvhv1UA+q8KXIVuxGdCelFYwxAnxOrxgb" - "Y8Ti1t4VA0QHYz4x3FnVC8OVLXv9fkKGSWDoW/4lG6Vbd" - "tBblesOs+MjmEmzJKNIJWFEfEQTCWNPFKvcKEymjLO1b8" - "bwYQd1hCiiDCl5KsrDCIlhj4fSuvcpfSpgJmyv6dzeZv+" - "nMPx3dhbt94II07/JZliEtm1N2RIYPkTYshwYm245a/zk" - "WjJwcyFh6ZIcYxxmqiaDSYxhOhFUsqngi3Fzcj3ljdYDN" - "E9uzA1YD/5MhnzW1KRqF7mYG8jFYXLcfLpjOe2LA0fuGq" - "QrQHl10sdK0sFcFSOSlzF0BgXQH9h3QZDBI0ccNEhftjX" - "uippBDD2/eMRiETmwwNEYHyqhdDyo22w+3QHuNbdve5a7" - "eOkHmDVJ0ixNmfbz1h0qo/Q6GuSB2wQJQbpOjOQAl7woW" - "SRJ0m2ewhvAOUiYYtZtaZL0CZZmtmVOQttLfr/dbveLZo" - "drfrL7W75wG/JjqkQxoNTtNsTKELQpQL6/D5loaSmyTT8" - "TUhsmi8iFA0hZiyltf7OiNKdarRm5w2So2lTNdPLuIzR+" - "AiLj8VTRJaj0LmX4VhJ27f/VJV/yycilWPOrk8NkXi7Qq" - "mj5bHqVZlJKZIRk1wFzKrt0WUbnXMPJ1fk4TJ5oWBA61p" - "1V76DeIs0MX+s3GxRlA1vtw83KhgNphc1nyErLO5zcvbO" - "srq+scbZnpzc6QVFPenLwGxmC+BOfYI+DN55QYddh4Q/N" - "E/yGYYj4TOGNngQavAZnzzTovEA+kcMJ+247uYexNA+4F" - "svjmuv662jsWxPZx2xg890bYMYnTgya7bjmCiEY0qgJ0v" - "MF3c+NoFdPyzxz6V3Uxs3AOWCDchRvOsQtBrbFsrT2fhH" - "Ec7ByGzu/dA4IO0A3HdfeP9yMqAwP6NPEb6cbwn0PWVU1" - "7/FDBQh/CPIrbfcg027IZrsAT/Bf3FNWyn9RSR4cvvwn3" - "e4HFmYPDl/thYcRVi8qPEoXVUWBl6FTBFTtnqmKKg5wnl" - "F4wZ1yeLv7TiwXKektE+iDBNicWEyLpnFhfDkpJc3q2kh" - "SPyQBbE0dMJnOoDzTwGsI7cdyMkL5gWqUjCF6Txst/twx" - "Cv1WzzHoy21ZDQ1xnuDzdPDWR4knr14v0tYn3IxaMFFdi" - "MOlEOJHw1jOQ4sWt5rQopRkXZhMEi7pmeDCVWBlfUKwhM" - "Z7rsF6elKsvbwiKxgxIdewa3ErsaYomCVZFYJb0GUu3Jq" - "GUNoplBxYiYby8vLBFWef+Cri4/I1sbQ/1OtYTrNtdXS+" - "rSe7kQ52eSObL99/iErCWUjCy5W4JLygmCouGfG9x9fmx" - "17XhBuDCaOerbt538erta7TFktLvdHghZcCbcPQO33zIJ" - "G9kxF5hoVXnzTzRz0r5js8oTj6uyPkGRf346HOLcasgFe" - "xueNUWFPtuFKzjoSFYYedhwVlhsRVYWWJpltv1XPQT1Rl" - "0bjZIBlb1XujVDzY/Kj4k6Ku3+Z0jo1owjVzDpFTXe1ju" - "vBSWNFmNWGZy8LvzUl5PN4JCwyNDzbQ0aAj4Zrjz0FatG" - "JJYhvq4j7mGSpvytGFlZtHf2C4o/28Zu8z7wo7eYPfXys" - "nF0i9NnPh1t1zR7VBb9GqaOXhtTmHQdgMFXE+Z608cnpO" - "DdZdjL+TuDY44Q38kJXHhccWLoOd9uv1AwwvO+48uu+fa" - "CSJPJ1bmy6ThyvpivBmYWgjxPDPAp7JTemY/yGKFEiRt/" - "jG/2P79s8KCwoLCgoLC/khUBA5F0SfQZ+RYfpNE/4Xosm" - "q7jsZAJsAAAAASUVORK5CYII=">>; -image_base64(<<"vcss.png">>) -> - <<"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAABUFvrSA" - "AABKVBMVEUAAAAjIx8MR51ZVUqAdlmdnZ3ejEWLDAuNjY" - "1kiMG0n2d9fX19Ghfrp1FtbW3y39+3Ph6lIRNdXV2qJBF" - "cVUhcVUhPT0/dsmpUfLr57+/u7u4/PDWZAACZAADOp1Gd" - "GxG+SyTgvnNdSySzk16+mkuxw+BOS0BOS0DOzs7MzMy4T" - "09RRDwsJBG+vr73wV6fkG6eCQRFcLSurq6/X1+ht9nXfz" - "5sepHuwV59ZTHetFjQ2+wMCQQ2ZK5tWCsmWajsz8+Sq9N" - "MPh4hVaY8MRj///////////////////////9MTEyOp9Lu" - "8vhXU1A8PDyjOSTBz+YLRJ2rLy8sLCwXTaKujEUcHByDn" - "82dfz7/zGafDw+fDw+zRSlzlMcMDAyNcji1tbXf5vIcFg" - "vATJOjAAAAY3RSTlP/8/////////////////8A//////P" - "/////ov//8//////////////z///T//////////+i////" - "//////////8w/////6IA/xAgMP//////////8////////" - "/8w0/////////+zehebAAACkUlEQVR42u2VfVPTQBDG19" - "VqC6LY+lKrRIxFQaFSBPuSvhBPF8SIUZK2J5Yav/+HcO8" - "uZdLqTCsU/nKnyWwvk1/unnt2D9ZmH+8/cMAaTRFy+ng6" - "9/yiwC/+gy8R3McGv5zHvGJEGAdR4eBgi1IbZwevIEZE2" - "4pFtBtzG1Q4AoD5zvw5pEDcJvIQV/TE3/l+H9GnNJwcdA" - "BS5wAbFQLMqI98/UReoAaOTlaJsp0zaHx7LwZvY0BUR2x" - "pWTzqam0gzY8KGzG4MhBCNGucha4QbpETy+Yk/BP85nt7" - "34AjpQLTsE4ZFpf/dnkUCglXVNYB+OfUZJHvAqAoa45Oe" - "uPgm4+Xjtv7xm4N7PMV4C61+Mrz3H2WImm3ATiWrAiwZR" - "WcUA5Ej4dgIEMxDv6yxHHcNuAutnjv2HZ1NeuycoVPh0m" - "wC834zZC9Ao5dkZZKwLVGwT+WdLw0YOZ1saEkUDoT+QGW" - "KZ0E2xpcrPakVW2KXwyUtYEtlEAj3GXD/fYwrryAdeiyG" - "qidQSw1eqtJcA8cZq4zXqhPuCBYE1fKJjh/5X6MwRm9c2" - "xf7WVdLf5oSdt64esVIwVAKC1HJ2oli8vj3L0YzC4zjkM" - "agt+arDAs6bApbL1RVlWIqrJbreqKZmh4y6VR7rAJeUYD" - "VRj9VqRXkErpJ9lbEwtE83KlIfeG4p52t7zWIMO1XcaGz" - "54uUyet+hBM7BXXDS8Xc5+8Gmmbu1xwSoGIokA3oTptQe" - "cQ4Iimm/Ew7jwbPfMi3TM91T9XVIGo+W9xC8oWpugVCXL" - "uwXijjxJ3r/6PjX7nlFua8QmyM+TO/Gja2TTc2Z95C5ua" - "ewGH6cJi6bJO6Z+TY276eH3tbgy+/3ly3Js+rj66osG/A" - "V5htgaQ9SeRAAAAAElFTkSuQmCC">>; -image_base64(<<"powered-by-ejabberd.png">>) -> - <<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAMAAADJG/NaA" - "AAAw1BMVEUAAAAjBgYtBAM5AwFCAAAYGAJNAABcAABIDQ" - "5qAAAoJRV7AACFAAAoKSdJHByLAAAwLwk1NQA1MzFJKyo" - "4NxtDQQBEQT5KSCxSTgBSUBlgQ0JYSEpZWQJPUU5hYABb" - "W0ZiYClcW1poaCVwbQRpaDhzYWNsakhuZ2VrbFZ8dwCEg" - "AB3dnd4d2+OjACDhYKcmACJi4iQkpWspgCYmJm5swCmqa" - "zEwACwsbS4ub3X0QLExsPLyszW1Nnc3ODm5ugMBwAWAwP" - "Hm1IFAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJ" - "cEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfVCRQOBA7VB" - "kCMAAACcElEQVRIx72WjXKiMBSFQalIFbNiy1pdrJZaRV" - "YR5deGwPs/VRNBSBB2OjvQO0oYjPfj5J6bCcdx8i2Uldx" - "KcDhk1HbIPwFBF/kHKJfjPSVAyIRHF9rRZ4sUX3EDdWOv" - "1+u2tESaavpnYTbv9zvd0WwDy3/QcGQXlH5uTxB1l07MJ" - "lRpsUei0JF6Qi+OHyGK7ijXxPklHe/umIllim3iUBMJDI" - "EULxxPP0TVWhhKJoN9fUpdmQLteV8aDgEAg9gIcTjL4F4" - "L+r4WVKEF+rbJdwYYAoQHY+oQjnGootyKwxapoi73WkyF" - "FySQBv988naEEp4+YMMec5VUCQDJTscEy7Kc0HsLmqNE7" - "rovDjMpIHHGYeidXn4TQcaxMYqP3RV3C8oCl2WvrlSPaN" - "pGZadRnmPGCk8ylM2okAJ4i9TEe1KersXxSl6jUt5uayi" - "IodirtcKLOaWblj50wiyMv1F9lm9TUDArGAD0FmEpvCUs" - "VoZy6dW81Fg0aDaHogQa36ekAPG5DDGsbdZrGsrzZUnzv" - "Bo1I2tLmuL69kSitAweyHKN9b3leDfQMnu3nIIKWfmXnq" - "GVKedJT6QpICbJvf2f8aOsvn68v+k7/cwUQdPoxaMoRTn" - "KFHNlKsKQphCTOa84u64vpi8bH31CqsbF6lSONRTkTyQG" - "Arq49/fEvjBwz4eDS2/JpaXRNOoXRD/VmOrDVTJJRIZCT" - "Lav3VrqbPvP3vdduGEhQJzilncbpSA4F3vsihErO+dayv" - "/sY5/yRE0GDEXCu2VoNiMlo5i+P2KlgMEvTNk2eYa5XEy" - "h12Ex17Z8vzQUR3KEPbYd6XG87eC4Ly75RneS5ZYHAAAA" - "AElFTkSuQmCC">>. - -create_image_files(Images_dir) -> - Filenames = [<<"powered-by-ejabberd.png">>, - <<"powered-by-erlang.png">>, <<"valid-xhtml10.png">>, - <<"vcss.png">>], - lists:foreach(fun (Filename) -> - Filename_full = fjoin([Images_dir, Filename]), - {ok, F} = file:open(Filename_full, [write]), - Image = jlib:decode_base64(image_base64(Filename)), - io:format(F, <<"~s">>, [Image]), - file:close(F) - end, - Filenames), - ok. - -fw(F, S) -> fw(F, S, [], html). - -fw(F, S, O) when is_list(O) -> fw(F, S, O, html); -fw(F, S, FileFormat) when is_atom(FileFormat) -> - fw(F, S, [], FileFormat). - -fw(F, S, O, FileFormat) -> - S1 = list_to_binary(io_lib:format(binary_to_list(S) ++ "~n", O)), - S2 = case FileFormat of - html -> - S1; - plaintext -> - S1a = ejabberd_regexp:greplace(S1, <<"<[^<^>]*>">>, <<"">>), - S1x = ejabberd_regexp:greplace(S1a, ?PLAINTEXT_CO, <<"~~">>), - S1y = ejabberd_regexp:greplace(S1x, ?PLAINTEXT_IN, <<"<">>), - ejabberd_regexp:greplace(S1y, ?PLAINTEXT_OUT, <<">">>) - end, - io:format(F, S2, []). - -put_header(_, _, _, _, _, _, _, _, _, plaintext) -> ok; -put_header(F, Room, Date, CSSFile, Lang, Hour_offset, - Date_prev, Date_next, Top_link, FileFormat) -> - fw(F, - <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD " - "XHTML 1.0 Transitional//EN\" \"http://www.w3." - "org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">">>), - fw(F, - <<"<html xmlns=\"http://www.w3.org/1999/xhtml\" " - "xml:lang=\"~s\" lang=\"~s\">">>, - [Lang, Lang]), - fw(F, <<"<head>">>), - fw(F, - <<"<meta http-equiv=\"Content-Type\" content=\"t" - "ext/html; charset=utf-8\" />">>), - fw(F, <<"<title>~s - ~s</title>">>, - [htmlize(Room#room.title), Date]), - put_header_css(F, CSSFile), - put_header_script(F), - fw(F, <<"</head>">>), - fw(F, <<"<body>">>), - {Top_url, Top_text} = Top_link, - fw(F, - <<"<div style=\"text-align: right;\"><a " - "style=\"color: #AAAAAA; font-family: " - "monospace; text-decoration: none; font-weight" - ": bold;\" href=\"~s\">~s</a></div>">>, - [Top_url, Top_text]), - fw(F, <<"<div class=\"roomtitle\">~s</div>">>, - [htmlize(Room#room.title)]), - fw(F, - <<"<a class=\"roomjid\" href=\"xmpp:~s?join\">~s" - "</a>">>, - [Room#room.jid, Room#room.jid]), - fw(F, - <<"<div class=\"logdate\">~s<span class=\"w3c\">" - "<a class=\"nav\" href=\"~s\"><</a> " - "<a class=\"nav\" href=\"./\">^</a> <a " - "class=\"nav\" href=\"~s\">></a></span></di" - "v>">>, - [Date, Date_prev, Date_next]), - case {htmlize(Room#room.subject_author), - htmlize(Room#room.subject)} - of - {<<"">>, <<"">>} -> ok; - {SuA, Su} -> - fw(F, <<"<div class=\"roomsubject\">~s~s~s</div>">>, - [SuA, ?T(<<" has set the subject to: ">>), Su]) - end, - RoomConfig = roomconfig_to_string(Room#room.config, - Lang, FileFormat), - put_room_config(F, RoomConfig, Lang, FileFormat), - Occupants = get_room_occupants(Room#room.jid), - RoomOccupants = roomoccupants_to_string(Occupants, - FileFormat), - put_room_occupants(F, RoomOccupants, Lang, FileFormat), - Time_offset_str = case Hour_offset < 0 of - true -> io_lib:format("~p", [Hour_offset]); - false -> io_lib:format("+~p", [Hour_offset]) - end, - fw(F, <<"<br/><a class=\"ts\">GMT~s</a><br/>">>, - [Time_offset_str]). - -put_header_css(F, false) -> - fw(F, <<"<style type=\"text/css\">">>), - fw(F, <<"<!--">>), - fw(F, - <<".ts {color: #AAAAAA; text-decoration: " - "none;}">>), - fw(F, - <<".mrcm {color: #009900; font-style: italic; " - "font-weight: bold;}">>), - fw(F, - <<".msc {color: #009900; font-style: italic; " - "font-weight: bold;}">>), - fw(F, - <<".msm {color: #000099; font-style: italic; " - "font-weight: bold;}">>), - fw(F, <<".mj {color: #009900; font-style: italic;}">>), - fw(F, <<".ml {color: #009900; font-style: italic;}">>), - fw(F, <<".mk {color: #009900; font-style: italic;}">>), - fw(F, <<".mb {color: #009900; font-style: italic;}">>), - fw(F, <<".mnc {color: #009900; font-style: italic;}">>), - fw(F, <<".mn {color: #0000AA;}">>), - fw(F, <<".mne {color: #AA0099;}">>), - fw(F, - <<"a.nav {color: #AAAAAA; font-family: " - "monospace; letter-spacing: 3px; text-decorati" - "on: none;}">>), - fw(F, - <<"div.roomtitle {border-bottom: #224466 " - "solid 3pt; margin-left: 20pt;}">>), - fw(F, - <<"div.roomtitle {color: #336699; font-size: " - "24px; font-weight: bold; font-family: " - "sans-serif; letter-spacing: 3px; text-decorat" - "ion: none;}">>), - fw(F, - <<"a.roomjid {color: #336699; font-size: " - "24px; font-weight: bold; font-family: " - "sans-serif; letter-spacing: 3px; margin-left: " - "20pt; text-decoration: none;}">>), - fw(F, - <<"div.logdate {color: #663399; font-size: " - "20px; font-weight: bold; font-family: " - "sans-serif; letter-spacing: 2px; border-botto" - "m: #224466 solid 1pt; margin-left:80pt; " - "margin-top:20px;}">>), - fw(F, - <<"div.roomsubject {color: #336699; font-size: " - "18px; font-family: sans-serif; margin-left: " - "80pt; margin-bottom: 10px;}">>), - fw(F, - <<"div.rc {color: #336699; font-size: 12px; " - "font-family: sans-serif; margin-left: " - "50%; text-align: right; background: " - "#f3f6f9; border-bottom: 1px solid #336699; " - "border-right: 4px solid #336699;}">>), - fw(F, - <<"div.rct {font-weight: bold; background: " - "#e3e6e9; padding-right: 10px;}">>), - fw(F, <<"div.rcos {padding-right: 10px;}">>), - fw(F, <<"div.rcoe {color: green;}">>), - fw(F, <<"div.rcod {color: red;}">>), - fw(F, <<"div.rcoe:after {content: \": v\";}">>), - fw(F, <<"div.rcod:after {content: \": x\";}">>), - fw(F, <<"div.rcot:after {}">>), - fw(F, - <<".legend {width: 100%; margin-top: 30px; " - "border-top: #224466 solid 1pt; padding: " - "10px 0px 10px 0px; text-align: left; " - "font-family: monospace; letter-spacing: " - "2px;}">>), - fw(F, - <<".w3c {position: absolute; right: 10px; " - "width: 60%; text-align: right; font-family: " - "monospace; letter-spacing: 1px;}">>), - fw(F, <<"//-->">>), - fw(F, <<"</style>">>); -put_header_css(F, CSSFile) -> - fw(F, - <<"<link rel=\"stylesheet\" type=\"text/css\" " - "href=\"~s\" media=\"all\">">>, - [CSSFile]). - -put_header_script(F) -> - fw(F, <<"<script type=\"text/javascript\">">>), - fw(F, <<"function sh(e) // Show/Hide an element">>), - fw(F, - <<"{if(document.getElementById(e).style.display=" - "='none')">>), - fw(F, - <<"{document.getElementById(e).style.display='bl" - "ock';}">>), - fw(F, - <<"else {document.getElementById(e).style.displa" - "y='none';}}">>), - fw(F, <<"</script>">>). - -put_room_config(_F, _RoomConfig, _Lang, plaintext) -> - ok; -put_room_config(F, RoomConfig, Lang, _FileFormat) -> - {_, Now2, _} = now(), - fw(F, <<"<div class=\"rc\">">>), - fw(F, - <<"<div class=\"rct\" onclick=\"sh('a~p');return " - "false;\">~s</div>">>, - [Now2, ?T(<<"Room Configuration">>)]), - fw(F, - <<"<div class=\"rcos\" id=\"a~p\" style=\"displa" - "y: none;\" ><br/>~s</div>">>, - [Now2, RoomConfig]), - fw(F, <<"</div>">>). - -put_room_occupants(_F, _RoomOccupants, _Lang, - plaintext) -> - ok; -put_room_occupants(F, RoomOccupants, Lang, - _FileFormat) -> - {_, Now2, _} = now(), -%% htmlize -%% The default behaviour is to ignore the nofollow spam prevention on links -%% (NoFollow=false) - fw(F, <<"<div class=\"rc\">">>), - fw(F, - <<"<div class=\"rct\" onclick=\"sh('o~p');return " - "false;\">~s</div>">>, - [Now2, ?T(<<"Room Occupants">>)]), - fw(F, - <<"<div class=\"rcos\" id=\"o~p\" style=\"displa" - "y: none;\" ><br/>~s</div>">>, - [Now2, RoomOccupants]), - fw(F, <<"</div>">>). - -htmlize(S1) -> htmlize(S1, html). - -htmlize(S1, plaintext) -> - ejabberd_regexp:greplace(S1, <<"~">>, ?PLAINTEXT_CO); -htmlize(S1, FileFormat) -> - htmlize(S1, false, FileFormat). - -%% The NoFollow parameter tell if the spam prevention should be applied to the link found -%% true means 'apply nofollow on links'. -htmlize(S0, _NoFollow, plaintext) -> - S1 = ejabberd_regexp:greplace(S0, <<"~">>, ?PLAINTEXT_CO), - S1x = ejabberd_regexp:greplace(S1, <<"<">>, ?PLAINTEXT_IN), - ejabberd_regexp:greplace(S1x, <<">">>, ?PLAINTEXT_OUT); -htmlize(S1, NoFollow, _FileFormat) -> - S2_list = str:tokens(S1, <<"\n">>), - lists:foldl(fun (Si, Res) -> - Si2 = htmlize2(Si, NoFollow), - case Res of - <<"">> -> Si2; - _ -> <<Res/binary, "<br/>", Si2/binary>> - end - end, - <<"">>, S2_list). - -htmlize2(S1, NoFollow) -> -%% Regexp link -%% Add the nofollow rel attribute when required - S2 = ejabberd_regexp:greplace(S1, <<"\\&">>, - <<"\\&">>), - S3 = ejabberd_regexp:greplace(S2, <<"<">>, - <<"\\<">>), - S4 = ejabberd_regexp:greplace(S3, <<">">>, - <<"\\>">>), - S5 = ejabberd_regexp:greplace(S4, - <<"((http|https|ftp)://|(mailto|xmpp):)[^] " - ")'\"}]+">>, - link_regexp(NoFollow)), - S6 = ejabberd_regexp:greplace(S5, <<" ">>, - <<"\\ \\ ">>), - S7 = ejabberd_regexp:greplace(S6, <<"\\t">>, - <<"\\ \\ \\ \\ ">>), - ejabberd_regexp:greplace(S7, <<226, 128, 174>>, - <<"[RLO]">>). - -link_regexp(false) -> <<"<a href=\"&\">&</a>">>; -link_regexp(true) -> - <<"<a href=\"&\" rel=\"nofollow\">&</a>">>. - -get_room_info(RoomJID, Opts) -> - Title = case lists:keysearch(title, 1, Opts) of - {value, {_, T}} -> T; - false -> <<"">> - end, - Subject = case lists:keysearch(subject, 1, Opts) of - {value, {_, S}} -> S; - false -> <<"">> - end, - SubjectAuthor = case lists:keysearch(subject_author, 1, - Opts) - of - {value, {_, SA}} -> SA; - false -> <<"">> - end, - #room{jid = jlib:jid_to_string(RoomJID), title = Title, - subject = Subject, subject_author = SubjectAuthor, - config = Opts}. - -roomconfig_to_string(Options, Lang, FileFormat) -> - Title = case lists:keysearch(title, 1, Options) of - {value, Tuple} -> [Tuple]; - false -> [] - end, - Os1 = lists:keydelete(title, 1, Options), - Os2 = lists:sort(Os1), - Options2 = Title ++ Os2, - lists:foldl(fun ({Opt, Val}, R) -> - case get_roomconfig_text(Opt) of - undefined -> R; - OptT -> - OptText = (?T(OptT)), - R2 = case Val of - false -> - <<"<div class=\"rcod\">", - OptText/binary, "</div>">>; - true -> - <<"<div class=\"rcoe\">", - OptText/binary, "</div>">>; - <<"">> -> - <<"<div class=\"rcod\">", - OptText/binary, "</div>">>; - T -> - case Opt of - password -> - <<"<div class=\"rcoe\">", - OptText/binary, "</div>">>; - max_users -> - <<"<div class=\"rcot\">", - OptText/binary, ": \"", - (htmlize(jlib:integer_to_binary(T), - FileFormat))/binary, - "\"</div>">>; - title -> - <<"<div class=\"rcot\">", - OptText/binary, ": \"", - (htmlize(T, - FileFormat))/binary, - "\"</div>">>; - description -> - <<"<div class=\"rcot\">", - OptText/binary, ": \"", - (htmlize(T, - FileFormat))/binary, - "\"</div>">>; - allow_private_messages_from_visitors -> - <<"<div class=\"rcot\">", - OptText/binary, ": \"", - (htmlize(?T((jlib:atom_to_binary(T))), - FileFormat))/binary, - "\"</div>">>; - _ -> <<"\"", T/binary, "\"">> - end - end, - <<R/binary, R2/binary>> - end - end, - <<"">>, Options2). - -get_roomconfig_text(title) -> <<"Room title">>; -get_roomconfig_text(persistent) -> - <<"Make room persistent">>; -get_roomconfig_text(public) -> - <<"Make room public searchable">>; -get_roomconfig_text(public_list) -> - <<"Make participants list public">>; -get_roomconfig_text(password_protected) -> - <<"Make room password protected">>; -get_roomconfig_text(password) -> <<"Password">>; -get_roomconfig_text(anonymous) -> - <<"This room is not anonymous">>; -get_roomconfig_text(members_only) -> - <<"Make room members-only">>; -get_roomconfig_text(moderated) -> - <<"Make room moderated">>; -get_roomconfig_text(members_by_default) -> - <<"Default users as participants">>; -get_roomconfig_text(allow_change_subj) -> - <<"Allow users to change the subject">>; -get_roomconfig_text(allow_private_messages) -> - <<"Allow users to send private messages">>; -get_roomconfig_text(allow_private_messages_from_visitors) -> - <<"Allow visitors to send private messages to">>; -get_roomconfig_text(allow_query_users) -> - <<"Allow users to query other users">>; -get_roomconfig_text(allow_user_invites) -> - <<"Allow users to send invites">>; -get_roomconfig_text(logging) -> <<"Enable logging">>; -get_roomconfig_text(allow_visitor_nickchange) -> - <<"Allow visitors to change nickname">>; -get_roomconfig_text(allow_visitor_status) -> - <<"Allow visitors to send status text in " - "presence updates">>; -get_roomconfig_text(captcha_protected) -> - <<"Make room captcha protected">>; -get_roomconfig_text(description) -> - <<"Room description">>; -%% get_roomconfig_text(subject) -> "Subject"; -%% get_roomconfig_text(subject_author) -> "Subject author"; -get_roomconfig_text(max_users) -> - <<"Maximum Number of Occupants">>; -get_roomconfig_text(_) -> undefined. - -%% Users = [{JID, Nick, Role}] -roomoccupants_to_string(Users, _FileFormat) -> - Res = [role_users_to_string(RoleS, Users1) - || {RoleS, Users1} <- group_by_role(Users), - Users1 /= []], - iolist_to_binary([<<"<div class=\"rcot\">">>, Res, <<"</div>">>]). - -%% Users = [{JID, Nick, Role}] -group_by_role(Users) -> -%% Role = atom() -%% Users = [{JID, Nick}] - {Ms, Ps, Vs, Ns} = lists:foldl(fun ({JID, Nick, - moderator}, - {Mod, Par, Vis, Non}) -> - {[{JID, Nick}] ++ Mod, Par, Vis, - Non}; - ({JID, Nick, participant}, - {Mod, Par, Vis, Non}) -> - {Mod, [{JID, Nick}] ++ Par, Vis, - Non}; - ({JID, Nick, visitor}, - {Mod, Par, Vis, Non}) -> - {Mod, Par, [{JID, Nick}] ++ Vis, - Non}; - ({JID, Nick, none}, - {Mod, Par, Vis, Non}) -> - {Mod, Par, Vis, [{JID, Nick}] ++ Non} - end, - {[], [], [], []}, Users), - case Ms of - [] -> []; - _ -> [{<<"Moderator">>, Ms}] - end - ++ - case Ms of - [] -> []; - _ -> [{<<"Participant">>, Ps}] - end - ++ - case Ms of - [] -> []; - _ -> [{<<"Visitor">>, Vs}] - end - ++ - case Ms of - [] -> []; - _ -> [{<<"None">>, Ns}] - end. - -role_users_to_string(RoleS, Users) -> - SortedUsers = lists:keysort(2, Users), - UsersString = << <<Nick/binary, "<br/>">> - || {_JID, Nick} <- SortedUsers >>, - <<RoleS/binary, ": ", UsersString/binary>>. - -get_room_occupants(RoomJIDString) -> - RoomJID = jlib:string_to_jid(RoomJIDString), - RoomName = RoomJID#jid.luser, - MucService = RoomJID#jid.lserver, - StateData = get_room_state(RoomName, MucService), - [{U#user.jid, U#user.nick, U#user.role} - || {_, U} <- (?DICT):to_list(StateData#state.users)]. - --spec get_room_state(binary(), binary()) -> muc_room_state(). - -get_room_state(RoomName, MucService) -> - case mnesia:dirty_read(muc_online_room, - {RoomName, MucService}) - of - [R] -> - RoomPid = R#muc_online_room.pid, - get_room_state(RoomPid); - [] -> #state{} - end. - --spec get_room_state(pid()) -> muc_room_state(). - -get_room_state(RoomPid) -> - {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid, - get_state), - R. - -get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME). - -calc_hour_offset(TimeHere) -> - TimeZero = calendar:now_to_universal_time(now()), - TimeHereHour = - calendar:datetime_to_gregorian_seconds(TimeHere) div - 3600, - TimeZeroHour = - calendar:datetime_to_gregorian_seconds(TimeZero) div - 3600, - TimeHereHour - TimeZeroHour. - -fjoin(FileList) -> - list_to_binary(filename:join([binary_to_list(File) || File <- FileList])). diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl deleted file mode 100644 index 61ec57766..000000000 --- a/src/mod_muc/mod_muc_room.erl +++ /dev/null @@ -1,4460 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_muc_room.erl -%%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : MUC room stuff -%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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_room). - --author('alexey@process-one.net'). - --behaviour(gen_fsm). - -%% External exports --export([start_link/9, - start_link/7, - start/9, - start/7, - route/4]). - -%% gen_fsm callbacks --export([init/1, - normal_state/2, - handle_event/3, - handle_sync_event/4, - handle_info/3, - terminate/3, - code_change/4]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("mod_muc_room.hrl"). - --define(MAX_USERS_DEFAULT_LIST, - [5, 10, 20, 30, 50, 100, 200, 500, 1000, 2000, 5000]). - -%-define(DBGFSM, true). - --ifdef(DBGFSM). - --define(FSMOPTS, [{debug, [trace]}]). - --else. - --define(FSMOPTS, []). - --endif. - -%% Module start with or without supervisor: --ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, - gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Creator, Nick, DefRoomOpts], - ?FSMOPTS)). --else. --define(SUPERVISOR_START, - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup), - supervisor:start_child( - Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Creator, Nick, DefRoomOpts])). --endif. - -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Creator, Nick, DefRoomOpts) -> - ?SUPERVISOR_START. - -start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) -> - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup), - supervisor:start_child( - Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Opts]). - -start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Creator, Nick, DefRoomOpts) -> - gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Creator, Nick, DefRoomOpts], - ?FSMOPTS). - -start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) -> - gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Opts], - ?FSMOPTS). - -%%%---------------------------------------------------------------------- -%%% Callback functions from gen_fsm -%%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- -init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, DefRoomOpts]) -> - process_flag(trap_exit, true), - Shaper = shaper:new(RoomShaper), - State = set_affiliation(Creator, owner, - #state{host = Host, server_host = ServerHost, - access = Access, room = Room, - history = lqueue_new(HistorySize), - jid = jlib:make_jid(Room, Host, <<"">>), - just_created = true, - room_shaper = Shaper}), - State1 = set_opts(DefRoomOpts, State), - ?INFO_MSG("Created MUC room ~s@~s by ~s", - [Room, Host, jlib:jid_to_string(Creator)]), - 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]) -> - process_flag(trap_exit, true), - Shaper = shaper:new(RoomShaper), - State = set_opts(Opts, #state{host = Host, - server_host = ServerHost, - access = Access, - room = Room, - history = lqueue_new(HistorySize), - jid = jlib:make_jid(Room, Host, <<"">>), - room_shaper = Shaper}), - add_to_log(room_existence, started, State), - {ok, normal_state, State}. - -%%---------------------------------------------------------------------- -%% Func: StateName/2 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -normal_state({route, From, <<"">>, - #xmlel{name = <<"message">>, attrs = Attrs, - children = Els} = - Packet}, - StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - case is_user_online(From, StateData) orelse - is_user_allowed_message_nonparticipant(From, StateData) - of - true -> - case xml:get_attr_s(<<"type">>, Attrs) of - <<"groupchat">> -> - Activity = get_user_activity(From, StateData), - Now = now_to_usec(now()), - MinMessageInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_message_interval, fun(MMI) when is_integer(MMI) -> MMI end, 0) - * 1000000), - Size = element_size(Packet), - {MessageShaper, MessageShaperInterval} = - shaper:update(Activity#activity.message_shaper, Size), - if Activity#activity.message /= undefined -> - ErrText = <<"Traffic rate limit is exceeded">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_RESOURCE_CONSTRAINT(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - Now >= - Activity#activity.message_time + MinMessageInterval, - MessageShaperInterval == 0 -> - {RoomShaper, RoomShaperInterval} = - shaper:update(StateData#state.room_shaper, Size), - RoomQueueEmpty = - queue:is_empty(StateData#state.room_queue), - if RoomShaperInterval == 0, RoomQueueEmpty -> - NewActivity = Activity#activity{message_time = - Now, - message_shaper = - MessageShaper}, - StateData1 = store_user_activity(From, - NewActivity, - StateData), - StateData2 = StateData1#state{room_shaper = - RoomShaper}, - process_groupchat_message(From, Packet, - StateData2); - true -> - StateData1 = if RoomQueueEmpty -> - erlang:send_after(RoomShaperInterval, - self(), - process_room_queue), - StateData#state{room_shaper = - RoomShaper}; - true -> StateData - end, - NewActivity = Activity#activity{message_time = - Now, - message_shaper = - MessageShaper, - message = Packet}, - RoomQueue = queue:in({message, From}, - StateData#state.room_queue), - StateData2 = store_user_activity(From, - NewActivity, - StateData1), - StateData3 = StateData2#state{room_queue = - RoomQueue}, - {next_state, normal_state, StateData3} - end; - true -> - MessageInterval = (Activity#activity.message_time + - MinMessageInterval - - Now) - div 1000, - Interval = lists:max([MessageInterval, - MessageShaperInterval]), - erlang:send_after(Interval, self(), - {process_user_message, From}), - NewActivity = Activity#activity{message = Packet, - message_shaper = - MessageShaper}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - {next_state, normal_state, StateData1} - end; - <<"error">> -> - case is_user_online(From, StateData) of - true -> - ErrorText = <<"This participant is kicked from the " - "room because he sent an error message">>, - NewState = expulse_participant(Packet, From, StateData, - translate:translate(Lang, - ErrorText)), - {next_state, normal_state, NewState}; - _ -> {next_state, normal_state, StateData} - end; - <<"chat">> -> - ErrText = - <<"It is not allowed to send private messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - Type when (Type == <<"">>) or (Type == <<"normal">>) -> - IsInvitation = is_invitation(Els), - IsVoiceRequest = is_voice_request(Els) and - is_visitor(From, StateData), - IsVoiceApprovement = is_voice_approvement(Els) and - not is_visitor(From, StateData), - if IsInvitation -> - case catch check_invitation(From, Els, Lang, StateData) - of - {error, Error} -> - Err = jlib:make_error_reply(Packet, Error), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - IJID -> - Config = StateData#state.config, - case Config#config.members_only of - true -> - case get_affiliation(IJID, StateData) of - none -> - NSD = set_affiliation(IJID, member, - StateData), - case - (NSD#state.config)#config.persistent - of - true -> - mod_muc:store_room(NSD#state.server_host, - NSD#state.host, - NSD#state.room, - make_opts(NSD)); - _ -> ok - end, - {next_state, normal_state, NSD}; - _ -> {next_state, normal_state, StateData} - end; - false -> {next_state, normal_state, StateData} - end - end; - IsVoiceRequest -> - NewStateData = case - (StateData#state.config)#config.allow_voice_requests - of - true -> - MinInterval = - (StateData#state.config)#config.voice_request_min_interval, - BareFrom = - jlib:jid_remove_resource(jlib:jid_tolower(From)), - NowPriority = -now_to_usec(now()), - CleanPriority = NowPriority + - MinInterval * - 1000000, - Times = - clean_treap(StateData#state.last_voice_request_time, - CleanPriority), - case treap:lookup(BareFrom, Times) - of - error -> - Times1 = - treap:insert(BareFrom, - NowPriority, - true, Times), - NSD = - StateData#state{last_voice_request_time - = - Times1}, - send_voice_request(From, NSD), - NSD; - {ok, _, _} -> - ErrText = - <<"Please, wait for a while before sending " - "new voice request">>, - Err = - jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData#state{last_voice_request_time - = Times} - end; - false -> - ErrText = - <<"Voice requests are disabled in this " - "conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData - end, - {next_state, normal_state, NewStateData}; - IsVoiceApprovement -> - NewStateData = case is_moderator(From, StateData) of - true -> - case - extract_jid_from_voice_approvement(Els) - of - error -> - ErrText = - <<"Failed to extract JID from your voice " - "request approval">>, - Err = - jlib:make_error_reply(Packet, - ?ERRT_BAD_REQUEST(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData; - {ok, TargetJid} -> - case is_visitor(TargetJid, - StateData) - of - true -> - Reason = <<>>, - NSD = - set_role(TargetJid, - participant, - StateData), - catch - send_new_presence(TargetJid, - Reason, - NSD), - NSD; - _ -> StateData - end - end; - _ -> - ErrText = - <<"Only moderators can approve voice requests">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData - end, - {next_state, normal_state, NewStateData}; - true -> {next_state, normal_state, StateData} - end; - _ -> - ErrText = <<"Improper message type">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} - end; - _ -> - case xml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - handle_roommessage_from_nonparticipant(Packet, Lang, - StateData, From) - end, - {next_state, normal_state, StateData} - end; -normal_state({route, From, <<"">>, - #xmlel{name = <<"iq">>} = Packet}, - StateData) -> - case jlib:iq_query_info(Packet) of - #iq{type = Type, xmlns = XMLNS, lang = Lang, - sub_el = SubEl} = - IQ - when (XMLNS == (?NS_MUC_ADMIN)) or - (XMLNS == (?NS_MUC_OWNER)) - or (XMLNS == (?NS_DISCO_INFO)) - or (XMLNS == (?NS_DISCO_ITEMS)) - or (XMLNS == (?NS_CAPTCHA)) -> - Res1 = case XMLNS of - ?NS_MUC_ADMIN -> - process_iq_admin(From, Type, Lang, SubEl, StateData); - ?NS_MUC_OWNER -> - process_iq_owner(From, Type, Lang, SubEl, StateData); - ?NS_DISCO_INFO -> - process_iq_disco_info(From, Type, Lang, StateData); - ?NS_DISCO_ITEMS -> - process_iq_disco_items(From, Type, Lang, StateData); - ?NS_CAPTCHA -> - process_iq_captcha(From, Type, Lang, SubEl, StateData) - end, - {IQRes, NewStateData} = case Res1 of - {result, Res, SD} -> - {IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = Res}]}, - SD}; - {error, Error} -> - {IQ#iq{type = error, - sub_el = [SubEl, Error]}, - StateData} - end, - ejabberd_router:route(StateData#state.jid, From, - jlib:iq_to_xml(IQRes)), - case NewStateData of - stop -> {stop, normal, StateData}; - _ -> {next_state, normal_state, NewStateData} - end; - reply -> {next_state, normal_state, StateData}; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} - end; -normal_state({route, From, Nick, - #xmlel{name = <<"presence">>} = Packet}, - StateData) -> - Activity = get_user_activity(From, StateData), - Now = now_to_usec(now()), - MinPresenceInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_presence_interval, - fun(I) when is_number(I), I>=0 -> - I - end, 0) - * 1000000), - if (Now >= - Activity#activity.presence_time + MinPresenceInterval) - and (Activity#activity.presence == undefined) -> - NewActivity = Activity#activity{presence_time = Now}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - process_presence(From, Nick, Packet, StateData1); - true -> - if Activity#activity.presence == undefined -> - Interval = (Activity#activity.presence_time + - MinPresenceInterval - - Now) - div 1000, - erlang:send_after(Interval, self(), - {process_user_presence, From}); - true -> ok - end, - NewActivity = Activity#activity{presence = - {Nick, Packet}}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - {next_state, normal_state, StateData1} - end; -normal_state({route, From, ToNick, - #xmlel{name = <<"message">>, attrs = Attrs} = Packet}, - StateData) -> - Type = xml:get_attr_s(<<"type">>, Attrs), - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - case decide_fate_message(Type, Packet, From, StateData) - of - {expulse_sender, Reason} -> - ?DEBUG(Reason, []), - ErrorText = <<"This participant is kicked from the " - "room because he sent an error message " - "to another participant">>, - NewState = expulse_participant(Packet, From, StateData, - translate:translate(Lang, ErrorText)), - {next_state, normal_state, NewState}; - forget_message -> {next_state, normal_state, StateData}; - continue_delivery -> - case - {(StateData#state.config)#config.allow_private_messages, - is_user_online(From, StateData)} - of - {true, true} -> - case Type of - <<"groupchat">> -> - ErrText = - <<"It is not allowed to send private messages " - "of type \"groupchat\"">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_BAD_REQUEST(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - ToNick), - From, Err); - _ -> - case find_jids_by_nick(ToNick, StateData) of - false -> - ErrText = - <<"Recipient is not in the conference room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_ITEM_NOT_FOUND(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - ToNick), - From, Err); - ToJIDs -> - SrcIsVisitor = is_visitor(From, StateData), - DstIsModerator = is_moderator(hd(ToJIDs), - StateData), - PmFromVisitors = - (StateData#state.config)#config.allow_private_messages_from_visitors, - if SrcIsVisitor == false; - PmFromVisitors == anyone; - (PmFromVisitors == moderators) and - DstIsModerator -> - {ok, #user{nick = FromNick}} = - (?DICT):find(jlib:jid_tolower(From), - StateData#state.users), - FromNickJID = - jlib:jid_replace_resource(StateData#state.jid, - FromNick), - [ejabberd_router:route(FromNickJID, ToJID, Packet) - || ToJID <- ToJIDs]; - true -> - ErrText = - <<"It is not allowed to send private messages">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - ToNick), - From, Err) - end - end - end; - {true, false} -> - ErrText = - <<"Only occupants are allowed to send messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - ToNick), - From, Err); - {false, _} -> - ErrText = - <<"It is not allowed to send private messages">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - ToNick), - From, Err) - end, - {next_state, normal_state, StateData} - end; -normal_state({route, From, ToNick, - #xmlel{name = <<"iq">>, attrs = Attrs} = Packet}, - StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - StanzaId = xml:get_attr_s(<<"id">>, Attrs), - case {(StateData#state.config)#config.allow_query_users, - is_user_online_iq(StanzaId, From, StateData)} - of - {true, {true, NewId, FromFull}} -> - case find_jid_by_nick(ToNick, StateData) of - false -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = <<"Recipient is not in the conference room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_ITEM_NOT_FOUND(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - ToNick), - From, Err) - end; - ToJID -> - {ok, #user{nick = FromNick}} = - (?DICT):find(jlib:jid_tolower(FromFull), - StateData#state.users), - {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID, - StanzaId, NewId, Packet), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - FromNick), - ToJID2, Packet2) - end; - {_, {false, _, _}} -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = - <<"Only occupants are allowed to send queries " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - ToNick), - From, Err) - end; - _ -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = <<"Queries to the conference members are " - "not allowed in this room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - ToNick), - From, Err) - end - end, - {next_state, normal_state, StateData}; -normal_state(_Event, StateData) -> - {next_state, normal_state, StateData}. - -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_event({service_message, Msg}, _StateName, - StateData) -> - MessagePkt = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg}]}]}, - lists:foreach( - fun({_LJID, Info}) -> - ejabberd_router:route( - StateData#state.jid, - Info#user.jid, - MessagePkt) - end, - ?DICT:to_list(StateData#state.users)), - NSD = add_message_to_history(<<"">>, - StateData#state.jid, MessagePkt, StateData), - {next_state, normal_state, NSD}; -handle_event({destroy, Reason}, _StateName, - StateData) -> - {result, [], stop} = destroy_room(#xmlel{name = - <<"destroy">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_OWNER}], - children = - case Reason of - none -> []; - _Else -> - [#xmlel{name = - <<"reason">>, - attrs = [], - children = - [{xmlcdata, - Reason}]}] - end}, - StateData), - ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", - [jlib:jid_to_string(StateData#state.jid), Reason]), - add_to_log(room_existence, destroyed, StateData), - {stop, shutdown, StateData}; -handle_event(destroy, StateName, StateData) -> - ?INFO_MSG("Destroyed MUC room ~s", - [jlib:jid_to_string(StateData#state.jid)]), - handle_event({destroy, none}, StateName, StateData); -handle_event({set_affiliations, Affiliations}, - StateName, StateData) -> - {next_state, StateName, - StateData#state{affiliations = Affiliations}}; -handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -handle_sync_event({get_disco_item, JID, Lang}, _From, StateName, StateData) -> - Reply = get_roomdesc_reply(JID, StateData, - get_roomdesc_tail(StateData, Lang)), - {reply, Reply, StateName, StateData}; -handle_sync_event(get_config, _From, StateName, - StateData) -> - {reply, {ok, StateData#state.config}, StateName, - StateData}; -handle_sync_event(get_state, _From, StateName, - StateData) -> - {reply, {ok, StateData}, StateName, StateData}; -handle_sync_event({change_config, Config}, _From, - StateName, StateData) -> - {result, [], NSD} = change_config(Config, StateData), - {reply, {ok, NSD#state.config}, StateName, NSD}; -handle_sync_event({change_state, NewStateData}, _From, - StateName, _StateData) -> - {reply, {ok, NewStateData}, StateName, NewStateData}; -handle_sync_event(_Event, _From, StateName, - StateData) -> - Reply = ok, {reply, Reply, StateName, StateData}. - -code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_info({process_user_presence, From}, normal_state = _StateName, StateData) -> - RoomQueueEmpty = queue:is_empty(StateData#state.room_queue), - RoomQueue = queue:in({presence, From}, StateData#state.room_queue), - StateData1 = StateData#state{room_queue = RoomQueue}, - if RoomQueueEmpty -> - StateData2 = prepare_room_queue(StateData1), - {next_state, normal_state, StateData2}; - true -> {next_state, normal_state, StateData1} - end; -handle_info({process_user_message, From}, - normal_state = _StateName, StateData) -> - RoomQueueEmpty = - queue:is_empty(StateData#state.room_queue), - RoomQueue = queue:in({message, From}, - StateData#state.room_queue), - StateData1 = StateData#state{room_queue = RoomQueue}, - if RoomQueueEmpty -> - StateData2 = prepare_room_queue(StateData1), - {next_state, normal_state, StateData2}; - true -> {next_state, normal_state, StateData1} - end; -handle_info(process_room_queue, - normal_state = StateName, StateData) -> - case queue:out(StateData#state.room_queue) of - {{value, {message, From}}, RoomQueue} -> - Activity = get_user_activity(From, StateData), - Packet = Activity#activity.message, - NewActivity = Activity#activity{message = undefined}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - StateData2 = StateData1#state{room_queue = RoomQueue}, - StateData3 = prepare_room_queue(StateData2), - process_groupchat_message(From, Packet, StateData3); - {{value, {presence, From}}, RoomQueue} -> - Activity = get_user_activity(From, StateData), - {Nick, Packet} = Activity#activity.presence, - NewActivity = Activity#activity{presence = undefined}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - StateData2 = StateData1#state{room_queue = RoomQueue}, - StateData3 = prepare_room_queue(StateData2), - process_presence(From, Nick, Packet, StateData3); - {empty, _} -> {next_state, StateName, StateData} - end; -handle_info({captcha_succeed, From}, normal_state, - StateData) -> - NewState = case (?DICT):find(From, - StateData#state.robots) - of - {ok, {Nick, Packet}} -> - Robots = (?DICT):store(From, passed, - StateData#state.robots), - add_new_user(From, Nick, Packet, - StateData#state{robots = Robots}); - _ -> StateData - end, - {next_state, normal_state, NewState}; -handle_info({captcha_failed, From}, normal_state, - StateData) -> - NewState = case (?DICT):find(From, - StateData#state.robots) - of - {ok, {Nick, Packet}} -> - Robots = (?DICT):erase(From, StateData#state.robots), - Err = jlib:make_error_reply(Packet, - ?ERR_NOT_AUTHORIZED), - ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData#state{robots = Robots}; - _ -> StateData - end, - {next_state, normal_state, NewState}; -handle_info(_Info, StateName, StateData) -> - {next_state, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- -terminate(Reason, _StateName, StateData) -> - ?INFO_MSG("Stopping MUC room ~s@~s", - [StateData#state.room, StateData#state.host]), - ReasonT = case Reason of - shutdown -> - <<"You are being removed from the room " - "because of a system shutdown">>; - _ -> <<"Room terminates">> - end, - ItemAttrs = [{<<"affiliation">>, <<"none">>}, - {<<"role">>, <<"none">>}], - ReasonEl = #xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, ReasonT}]}, - Packet = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = ItemAttrs, - children = [ReasonEl]}, - #xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"332">>}], - children = []}]}]}, - (?DICT):fold(fun (LJID, Info, _) -> - Nick = Info#user.nick, - case Reason of - shutdown -> - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet); - _ -> ok - end, - 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), - ok. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -route(Pid, From, ToNick, Packet) -> - gen_fsm:send_event(Pid, {route, From, ToNick, Packet}). - -process_groupchat_message(From, - #xmlel{name = <<"message">>, attrs = Attrs} = Packet, - StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - case is_user_online(From, StateData) orelse - is_user_allowed_message_nonparticipant(From, StateData) - of - true -> - {FromNick, Role} = get_participant_data(From, - StateData), - if (Role == moderator) or (Role == participant) or - ((StateData#state.config)#config.moderated == false) -> - {NewStateData1, IsAllowed} = case check_subject(Packet) - of - false -> {StateData, true}; - Subject -> - case - can_change_subject(Role, - StateData) - of - true -> - NSD = - StateData#state{subject - = - Subject, - subject_author - = - FromNick}, - case - (NSD#state.config)#config.persistent - of - true -> - mod_muc:store_room(NSD#state.server_host, - NSD#state.host, - NSD#state.room, - make_opts(NSD)); - _ -> ok - end, - {NSD, true}; - _ -> {StateData, false} - end - end, - case IsAllowed of - true -> - lists:foreach( - fun({_LJID, Info}) -> - ejabberd_router:route( - jlib:jid_replace_resource( - StateData#state.jid, - FromNick), - Info#user.jid, - Packet) - end, - ?DICT:to_list(StateData#state.users)), - NewStateData2 = add_message_to_history(FromNick, From, - Packet, - NewStateData1), - {next_state, normal_state, NewStateData2}; - _ -> - Err = case - (StateData#state.config)#config.allow_change_subj - of - true -> - ?ERRT_FORBIDDEN(Lang, - <<"Only moderators and participants are " - "allowed to change the subject in this " - "room">>); - _ -> - ?ERRT_FORBIDDEN(Lang, - <<"Only moderators are allowed to change " - "the subject in this room">>) - end, - ejabberd_router:route(StateData#state.jid, From, - jlib:make_error_reply(Packet, Err)), - {next_state, normal_state, StateData} - end; - true -> - ErrText = <<"Visitors are not allowed to send messages " - "to all occupants">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} - end; - false -> - ErrText = - <<"Only occupants are allowed to send messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} - end. - -%% @doc Check if this non participant can send message to room. -%% -%% XEP-0045 v1.23: -%% 7.9 Sending a Message to All Occupants -%% an implementation MAY allow users with certain privileges -%% (e.g., a room owner, room admin, or service-level admin) -%% to send messages to the room even if those users are not occupants. -is_user_allowed_message_nonparticipant(JID, - StateData) -> - case get_service_affiliation(JID, StateData) of - owner -> true; - _ -> false - end. - -%% @doc Get information of this participant, or default values. -%% If the JID is not a participant, return values for a service message. -get_participant_data(From, StateData) -> - case (?DICT):find(jlib:jid_tolower(From), - StateData#state.users) - of - {ok, #user{nick = FromNick, role = Role}} -> - {FromNick, Role}; - error -> {<<"">>, moderator} - end. - -process_presence(From, Nick, - #xmlel{name = <<"presence">>, attrs = Attrs} = Packet, - StateData) -> - Type = xml:get_attr_s(<<"type">>, Attrs), - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - StateData1 = case Type of - <<"unavailable">> -> - case is_user_online(From, StateData) of - true -> - NewPacket = case - {(StateData#state.config)#config.allow_visitor_status, - is_visitor(From, StateData)} - of - {false, true} -> - strip_status(Packet); - _ -> Packet - end, - NewState = add_user_presence_un(From, NewPacket, - StateData), - case (?DICT):find(Nick, StateData#state.nicks) of - {ok, [_, _ | _]} -> ok; - _ -> send_new_presence(From, NewState) - end, - Reason = case xml:get_subtag(NewPacket, - <<"status">>) - of - false -> <<"">>; - Status_el -> - xml:get_tag_cdata(Status_el) - end, - remove_online_user(From, NewState, Reason); - _ -> StateData - end; - <<"error">> -> - case is_user_online(From, StateData) of - true -> - ErrorText = - <<"This participant is kicked from the " - "room because he sent an error presence">>, - expulse_participant(Packet, From, StateData, - translate:translate(Lang, - ErrorText)); - _ -> StateData - end; - <<"">> -> - case is_user_online(From, StateData) of - true -> - case is_nick_change(From, Nick, StateData) of - true -> - case {nick_collision(From, Nick, StateData), - mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, - From, Nick), - {(StateData#state.config)#config.allow_visitor_nickchange, - is_visitor(From, StateData)}} - of - {_, _, {false, true}} -> - ErrText = - <<"Visitors are not allowed to change their " - "nicknames in this room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - {true, _, _} -> - Lang = xml:get_attr_s(<<"xml:lang">>, - Attrs), - ErrText = - <<"That nickname is already in use by another " - "occupant">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), % TODO: s/Nick/""/ - From, Err), - StateData; - {_, false, _} -> - ErrText = - <<"That nickname is registered by another " - "person">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - _ -> change_nick(From, Nick, StateData) - end; - _NotNickChange -> - Stanza = case - {(StateData#state.config)#config.allow_visitor_status, - is_visitor(From, StateData)} - of - {false, true} -> - strip_status(Packet); - _Allowed -> Packet - end, - NewState = add_user_presence(From, Stanza, - StateData), - send_new_presence(From, NewState), - NewState - end; - _ -> add_new_user(From, Nick, Packet, StateData) - end; - _ -> StateData - end, - case not (StateData1#state.config)#config.persistent - andalso (?DICT):to_list(StateData1#state.users) == [] - of - true -> - ?INFO_MSG("Destroyed MUC room ~s because it's temporary " - "and empty", - [jlib:jid_to_string(StateData#state.jid)]), - add_to_log(room_existence, destroyed, StateData), - {stop, normal, StateData1}; - _ -> {next_state, normal_state, StateData1} - end. - -is_user_online(JID, StateData) -> - LJID = jlib:jid_tolower(JID), - (?DICT):is_key(LJID, StateData#state.users). - -%% Check if the user is occupant of the room, or at least is an admin or owner. -is_occupant_or_admin(JID, StateData) -> - FAffiliation = get_affiliation(JID, StateData), - FRole = get_role(JID, StateData), - case FRole /= none orelse - FAffiliation == admin orelse FAffiliation == owner - of - true -> true; - _ -> false - end. - -%%% -%%% Handle IQ queries of vCard -%%% -is_user_online_iq(StanzaId, JID, StateData) - when JID#jid.lresource /= <<"">> -> - {is_user_online(JID, StateData), StanzaId, JID}; -is_user_online_iq(StanzaId, JID, StateData) - when JID#jid.lresource == <<"">> -> - try stanzaid_unpack(StanzaId) of - {OriginalId, Resource} -> - JIDWithResource = jlib:jid_replace_resource(JID, - Resource), - {is_user_online(JIDWithResource, StateData), OriginalId, - JIDWithResource} - catch - _:_ -> {is_user_online(JID, StateData), StanzaId, JID} - end. - -handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, - Packet) -> - ToBareJID = jlib:jid_remove_resource(ToJID), - IQ = jlib:iq_query_info(Packet), - handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, - NewId, IQ, Packet). - -handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, - _NewId, #iq{type = get, xmlns = ?NS_VCARD}, Packet) - when ToBareJID /= ToJID -> - {ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)}; -handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, - _StanzaId, NewId, _IQ, Packet) -> - {ToJID, change_stanzaid(NewId, Packet)}. - -stanzaid_pack(OriginalId, Resource) -> - <<"berd", - (jlib:encode_base64(<<"ejab\000", - OriginalId/binary, "\000", - Resource/binary>>))/binary>>. - -stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) -> - StanzaId = jlib:decode_base64(StanzaIdBase64), - [<<"ejab">>, OriginalId, Resource] = - str:tokens(StanzaId, <<"\000">>), - {OriginalId, Resource}. - -change_stanzaid(NewId, Packet) -> - #xmlel{name = Name, attrs = Attrs, children = Els} = - jlib:remove_attr(<<"id">>, Packet), - #xmlel{name = Name, attrs = [{<<"id">>, NewId} | Attrs], - children = Els}. - -change_stanzaid(PreviousId, ToJID, Packet) -> - NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource), - change_stanzaid(NewId, Packet). - -%%% -%%% - -role_to_list(Role) -> - case Role of - moderator -> <<"moderator">>; - participant -> <<"participant">>; - visitor -> <<"visitor">>; - none -> <<"none">> - end. - -affiliation_to_list(Affiliation) -> - case Affiliation of - owner -> <<"owner">>; - admin -> <<"admin">>; - member -> <<"member">>; - outcast -> <<"outcast">>; - none -> <<"none">> - end. - -list_to_role(Role) -> - case Role of - <<"moderator">> -> moderator; - <<"participant">> -> participant; - <<"visitor">> -> visitor; - <<"none">> -> none - end. - -list_to_affiliation(Affiliation) -> - case Affiliation of - <<"owner">> -> owner; - <<"admin">> -> admin; - <<"member">> -> member; - <<"outcast">> -> outcast; - <<"none">> -> none - end. - -%% Decide the fate of the message and its sender -%% Returns: continue_delivery | forget_message | {expulse_sender, Reason} -decide_fate_message(<<"error">>, Packet, From, - StateData) -> - PD = case check_error_kick(Packet) of - %% If this is an error stanza and its condition matches a criteria - true -> - Reason = - io_lib:format("This participant is considered a ghost " - "and is expulsed: ~s", - [jlib:jid_to_string(From)]), - {expulse_sender, Reason}; - false -> continue_delivery - end, - case PD of - {expulse_sender, R} -> - case is_user_online(From, StateData) of - true -> {expulse_sender, R}; - false -> forget_message - end; - Other -> Other - end; -decide_fate_message(_, _, _, _) -> continue_delivery. - -%% Check if the elements of this error stanza indicate -%% that the sender is a dead participant. -%% If so, return true to kick the participant. -check_error_kick(Packet) -> - case get_error_condition(Packet) of - <<"gone">> -> true; - <<"internal-server-error">> -> true; - <<"item-not-found">> -> true; - <<"jid-malformed">> -> true; - <<"recipient-unavailable">> -> true; - <<"redirect">> -> true; - <<"remote-server-not-found">> -> true; - <<"remote-server-timeout">> -> true; - <<"service-unavailable">> -> true; - _ -> false - end. - -get_error_condition(Packet) -> - case catch get_error_condition2(Packet) of - {condition, ErrorCondition} -> ErrorCondition; - {'EXIT', _} -> <<"badformed error stanza">> - end. - -get_error_condition2(Packet) -> - #xmlel{children = EEls} = xml:get_subtag(Packet, - <<"error">>), - [Condition] = [Name - || #xmlel{name = Name, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], - children = []} - <- EEls], - {condition, Condition}. - -expulse_participant(Packet, From, StateData, Reason1) -> - ErrorCondition = get_error_condition(Packet), - Reason2 = iolist_to_binary( - io_lib:format(binary_to_list(Reason1) ++ ": " ++ "~s", - [ErrorCondition])), - NewState = add_user_presence_un(From, - #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name = <<"status">>, - attrs = [], - children = - [{xmlcdata, - Reason2}]}]}, - StateData), - send_new_presence(From, NewState), - remove_online_user(From, NewState). - -set_affiliation(JID, Affiliation, StateData) -> - set_affiliation(JID, Affiliation, StateData, <<"">>). - -set_affiliation(JID, Affiliation, StateData, Reason) -> - LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)), - Affiliations = case Affiliation of - none -> - (?DICT):erase(LJID, StateData#state.affiliations); - _ -> - (?DICT):store(LJID, {Affiliation, Reason}, - StateData#state.affiliations) - end, - StateData#state{affiliations = Affiliations}. - -get_affiliation(JID, StateData) -> - {_AccessRoute, _AccessCreate, AccessAdmin, - _AccessPersistent} = - StateData#state.access, - Res = case acl:match_rule(StateData#state.server_host, - AccessAdmin, JID) - of - allow -> owner; - _ -> - LJID = jlib:jid_tolower(JID), - case (?DICT):find(LJID, StateData#state.affiliations) of - {ok, Affiliation} -> Affiliation; - _ -> - LJID1 = jlib:jid_remove_resource(LJID), - case (?DICT):find(LJID1, StateData#state.affiliations) - of - {ok, Affiliation} -> Affiliation; - _ -> - LJID2 = setelement(1, LJID, <<"">>), - case (?DICT):find(LJID2, - StateData#state.affiliations) - of - {ok, Affiliation} -> Affiliation; - _ -> - LJID3 = jlib:jid_remove_resource(LJID2), - case (?DICT):find(LJID3, - StateData#state.affiliations) - of - {ok, Affiliation} -> Affiliation; - _ -> none - end - end - end - end - end, - case Res of - {A, _Reason} -> A; - _ -> Res - end. - -get_service_affiliation(JID, StateData) -> - {_AccessRoute, _AccessCreate, AccessAdmin, - _AccessPersistent} = - StateData#state.access, - case acl:match_rule(StateData#state.server_host, - AccessAdmin, JID) - of - allow -> owner; - _ -> none - end. - -set_role(JID, Role, StateData) -> - LJID = jlib:jid_tolower(JID), - LJIDs = case LJID of - {U, S, <<"">>} -> - (?DICT):fold(fun (J, _, Js) -> - case J of - {U, S, _} -> [J | Js]; - _ -> Js - end - end, - [], StateData#state.users); - _ -> - case (?DICT):is_key(LJID, StateData#state.users) of - true -> [LJID]; - _ -> [] - end - end, - {Users, Nicks} = case Role of - none -> - lists:foldl(fun (J, {Us, Ns}) -> - NewNs = case (?DICT):find(J, Us) - of - {ok, - #user{nick = Nick}} -> - (?DICT):erase(Nick, - Ns); - _ -> Ns - end, - {(?DICT):erase(J, Us), NewNs} - end, - {StateData#state.users, - StateData#state.nicks}, - LJIDs); - _ -> - {lists:foldl(fun (J, Us) -> - {ok, User} = (?DICT):find(J, - Us), - (?DICT):store(J, - User#user{role = - Role}, - Us) - end, - StateData#state.users, LJIDs), - StateData#state.nicks} - end, - StateData#state{users = Users, nicks = Nicks}. - -get_role(JID, StateData) -> - LJID = jlib:jid_tolower(JID), - case (?DICT):find(LJID, StateData#state.users) of - {ok, #user{role = Role}} -> Role; - _ -> none - end. - -get_default_role(Affiliation, StateData) -> - case Affiliation of - owner -> moderator; - admin -> moderator; - member -> participant; - outcast -> none; - none -> - case (StateData#state.config)#config.members_only of - true -> none; - _ -> - case (StateData#state.config)#config.members_by_default - of - true -> participant; - _ -> visitor - end - end - end. - -is_visitor(Jid, StateData) -> - get_role(Jid, StateData) =:= visitor. - -is_moderator(Jid, StateData) -> - get_role(Jid, StateData) =:= moderator. - -get_max_users(StateData) -> - MaxUsers = (StateData#state.config)#config.max_users, - ServiceMaxUsers = get_service_max_users(StateData), - if MaxUsers =< ServiceMaxUsers -> MaxUsers; - true -> ServiceMaxUsers - end. - -get_service_max_users(StateData) -> - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users, - fun(I) when is_integer(I), I>0 -> I end, - ?MAX_USERS_DEFAULT). - -get_max_users_admin_threshold(StateData) -> - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users_admin_threshold, - fun(I) when is_integer(I), I>0 -> I end, - 5). - -get_user_activity(JID, StateData) -> - case treap:lookup(jlib:jid_tolower(JID), - StateData#state.activity) - of - {ok, _P, A} -> A; - error -> - MessageShaper = - shaper:new(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, user_message_shaper, - fun(A) when is_atom(A) -> A end, - none)), - PresenceShaper = - shaper:new(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, user_presence_shaper, - fun(A) when is_atom(A) -> A end, - none)), - #activity{message_shaper = MessageShaper, - presence_shaper = PresenceShaper} - end. - -store_user_activity(JID, UserActivity, StateData) -> - MinMessageInterval = - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_message_interval, - fun(I) when is_integer(I), I>=0 -> I end, - 0), - MinPresenceInterval = - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_presence_interval, - fun(I) when is_integer(I), I>=0 -> I end, - 0), - Key = jlib:jid_tolower(JID), - Now = now_to_usec(now()), - Activity1 = clean_treap(StateData#state.activity, - {1, -Now}), - Activity = case treap:lookup(Key, Activity1) of - {ok, _P, _A} -> treap:delete(Key, Activity1); - error -> Activity1 - end, - StateData1 = case MinMessageInterval == 0 andalso - MinPresenceInterval == 0 andalso - UserActivity#activity.message_shaper == none andalso - UserActivity#activity.presence_shaper == none - andalso - UserActivity#activity.message == undefined andalso - UserActivity#activity.presence == undefined - of - true -> StateData#state{activity = Activity}; - false -> - case UserActivity#activity.message == undefined andalso - UserActivity#activity.presence == undefined - of - true -> - {_, MessageShaperInterval} = - shaper:update(UserActivity#activity.message_shaper, - 100000), - {_, PresenceShaperInterval} = - shaper:update(UserActivity#activity.presence_shaper, - 100000), - Delay = lists:max([MessageShaperInterval, - PresenceShaperInterval, - MinMessageInterval * 1000, - MinPresenceInterval * 1000]) - * 1000, - Priority = {1, -(Now + Delay)}, - StateData#state{activity = - treap:insert(Key, Priority, - UserActivity, - Activity)}; - false -> - Priority = {0, 0}, - StateData#state{activity = - treap:insert(Key, Priority, - UserActivity, - Activity)} - end - end, - StateData1. - -clean_treap(Treap, CleanPriority) -> - case treap:is_empty(Treap) of - true -> Treap; - false -> - {_Key, Priority, _Value} = treap:get_root(Treap), - if Priority > CleanPriority -> - clean_treap(treap:delete_root(Treap), CleanPriority); - true -> Treap - end - end. - -prepare_room_queue(StateData) -> - case queue:out(StateData#state.room_queue) of - {{value, {message, From}}, _RoomQueue} -> - Activity = get_user_activity(From, StateData), - Packet = Activity#activity.message, - Size = element_size(Packet), - {RoomShaper, RoomShaperInterval} = - shaper:update(StateData#state.room_shaper, Size), - erlang:send_after(RoomShaperInterval, self(), - process_room_queue), - StateData#state{room_shaper = RoomShaper}; - {{value, {presence, From}}, _RoomQueue} -> - Activity = get_user_activity(From, StateData), - {_Nick, Packet} = Activity#activity.presence, - Size = element_size(Packet), - {RoomShaper, RoomShaperInterval} = - shaper:update(StateData#state.room_shaper, Size), - erlang:send_after(RoomShaperInterval, self(), - process_room_queue), - StateData#state{room_shaper = RoomShaper}; - {empty, _} -> StateData - end. - -add_online_user(JID, Nick, Role, StateData) -> - LJID = jlib:jid_tolower(JID), - Users = (?DICT):store(LJID, - #user{jid = JID, nick = Nick, role = Role}, - StateData#state.users), - add_to_log(join, Nick, StateData), - Nicks = (?DICT):update(Nick, - fun (Entry) -> - case lists:member(LJID, Entry) of - true -> Entry; - false -> [LJID | Entry] - end - end, - [LJID], StateData#state.nicks), - tab_add_online_user(JID, StateData), - StateData#state{users = Users, nicks = Nicks}. - -remove_online_user(JID, StateData) -> - remove_online_user(JID, StateData, <<"">>). - -remove_online_user(JID, StateData, Reason) -> - LJID = jlib:jid_tolower(JID), - {ok, #user{nick = Nick}} = (?DICT):find(LJID, - StateData#state.users), - add_to_log(leave, {Nick, Reason}, StateData), - tab_remove_online_user(JID, StateData), - Users = (?DICT):erase(LJID, StateData#state.users), - Nicks = case (?DICT):find(Nick, StateData#state.nicks) - of - {ok, [LJID]} -> - (?DICT):erase(Nick, StateData#state.nicks); - {ok, U} -> - (?DICT):store(Nick, U -- [LJID], StateData#state.nicks); - error -> StateData#state.nicks - end, - StateData#state{users = Users, nicks = Nicks}. - -filter_presence(#xmlel{name = <<"presence">>, - attrs = Attrs, children = Els}) -> - FEls = lists:filter(fun (El) -> - case El of - {xmlcdata, _} -> false; - #xmlel{attrs = Attrs1} -> - XMLNS = xml:get_attr_s(<<"xmlns">>, - Attrs1), - NS_MUC = ?NS_MUC, - Size = byte_size(NS_MUC), - case XMLNS of - <<NS_MUC:Size/binary, _/binary>> -> - false; - _ -> - true - end - end - end, - Els), - #xmlel{name = <<"presence">>, attrs = Attrs, - children = FEls}. - -strip_status(#xmlel{name = <<"presence">>, - attrs = Attrs, children = Els}) -> - FEls = lists:filter(fun (#xmlel{name = <<"status">>}) -> - false; - (_) -> true - end, - Els), - #xmlel{name = <<"presence">>, attrs = Attrs, - children = FEls}. - -add_user_presence(JID, Presence, StateData) -> - LJID = jlib:jid_tolower(JID), - FPresence = filter_presence(Presence), - Users = (?DICT):update(LJID, - fun (#user{} = User) -> - User#user{last_presence = FPresence} - end, - StateData#state.users), - StateData#state{users = Users}. - -add_user_presence_un(JID, Presence, StateData) -> - LJID = jlib:jid_tolower(JID), - FPresence = filter_presence(Presence), - Users = (?DICT):update(LJID, - fun (#user{} = User) -> - User#user{last_presence = FPresence, - role = none} - end, - StateData#state.users), - StateData#state{users = Users}. - -%% Find and return a list of the full JIDs of the users of Nick. -%% Return jid record. -find_jids_by_nick(Nick, StateData) -> - case (?DICT):find(Nick, StateData#state.nicks) of - {ok, [User]} -> [jlib:make_jid(User)]; - {ok, Users} -> [jlib:make_jid(LJID) || LJID <- Users]; - error -> false - end. - -%% Find and return the full JID of the user of Nick with -%% highest-priority presence. Return jid record. -find_jid_by_nick(Nick, StateData) -> - case (?DICT):find(Nick, StateData#state.nicks) of - {ok, [User]} -> jlib:make_jid(User); - {ok, [FirstUser | Users]} -> - #user{last_presence = FirstPresence} = - (?DICT):fetch(FirstUser, StateData#state.users), - {LJID, _} = lists:foldl(fun (Compare, - {HighestUser, HighestPresence}) -> - #user{last_presence = P1} = - (?DICT):fetch(Compare, - StateData#state.users), - case higher_presence(P1, - HighestPresence) - of - true -> {Compare, P1}; - false -> - {HighestUser, HighestPresence} - end - end, - {FirstUser, FirstPresence}, Users), - jlib:make_jid(LJID); - error -> false - end. - -higher_presence(Pres1, Pres2) -> - Pri1 = get_priority_from_presence(Pres1), - Pri2 = get_priority_from_presence(Pres2), - Pri1 > Pri2. - -get_priority_from_presence(PresencePacket) -> - case xml:get_subtag(PresencePacket, <<"priority">>) of - false -> 0; - SubEl -> - case catch - jlib:binary_to_integer(xml:get_tag_cdata(SubEl)) - of - P when is_integer(P) -> P; - _ -> 0 - end - end. - -find_nick_by_jid(Jid, StateData) -> - [{_, #user{nick = Nick}}] = lists:filter(fun ({_, - #user{jid = FJid}}) -> - FJid == Jid - end, - (?DICT):to_list(StateData#state.users)), - Nick. - -is_nick_change(JID, Nick, StateData) -> - LJID = jlib:jid_tolower(JID), - case Nick of - <<"">> -> false; - _ -> - {ok, #user{nick = OldNick}} = (?DICT):find(LJID, - StateData#state.users), - Nick /= OldNick - end. - -nick_collision(User, Nick, StateData) -> - UserOfNick = find_jid_by_nick(Nick, StateData), - UserOfNick /= false andalso - jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick)) - /= jlib:jid_remove_resource(jlib:jid_tolower(User)). - -add_new_user(From, Nick, - #xmlel{attrs = Attrs, children = Els} = Packet, - StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - MaxUsers = get_max_users(StateData), - MaxAdminUsers = MaxUsers + - get_max_users_admin_threshold(StateData), - NUsers = dict:fold(fun (_, _, Acc) -> Acc + 1 end, 0, - StateData#state.users), - Affiliation = get_affiliation(From, StateData), - ServiceAffiliation = get_service_affiliation(From, - StateData), - NConferences = tab_count_user(From), - MaxConferences = - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_user_conferences, - fun(I) when is_integer(I), I>0 -> I end, - 10), - Collision = nick_collision(From, Nick, StateData), - case {(ServiceAffiliation == owner orelse - (Affiliation == admin orelse Affiliation == owner) - andalso NUsers < MaxAdminUsers - orelse NUsers < MaxUsers) - andalso NConferences < MaxConferences, - Collision, - mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, From, Nick), - get_default_role(Affiliation, StateData)} - of - {false, _, _, _} -> - Err = jlib:make_error_reply(Packet, - ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, Nick), - From, Err), - StateData; - {_, _, _, none} -> - Err = jlib:make_error_reply(Packet, - case Affiliation of - outcast -> - ErrText = - <<"You have been banned from this room">>, - ?ERRT_FORBIDDEN(Lang, ErrText); - _ -> - ErrText = - <<"Membership is required to enter this room">>, - ?ERRT_REGISTRATION_REQUIRED(Lang, - ErrText) - end), - ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, Nick), - From, Err), - StateData; - {_, true, _, _} -> - ErrText = <<"That nickname is already in use by another occupant">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - {_, _, false, _} -> - ErrText = <<"That nickname is registered by another person">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - {_, _, _, Role} -> - case check_password(ServiceAffiliation, Affiliation, - Els, From, StateData) - of - true -> - NewState = add_user_presence(From, Packet, - add_online_user(From, Nick, Role, - StateData)), - if not (NewState#state.config)#config.anonymous -> - WPacket = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, - attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"This room is not anonymous">>)}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"status">>, - attrs = - [{<<"code">>, - <<"100">>}], - children = - []}]}]}, - ejabberd_router:route(StateData#state.jid, From, WPacket); - true -> ok - end, - send_existing_presences(From, NewState), - send_new_presence(From, NewState), - Shift = count_stanza_shift(Nick, Els, NewState), - case send_history(From, Shift, NewState) of - true -> ok; - _ -> send_subject(From, Lang, StateData) - end, - case NewState#state.just_created of - true -> NewState#state{just_created = false}; - false -> - Robots = (?DICT):erase(From, StateData#state.robots), - NewState#state{robots = Robots} - end; - nopass -> - ErrText = <<"A password is required to enter this room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_AUTHORIZED(Lang, - ErrText)), - ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - captcha_required -> - SID = xml:get_attr_s(<<"id">>, Attrs), - RoomJID = StateData#state.jid, - To = jlib:jid_replace_resource(RoomJID, Nick), - Limiter = {From#jid.luser, From#jid.lserver}, - case ejabberd_captcha:create_captcha(SID, RoomJID, To, - Lang, Limiter, From) - of - {ok, ID, CaptchaEls} -> - MsgPkt = #xmlel{name = <<"message">>, - attrs = [{<<"id">>, ID}], - children = CaptchaEls}, - Robots = (?DICT):store(From, {Nick, Packet}, - StateData#state.robots), - ejabberd_router:route(RoomJID, From, MsgPkt), - StateData#state{robots = Robots}; - {error, limit} -> - ErrText = <<"Too many CAPTCHA requests">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_RESOURCE_CONSTRAINT(Lang, - ErrText)), - ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - _ -> - ErrText = <<"Unable to generate a CAPTCHA">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_INTERNAL_SERVER_ERROR(Lang, - ErrText)), - ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData - end; - _ -> - ErrText = <<"Incorrect password">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_AUTHORIZED(Lang, - ErrText)), - ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData - end - end. - -check_password(owner, _Affiliation, _Els, _From, - _StateData) -> - %% Don't check pass if user is owner in MUC service (access_admin option) - true; -check_password(_ServiceAffiliation, Affiliation, Els, - From, StateData) -> - case (StateData#state.config)#config.password_protected - of - false -> check_captcha(Affiliation, From, StateData); - true -> - Pass = extract_password(Els), - case Pass of - false -> nopass; - _ -> - case (StateData#state.config)#config.password of - Pass -> true; - _ -> false - end - end - end. - -check_captcha(Affiliation, From, StateData) -> - case (StateData#state.config)#config.captcha_protected - andalso ejabberd_captcha:is_feature_available() - of - true when Affiliation == none -> - case (?DICT):find(From, StateData#state.robots) of - {ok, passed} -> true; - _ -> - WList = - (StateData#state.config)#config.captcha_whitelist, - #jid{luser = U, lserver = S, lresource = R} = From, - case (?SETS):is_element({U, S, R}, WList) of - true -> true; - false -> - case (?SETS):is_element({U, S, <<"">>}, WList) of - true -> true; - false -> - case (?SETS):is_element({<<"">>, S, <<"">>}, WList) - of - true -> true; - false -> captcha_required - end - end - end - end; - _ -> true - end. - -extract_password([]) -> false; -extract_password([#xmlel{attrs = Attrs} = El | Els]) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC -> - case xml:get_subtag(El, <<"password">>) of - false -> false; - SubEl -> xml:get_tag_cdata(SubEl) - end; - _ -> extract_password(Els) - end; -extract_password([_ | Els]) -> extract_password(Els). - -count_stanza_shift(Nick, Els, StateData) -> - HL = lqueue_to_list(StateData#state.history), - Since = extract_history(Els, <<"since">>), - Shift0 = case Since of - false -> 0; - _ -> - Sin = calendar:datetime_to_gregorian_seconds(Since), - count_seconds_shift(Sin, HL) - end, - Seconds = extract_history(Els, <<"seconds">>), - Shift1 = case Seconds of - false -> 0; - _ -> - Sec = - calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(now())) - - Seconds, - count_seconds_shift(Sec, HL) - end, - MaxStanzas = extract_history(Els, <<"maxstanzas">>), - Shift2 = case MaxStanzas of - false -> 0; - _ -> count_maxstanzas_shift(MaxStanzas, HL) - end, - MaxChars = extract_history(Els, <<"maxchars">>), - Shift3 = case MaxChars of - false -> 0; - _ -> count_maxchars_shift(Nick, MaxChars, HL) - end, - lists:max([Shift0, Shift1, Shift2, Shift3]). - -count_seconds_shift(Seconds, HistoryList) -> - lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject, - TimeStamp, _Size}) -> - T = - calendar:datetime_to_gregorian_seconds(TimeStamp), - if T < Seconds -> 1; - true -> 0 - end - end, - HistoryList)). - -count_maxstanzas_shift(MaxStanzas, HistoryList) -> - S = length(HistoryList) - MaxStanzas, - if S =< 0 -> 0; - true -> S - end. - -count_maxchars_shift(Nick, MaxSize, HistoryList) -> - NLen = byte_size(Nick) + 1, - Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject, - _TimeStamp, Size}) -> - Size + NLen - end, - HistoryList), - calc_shift(MaxSize, Sizes). - -calc_shift(MaxSize, Sizes) -> - Total = lists:sum(Sizes), - calc_shift(MaxSize, Total, 0, Sizes). - -calc_shift(_MaxSize, _Size, Shift, []) -> Shift; -calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> - if MaxSize >= Size -> Shift; - true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes) - end. - -extract_history([], _Type) -> false; -extract_history([#xmlel{attrs = Attrs} = El | Els], - Type) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC -> - AttrVal = xml:get_path_s(El, - [{elem, <<"history">>}, {attr, Type}]), - case Type of - <<"since">> -> - case jlib:datetime_string_to_timestamp(AttrVal) of - undefined -> false; - TS -> calendar:now_to_universal_time(TS) - end; - _ -> - case catch jlib:binary_to_integer(AttrVal) of - IntVal when is_integer(IntVal) and (IntVal >= 0) -> - IntVal; - _ -> false - end - end; - _ -> extract_history(Els, Type) - end; -extract_history([_ | Els], Type) -> - extract_history(Els, Type). - -send_update_presence(JID, StateData) -> - send_update_presence(JID, <<"">>, StateData). - -send_update_presence(JID, Reason, StateData) -> - LJID = jlib:jid_tolower(JID), - LJIDs = case LJID of - {U, S, <<"">>} -> - (?DICT):fold(fun (J, _, Js) -> - case J of - {U, S, _} -> [J | Js]; - _ -> Js - end - end, - [], StateData#state.users); - _ -> - case (?DICT):is_key(LJID, StateData#state.users) of - true -> [LJID]; - _ -> [] - end - end, - lists:foreach(fun (J) -> - send_new_presence(J, Reason, StateData) - end, - LJIDs). - -send_new_presence(NJID, StateData) -> - send_new_presence(NJID, <<"">>, StateData). - -send_new_presence(NJID, Reason, StateData) -> - #user{nick = Nick} = - (?DICT):fetch(jlib:jid_tolower(NJID), - StateData#state.users), - LJID = find_jid_by_nick(Nick, StateData), - {ok, - #user{jid = RealJID, role = Role, - last_presence = Presence}} = - (?DICT):find(jlib:jid_tolower(LJID), - StateData#state.users), - Affiliation = get_affiliation(LJID, StateData), - SAffiliation = affiliation_to_list(Affiliation), - SRole = role_to_list(Role), - lists:foreach(fun ({_LJID, Info}) -> - ItemAttrs = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jlib:jid_to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}] - end, - ItemEls = case Reason of - <<"">> -> []; - _ -> - [#xmlel{name = <<"reason">>, - attrs = [], - children = - [{xmlcdata, Reason}]}] - end, - Status = case StateData#state.just_created of - true -> - [#xmlel{name = <<"status">>, - attrs = - [{<<"code">>, <<"201">>}], - children = []}]; - false -> [] - end, - Status2 = case - (StateData#state.config)#config.anonymous - == false - andalso NJID == Info#user.jid - of - true -> - [#xmlel{name = <<"status">>, - attrs = - [{<<"code">>, <<"100">>}], - children = []} - | Status]; - false -> Status - end, - Status3 = case NJID == Info#user.jid of - true -> - [#xmlel{name = <<"status">>, - attrs = - [{<<"code">>, <<"110">>}], - children = []} - | Status2]; - false -> Status2 - end, - Packet = xml:append_subtags(Presence, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs - = - ItemAttrs, - children - = - ItemEls} - | Status3]}]), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet) - end, - (?DICT):to_list(StateData#state.users)). - -send_existing_presences(ToJID, StateData) -> - LToJID = jlib:jid_tolower(ToJID), - {ok, #user{jid = RealToJID, role = Role}} = - (?DICT):find(LToJID, StateData#state.users), - lists:foreach(fun ({FromNick, _Users}) -> - LJID = find_jid_by_nick(FromNick, StateData), - #user{jid = FromJID, role = FromRole, - last_presence = Presence} = - (?DICT):fetch(jlib:jid_tolower(LJID), - StateData#state.users), - case RealToJID of - FromJID -> ok; - _ -> - FromAffiliation = get_affiliation(LJID, - StateData), - ItemAttrs = case Role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jlib:jid_to_string(FromJID)}, - {<<"affiliation">>, - affiliation_to_list(FromAffiliation)}, - {<<"role">>, - role_to_list(FromRole)}]; - _ -> - [{<<"affiliation">>, - affiliation_to_list(FromAffiliation)}, - {<<"role">>, - role_to_list(FromRole)}] - end, - Packet = xml:append_subtags(Presence, - [#xmlel{name = - <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - ItemAttrs, - children - = - []}]}]), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - FromNick), - RealToJID, Packet) - end - end, - (?DICT):to_list(StateData#state.nicks)). - -now_to_usec({MSec, Sec, USec}) -> - (MSec * 1000000 + Sec) * 1000000 + USec. - -change_nick(JID, Nick, StateData) -> - LJID = jlib:jid_tolower(JID), - {ok, #user{nick = OldNick}} = (?DICT):find(LJID, - StateData#state.users), - Users = (?DICT):update(LJID, - fun (#user{} = User) -> User#user{nick = Nick} end, - StateData#state.users), - OldNickUsers = (?DICT):fetch(OldNick, - StateData#state.nicks), - NewNickUsers = case (?DICT):find(Nick, - StateData#state.nicks) - of - {ok, U} -> U; - error -> [] - end, - SendOldUnavailable = length(OldNickUsers) == 1, - SendNewAvailable = SendOldUnavailable orelse - NewNickUsers == [], - Nicks = case OldNickUsers of - [LJID] -> - (?DICT):store(Nick, [LJID | NewNickUsers], - (?DICT):erase(OldNick, StateData#state.nicks)); - [_ | _] -> - (?DICT):store(Nick, [LJID | NewNickUsers], - (?DICT):store(OldNick, OldNickUsers -- [LJID], - StateData#state.nicks)) - end, - NewStateData = StateData#state{users = Users, - nicks = Nicks}, - send_nick_changing(JID, OldNick, NewStateData, - SendOldUnavailable, SendNewAvailable), - add_to_log(nickchange, {OldNick, Nick}, StateData), - NewStateData. - -send_nick_changing(JID, OldNick, StateData, - SendOldUnavailable, SendNewAvailable) -> - {ok, - #user{jid = RealJID, nick = Nick, role = Role, - last_presence = Presence}} = - (?DICT):find(jlib:jid_tolower(JID), - StateData#state.users), - Affiliation = get_affiliation(JID, StateData), - SAffiliation = affiliation_to_list(Affiliation), - SRole = role_to_list(Role), - lists:foreach(fun ({_LJID, Info}) -> - ItemAttrs1 = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jlib:jid_to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}, - {<<"nick">>, Nick}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}, - {<<"nick">>, Nick}] - end, - ItemAttrs2 = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jlib:jid_to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}] - end, - Packet1 = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs1, - children = - []}, - #xmlel{name = - <<"status">>, - attrs = - [{<<"code">>, - <<"303">>}], - children = - []}]}]}, - Packet2 = xml:append_subtags(Presence, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - ItemAttrs2, - children - = - []}]}]), - if SendOldUnavailable -> - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - OldNick), - Info#user.jid, Packet1); - true -> ok - end, - if SendNewAvailable -> - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet2); - true -> ok - end - end, - (?DICT):to_list(StateData#state.users)). - -lqueue_new(Max) -> - #lqueue{queue = queue:new(), len = 0, max = Max}. - -%% If the message queue limit is set to 0, do not store messages. -lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ; -%% Otherwise, rotate messages in the queue store. -lqueue_in(Item, - #lqueue{queue = Q1, len = Len, max = Max}) -> - Q2 = queue:in(Item, Q1), - if Len >= Max -> - Q3 = lqueue_cut(Q2, Len - Max + 1), - #lqueue{queue = Q3, len = Max, max = Max}; - true -> #lqueue{queue = Q2, len = Len + 1, max = Max} - end. - -lqueue_cut(Q, 0) -> Q; -lqueue_cut(Q, N) -> - {_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1). - -lqueue_to_list(#lqueue{queue = Q1}) -> - queue:to_list(Q1). - - -add_message_to_history(FromNick, FromJID, Packet, StateData) -> - HaveSubject = case xml:get_subtag(Packet, <<"subject">>) - of - false -> false; - _ -> true - end, - TimeStamp = calendar:now_to_universal_time(now()), - SenderJid = case - (StateData#state.config)#config.anonymous - of - true -> StateData#state.jid; - false -> FromJID - end, - TSPacket = xml:append_subtags(Packet, - [jlib:timestamp_to_xml(TimeStamp, utc, - SenderJid, <<"">>), - jlib:timestamp_to_xml(TimeStamp)]), - SPacket = - jlib:replace_from_to(jlib:jid_replace_resource(StateData#state.jid, - FromNick), - StateData#state.jid, TSPacket), - Size = element_size(SPacket), - Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, - TimeStamp, Size}, - StateData#state.history), - add_to_log(text, {FromNick, Packet}, StateData), - StateData#state{history = Q1}. - -send_history(JID, Shift, StateData) -> - lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp, - _Size}, - B) -> - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - JID, Packet), - B or HaveSubject - end, - false, - lists:nthtail(Shift, - lqueue_to_list(StateData#state.history))). - -send_subject(JID, Lang, StateData) -> - case StateData#state.subject_author of - <<"">> -> ok; - Nick -> - Subject = StateData#state.subject, - Packet = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Subject}]}, - #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - <<Nick/binary, - (translate:translate(Lang, - <<" has set the subject to: ">>))/binary, - Subject/binary>>}]}]}, - ejabberd_router:route(StateData#state.jid, JID, Packet) - end. - -check_subject(Packet) -> - case xml:get_subtag(Packet, <<"subject">>) of - false -> false; - SubjEl -> xml:get_tag_cdata(SubjEl) - end. - -can_change_subject(Role, StateData) -> - case (StateData#state.config)#config.allow_change_subj - of - true -> Role == moderator orelse Role == participant; - _ -> Role == moderator - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Admin stuff - -process_iq_admin(From, set, Lang, SubEl, StateData) -> - #xmlel{children = Items} = SubEl, - process_admin_items_set(From, Items, Lang, StateData); -process_iq_admin(From, get, Lang, SubEl, StateData) -> - case xml:get_subtag(SubEl, <<"item">>) of - false -> {error, ?ERR_BAD_REQUEST}; - Item -> - FAffiliation = get_affiliation(From, StateData), - FRole = get_role(From, StateData), - case xml:get_tag_attr(<<"role">>, Item) of - false -> - case xml:get_tag_attr(<<"affiliation">>, Item) of - false -> {error, ?ERR_BAD_REQUEST}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; - SAffiliation -> - if (FAffiliation == owner) or - (FAffiliation == admin) -> - Items = items_with_affiliation(SAffiliation, - StateData), - {result, Items, StateData}; - true -> - ErrText = - <<"Administrator privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end - end - end; - {value, StrRole} -> - case catch list_to_role(StrRole) of - {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; - SRole -> - if FRole == moderator -> - Items = items_with_role(SRole, StateData), - {result, Items, StateData}; - true -> - ErrText = <<"Moderator privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end - end - end - end. - -items_with_role(SRole, StateData) -> - lists:map(fun ({_, U}) -> user_to_item(U, StateData) - end, - search_role(SRole, StateData)). - -items_with_affiliation(SAffiliation, StateData) -> - lists:map(fun ({JID, {Affiliation, Reason}}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - affiliation_to_list(Affiliation)}, - {<<"jid">>, jlib:jid_to_string(JID)}], - children = - [#xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, Reason}]}]}; - ({JID, Affiliation}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - affiliation_to_list(Affiliation)}, - {<<"jid">>, jlib:jid_to_string(JID)}], - children = []} - end, - search_affiliation(SAffiliation, StateData)). - -user_to_item(#user{role = Role, nick = Nick, jid = JID}, - StateData) -> - Affiliation = get_affiliation(JID, StateData), - #xmlel{name = <<"item">>, - attrs = - [{<<"role">>, role_to_list(Role)}, - {<<"affiliation">>, affiliation_to_list(Affiliation)}, - {<<"nick">>, Nick}, - {<<"jid">>, jlib:jid_to_string(JID)}], - children = []}. - -search_role(Role, StateData) -> - lists:filter(fun ({_, #user{role = R}}) -> Role == R - end, - (?DICT):to_list(StateData#state.users)). - -search_affiliation(Affiliation, StateData) -> - lists:filter(fun ({_, A}) -> - case A of - {A1, _Reason} -> Affiliation == A1; - _ -> Affiliation == A - end - end, - (?DICT):to_list(StateData#state.affiliations)). - -process_admin_items_set(UJID, Items, Lang, StateData) -> - UAffiliation = get_affiliation(UJID, StateData), - URole = get_role(UJID, StateData), - case find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, []) - of - {result, Res} -> - ?INFO_MSG("Processing MUC admin query from ~s in " - "room ~s:~n ~p", - [jlib:jid_to_string(UJID), - jlib:jid_to_string(StateData#state.jid), Res]), - NSD = lists:foldl(fun (E, SD) -> - case catch case E of - {JID, affiliation, owner, _} - when JID#jid.luser == - <<"">> -> - %% If the provided JID does not have username, - %% forget the affiliation completely - SD; - {JID, role, none, Reason} -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"307">>, - SD), - set_role(JID, none, SD); - {JID, affiliation, none, - Reason} -> - case - (SD#state.config)#config.members_only - of - true -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"321">>, - none, - SD), - SD1 = - set_affiliation(JID, - none, - SD), - set_role(JID, none, - SD1); - _ -> - SD1 = - set_affiliation(JID, - none, - SD), - send_update_presence(JID, - SD1), - SD1 - end; - {JID, affiliation, outcast, - Reason} -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"301">>, - outcast, - SD), - set_affiliation(JID, - outcast, - set_role(JID, - none, - SD), - Reason); - {JID, affiliation, A, Reason} - when (A == admin) or - (A == owner) -> - SD1 = set_affiliation(JID, - A, - SD, - Reason), - SD2 = set_role(JID, - moderator, - SD1), - send_update_presence(JID, - Reason, - SD2), - SD2; - {JID, affiliation, member, - Reason} -> - SD1 = set_affiliation(JID, - member, - SD, - Reason), - SD2 = set_role(JID, - participant, - SD1), - send_update_presence(JID, - Reason, - SD2), - SD2; - {JID, role, Role, Reason} -> - SD1 = set_role(JID, Role, - SD), - catch - send_new_presence(JID, - Reason, - SD1), - SD1; - {JID, affiliation, A, - _Reason} -> - SD1 = set_affiliation(JID, - A, - SD), - send_update_presence(JID, - SD1), - SD1 - end - of - {'EXIT', ErrReason} -> - ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n", - [ErrReason]), - SD; - NSD -> NSD - end - end, - StateData, lists:flatten(Res)), - case (NSD#state.config)#config.persistent of - true -> - mod_muc:store_room(NSD#state.server_host, - NSD#state.host, NSD#state.room, - make_opts(NSD)); - _ -> ok - end, - {result, [], NSD}; - Err -> Err - end. - -find_changed_items(_UJID, _UAffiliation, _URole, [], - _Lang, _StateData, Res) -> - {result, Res}; -find_changed_items(UJID, UAffiliation, URole, - [{xmlcdata, _} | Items], Lang, StateData, Res) -> - find_changed_items(UJID, UAffiliation, URole, Items, - Lang, StateData, Res); -find_changed_items(UJID, UAffiliation, URole, - [#xmlel{name = <<"item">>, attrs = Attrs} = Item - | Items], - Lang, StateData, Res) -> - TJID = case xml:get_attr(<<"jid">>, Attrs) of - {value, S} -> - case jlib:string_to_jid(S) of - error -> - ErrText = iolist_to_binary( - io_lib:format(translate:translate( - Lang, - <<"Jabber ID ~s is invalid">>), - [S])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - J -> {value, [J]} - end; - _ -> - case xml:get_attr(<<"nick">>, Attrs) of - {value, N} -> - case find_jids_by_nick(N, StateData) of - false -> - ErrText = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Nickname ~s does not exist in the room">>), - [N])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - J -> {value, J} - end; - _ -> {error, ?ERR_BAD_REQUEST} - end - end, - case TJID of - {value, [JID | _] = JIDs} -> - TAffiliation = get_affiliation(JID, StateData), - TRole = get_role(JID, StateData), - case xml:get_attr(<<"role">>, Attrs) of - false -> - case xml:get_attr(<<"affiliation">>, Attrs) of - false -> {error, ?ERR_BAD_REQUEST}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> - ErrText1 = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Invalid affiliation: ~s">>), - [StrAffiliation])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)}; - SAffiliation -> - ServiceAf = get_service_affiliation(JID, StateData), - CanChangeRA = case can_change_ra(UAffiliation, - URole, - TAffiliation, - TRole, affiliation, - SAffiliation, - ServiceAf) - of - nothing -> nothing; - true -> true; - check_owner -> - case search_affiliation(owner, - StateData) - of - [{OJID, _}] -> - jlib:jid_remove_resource(OJID) - /= - jlib:jid_tolower(jlib:jid_remove_resource(UJID)); - _ -> true - end; - _ -> false - end, - case CanChangeRA of - nothing -> - find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, - Res); - true -> - Reason = xml:get_path_s(Item, - [{elem, <<"reason">>}, - cdata]), - MoreRes = [{jlib:jid_remove_resource(Jidx), - affiliation, SAffiliation, Reason} - || Jidx <- JIDs], - find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, - [MoreRes | Res]); - false -> {error, ?ERR_NOT_ALLOWED} - end - end - end; - {value, StrRole} -> - case catch list_to_role(StrRole) of - {'EXIT', _} -> - ErrText1 = iolist_to_binary( - io_lib:format(translate:translate( - Lang, - <<"Invalid role: ~s">>), - [StrRole])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)}; - SRole -> - ServiceAf = get_service_affiliation(JID, StateData), - CanChangeRA = case can_change_ra(UAffiliation, URole, - TAffiliation, TRole, - role, SRole, ServiceAf) - of - nothing -> nothing; - true -> true; - check_owner -> - case search_affiliation(owner, - StateData) - of - [{OJID, _}] -> - jlib:jid_remove_resource(OJID) - /= - jlib:jid_tolower(jlib:jid_remove_resource(UJID)); - _ -> true - end; - _ -> false - end, - case CanChangeRA of - nothing -> - find_changed_items(UJID, UAffiliation, URole, Items, - Lang, StateData, Res); - true -> - Reason = xml:get_path_s(Item, - [{elem, <<"reason">>}, - cdata]), - MoreRes = [{Jidx, role, SRole, Reason} - || Jidx <- JIDs], - find_changed_items(UJID, UAffiliation, URole, Items, - Lang, StateData, - [MoreRes | Res]); - _ -> {error, ?ERR_NOT_ALLOWED} - end - end - end; - Err -> Err - end; -find_changed_items(_UJID, _UAffiliation, _URole, _Items, - _Lang, _StateData, _Res) -> - {error, ?ERR_BAD_REQUEST}. - -can_change_ra(_FAffiliation, _FRole, owner, _TRole, - affiliation, owner, owner) -> - %% A room owner tries to add as persistent owner a - %% participant that is already owner because he is MUC admin - true; -can_change_ra(_FAffiliation, _FRole, _TAffiliation, - _TRole, _RoleorAffiliation, _Value, owner) -> - %% Nobody can decrease MUC admin's role/affiliation - false; -can_change_ra(_FAffiliation, _FRole, TAffiliation, - _TRole, affiliation, Value, _ServiceAf) - when TAffiliation == Value -> - nothing; -can_change_ra(_FAffiliation, _FRole, _TAffiliation, - TRole, role, Value, _ServiceAf) - when TRole == Value -> - nothing; -can_change_ra(FAffiliation, _FRole, outcast, _TRole, - affiliation, none, _ServiceAf) - when (FAffiliation == owner) or - (FAffiliation == admin) -> - true; -can_change_ra(FAffiliation, _FRole, outcast, _TRole, - affiliation, member, _ServiceAf) - when (FAffiliation == owner) or - (FAffiliation == admin) -> - true; -can_change_ra(owner, _FRole, outcast, _TRole, - affiliation, admin, _ServiceAf) -> - true; -can_change_ra(owner, _FRole, outcast, _TRole, - affiliation, owner, _ServiceAf) -> - true; -can_change_ra(FAffiliation, _FRole, none, _TRole, - affiliation, outcast, _ServiceAf) - when (FAffiliation == owner) or - (FAffiliation == admin) -> - true; -can_change_ra(FAffiliation, _FRole, none, _TRole, - affiliation, member, _ServiceAf) - when (FAffiliation == owner) or - (FAffiliation == admin) -> - true; -can_change_ra(owner, _FRole, none, _TRole, affiliation, - admin, _ServiceAf) -> - true; -can_change_ra(owner, _FRole, none, _TRole, affiliation, - owner, _ServiceAf) -> - true; -can_change_ra(FAffiliation, _FRole, member, _TRole, - affiliation, outcast, _ServiceAf) - when (FAffiliation == owner) or - (FAffiliation == admin) -> - true; -can_change_ra(FAffiliation, _FRole, member, _TRole, - affiliation, none, _ServiceAf) - when (FAffiliation == owner) or - (FAffiliation == admin) -> - true; -can_change_ra(owner, _FRole, member, _TRole, - affiliation, admin, _ServiceAf) -> - true; -can_change_ra(owner, _FRole, member, _TRole, - affiliation, owner, _ServiceAf) -> - true; -can_change_ra(owner, _FRole, admin, _TRole, affiliation, - _Affiliation, _ServiceAf) -> - true; -can_change_ra(owner, _FRole, owner, _TRole, affiliation, - _Affiliation, _ServiceAf) -> - check_owner; -can_change_ra(_FAffiliation, _FRole, _TAffiliation, - _TRole, affiliation, _Value, _ServiceAf) -> - false; -can_change_ra(_FAffiliation, moderator, _TAffiliation, - visitor, role, none, _ServiceAf) -> - true; -can_change_ra(_FAffiliation, moderator, _TAffiliation, - visitor, role, participant, _ServiceAf) -> - true; -can_change_ra(FAffiliation, _FRole, _TAffiliation, - visitor, role, moderator, _ServiceAf) - when (FAffiliation == owner) or - (FAffiliation == admin) -> - true; -can_change_ra(_FAffiliation, moderator, _TAffiliation, - participant, role, none, _ServiceAf) -> - true; -can_change_ra(_FAffiliation, moderator, _TAffiliation, - participant, role, visitor, _ServiceAf) -> - true; -can_change_ra(FAffiliation, _FRole, _TAffiliation, - participant, role, moderator, _ServiceAf) - when (FAffiliation == owner) or - (FAffiliation == admin) -> - true; -can_change_ra(_FAffiliation, _FRole, owner, moderator, - role, visitor, _ServiceAf) -> - false; -can_change_ra(owner, _FRole, _TAffiliation, moderator, - role, visitor, _ServiceAf) -> - true; -can_change_ra(_FAffiliation, _FRole, admin, moderator, - role, visitor, _ServiceAf) -> - false; -can_change_ra(admin, _FRole, _TAffiliation, moderator, - role, visitor, _ServiceAf) -> - true; -can_change_ra(_FAffiliation, _FRole, owner, moderator, - role, participant, _ServiceAf) -> - false; -can_change_ra(owner, _FRole, _TAffiliation, moderator, - role, participant, _ServiceAf) -> - true; -can_change_ra(_FAffiliation, _FRole, admin, moderator, - role, participant, _ServiceAf) -> - false; -can_change_ra(admin, _FRole, _TAffiliation, moderator, - role, participant, _ServiceAf) -> - true; -can_change_ra(_FAffiliation, _FRole, _TAffiliation, - _TRole, role, _Value, _ServiceAf) -> - false. - -send_kickban_presence(UJID, JID, Reason, Code, StateData) -> - NewAffiliation = get_affiliation(JID, StateData), - send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, - StateData). - -send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, - StateData) -> - LJID = jlib:jid_tolower(JID), - LJIDs = case LJID of - {U, S, <<"">>} -> - (?DICT):fold(fun (J, _, Js) -> - case J of - {U, S, _} -> [J | Js]; - _ -> Js - end - end, - [], StateData#state.users); - _ -> - case (?DICT):is_key(LJID, StateData#state.users) of - true -> [LJID]; - _ -> [] - end - end, - lists:foreach(fun (J) -> - {ok, #user{nick = Nick}} = (?DICT):find(J, - StateData#state.users), - add_to_log(kickban, {Nick, Reason, Code}, StateData), - tab_remove_online_user(J, StateData), - send_kickban_presence1(UJID, J, Reason, Code, - NewAffiliation, StateData) - end, - LJIDs). - -send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, - StateData) -> - {ok, #user{jid = RealJID, nick = Nick}} = - (?DICT):find(jlib:jid_tolower(UJID), - StateData#state.users), - SAffiliation = affiliation_to_list(Affiliation), - BannedJIDString = jlib:jid_to_string(RealJID), - case MJID /= <<"">> of - true -> - {ok, #user{nick = ActorNick}} = - (?DICT):find(jlib:jid_tolower(MJID), - StateData#state.users); - false -> - ActorNick = <<"">> - end, - lists:foreach(fun ({_LJID, Info}) -> - JidAttrList = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, BannedJIDString}]; - false -> [] - end, - ItemAttrs = [{<<"affiliation">>, SAffiliation}, - {<<"role">>, <<"none">>}] - ++ JidAttrList, - ItemEls = case Reason of - <<"">> -> []; - _ -> - [#xmlel{name = <<"reason">>, - attrs = [], - children = - [{xmlcdata, Reason}]}] - end, - ItemElsActor = case MJID of - <<"">> -> []; - _ -> [#xmlel{name = <<"actor">>, - attrs = - [{<<"nick">>, ActorNick}]}] - end, - Packet = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs, - children = - ItemElsActor ++ ItemEls}, - #xmlel{name = - <<"status">>, - attrs = - [{<<"code">>, - Code}], - children = - []}]}]}, - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet) - end, - (?DICT):to_list(StateData#state.users)). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Owner stuff - -process_iq_owner(From, set, Lang, SubEl, StateData) -> - FAffiliation = get_affiliation(From, StateData), - case FAffiliation of - owner -> - #xmlel{children = Els} = SubEl, - case xml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = XEl] -> - case {xml:get_tag_attr_s(<<"xmlns">>, XEl), - xml:get_tag_attr_s(<<"type">>, XEl)} - of - {?NS_XDATA, <<"cancel">>} -> {result, [], StateData}; - {?NS_XDATA, <<"submit">>} -> - case is_allowed_log_change(XEl, StateData, From) andalso - is_allowed_persistent_change(XEl, StateData, From) - andalso - is_allowed_room_name_desc_limits(XEl, StateData) - andalso - is_password_settings_correct(XEl, StateData) - of - true -> set_config(XEl, StateData); - false -> {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> {error, ?ERR_BAD_REQUEST} - end; - [#xmlel{name = <<"destroy">>} = SubEl1] -> - ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", - [jlib:jid_to_string(StateData#state.jid), - jlib:jid_to_string(From)]), - add_to_log(room_existence, destroyed, StateData), - destroy_room(SubEl1, StateData); - Items -> - process_admin_items_set(From, Items, Lang, StateData) - end; - _ -> - ErrText = <<"Owner privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end; -process_iq_owner(From, get, Lang, SubEl, StateData) -> - FAffiliation = get_affiliation(From, StateData), - case FAffiliation of - owner -> - #xmlel{children = Els} = SubEl, - case xml:remove_cdata(Els) of - [] -> get_config(Lang, StateData, From); - [Item] -> - case xml:get_tag_attr(<<"affiliation">>, Item) of - false -> {error, ?ERR_BAD_REQUEST}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> - ErrText = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Invalid affiliation: ~s">>), - [StrAffiliation])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - SAffiliation -> - Items = items_with_affiliation(SAffiliation, - StateData), - {result, Items, StateData} - end - end; - _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - _ -> - ErrText = <<"Owner privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end. - -is_allowed_log_change(XEl, StateData, From) -> - case lists:keymember(<<"muc#roomconfig_enablelogging">>, - 1, jlib:parse_xdata_submit(XEl)) - of - false -> true; - true -> - allow == - mod_muc_log:check_access_log(StateData#state.server_host, - From) - end. - -is_allowed_persistent_change(XEl, StateData, From) -> - case - lists:keymember(<<"muc#roomconfig_persistentroom">>, 1, - jlib:parse_xdata_submit(XEl)) - of - false -> true; - true -> - {_AccessRoute, _AccessCreate, _AccessAdmin, - AccessPersistent} = - StateData#state.access, - allow == - acl:match_rule(StateData#state.server_host, - AccessPersistent, From) - end. - -%% Check if the Room Name and Room Description defined in the Data Form -%% are conformant to the configured limits -is_allowed_room_name_desc_limits(XEl, StateData) -> - IsNameAccepted = case - lists:keysearch(<<"muc#roomconfig_roomname">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [N]}} -> - byte_size(N) =< - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_room_name, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> I - end, infinity); - _ -> true - end, - IsDescAccepted = case - lists:keysearch(<<"muc#roomconfig_roomdesc">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [D]}} -> - byte_size(D) =< - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_room_desc, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> - I - end, infinity); - _ -> true - end, - IsNameAccepted and IsDescAccepted. - -%% Return false if: -%% "the password for a password-protected room is blank" -is_password_settings_correct(XEl, StateData) -> - Config = StateData#state.config, - OldProtected = Config#config.password_protected, - OldPassword = Config#config.password, - NewProtected = case - lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>, - 1, jlib:parse_xdata_submit(XEl)) - of - {value, {_, [<<"1">>]}} -> true; - {value, {_, [<<"0">>]}} -> false; - _ -> undefined - end, - NewPassword = case - lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [P]}} -> P; - _ -> undefined - end, - case {OldProtected, NewProtected, OldPassword, - NewPassword} - of - {true, undefined, <<"">>, undefined} -> false; - {true, undefined, _, <<"">>} -> false; - {_, true, <<"">>, undefined} -> false; - {_, true, _, <<"">>} -> false; - _ -> true - end. - --define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(BOOLXFIELD(Label, Var, Val), - ?XFIELD(<<"boolean">>, Label, Var, - case Val of - true -> <<"1">>; - _ -> <<"0">> - end)). - --define(STRINGXFIELD(Label, Var, Val), - ?XFIELD(<<"text-single">>, Label, Var, Val)). - --define(PRIVATEXFIELD(Label, Var, Val), - ?XFIELD(<<"text-private">>, Label, Var, Val)). - --define(JIDMULTIXFIELD(Label, Var, JIDList), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, jlib:jid_to_string(JID)}]} - || JID <- JIDList]}). - -get_default_room_maxusers(RoomState) -> - DefRoomOpts = - gen_mod:get_module_opt(RoomState#state.server_host, - mod_muc, default_room_options, - fun(L) when is_list(L) -> L end, - []), - RoomState2 = set_opts(DefRoomOpts, RoomState), - (RoomState2#state.config)#config.max_users. - -get_config(Lang, StateData, From) -> - {_AccessRoute, _AccessCreate, _AccessAdmin, - AccessPersistent} = - StateData#state.access, - ServiceMaxUsers = get_service_max_users(StateData), - DefaultRoomMaxUsers = - get_default_room_maxusers(StateData), - Config = StateData#state.config, - {MaxUsersRoomInteger, MaxUsersRoomString} = case - get_max_users(StateData) - of - N when is_integer(N) -> - {N, - jlib:integer_to_binary(N)}; - _ -> {0, <<"none">>} - end, - Res = [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Configuration of room ~s">>), - [jlib:jid_to_string(StateData#state.jid)]))}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"hidden">>}, - {<<"var">>, <<"FORM_TYPE">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"http://jabber.org/protocol/muc#roomconfig">>}]}]}, - ?STRINGXFIELD(<<"Room title">>, - <<"muc#roomconfig_roomname">>, (Config#config.title)), - ?STRINGXFIELD(<<"Room description">>, - <<"muc#roomconfig_roomdesc">>, - (Config#config.description))] - ++ - case acl:match_rule(StateData#state.server_host, - AccessPersistent, From) - of - allow -> - [?BOOLXFIELD(<<"Make room persistent">>, - <<"muc#roomconfig_persistentroom">>, - (Config#config.persistent))]; - _ -> [] - end - ++ - [?BOOLXFIELD(<<"Make room public searchable">>, - <<"muc#roomconfig_publicroom">>, - (Config#config.public)), - ?BOOLXFIELD(<<"Make participants list public">>, - <<"public_list">>, (Config#config.public_list)), - ?BOOLXFIELD(<<"Make room password protected">>, - <<"muc#roomconfig_passwordprotectedroom">>, - (Config#config.password_protected)), - ?PRIVATEXFIELD(<<"Password">>, - <<"muc#roomconfig_roomsecret">>, - case Config#config.password_protected of - true -> Config#config.password; - false -> <<"">> - end), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Maximum Number of Occupants">>)}, - {<<"var">>, <<"muc#roomconfig_maxusers">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, MaxUsersRoomString}]}] - ++ - if is_integer(ServiceMaxUsers) -> []; - true -> - [#xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"No limit">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - <<"none">>}]}]}] - end - ++ - [#xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - jlib:integer_to_binary(N)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - jlib:integer_to_binary(N)}]}]} - || N - <- lists:usort([ServiceMaxUsers, - DefaultRoomMaxUsers, - MaxUsersRoomInteger - | ?MAX_USERS_DEFAULT_LIST]), - N =< ServiceMaxUsers]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Present real Jabber IDs to">>)}, - {<<"var">>, <<"muc#roomconfig_whois">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - if Config#config.anonymous -> - <<"moderators">>; - true -> <<"anyone">> - end}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"moderators only">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"moderators">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"anyone">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"anyone">>}]}]}]}, - ?BOOLXFIELD(<<"Make room members-only">>, - <<"muc#roomconfig_membersonly">>, - (Config#config.members_only)), - ?BOOLXFIELD(<<"Make room moderated">>, - <<"muc#roomconfig_moderatedroom">>, - (Config#config.moderated)), - ?BOOLXFIELD(<<"Default users as participants">>, - <<"members_by_default">>, - (Config#config.members_by_default)), - ?BOOLXFIELD(<<"Allow users to change the subject">>, - <<"muc#roomconfig_changesubject">>, - (Config#config.allow_change_subj)), - ?BOOLXFIELD(<<"Allow users to send private messages">>, - <<"allow_private_messages">>, - (Config#config.allow_private_messages)), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Allow visitors to send private messages to">>)}, - {<<"var">>, - <<"allow_private_messages_from_visitors">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - case - Config#config.allow_private_messages_from_visitors - of - anyone -> <<"anyone">>; - moderators -> <<"moderators">>; - nobody -> <<"nobody">> - end}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"nobody">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, <<"nobody">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"moderators only">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"moderators">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"anyone">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"anyone">>}]}]}]}, - ?BOOLXFIELD(<<"Allow users to query other users">>, - <<"allow_query_users">>, - (Config#config.allow_query_users)), - ?BOOLXFIELD(<<"Allow users to send invites">>, - <<"muc#roomconfig_allowinvites">>, - (Config#config.allow_user_invites)), - ?BOOLXFIELD(<<"Allow visitors to send status text in " - "presence updates">>, - <<"muc#roomconfig_allowvisitorstatus">>, - (Config#config.allow_visitor_status)), - ?BOOLXFIELD(<<"Allow visitors to change nickname">>, - <<"muc#roomconfig_allowvisitornickchange">>, - (Config#config.allow_visitor_nickchange)), - ?BOOLXFIELD(<<"Allow visitors to send voice requests">>, - <<"muc#roomconfig_allowvoicerequests">>, - (Config#config.allow_voice_requests)), - ?STRINGXFIELD(<<"Minimum interval between voice requests " - "(in seconds)">>, - <<"muc#roomconfig_voicerequestmininterval">>, - (jlib:integer_to_binary(Config#config.voice_request_min_interval)))] - ++ - case ejabberd_captcha:is_feature_available() of - true -> - [?BOOLXFIELD(<<"Make room CAPTCHA protected">>, - <<"captcha_protected">>, - (Config#config.captcha_protected))]; - false -> [] - end - ++ - [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>, - <<"muc#roomconfig_captcha_whitelist">>, - ((?SETS):to_list(Config#config.captcha_whitelist)))] - ++ - case - mod_muc_log:check_access_log(StateData#state.server_host, - From) - of - allow -> - [?BOOLXFIELD(<<"Enable logging">>, - <<"muc#roomconfig_enablelogging">>, - (Config#config.logging))]; - _ -> [] - end, - {result, - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need an x:data capable client to " - "configure room">>)}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = Res}], - StateData}. - -set_config(XEl, StateData) -> - XData = jlib:parse_xdata_submit(XEl), - case XData of - invalid -> {error, ?ERR_BAD_REQUEST}; - _ -> - case set_xoption(XData, StateData#state.config) of - #config{} = Config -> - Res = change_config(Config, StateData), - {result, _, NSD} = Res, - Type = case {(StateData#state.config)#config.logging, - Config#config.logging} - of - {true, false} -> roomconfig_change_disabledlogging; - {false, true} -> roomconfig_change_enabledlogging; - {_, _} -> roomconfig_change - end, - Users = [{U#user.jid, U#user.nick, U#user.role} - || {_, U} <- (?DICT):to_list(StateData#state.users)], - add_to_log(Type, Users, NSD), - Res; - Err -> Err - end - end. - --define(SET_BOOL_XOPT(Opt, Val), - case Val of - <<"0">> -> - set_xoption(Opts, Config#config{Opt = false}); - <<"false">> -> - set_xoption(Opts, Config#config{Opt = false}); - <<"1">> -> set_xoption(Opts, Config#config{Opt = true}); - <<"true">> -> - set_xoption(Opts, Config#config{Opt = true}); - _ -> {error, ?ERR_BAD_REQUEST} - end). - --define(SET_NAT_XOPT(Opt, Val), - case catch jlib:binary_to_integer(Val) of - I when is_integer(I), I > 0 -> - set_xoption(Opts, Config#config{Opt = I}); - _ -> {error, ?ERR_BAD_REQUEST} - end). - --define(SET_STRING_XOPT(Opt, Val), - set_xoption(Opts, Config#config{Opt = Val})). - --define(SET_JIDMULTI_XOPT(Opt, Vals), - begin - Set = lists:foldl(fun ({U, S, R}, Set1) -> - (?SETS):add_element({U, S, R}, Set1); - (#jid{luser = U, lserver = S, lresource = R}, - Set1) -> - (?SETS):add_element({U, S, R}, Set1); - (_, Set1) -> Set1 - end, - (?SETS):empty(), Vals), - set_xoption(Opts, Config#config{Opt = Set}) - end). - -set_xoption([], Config) -> Config; -set_xoption([{<<"muc#roomconfig_roomname">>, [Val]} - | Opts], - Config) -> - ?SET_STRING_XOPT(title, Val); -set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]} - | Opts], - Config) -> - ?SET_STRING_XOPT(description, Val); -set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(allow_change_subj, Val); -set_xoption([{<<"allow_query_users">>, [Val]} | Opts], - Config) -> - ?SET_BOOL_XOPT(allow_query_users, Val); -set_xoption([{<<"allow_private_messages">>, [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(allow_private_messages, Val); -set_xoption([{<<"allow_private_messages_from_visitors">>, - [Val]} - | Opts], - Config) -> - case Val of - <<"anyone">> -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, - anyone); - <<"moderators">> -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, - moderators); - <<"nobody">> -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, - nobody); - _ -> {error, ?ERR_BAD_REQUEST} - end; -set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>, - [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(allow_visitor_status, Val); -set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>, - [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(allow_visitor_nickchange, Val); -set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(public, Val); -set_xoption([{<<"public_list">>, [Val]} | Opts], - Config) -> - ?SET_BOOL_XOPT(public_list, Val); -set_xoption([{<<"muc#roomconfig_persistentroom">>, - [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(persistent, Val); -set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(moderated, Val); -set_xoption([{<<"members_by_default">>, [Val]} | Opts], - Config) -> - ?SET_BOOL_XOPT(members_by_default, Val); -set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(members_only, Val); -set_xoption([{<<"captcha_protected">>, [Val]} | Opts], - Config) -> - ?SET_BOOL_XOPT(captcha_protected, Val); -set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(allow_user_invites, Val); -set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>, - [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(password_protected, Val); -set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]} - | Opts], - Config) -> - ?SET_STRING_XOPT(password, Val); -set_xoption([{<<"anonymous">>, [Val]} | Opts], - Config) -> - ?SET_BOOL_XOPT(anonymous, Val); -set_xoption([{<<"muc#roomconfig_allowvoicerequests">>, - [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(allow_voice_requests, Val); -set_xoption([{<<"muc#roomconfig_voicerequestmininterval">>, - [Val]} - | Opts], - Config) -> - ?SET_NAT_XOPT(voice_request_min_interval, Val); -set_xoption([{<<"muc#roomconfig_whois">>, [Val]} - | Opts], - Config) -> - case Val of - <<"moderators">> -> - ?SET_BOOL_XOPT(anonymous, - (iolist_to_binary(integer_to_list(1)))); - <<"anyone">> -> - ?SET_BOOL_XOPT(anonymous, - (iolist_to_binary(integer_to_list(0)))); - _ -> {error, ?ERR_BAD_REQUEST} - end; -set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]} - | Opts], - Config) -> - case Val of - <<"none">> -> ?SET_STRING_XOPT(max_users, none); - _ -> ?SET_NAT_XOPT(max_users, Val) - end; -set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]} - | Opts], - Config) -> - ?SET_BOOL_XOPT(logging, Val); -set_xoption([{<<"muc#roomconfig_captcha_whitelist">>, - Vals} - | Opts], - Config) -> - JIDs = [jlib:string_to_jid(Val) || Val <- Vals], - ?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs); -set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config) -> - set_xoption(Opts, Config); -set_xoption([_ | _Opts], _Config) -> - {error, ?ERR_BAD_REQUEST}. - -change_config(Config, StateData) -> - NSD = StateData#state{config = Config}, - case {(StateData#state.config)#config.persistent, - Config#config.persistent} - of - {_, true} -> - mod_muc:store_room(NSD#state.server_host, - NSD#state.host, NSD#state.room, make_opts(NSD)); - {true, false} -> - mod_muc:forget_room(NSD#state.server_host, - NSD#state.host, NSD#state.room); - {false, false} -> ok - end, - case {(StateData#state.config)#config.members_only, - Config#config.members_only} - of - {false, true} -> - NSD1 = remove_nonmembers(NSD), {result, [], NSD1}; - _ -> {result, [], NSD} - end. - -remove_nonmembers(StateData) -> - lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) -> - Affiliation = get_affiliation(JID, SD), - case Affiliation of - none -> - catch send_kickban_presence(<<"">>, JID, <<"">>, - <<"322">>, SD), - set_role(JID, none, SD); - _ -> SD - end - end, - StateData, (?DICT):to_list(StateData#state.users)). - -set_opts([], StateData) -> StateData; -set_opts([{Opt, Val} | Opts], StateData) -> - NSD = case Opt of - title -> - StateData#state{config = - (StateData#state.config)#config{title = - Val}}; - description -> - StateData#state{config = - (StateData#state.config)#config{description - = Val}}; - allow_change_subj -> - StateData#state{config = - (StateData#state.config)#config{allow_change_subj - = Val}}; - allow_query_users -> - StateData#state{config = - (StateData#state.config)#config{allow_query_users - = Val}}; - allow_private_messages -> - StateData#state{config = - (StateData#state.config)#config{allow_private_messages - = Val}}; - allow_private_messages_from_visitors -> - StateData#state{config = - (StateData#state.config)#config{allow_private_messages_from_visitors - = Val}}; - allow_visitor_nickchange -> - StateData#state{config = - (StateData#state.config)#config{allow_visitor_nickchange - = Val}}; - allow_visitor_status -> - StateData#state{config = - (StateData#state.config)#config{allow_visitor_status - = Val}}; - public -> - StateData#state{config = - (StateData#state.config)#config{public = - Val}}; - public_list -> - StateData#state{config = - (StateData#state.config)#config{public_list - = Val}}; - persistent -> - StateData#state{config = - (StateData#state.config)#config{persistent = - Val}}; - moderated -> - StateData#state{config = - (StateData#state.config)#config{moderated = - Val}}; - members_by_default -> - StateData#state{config = - (StateData#state.config)#config{members_by_default - = Val}}; - members_only -> - StateData#state{config = - (StateData#state.config)#config{members_only - = Val}}; - allow_user_invites -> - StateData#state{config = - (StateData#state.config)#config{allow_user_invites - = Val}}; - password_protected -> - StateData#state{config = - (StateData#state.config)#config{password_protected - = Val}}; - captcha_protected -> - StateData#state{config = - (StateData#state.config)#config{captcha_protected - = Val}}; - password -> - StateData#state{config = - (StateData#state.config)#config{password = - Val}}; - anonymous -> - StateData#state{config = - (StateData#state.config)#config{anonymous = - Val}}; - logging -> - StateData#state{config = - (StateData#state.config)#config{logging = - Val}}; - captcha_whitelist -> - StateData#state{config = - (StateData#state.config)#config{captcha_whitelist - = - (?SETS):from_list(Val)}}; - allow_voice_requests -> - StateData#state{config = - (StateData#state.config)#config{allow_voice_requests - = Val}}; - voice_request_min_interval -> - StateData#state{config = - (StateData#state.config)#config{voice_request_min_interval - = Val}}; - max_users -> - ServiceMaxUsers = get_service_max_users(StateData), - MaxUsers = if Val =< ServiceMaxUsers -> Val; - true -> ServiceMaxUsers - end, - StateData#state{config = - (StateData#state.config)#config{max_users = - MaxUsers}}; - affiliations -> - StateData#state{affiliations = (?DICT):from_list(Val)}; - subject -> StateData#state{subject = Val}; - subject_author -> StateData#state{subject_author = Val}; - _ -> StateData - end, - set_opts(Opts, NSD). - --define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}). - - -make_opts(StateData) -> - Config = StateData#state.config, - [?MAKE_CONFIG_OPT(title), ?MAKE_CONFIG_OPT(description), - ?MAKE_CONFIG_OPT(allow_change_subj), - ?MAKE_CONFIG_OPT(allow_query_users), - ?MAKE_CONFIG_OPT(allow_private_messages), - ?MAKE_CONFIG_OPT(allow_private_messages_from_visitors), - ?MAKE_CONFIG_OPT(allow_visitor_status), - ?MAKE_CONFIG_OPT(allow_visitor_nickchange), - ?MAKE_CONFIG_OPT(public), ?MAKE_CONFIG_OPT(public_list), - ?MAKE_CONFIG_OPT(persistent), - ?MAKE_CONFIG_OPT(moderated), - ?MAKE_CONFIG_OPT(members_by_default), - ?MAKE_CONFIG_OPT(members_only), - ?MAKE_CONFIG_OPT(allow_user_invites), - ?MAKE_CONFIG_OPT(password_protected), - ?MAKE_CONFIG_OPT(captcha_protected), - ?MAKE_CONFIG_OPT(password), ?MAKE_CONFIG_OPT(anonymous), - ?MAKE_CONFIG_OPT(logging), ?MAKE_CONFIG_OPT(max_users), - ?MAKE_CONFIG_OPT(allow_voice_requests), - ?MAKE_CONFIG_OPT(voice_request_min_interval), - {captcha_whitelist, - (?SETS):to_list((StateData#state.config)#config.captcha_whitelist)}, - {affiliations, - (?DICT):to_list(StateData#state.affiliations)}, - {subject, StateData#state.subject}, - {subject_author, StateData#state.subject_author}]. - -destroy_room(DEl, StateData) -> - lists:foreach(fun ({_LJID, Info}) -> - Nick = Info#user.nick, - ItemAttrs = [{<<"affiliation">>, <<"none">>}, - {<<"role">>, <<"none">>}], - Packet = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs, - children = - []}, - DEl]}]}, - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet) - end, - (?DICT):to_list(StateData#state.users)), - case (StateData#state.config)#config.persistent of - true -> - mod_muc:forget_room(StateData#state.server_host, - StateData#state.host, StateData#state.room); - false -> ok - end, - {result, [], stop}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Disco - --define(FEATURE(Var), - #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}], - children = []}). - --define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse), - case Opt of - true -> ?FEATURE(Fiftrue); - false -> ?FEATURE(Fiffalse) - end). - -process_iq_disco_info(_From, set, _Lang, _StateData) -> - {error, ?ERR_NOT_ALLOWED}; -process_iq_disco_info(_From, get, Lang, StateData) -> - Config = StateData#state.config, - {result, - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, get_title(StateData)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC}], children = []}, - ?CONFIG_OPT_TO_FEATURE((Config#config.public), - <<"muc_public">>, <<"muc_hidden">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.persistent), - <<"muc_persistent">>, <<"muc_temporary">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.members_only), - <<"muc_membersonly">>, <<"muc_open">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous), - <<"muc_semianonymous">>, <<"muc_nonanonymous">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.moderated), - <<"muc_moderated">>, <<"muc_unmoderated">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected), - <<"muc_passwordprotected">>, <<"muc_unsecured">>)] - ++ iq_disco_info_extras(Lang, StateData), - StateData}. - --define(RFIELDT(Type, Var, Val), - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, Type}, {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(RFIELD(Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - -iq_disco_info_extras(Lang, StateData) -> - Len = (?DICT):size(StateData#state.users), - RoomDescription = - (StateData#state.config)#config.description, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = - [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>, - <<"http://jabber.org/protocol/muc#roominfo">>), - ?RFIELD(<<"Room description">>, - <<"muc#roominfo_description">>, RoomDescription), - ?RFIELD(<<"Number of occupants">>, - <<"muc#roominfo_occupants">>, - (iolist_to_binary(integer_to_list(Len))))]}]. - -process_iq_disco_items(_From, set, _Lang, _StateData) -> - {error, ?ERR_NOT_ALLOWED}; -process_iq_disco_items(From, get, _Lang, StateData) -> - case (StateData#state.config)#config.public_list of - true -> - {result, get_mucroom_disco_items(StateData), StateData}; - _ -> - case is_occupant_or_admin(From, StateData) of - true -> - {result, get_mucroom_disco_items(StateData), StateData}; - _ -> {error, ?ERR_FORBIDDEN} - end - end. - -process_iq_captcha(_From, get, _Lang, _SubEl, - _StateData) -> - {error, ?ERR_NOT_ALLOWED}; -process_iq_captcha(_From, set, _Lang, SubEl, - StateData) -> - case ejabberd_captcha:process_reply(SubEl) of - ok -> {result, [], StateData}; - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end. - -get_title(StateData) -> - case (StateData#state.config)#config.title of - <<"">> -> StateData#state.room; - Name -> Name - end. - -get_roomdesc_reply(JID, StateData, Tail) -> - IsOccupantOrAdmin = is_occupant_or_admin(JID, - StateData), - if (StateData#state.config)#config.public or - IsOccupantOrAdmin -> - if (StateData#state.config)#config.public_list or - IsOccupantOrAdmin -> - {item, <<(get_title(StateData))/binary,Tail/binary>>}; - true -> {item, get_title(StateData)} - end; - true -> false - end. - -get_roomdesc_tail(StateData, Lang) -> - Desc = case (StateData#state.config)#config.public of - true -> <<"">>; - _ -> translate:translate(Lang, <<"private, ">>) - end, - Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0, - StateData#state.users), - <<" (", Desc/binary, - (iolist_to_binary(integer_to_list(Len)))/binary, ")">>. - -get_mucroom_disco_items(StateData) -> - lists:map(fun ({_LJID, Info}) -> - Nick = Info#user.nick, - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string({StateData#state.room, - StateData#state.host, - Nick})}, - {<<"name">>, Nick}], - children = []} - end, - (?DICT):to_list(StateData#state.users)). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Voice request support - -is_voice_request(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - case jlib:parse_xdata_submit(El) of - [_ | _] = Fields -> - case {lists:keysearch(<<"FORM_TYPE">>, 1, - Fields), - lists:keysearch(<<"muc#role">>, 1, - Fields)} - of - {{value, - {_, - [<<"http://jabber.org/protocol/muc#request">>]}}, - {value, {_, [<<"participant">>]}}} -> - true; - _ -> false - end; - _ -> false - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). - -prepare_request_form(Requester, Nick, Lang) -> - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"normal">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Voice request">>)}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Either approve or decline the voice " - "request.">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"http://jabber.org/protocol/muc#request">>}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"muc#role">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"participant">>}]}]}, - ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>, - (jlib:jid_to_string(Requester))), - ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, - Nick), - ?BOOLXFIELD(<<"Grant voice to this person?">>, - <<"muc#request_allow">>, - (jlib:binary_to_atom(<<"false">>)))]}]}. - -send_voice_request(From, StateData) -> - Moderators = search_role(moderator, StateData), - FromNick = find_nick_by_jid(From, StateData), - lists:foreach(fun ({_, User}) -> - ejabberd_router:route(StateData#state.jid, User#user.jid, - prepare_request_form(From, FromNick, - <<"">>)) - end, - Moderators). - -is_voice_approvement(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - case jlib:parse_xdata_submit(El) of - [_ | _] = Fs -> - case {lists:keysearch(<<"FORM_TYPE">>, 1, - Fs), - lists:keysearch(<<"muc#role">>, 1, - Fs), - lists:keysearch(<<"muc#request_allow">>, - 1, Fs)} - of - {{value, - {_, - [<<"http://jabber.org/protocol/muc#request">>]}}, - {value, {_, [<<"participant">>]}}, - {value, {_, [Flag]}}} - when Flag == <<"true">>; - Flag == <<"1">> -> - true; - _ -> false - end; - _ -> false - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). - -extract_jid_from_voice_approvement(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>} = El, error) -> - Fields = case jlib:parse_xdata_submit(El) of - invalid -> []; - Res -> Res - end, - lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) -> - case jlib:string_to_jid(JIDStr) of - error -> error; - J -> {ok, J} - end; - (_, Acc) -> Acc - end, - error, Fields); - (_, Acc) -> Acc - end, - error, Els). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Invitation support - -is_invitation(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC_USER -> - case xml:get_subtag(El, <<"invite">>) of - false -> false; - _ -> true - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). - -check_invitation(From, Els, Lang, StateData) -> - FAffiliation = get_affiliation(From, StateData), - CanInvite = - (StateData#state.config)#config.allow_user_invites - orelse - FAffiliation == admin orelse FAffiliation == owner, - InviteEl = case xml:remove_cdata(Els) of - [#xmlel{name = <<"x">>, children = Els1} = XEl] -> - case xml:get_tag_attr_s(<<"xmlns">>, XEl) of - ?NS_MUC_USER -> ok; - _ -> throw({error, ?ERR_BAD_REQUEST}) - end, - case xml:remove_cdata(Els1) of - [#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1; - _ -> throw({error, ?ERR_BAD_REQUEST}) - end; - _ -> throw({error, ?ERR_BAD_REQUEST}) - end, - JID = case - jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>, - InviteEl)) - of - error -> throw({error, ?ERR_JID_MALFORMED}); - JID1 -> JID1 - end, - case CanInvite of - false -> throw({error, ?ERR_NOT_ALLOWED}); - true -> - Reason = xml:get_path_s(InviteEl, - [{elem, <<"reason">>}, cdata]), - ContinueEl = case xml:get_path_s(InviteEl, - [{elem, <<"continue">>}]) - of - <<>> -> []; - Continue1 -> [Continue1] - end, - IEl = [#xmlel{name = <<"invite">>, - attrs = [{<<"from">>, jlib:jid_to_string(From)}], - children = - [#xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, Reason}]}] - ++ ContinueEl}], - PasswdEl = case - (StateData#state.config)#config.password_protected - of - true -> - [#xmlel{name = <<"password">>, attrs = [], - children = - [{xmlcdata, - (StateData#state.config)#config.password}]}]; - _ -> [] - end, - Body = #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [io_lib:format( - translate:translate( - Lang, - <<"~s invites you to the room ~s">>), - [jlib:jid_to_string(From), - jlib:jid_to_string({StateData#state.room, - StateData#state.host, - <<"">>})]), - - case - (StateData#state.config)#config.password_protected - of - true -> - <<", ", - (translate:translate(Lang, - <<"the password is">>))/binary, - " '", - ((StateData#state.config)#config.password)/binary, - "'">>; - _ -> <<"">> - end - , - case Reason of - <<"">> -> <<"">>; - _ -> <<" (", Reason/binary, ") ">> - end])}]}, - Msg = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"normal">>}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = IEl ++ PasswdEl}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XCONFERENCE}, - {<<"jid">>, - jlib:jid_to_string({StateData#state.room, - StateData#state.host, - <<"">>})}], - children = [{xmlcdata, Reason}]}, - Body]}, - ejabberd_router:route(StateData#state.jid, JID, Msg), - JID - end. - -%% Handle a message sent to the room by a non-participant. -%% If it is a decline, send to the inviter. -%% Otherwise, an error message is sent to the sender. -handle_roommessage_from_nonparticipant(Packet, Lang, - StateData, From) -> - case catch check_decline_invitation(Packet) of - {true, Decline_data} -> - send_decline_invitation(Decline_data, - StateData#state.jid, From); - _ -> - send_error_only_occupants(Packet, Lang, - StateData#state.jid, From) - end. - -%% Check in the packet is a decline. -%% If so, also returns the splitted packet. -%% This function must be catched, -%% because it crashes when the packet is not a decline message. -check_decline_invitation(Packet) -> - #xmlel{name = <<"message">>} = Packet, - XEl = xml:get_subtag(Packet, <<"x">>), - (?NS_MUC_USER) = xml:get_tag_attr_s(<<"xmlns">>, XEl), - DEl = xml:get_subtag(XEl, <<"decline">>), - ToString = xml:get_tag_attr_s(<<"to">>, DEl), - ToJID = jlib:string_to_jid(ToString), - {true, {Packet, XEl, DEl, ToJID}}. - -%% Send the decline to the inviter user. -%% The original stanza must be slightly modified. -send_decline_invitation({Packet, XEl, DEl, ToJID}, - RoomJID, FromJID) -> - FromString = - jlib:jid_to_string(jlib:jid_remove_resource(FromJID)), - #xmlel{name = <<"decline">>, attrs = DAttrs, - children = DEls} = - DEl, - DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs), - DAttrs3 = [{<<"from">>, FromString} | DAttrs2], - DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3, - children = DEls}, - XEl2 = replace_subelement(XEl, DEl2), - Packet2 = replace_subelement(Packet, XEl2), - ejabberd_router:route(RoomJID, ToJID, Packet2). - -%% Given an element and a new subelement, -%% replace the instance of the subelement in element with the new subelement. -replace_subelement(#xmlel{name = Name, attrs = Attrs, - children = SubEls}, - NewSubEl) -> - {_, NameNewSubEl, _, _} = NewSubEl, - SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl), - #xmlel{name = Name, attrs = Attrs, children = SubEls2}. - -send_error_only_occupants(Packet, Lang, RoomJID, From) -> - ErrText = - <<"Only occupants are allowed to send messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(RoomJID, From, Err). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Logging - -add_to_log(Type, Data, StateData) - when Type == roomconfig_change_disabledlogging -> - mod_muc_log:add_to_log(StateData#state.server_host, - roomconfig_change, Data, StateData#state.jid, - make_opts(StateData)); -add_to_log(Type, Data, StateData) -> - case (StateData#state.config)#config.logging of - true -> - mod_muc_log:add_to_log(StateData#state.server_host, - Type, Data, StateData#state.jid, - make_opts(StateData)); - false -> ok - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Users number checking - -tab_add_online_user(JID, StateData) -> - {LUser, LServer, LResource} = jlib:jid_tolower(JID), - US = {LUser, LServer}, - Room = StateData#state.room, - Host = StateData#state.host, - catch ets:insert(muc_online_users, - #muc_online_users{us = US, resource = LResource, - room = Room, host = Host}). - -tab_remove_online_user(JID, StateData) -> - {LUser, LServer, LResource} = jlib:jid_tolower(JID), - US = {LUser, LServer}, - Room = StateData#state.room, - Host = StateData#state.host, - catch ets:delete_object(muc_online_users, - #muc_online_users{us = US, resource = LResource, - room = Room, host = Host}). - -tab_count_user(JID) -> - {LUser, LServer, _} = jlib:jid_tolower(JID), - US = {LUser, LServer}, - case catch ets:select(muc_online_users, - [{#muc_online_users{us = US, _ = '_'}, [], [[]]}]) - of - Res when is_list(Res) -> length(Res); - _ -> 0 - end. - -element_size(El) -> - byte_size(xml:element_to_binary(El)). diff --git a/src/mod_muc/mod_muc_room.hrl b/src/mod_muc/mod_muc_room.hrl deleted file mode 100644 index 3bbc9318e..000000000 --- a/src/mod_muc/mod_muc_room.hrl +++ /dev/null @@ -1,116 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% -%%% ejabberd, Copyright (C) 2002-2013 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 -%%% -%%%---------------------------------------------------------------------- - --define(MAX_USERS_DEFAULT, 200). - --define(SETS, gb_sets). - --define(DICT, dict). - --record(lqueue, -{ - queue :: queue(), - len :: integer(), - max :: integer() -}). - --type lqueue() :: #lqueue{}. - --record(config, -{ - title = <<"">> :: binary(), - description = <<"">> :: binary(), - allow_change_subj = true :: boolean(), - allow_query_users = true :: boolean(), - allow_private_messages = true :: boolean(), - allow_private_messages_from_visitors = anyone :: anyone | moderators | nobody , - allow_visitor_status = true :: boolean(), - allow_visitor_nickchange = true :: boolean(), - public = true :: boolean(), - public_list = true :: boolean(), - persistent = false :: boolean(), - moderated = true :: boolean(), - captcha_protected = false :: boolean(), - members_by_default = true :: boolean(), - members_only = false :: boolean(), - allow_user_invites = false :: boolean(), - password_protected = false :: boolean(), - password = <<"">> :: binary(), - anonymous = true :: boolean(), - allow_voice_requests = true :: boolean(), - voice_request_min_interval = 1800 :: non_neg_integer(), - max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none, - logging = false :: boolean(), - captcha_whitelist = (?SETS):empty() :: gb_set() -}). - --type config() :: #config{}. - --type role() :: moderator | participant | visitor | none. - --record(user, -{ - jid :: jid(), - nick :: binary(), - role :: role(), - last_presence :: xmlel() -}). - --record(activity, -{ - message_time = 0 :: integer(), - presence_time = 0 :: integer(), - message_shaper :: shaper:shaper(), - presence_shaper :: shaper:shaper(), - message :: xmlel(), - presence :: {binary(), xmlel()} -}). - --record(state, -{ - room = <<"">> :: binary(), - host = <<"">> :: binary(), - server_host = <<"">> :: binary(), - access = {none,none,none,none} :: {atom(), atom(), atom(), atom()}, - jid = #jid{} :: jid(), - config = #config{} :: config(), - users = (?DICT):new() :: dict(), - last_voice_request_time = treap:empty() :: treap:treap(), - robots = (?DICT):new() :: dict(), - nicks = (?DICT):new() :: dict(), - affiliations = (?DICT):new() :: dict(), - history :: lqueue(), - subject = <<"">> :: binary(), - subject_author = <<"">> :: binary(), - just_created = false :: boolean(), - activity = treap:empty() :: treap:treap(), - room_shaper = none :: shaper:shaper(), - room_queue = queue:new() :: queue() -}). - --record(muc_online_users, {us = {<<>>, <<>>} :: {binary(), binary()}, - resource = <<>> :: binary() | '_', - room = <<>> :: binary() | '_', - host = <<>> :: binary() | '_'}). - --type muc_online_users() :: #muc_online_users{}. - --type muc_room_state() :: #state{}. |