24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-22 22:23:42 +02:00

* src/mod_muc/mod_muc_room.erl: New anti-abuse options: min_presence_interval and min_message_interval (EJAB-227)

* doc/guide.tex: Likewise

SVN Revision: 794
This commit is contained in:
Mickaël Rémond 2007-06-25 16:43:42 +00:00
parent 7ec8ce9e50
commit 20da906311
3 changed files with 306 additions and 158 deletions

View File

@ -1,5 +1,9 @@
2007-06-25 Mickael Remond <mickael.remond@process-one.net>
* src/mod_muc/mod_muc_room.erl: New anti-abuse options:
min_presence_interval and min_message_interval
* doc/guide.tex: Likewise
* doc/guide.tex: Documentation improvements on watchdog
* doc/guide.tex: No need to escape underscore in Latex verbatim

View File

@ -1935,6 +1935,28 @@ Options:
disables the history feature and, as a result, nothing is kept in memory. The
default value is \term{20}. This value is global and thus affects all rooms on
the server.
\titem{min\_message\_interval} \ind{options!min\_message\_interval}
This option defines the minimum interval between two messages send by
a user in seconds. This option is global and valid for all chat
rooms. A decimal value can be used. When this option is not defined,
message rate is not limited. This feature can be used to protect a MUC
service from users abuses and limit number of messages that will be
broadcasted by the service. A good value for this minimum message
interval is 0.4 second. If a user tries to send messages faster, an
error is send back explaining that the message have been discarded and
describing the reason why the message is not acceptable.
\titem{min\_presence\_interval} \ind{options!min\_presence\_interval}
This option defines the minimum of time between presence changes
coming from a given user in seconds. This option is global and valid
for all chat rooms. A decimal value can be used. When this option is
not defined, no restriction are applied. This option can be used to
protect a MUC service for users abuses, as fastly changing a user
presence will result in possible large presence packet broadcast. If a
user tries to change its presence more often than the specified
interval, the presence is cached by ejabberd and only the last
presence is broadcasted to all users in the room after expiration of
the interval delay. Intermediate presence packets are silently
discarded. A good value for this option is 4 seconds.
\end{description}
Examples:
@ -1994,9 +2016,32 @@ Examples:
...
]}.
\end{verbatim}
\item In the following example, MUC anti abuse options are used. A
user cannot send more than one message every 0.4 seconds and cannot
change its presence more than once every 4 seconds. No ACLs are
defined, but some user restriction could be added as well:
\begin{verbatim}
...
{modules,
[
...
{mod_muc, [{min_message_interval, 0.4},
{min_presence_interval, 4}]},
...
]}.
\end{verbatim}
\end{itemize}
The Multi-Users Chat module now supports clustering and load balancing. One module can be started per cluster node. Rooms are distributed at creation time on all available MUC module instances. The multi-user chat module is clustered but the room themselves are not clustered nor fault-tolerant: If the not managing a set of rooms goes down, the rooms disappear and they will be recreated on an available node on first connection attempt.
The Multi-Users Chat module now supports clustering and load
balancing. One module can be started per cluster node. Rooms are
distributed at creation time on all available MUC module
instances. The multi-user chat module is clustered but the room
themselves are not clustered nor fault-tolerant: If the not managing a
set of rooms goes down, the rooms disappear and they will be recreated
on an available node on first connection attempt.
\subsection{\modmuclog{}}
\label{modmuclog}
@ -2055,7 +2100,7 @@ Options:
as reported to Erlang by the operating system, will be used. With the latter,
GMT/UTC time will be used. The default value is \term{local}.
\titem{spam\_prevention}\ind{options!spam\_prevention}
To prevent spam, the \term{spam_prevention} option adds a special attribute
To prevent spam, the \term{spam\_prevention} option adds a special attribute
to links that prevent their indexation by search engines. The default value
is \term{true}, which mean that nofollow attributes will be added to user
submitted links.

View File

@ -59,6 +59,10 @@
role,
last_presence}).
-record(activity, {message_time = 0,
presence_time = 0,
presence}).
-record(state, {room,
host,
server_host,
@ -70,7 +74,8 @@
history = lqueue_new(20),
subject = "",
subject_author = "",
just_created = false}).
just_created = false,
activity = ?DICT:new()}).
%-define(DBGFSM, true).
@ -147,79 +152,30 @@ normal_state({route, From, "",
true ->
case xml:get_attr_s("type", Attrs) of
"groupchat" ->
{ok, #user{nick = FromNick, role = Role}} =
?DICT:find(jlib:jid_tolower(From),
StateData#state.users),
Activity = case ?DICT:find(jlib:jid_tolower(From),
StateData#state.activity) of
{ok, A} -> A;
error -> #activity{}
end,
Now = now_to_usec(now()),
MinMessageInterval =
trunc(gen_mod:get_module_opt(
StateData#state.server_host,
mod_muc, min_message_interval, 0) * 1000000),
if
(Role == moderator) or (Role == participant) ->
{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.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,
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 subject in this room");
_ ->
?ERRT_FORBIDDEN(
Lang,
"Only moderators "
"are allowed to change subject in this room")
end,
ejabberd_router:route(
StateData#state.jid,
From,
jlib:make_error_reply(Packet, Err)),
{next_state, normal_state, StateData}
end;
Now >= Activity#activity.message_time + MinMessageInterval ->
NewActivity = Activity#activity{message_time = Now},
StateData1 =
StateData#state{
activity = ?DICT:store(
jlib:jid_tolower(From),
NewActivity,
StateData#state.activity)},
process_groupchat_message(From, Packet, StateData1);
true ->
ErrText = "Visitors are not allowed to send messages to all occupants",
ErrText = "Message frequency is too high",
Err = jlib:make_error_reply(
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
ejabberd_router:route(
StateData#state.jid,
From, Err),
@ -361,92 +317,44 @@ normal_state({route, From, "",
normal_state({route, From, Nick,
{xmlelement, "presence", Attrs, _Els} = 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 ->
NewState =
add_user_presence_un(From, Packet, StateData),
send_new_presence(From, NewState),
Reason = case xml:get_subtag(Packet, "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 ->
NewState =
add_user_presence_un(
From,
{xmlelement, "presence",
[{"type", "unavailable"}], []},
StateData),
send_new_presence(From, NewState),
remove_online_user(From, NewState);
_ ->
StateData
end;
"" ->
case is_user_online(From, StateData) of
true ->
case is_nick_change(From, Nick, StateData) of
true ->
case {is_nick_exists(Nick, StateData),
mod_muc:can_use_nick(
StateData#state.host, From, Nick)} of
{true, _} ->
Lang = xml:get_attr_s("xml:lang", Attrs),
ErrText = "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 = "Nickname is registered by another person",
Err = jlib:make_error_reply(
Packet,
?ERRT_CONFLICT(Lang, ErrText)),
ejabberd_router:route(
% TODO: s/Nick/""/
jlib:jid_replace_resource(
StateData#state.jid,
Nick),
From, Err),
StateData;
_ ->
change_nick(From, Nick, StateData)
end;
_ ->
NewState =
add_user_presence(From, Packet, StateData),
send_new_presence(From, NewState),
NewState
end;
_ ->
add_new_user(From, Nick, Packet, StateData)
end;
_ ->
StateData
end,
%io:format("STATE1: ~p~n", [?DICT:to_list(StateData#state.users)]),
%io:format("STATE2: ~p~n", [?DICT:to_list(StateData1#state.users)]),
case (not (StateData1#state.config)#config.persistent) andalso
(?DICT:to_list(StateData1#state.users) == []) of
Activity = case ?DICT:find(jlib:jid_tolower(From),
StateData#state.activity) of
{ok, A} -> A;
error -> #activity{}
end,
Now = now_to_usec(now()),
MinPresenceInterval =
trunc(gen_mod:get_module_opt(
StateData#state.server_host,
mod_muc, min_presence_interval, 0) * 1000000),
if
(Now >= Activity#activity.presence_time + MinPresenceInterval) and
(Activity#activity.presence == undefined) ->
NewActivity = Activity#activity{presence_time = Now},
StateData1 =
StateData#state{
activity = ?DICT:store(
jlib:jid_tolower(From),
NewActivity,
StateData#state.activity)},
process_presence(From, Nick, Packet, StateData1);
true ->
{stop, normal, StateData1};
_ ->
if
Activity#activity.presence == undefined ->
Interval = (Activity#activity.presence_time +
MinPresenceInterval - Now) div 1000,
erlang:send_after(
Interval, self(), {process_presence, From});
true ->
ok
end,
NewActivity = Activity#activity{presence = {Nick, Packet}},
StateData1 =
StateData#state{
activity = ?DICT:store(
jlib:jid_tolower(From),
NewActivity,
StateData#state.activity)},
{next_state, normal_state, StateData1}
end;
@ -685,6 +593,23 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({process_presence, From}, normal_state = StateName, StateData) ->
Activity = case ?DICT:find(jlib:jid_tolower(From),
StateData#state.activity) of
{ok, A} -> A;
error -> #activity{}
end,
Now = now_to_usec(now()),
{Nick, Packet} = Activity#activity.presence,
NewActivity = Activity#activity{presence_time = Now,
presence = undefined},
StateData1 =
StateData#state{
activity = ?DICT:store(
jlib:jid_tolower(From),
NewActivity,
StateData#state.activity)},
process_presence(From, Nick, Packet, StateData1);
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
@ -705,6 +630,176 @@ terminate(_Reason, _StateName, StateData) ->
route(Pid, From, ToNick, Packet) ->
gen_fsm:send_event(Pid, {route, From, ToNick, Packet}).
process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
StateData) ->
Lang = xml:get_attr_s("xml:lang", Attrs),
{ok, #user{nick = FromNick, role = Role}} =
?DICT:find(jlib:jid_tolower(From),
StateData#state.users),
if
(Role == moderator) or (Role == participant) ->
{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.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,
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 subject in this room");
_ ->
?ERRT_FORBIDDEN(
Lang,
"Only moderators "
"are allowed to change 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.
process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = 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 ->
NewState =
add_user_presence_un(From, Packet, StateData),
send_new_presence(From, NewState),
Reason = case xml:get_subtag(Packet, "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 ->
NewState =
add_user_presence_un(
From,
{xmlelement, "presence",
[{"type", "unavailable"}], []},
StateData),
send_new_presence(From, NewState),
remove_online_user(From, NewState);
_ ->
StateData
end;
"" ->
case is_user_online(From, StateData) of
true ->
case is_nick_change(From, Nick, StateData) of
true ->
case {is_nick_exists(Nick, StateData),
mod_muc:can_use_nick(
StateData#state.host, From, Nick)} of
{true, _} ->
Lang = xml:get_attr_s("xml:lang", Attrs),
ErrText = "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 = "Nickname is registered by another person",
Err = jlib:make_error_reply(
Packet,
?ERRT_CONFLICT(Lang, ErrText)),
ejabberd_router:route(
% TODO: s/Nick/""/
jlib:jid_replace_resource(
StateData#state.jid,
Nick),
From, Err),
StateData;
_ ->
change_nick(From, Nick, StateData)
end;
_ ->
NewState =
add_user_presence(From, Packet, 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 ->
{stop, normal, StateData1};
_ ->
{next_state, normal_state, StateData1}
end.
is_user_online(JID, StateData) ->
LJID = jlib:jid_tolower(JID),
@ -1320,6 +1415,10 @@ append_subtags({xmlelement, Name, Attrs, SubTags1}, SubTags2) ->
{xmlelement, Name, Attrs, SubTags1 ++ SubTags2}.
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}} =