aboutsummaryrefslogtreecommitdiff
path: root/src/mod_muc
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_muc')
-rw-r--r--src/mod_muc/Makefile.in42
-rw-r--r--src/mod_muc/Makefile.win3221
-rw-r--r--src/mod_muc/mod_muc.erl1127
-rw-r--r--src/mod_muc/mod_muc_log.erl1236
-rw-r--r--src/mod_muc/mod_muc_room.erl4460
-rw-r--r--src/mod_muc/mod_muc_room.hrl116
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\">&lt;</a> "
- "<a class=\"nav\" href=\"./\">^</a> <a "
- "class=\"nav\" href=\"~s\">&gt;</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, <<"\\&">>,
- <<"\\&amp;">>),
- S3 = ejabberd_regexp:greplace(S2, <<"<">>,
- <<"\\&lt;">>),
- S4 = ejabberd_regexp:greplace(S3, <<">">>,
- <<"\\&gt;">>),
- S5 = ejabberd_regexp:greplace(S4,
- <<"((http|https|ftp)://|(mailto|xmpp):)[^] "
- ")'\"}]+">>,
- link_regexp(NoFollow)),
- S6 = ejabberd_regexp:greplace(S5, <<" ">>,
- <<"\\&nbsp;\\&nbsp;">>),
- S7 = ejabberd_regexp:greplace(S6, <<"\\t">>,
- <<"\\&nbsp;\\&nbsp;\\&nbsp;\\&nbsp;">>),
- 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{}.