25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-10-03 14:45:16 +02:00
xmpp.chapril.org-ejabberd/src/mod_muc/mod_muc_room.erl

4515 lines
140 KiB
Erlang
Raw Normal View History

%%%----------------------------------------------------------------------
%%% 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-2012 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).
2012-09-11 15:45:59 +02:00
-author('alexey@process-one.net').
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
%% External exports
2012-09-11 15:45:59 +02:00
-export([start_link/10, start_link/8, start_link/2,
start/10, start/8, start/2, migrate/3, route/4,
moderate_room_history/2, persist_recent_messages/1]).
%% gen_fsm callbacks
2012-09-11 15:45:59 +02:00
-export([init/1, normal_state/2, handle_event/3,
handle_sync_event/4, handle_info/3, terminate/3,
print_state/1, code_change/4]).
-include("ejabberd.hrl").
2012-09-11 15:45:59 +02:00
-include("jlib.hrl").
2012-09-11 15:45:59 +02:00
-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).
2012-09-11 15:45:59 +02:00
-define(FSMOPTS, [{debug, [trace]}]).
2012-09-11 15:45:59 +02:00
-else.
2012-09-11 15:45:59 +02:00
-define(FSMOPTS, []).
2012-09-11 15:45:59 +02:00
-endif.
%% Module start with or without supervisor:
-ifdef(NO_TRANSIENT_SUPERVISORS).
2012-09-11 15:45:59 +02:00
-define(SUPERVISOR_START(Args),
(?GEN_FSM):start(?MODULE, Args, ?FSMOPTS)).
-else.
2012-09-11 15:45:59 +02:00
-define(SUPERVISOR_START(Args),
Supervisor = gen_mod:get_module_proc(ServerHost,
ejabberd_mod_muc_sup),
supervisor:start_child(Supervisor, Args)).
2012-09-11 15:45:59 +02:00
-endif.
2012-09-11 15:45:59 +02:00
start(Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Creator, Nick,
DefRoomOpts) ->
?SUPERVISOR_START([Host, ServerHost, Access, Room,
HistorySize, PersistHistory, RoomShaper, Creator, Nick,
DefRoomOpts]).
start(Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Opts) ->
Supervisor = gen_mod:get_module_proc(ServerHost,
ejabberd_mod_muc_sup),
supervisor:start_child(Supervisor,
[Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Opts]).
start(StateName, StateData) ->
ServerHost = StateData#state.server_host,
?SUPERVISOR_START([StateName, StateData]).
2012-09-11 15:45:59 +02:00
start_link(Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Creator, Nick,
DefRoomOpts) ->
(?GEN_FSM):start_link(?MODULE,
[Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Creator, Nick,
DefRoomOpts],
?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Opts) ->
(?GEN_FSM):start_link(?MODULE,
[Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Opts],
?FSMOPTS).
start_link(StateName, StateData) ->
2012-09-11 15:45:59 +02:00
(?GEN_FSM):start_link(?MODULE, [StateName, StateData],
?FSMOPTS).
migrate(FsmRef, Node, After) ->
erlang:send_after(After, FsmRef, {migrate, Node}).
moderate_room_history(FsmRef, Nick) ->
2012-09-11 15:45:59 +02:00
(?GEN_FSM):sync_send_all_state_event(FsmRef,
{moderate_room_history, Nick}).
persist_recent_messages(FsmRef) ->
2012-09-11 15:45:59 +02:00
(?GEN_FSM):sync_send_all_state_event(FsmRef,
persist_recent_messages).
2012-09-11 15:45:59 +02:00
init([Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Creator, _Nick,
DefRoomOpts]) ->
process_flag(trap_exit, true),
Shaper = shaper:new(RoomShaper),
State = set_affiliation(Creator, owner,
2012-09-11 15:45:59 +02:00
#state{host = Host, server_host = ServerHost,
access = Access, room = Room,
history = lqueue_new(HistorySize),
persist_history = PersistHistory,
2012-09-11 15:45:59 +02:00
jid = jlib:make_jid(Room, Host, <<"">>),
just_created = true, room_shaper = Shaper}),
State1 = set_opts(DefRoomOpts, State),
2012-09-11 15:45:59 +02:00
if (State1#state.config)#config.persistent ->
mod_muc:store_room(State1#state.server_host,
State1#state.host, State1#state.room,
make_opts(State1));
true -> ok
end,
2012-09-11 15:45:59 +02:00
?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};
2012-09-11 15:45:59 +02:00
init([Host, ServerHost, Access, Room, HistorySize,
PersistHistory, RoomShaper, Opts]) ->
process_flag(trap_exit, true),
Shaper = shaper:new(RoomShaper),
2012-09-11 15:45:59 +02:00
State = set_opts(Opts,
#state{host = Host, server_host = ServerHost,
access = Access, room = Room,
history =
load_history(ServerHost, Room, PersistHistory,
lqueue_new(HistorySize)),
persist_history = PersistHistory,
jid = jlib:make_jid(Room, Host, <<"">>),
room_shaper = Shaper}),
add_to_log(room_existence, started, State),
{ok, normal_state, State};
2012-09-11 15:45:59 +02:00
init([StateName,
#state{room = Room, host = Host} = StateData]) ->
process_flag(trap_exit, true),
mod_muc:register_room(Host, Room, self()),
{ok, StateName, StateData}.
2012-09-11 15:45:59 +02:00
normal_state({route, From, <<"">>,
#xmlel{name = <<"message">>, attrs = Attrs,
children = Els} =
Packet},
StateData) ->
2012-09-11 15:45:59 +02:00
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
2012-09-11 15:45:59 +02:00
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(I) when is_number(I),
I>=0 ->
I
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)),
route_stanza(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)),
route_stanza(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),
route_stanza(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 ->
2012-09-11 15:45:59 +02:00
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)),
route_stanza(StateData#state.jid,
From, Err),
StateData#state{last_voice_request_time
= Times}
end;
false ->
2012-09-11 15:45:59 +02:00
ErrText =
<<"Voice requests are disabled in this "
"conference">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang,
ErrText)),
route_stanza(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)),
route_stanza(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;
2012-09-11 15:45:59 +02:00
_ -> StateData
end
end;
_ ->
ErrText =
<<"Only moderators can approve voice requests">>,
Err = jlib:make_error_reply(Packet,
?ERRT_NOT_ALLOWED(Lang,
ErrText)),
route_stanza(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)),
route_stanza(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;
2012-09-11 15:45:59 +02:00
normal_state({route, From, <<"">>,
#xmlel{name = <<"iq">>} = Packet},
StateData) ->
case jlib:iq_query_info(Packet) of
2012-09-11 15:45:59 +02:00
#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,
route_stanza(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),
route_stanza(StateData#state.jid, From, Err),
{next_state, normal_state, StateData}
end;
normal_state({route, From, Nick,
2012-09-11 15:45:59 +02:00
#xmlel{name = <<"presence">>} = Packet},
StateData) ->
Activity = get_user_activity(From, StateData),
Now = now_to_usec(now()),
MinPresenceInterval =
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
#xmlel{name = <<"message">>, attrs = Attrs} = Packet},
StateData) ->
2012-09-11 15:45:59 +02:00
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)),
route_stanza(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)),
route_stanza(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),
[route_stanza(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)),
route_stanza(jlib:jid_replace_resource(StateData#state.jid,
ToNick),
From, Err)
end
2012-09-11 15:45:59 +02:00
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)),
route_stanza(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)),
route_stanza(jlib:jid_replace_resource(StateData#state.jid,
ToNick),
From, Err)
end,
{next_state, normal_state, StateData}
end;
normal_state({route, From, ToNick,
2012-09-11 15:45:59 +02:00
#xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
StateData) ->
2012-09-11 15:45:59 +02:00
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
StanzaId = xml:get_attr_s(<<"id">>, Attrs),
case {(StateData#state.config)#config.allow_query_users,
2012-09-11 15:45:59 +02:00
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)),
route_stanza(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),
route_stanza(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)),
route_stanza(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)),
route_stanza(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}.
2012-09-11 15:45:59 +02:00
handle_event({service_message, Msg}, _StateName,
StateData) ->
MessagePkt = #xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"groupchat">>}],
children =
[#xmlel{name = <<"body">>, attrs = [],
children = [{xmlcdata, Msg}]}]},
send_multiple(
StateData#state.jid,
StateData#state.server_host,
StateData#state.users,
MessagePkt),
NSD = add_message_to_history(<<"">>,
StateData#state.jid, MessagePkt, StateData),
{next_state, normal_state, NSD};
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
?INFO_MSG("Destroyed MUC room ~s",
[jlib:jid_to_string(StateData#state.jid)]),
handle_event({destroy, none}, StateName, StateData);
2012-09-11 15:45:59 +02:00
handle_event({set_affiliations, Affiliations},
StateName, StateData) ->
{next_state, StateName,
StateData#state{affiliations = Affiliations}};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
2012-09-11 15:45:59 +02:00
handle_sync_event({moderate_room_history, Nick}, _From,
StateName, #state{history = History} = StateData) ->
NewHistory = lqueue_filter(fun ({FromNick, _TSPacket,
_HaveSubject, _Timestamp, _Size}) ->
FromNick /= Nick
end,
History),
Moderated = History#lqueue.len - NewHistory#lqueue.len,
{reply,
{ok, iolist_to_binary(integer_to_list(Moderated))},
StateName, StateData#state{history = NewHistory}};
handle_sync_event(persist_recent_messages, _From,
StateName, StateData) ->
{reply, persist_muc_history(StateData), StateName,
StateData};
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};
2012-09-11 15:45:59 +02:00
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};
2012-09-11 15:45:59 +02:00
handle_sync_event({change_config, Config}, _From,
StateName, StateData) ->
{result, [], NSD} = change_config(Config, StateData),
{reply, {ok, NSD#state.config}, StateName, NSD};
2012-09-11 15:45:59 +02:00
handle_sync_event({change_state, NewStateData}, _From,
StateName, _StateData) ->
{reply, {ok, NewStateData}, StateName, NewStateData};
2012-09-11 15:45:59 +02:00
handle_sync_event(_Event, _From, StateName,
StateData) ->
Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
2012-09-11 15:45:59 +02:00
print_state(StateData) -> StateData.
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},
2012-09-11 15:45:59 +02:00
if RoomQueueEmpty ->
StateData2 = prepare_room_queue(StateData1),
{next_state, normal_state, StateData2};
true -> {next_state, normal_state, StateData1}
end;
2012-09-11 15:45:59 +02:00
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},
2012-09-11 15:45:59 +02:00
if RoomQueueEmpty ->
StateData2 = prepare_room_queue(StateData1),
{next_state, normal_state, StateData2};
true -> {next_state, normal_state, StateData1}
end;
2012-09-11 15:45:59 +02:00
handle_info(process_room_queue,
normal_state = StateName, StateData) ->
case queue:out(StateData#state.room_queue) of
2012-09-11 15:45:59 +02:00
{{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;
2012-09-11 15:45:59 +02:00
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};
2012-09-11 15:45:59 +02:00
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),
route_stanza % 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({migrate, Node}, StateName, StateData) ->
if Node /= node() ->
2012-09-11 15:45:59 +02:00
{migrate, StateData,
{Node, ?MODULE, start, [StateName, StateData]}, 0};
true -> {next_state, StateName, StateData}
end;
2012-09-11 15:45:59 +02:00
handle_info(shutdown, _StateName, StateData) ->
{stop, shutdown, StateData};
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
terminate({migrated, Clone}, _StateName, StateData) ->
?INFO_MSG("Migrating room ~s@~s to ~p on node ~p",
2012-09-11 15:45:59 +02:00
[StateData#state.room, StateData#state.host, Clone,
node(Clone)]),
mod_muc:room_destroyed(StateData#state.host,
StateData#state.room, self(),
StateData#state.server_host),
ok;
terminate(Reason, _StateName, StateData) ->
?INFO_MSG("Stopping MUC room ~s@~s",
[StateData#state.room, StateData#state.host]),
ReasonT = case Reason of
2012-09-11 15:45:59 +02:00
shutdown ->
<<"You are being removed from the room "
"because of a system shutdown">>;
_ -> <<"Room terminates">>
end,
2012-09-11 15:45:59 +02:00
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 ->
route_stanza(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),
2012-09-11 15:45:59 +02:00
if Reason == shutdown -> persist_muc_history(StateData);
true -> ok
end,
2012-09-11 15:45:59 +02:00
mod_muc:room_destroyed(StateData#state.host,
StateData#state.room, self(),
StateData#state.server_host),
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
2012-09-11 15:45:59 +02:00
load_history(_Host, _Room, false, Queue) -> Queue;
load_history(Host, Room, true, Queue) ->
2012-09-11 15:45:59 +02:00
?INFO_MSG("Loading history for room ~s on host ~s",
[Room, Host]),
case odbc_queries:load_roomhistory(Host,
ejabberd_odbc:escape(Room))
of
{selected,
[<<"nick">>, <<"packet">>, <<"have_subject">>,
<<"timestamp">>, <<"size">>],
Items} ->
?DEBUG("Found ~p messages on history for ~s",
[length(Items), Room]),
lists:foldl(fun (I, Q) ->
[Nick, XML, HS, Ts, Size] = I,
Item = {Nick, xml_stream:parse_element(XML),
HS /= <<"0">>,
calendar:gregorian_seconds_to_datetime(jlib:binary_to_integer(Ts)),
jlib:binary_to_integer(Size)},
lqueue_in(Item, Q)
end,
Queue, Items);
_ -> Queue
end.
persist_muc_history(#state{room = Room,
server_host = Server,
config = #config{persistent = true},
persist_history = true, history = Q}) ->
?INFO_MSG("Persisting history for room ~s on host ~s",
[Room, Server]),
Queries = lists:map(fun ({FromNick, Packet, HaveSubject,
Timestamp, Size}) ->
odbc_queries:add_roomhistory_sql(ejabberd_odbc:escape(Room),
ejabberd_odbc:escape(FromNick),
ejabberd_odbc:escape(xml:element_to_binary(Packet)),
iolist_to_binary(atom_to_list(HaveSubject)),
iolist_to_binary(integer_to_list(calendar:datetime_to_gregorian_seconds(Timestamp))),
iolist_to_binary(integer_to_list(Size)))
end,
lqueue_to_list(Q)),
odbc_queries:clear_and_add_roomhistory(Server,
ejabberd_odbc:escape(Room), Queries),
{ok, {persisted, length(Queries)}};
%% en mod_muc, cuando se levantan los muc persistentes, si se crea, y el flag persist_history esta en true,
%% se levantan los mensajes persistentes tb.
persist_muc_history(_) -> {ok, not_persistent}.
route(Pid, From, ToNick, Packet) ->
2012-09-11 15:45:59 +02:00
(?GEN_FSM):send_event(Pid,
{route, From, ToNick, Packet}).
2012-09-11 15:45:59 +02:00
process_groupchat_message(From,
#xmlel{name = <<"message">>, attrs = Attrs} = Packet,
StateData) ->
2012-09-11 15:45:59 +02:00
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
2012-09-11 15:45:59 +02:00
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 ->
send_multiple(
jlib:jid_replace_resource(StateData#state.jid, FromNick),
StateData#state.server_host,
StateData#state.users,
Packet),
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,
route_stanza(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)),
route_stanza(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)),
route_stanza(StateData#state.jid, From, Err),
{next_state, normal_state, StateData}
end.
2012-09-11 15:45:59 +02:00
is_user_allowed_message_nonparticipant(JID,
StateData) ->
case get_service_affiliation(JID, StateData) of
2012-09-11 15:45:59 +02:00
owner -> true;
_ -> false
end.
get_participant_data(From, StateData) ->
2012-09-11 15:45:59 +02:00
case (?DICT):find(jlib:jid_tolower(From),
StateData#state.users)
of
{ok, #user{nick = FromNick, role = Role}} ->
{FromNick, Role};
error -> {<<"">>, moderator}
end.
2012-09-11 15:45:59 +02:00
process_presence(From, Nick,
#xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
StateData) ->
2012-09-11 15:45:59 +02:00
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)),
route_stanza(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)),
route_stanza(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)),
route_stanza(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),
2012-09-11 15:45:59 +02:00
(?DICT):is_key(LJID, StateData#state.users).
is_occupant_or_admin(JID, StateData) ->
FAffiliation = get_affiliation(JID, StateData),
FRole = get_role(JID, StateData),
2012-09-11 15:45:59 +02:00
case FRole /= none orelse
FAffiliation == admin orelse FAffiliation == owner
of
true -> true;
_ -> false
end.
2012-09-11 15:45:59 +02:00
is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource /= <<"">> ->
{is_user_online(JID, StateData), StanzaId, JID};
2012-09-11 15:45:59 +02:00
is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource == <<"">> ->
try stanzaid_unpack(StanzaId) of
2012-09-11 15:45:59 +02:00
{OriginalId, Resource} ->
JIDWithResource = jlib:jid_replace_resource(JID,
Resource),
{is_user_online(JIDWithResource, StateData), OriginalId,
JIDWithResource}
catch
2012-09-11 15:45:59 +02:00
_:_ -> {is_user_online(JID, StateData), StanzaId, JID}
end.
2012-09-11 15:45:59 +02:00
handle_iq_vcard(FromFull, ToJID, StanzaId, NewId,
Packet) ->
ToBareJID = jlib:jid_remove_resource(ToJID),
IQ = jlib:iq_query_info(Packet),
2012-09-11 15:45:59 +02:00
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)};
2012-09-11 15:45:59 +02:00
handle_iq_vcard2(_FromFull, ToJID, _ToBareJID,
_StanzaId, NewId, _IQ, Packet) ->
{ToJID, change_stanzaid(NewId, Packet)}.
stanzaid_pack(OriginalId, Resource) ->
2012-09-11 15:45:59 +02:00
<<"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) ->
2012-09-11 15:45:59 +02:00
#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).
2012-09-11 15:45:59 +02:00
%%%
%%%
role_to_list(Role) ->
case Role of
2012-09-11 15:45:59 +02:00
moderator -> <<"moderator">>;
participant -> <<"participant">>;
visitor -> <<"visitor">>;
none -> <<"none">>
end.
affiliation_to_list(Affiliation) ->
case Affiliation of
2012-09-11 15:45:59 +02:00
owner -> <<"owner">>;
admin -> <<"admin">>;
member -> <<"member">>;
outcast -> <<"outcast">>;
none -> <<"none">>
end.
list_to_role(Role) ->
case Role of
2012-09-11 15:45:59 +02:00
<<"moderator">> -> moderator;
<<"participant">> -> participant;
<<"visitor">> -> visitor;
<<"none">> -> none
end.
list_to_affiliation(Affiliation) ->
case Affiliation of
2012-09-11 15:45:59 +02:00
<<"owner">> -> owner;
<<"admin">> -> admin;
<<"member">> -> member;
<<"outcast">> -> outcast;
<<"none">> -> none
end.
2012-09-11 15:45:59 +02:00
decide_fate_message(<<"error">>, Packet, From,
StateData) ->
PD = case check_error_kick(Packet) of
2012-09-11 15:45:59 +02:00
%% 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
2012-09-11 15:45:59 +02:00
{expulse_sender, R} ->
case is_user_online(From, StateData) of
true -> {expulse_sender, R};
false -> forget_message
end;
Other -> Other
end;
2012-09-11 15:45:59 +02:00
decide_fate_message(_, _, _, _) -> continue_delivery.
check_error_kick(Packet) ->
case get_error_condition(Packet) of
2012-09-11 15:45:59 +02:00
<<"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) ->
2012-09-11 15:45:59 +02:00
case catch get_error_condition2(Packet) of
{condition, ErrorCondition} -> ErrorCondition;
{'EXIT', _} -> <<"badformed error stanza">>
end.
get_error_condition2(Packet) ->
2012-09-11 15:45:59 +02:00
#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) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
set_affiliation(JID, Affiliation, StateData, <<"">>).
set_affiliation(JID, Affiliation, StateData, Reason) ->
LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
Affiliations = case Affiliation of
2012-09-11 15:45:59 +02:00
none ->
(?DICT):erase(LJID, StateData#state.affiliations);
_ ->
(?DICT):store(LJID, {Affiliation, Reason},
StateData#state.affiliations)
end,
StateData#state{affiliations = Affiliations}.
get_affiliation(JID, StateData) ->
2012-09-11 15:45:59 +02:00
{_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),
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
end,
case Res of
2012-09-11 15:45:59 +02:00
{A, _Reason} -> A;
_ -> Res
end.
get_service_affiliation(JID, StateData) ->
2012-09-11 15:45:59 +02:00
{_AccessRoute, _AccessCreate, AccessAdmin,
_AccessPersistent} =
StateData#state.access,
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
{U, S, <<"">>} ->
(?DICT):fold(fun (J, _, Js) ->
case J of
{U, S, _} -> [J | Js];
_ -> Js
end
end,
[], StateData#state.users);
_ ->
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
case (?DICT):find(LJID, StateData#state.users) of
{ok, #user{role = Role}} -> Role;
_ -> none
end.
get_default_role(Affiliation, StateData) ->
case Affiliation of
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
if MaxUsers =< ServiceMaxUsers -> MaxUsers;
true -> ServiceMaxUsers
end.
get_service_max_users(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
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 =
2012-09-11 15:45:59 +02:00
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 =
2012-09-11 15:45:59 +02:00
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()),
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
{{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),
2012-09-11 15:45:59 +02:00
Users = (?DICT):store(LJID,
#user{jid = JID, nick = Nick, role = Role},
StateData#state.users),
add_to_log(join, Nick, StateData),
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
remove_online_user(JID, StateData, <<"">>).
remove_online_user(JID, StateData, Reason) ->
LJID = jlib:jid_tolower(JID),
2012-09-11 15:45:59 +02:00
{ok, #user{nick = Nick}} = (?DICT):find(LJID,
StateData#state.users),
add_to_log(leave, {Nick, Reason}, StateData),
tab_remove_online_user(JID, StateData),
2012-09-11 15:45:59 +02:00
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}.
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
Users = (?DICT):update(LJID,
fun (#user{} = User) ->
User#user{last_presence = FPresence,
role = none}
end,
StateData#state.users),
StateData#state{users = Users}.
find_jids_by_nick(Nick, StateData) ->
2012-09-11 15:45:59 +02:00
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_jid_by_nick(Nick, StateData) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
[{_, #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
2012-09-11 15:45:59 +02:00
<<"">> -> 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
2012-09-11 15:45:59 +02:00
jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick))
/= jlib:jid_remove_resource(jlib:jid_tolower(User)).
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
ServiceAffiliation = get_service_affiliation(From,
StateData),
NConferences = tab_count_user(From),
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
(Affiliation == admin orelse Affiliation == owner)
andalso NUsers < MaxAdminUsers
orelse NUsers < MaxUsers)
andalso NConferences < MaxConferences,
Collision,
2012-09-11 15:45:59 +02:00
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),
route_stanza % 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),
route_stanza % 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)),
route_stanza(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)),
route_stanza(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 =
[]}]}]},
route_stanza(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)),
route_stanza % 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),
route_stanza(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)),
route_stanza % 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)),
route_stanza % 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)),
route_stanza % TODO: s/Nick/""/
(jlib:jid_replace_resource(StateData#state.jid,
Nick),
From, Err),
StateData
end
end.
2012-09-11 15:45:59 +02:00
check_password(owner, _Affiliation, _Els, _From,
_StateData) ->
%% Don't check pass if user is owner in MUC service (access_admin option)
true;
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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.
2012-09-11 15:45:59 +02:00
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;
2012-09-11 15:45:59 +02:00
extract_password([_ | Els]) -> extract_password(Els).
count_stanza_shift(Nick, Els, StateData) ->
HL = lqueue_to_list(StateData#state.history),
2012-09-11 15:45:59 +02:00
Since = extract_history(Els, <<"since">>),
Shift0 = case Since of
2012-09-11 15:45:59 +02:00
false -> 0;
_ ->
Sin = calendar:datetime_to_gregorian_seconds(Since),
count_seconds_shift(Sin, HL)
end,
2012-09-11 15:45:59 +02:00
Seconds = extract_history(Els, <<"seconds">>),
Shift1 = case Seconds of
2012-09-11 15:45:59 +02:00
false -> 0;
_ ->
Sec =
calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(now()))
- Seconds,
count_seconds_shift(Sec, HL)
end,
2012-09-11 15:45:59 +02:00
MaxStanzas = extract_history(Els, <<"maxstanzas">>),
Shift2 = case MaxStanzas of
2012-09-11 15:45:59 +02:00
false -> 0;
_ -> count_maxstanzas_shift(MaxStanzas, HL)
end,
2012-09-11 15:45:59 +02:00
MaxChars = extract_history(Els, <<"maxchars">>),
Shift3 = case MaxChars of
2012-09-11 15:45:59 +02:00
false -> 0;
_ -> count_maxchars_shift(Nick, MaxChars, HL)
end,
lists:max([Shift0, Shift1, Shift2, Shift3]).
count_seconds_shift(Seconds, HistoryList) ->
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
if S =< 0 -> 0;
true -> S
end.
count_maxchars_shift(Nick, MaxSize, HistoryList) ->
2012-09-11 15:45:59 +02:00
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).
2012-09-11 15:45:59 +02:00
calc_shift(_MaxSize, _Size, Shift, []) -> Shift;
calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
2012-09-11 15:45:59 +02:00
if MaxSize >= Size -> Shift;
true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes)
end.
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
send_update_presence(JID, <<"">>, StateData).
send_update_presence(JID, Reason, StateData) ->
LJID = jlib:jid_tolower(JID),
LJIDs = case LJID of
2012-09-11 15:45:59 +02:00
{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,
2012-09-11 15:45:59 +02:00
lists:foreach(fun (J) ->
send_new_presence(J, Reason, StateData)
2012-09-11 15:45:59 +02:00
end,
LJIDs).
send_new_presence(NJID, StateData) ->
2012-09-11 15:45:59 +02:00
send_new_presence(NJID, <<"">>, StateData).
send_new_presence(NJID, Reason, StateData) ->
2012-09-11 15:45:59 +02:00
#user{nick = Nick} =
(?DICT):fetch(jlib:jid_tolower(NJID),
StateData#state.users),
LJID = find_jid_by_nick(Nick, StateData),
2012-09-11 15:45:59 +02:00
{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),
2012-09-11 15:45:59 +02:00
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]}]),
route_stanza(jlib:jid_replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet)
end,
2012-09-11 15:45:59 +02:00
(?DICT):to_list(StateData#state.users)).
send_existing_presences(ToJID, StateData) ->
LToJID = jlib:jid_tolower(ToJID),
2012-09-11 15:45:59 +02:00
{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
=
[]}]}]),
route_stanza(jlib:jid_replace_resource(StateData#state.jid,
FromNick),
RealToJID, Packet)
end
end,
(?DICT):to_list(StateData#state.nicks)).
now_to_usec({MSec, Sec, USec}) ->
2012-09-11 15:45:59 +02:00
(MSec * 1000000 + Sec) * 1000000 + USec.
change_nick(JID, Nick, StateData) ->
LJID = jlib:jid_tolower(JID),
2012-09-11 15:45:59 +02:00
{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
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
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 ->
route_stanza(jlib:jid_replace_resource(StateData#state.jid,
OldNick),
Info#user.jid, Packet1);
true -> ok
end,
if SendNewAvailable ->
route_stanza(jlib:jid_replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet2);
true -> ok
end
end,
2012-09-11 15:45:59 +02:00
(?DICT):to_list(StateData#state.users)).
lqueue_new(Max) ->
2012-09-11 15:45:59 +02:00
#lqueue{queue = queue:new(), len = 0, max = Max}.
2012-09-11 15:45:59 +02:00
lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ;
%% Otherwise, rotate messages in the queue store.
2012-09-11 15:45:59 +02:00
lqueue_in(Item,
#lqueue{queue = Q1, len = Len, max = Max}) ->
Q2 = queue:in(Item, Q1),
2012-09-11 15:45:59 +02:00
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.
2012-09-11 15:45:59 +02:00
lqueue_cut(Q, 0) -> Q;
lqueue_cut(Q, N) ->
2012-09-11 15:45:59 +02:00
{_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1).
lqueue_to_list(#lqueue{queue = Q1}) ->
queue:to_list(Q1).
lqueue_filter(F, #lqueue{queue = Q1} = LQ) ->
2012-09-11 15:45:59 +02:00
Q2 = queue:filter(F, Q1),
LQ#lqueue{queue = Q2, len = queue:len(Q2)}.
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()),
2012-09-11 15:45:59 +02:00
SenderJid = case
(StateData#state.config)#config.anonymous
of
true -> StateData#state.jid;
false -> FromJID
end,
TSPacket = xml:append_subtags(Packet,
2012-09-11 15:45:59 +02:00
[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),
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp,
_Size},
B) ->
route_stanza(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
2012-09-11 15:45:59 +02:00
<<"">> -> 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>>}]}]},
route_stanza(StateData#state.jid, JID, Packet)
end.
check_subject(Packet) ->
2012-09-11 15:45:59 +02:00
case xml:get_subtag(Packet, <<"subject">>) of
false -> false;
SubjEl -> xml:get_tag_cdata(SubjEl)
end.
can_change_subject(Role, StateData) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
#xmlel{children = Items} = SubEl,
process_admin_items_set(From, Items, Lang, StateData);
process_iq_admin(From, get, Lang, SubEl, StateData) ->
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
lists:map(fun ({_, U}) -> user_to_item(U, StateData)
end,
search_role(SRole, StateData)).
items_with_affiliation(SAffiliation, StateData) ->
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
#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) ->
2012-09-11 15:45:59 +02:00
lists:filter(fun ({_, #user{role = R}}) -> Role == R
end,
(?DICT):to_list(StateData#state.users)).
search_affiliation(Affiliation, StateData) ->
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
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(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(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(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.
2012-09-11 15:45:59 +02:00
find_changed_items(_UJID, _UAffiliation, _URole, [],
_Lang, _StateData, Res) ->
{result, Res};
find_changed_items(UJID, UAffiliation, URole,
2012-09-11 15:45:59 +02:00
[{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) ->
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
{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', _} ->
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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}.
2012-09-11 15:45:59 +02:00
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;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
_TRole, _RoleorAffiliation, _Value, owner) ->
%% Nobody can decrease MUC admin's role/affiliation
false;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, TAffiliation,
_TRole, affiliation, Value, _ServiceAf)
when TAffiliation == Value ->
nothing;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
TRole, role, Value, _ServiceAf)
when TRole == Value ->
nothing;
2012-09-11 15:45:59 +02:00
can_change_ra(FAffiliation, _FRole, outcast, _TRole,
affiliation, none, _ServiceAf)
2012-09-11 15:45:59 +02:00
when (FAffiliation == owner) or
(FAffiliation == admin) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(FAffiliation, _FRole, outcast, _TRole,
affiliation, member, _ServiceAf)
2012-09-11 15:45:59 +02:00
when (FAffiliation == owner) or
(FAffiliation == admin) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, outcast, _TRole,
affiliation, admin, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, outcast, _TRole,
affiliation, owner, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(FAffiliation, _FRole, none, _TRole,
affiliation, outcast, _ServiceAf)
2012-09-11 15:45:59 +02:00
when (FAffiliation == owner) or
(FAffiliation == admin) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(FAffiliation, _FRole, none, _TRole,
affiliation, member, _ServiceAf)
2012-09-11 15:45:59 +02:00
when (FAffiliation == owner) or
(FAffiliation == admin) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, none, _TRole, affiliation,
admin, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, none, _TRole, affiliation,
owner, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(FAffiliation, _FRole, member, _TRole,
affiliation, outcast, _ServiceAf)
2012-09-11 15:45:59 +02:00
when (FAffiliation == owner) or
(FAffiliation == admin) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(FAffiliation, _FRole, member, _TRole,
affiliation, none, _ServiceAf)
2012-09-11 15:45:59 +02:00
when (FAffiliation == owner) or
(FAffiliation == admin) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, member, _TRole,
affiliation, admin, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, member, _TRole,
affiliation, owner, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, admin, _TRole, affiliation,
_Affiliation, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, owner, _TRole, affiliation,
_Affiliation, _ServiceAf) ->
check_owner;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
_TRole, affiliation, _Value, _ServiceAf) ->
false;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, moderator, _TAffiliation,
visitor, role, none, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, moderator, _TAffiliation,
visitor, role, participant, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(FAffiliation, _FRole, _TAffiliation,
visitor, role, moderator, _ServiceAf)
when (FAffiliation == owner) or
(FAffiliation == admin) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, moderator, _TAffiliation,
participant, role, none, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, moderator, _TAffiliation,
participant, role, visitor, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(FAffiliation, _FRole, _TAffiliation,
participant, role, moderator, _ServiceAf)
when (FAffiliation == owner) or
(FAffiliation == admin) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, owner, moderator,
role, visitor, _ServiceAf) ->
false;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, _TAffiliation, moderator,
role, visitor, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, admin, moderator,
role, visitor, _ServiceAf) ->
false;
2012-09-11 15:45:59 +02:00
can_change_ra(admin, _FRole, _TAffiliation, moderator,
role, visitor, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, owner, moderator,
role, participant, _ServiceAf) ->
false;
2012-09-11 15:45:59 +02:00
can_change_ra(owner, _FRole, _TAffiliation, moderator,
role, participant, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, admin, moderator,
role, participant, _ServiceAf) ->
false;
2012-09-11 15:45:59 +02:00
can_change_ra(admin, _FRole, _TAffiliation, moderator,
role, participant, _ServiceAf) ->
true;
2012-09-11 15:45:59 +02:00
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
_TRole, role, _Value, _ServiceAf) ->
false.
send_kickban_presence(JID, Reason, Code, StateData) ->
NewAffiliation = get_affiliation(JID, StateData),
2012-09-11 15:45:59 +02:00
send_kickban_presence(JID, Reason, Code, NewAffiliation,
StateData).
2012-09-11 15:45:59 +02:00
send_kickban_presence(JID, Reason, Code, NewAffiliation,
StateData) ->
LJID = jlib:jid_tolower(JID),
LJIDs = case LJID of
2012-09-11 15:45:59 +02:00
{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,
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
send_kickban_presence1(J, Reason, Code,
NewAffiliation, StateData)
end,
LJIDs).
2012-09-11 15:45:59 +02:00
send_kickban_presence1(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),
2012-09-11 15:45:59 +02:00
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,
Packet = #xmlel{name = <<"presence">>,
attrs =
[{<<"type">>, <<"unavailable">>}],
children =
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
?NS_MUC_USER}],
children =
[#xmlel{name =
<<"item">>,
attrs =
ItemAttrs,
children =
ItemEls},
#xmlel{name =
<<"status">>,
attrs =
[{<<"code">>,
Code}],
children =
[]}]}]},
route_stanza(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
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
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.
is_allowed_room_name_desc_limits(XEl, StateData) ->
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
fun(infinity) -> infinity;
(I) when is_integer(I),
I>0 ->
I
end, infinity);
_ -> true
end,
IsNameAccepted and IsDescAccepted.
is_password_settings_correct(XEl, StateData) ->
Config = StateData#state.config,
OldProtected = Config#config.password_protected,
OldPassword = Config#config.password,
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
#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),
2012-09-11 15:45:59 +02:00
?XFIELD(<<"boolean">>, Label, Var,
case Val of
2012-09-11 15:45:59 +02:00
true -> <<"1">>;
_ -> <<"0">>
end)).
-define(STRINGXFIELD(Label, Var, Val),
2012-09-11 15:45:59 +02:00
?XFIELD(<<"text-single">>, Label, Var, Val)).
-define(PRIVATEXFIELD(Label, Var, Val),
2012-09-11 15:45:59 +02:00
?XFIELD(<<"text-private">>, Label, Var, Val)).
-define(JIDMULTIXFIELD(Label, Var, JIDList),
2012-09-11 15:45:59 +02:00
#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) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
{_AccessRoute, _AccessCreate, _AccessAdmin,
AccessPersistent} =
StateData#state.access,
ServiceMaxUsers = get_service_max_users(StateData),
2012-09-11 15:45:59 +02:00
DefaultRoomMaxUsers =
get_default_room_maxusers(StateData),
Config = StateData#state.config,
2012-09-11 15:45:59 +02:00
{MaxUsersRoomInteger, MaxUsersRoomString} = case
get_max_users(StateData)
of
N when is_integer(N) ->
{N,
erlang:integer_to_list(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
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
<<"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),
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
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);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]}
| Opts],
Config) ->
?SET_STRING_XOPT(description, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(allow_change_subj, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"allow_query_users">>, [Val]} | Opts],
Config) ->
?SET_BOOL_XOPT(allow_query_users, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"allow_private_messages">>, [Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(allow_private_messages, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"allow_private_messages_from_visitors">>,
[Val]}
| Opts],
Config) ->
case Val of
2012-09-11 15:45:59 +02:00
<<"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;
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
[Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(allow_visitor_status, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>,
[Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(allow_visitor_nickchange, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(public, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"public_list">>, [Val]} | Opts],
Config) ->
?SET_BOOL_XOPT(public_list, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_persistentroom">>,
[Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(persistent, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(moderated, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"members_by_default">>, [Val]} | Opts],
Config) ->
?SET_BOOL_XOPT(members_by_default, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(members_only, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"captcha_protected">>, [Val]} | Opts],
Config) ->
?SET_BOOL_XOPT(captcha_protected, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(allow_user_invites, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>,
[Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(password_protected, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]}
| Opts],
Config) ->
?SET_STRING_XOPT(password, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"anonymous">>, [Val]} | Opts],
Config) ->
?SET_BOOL_XOPT(anonymous, Val);
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
<<"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;
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
| Opts],
Config) ->
case Val of
2012-09-11 15:45:59 +02:00
<<"none">> -> ?SET_STRING_XOPT(max_users, none);
_ -> ?SET_NAT_XOPT(max_users, Val)
end;
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(logging, Val);
2012-09-11 15:45:59 +02:00
set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
Vals}
| Opts],
Config) ->
JIDs = [jlib:string_to_jid(Val) || Val <- Vals],
?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs);
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
Config#config.members_only}
of
{false, true} ->
NSD1 = remove_nonmembers(NSD), {result, [], NSD1};
_ -> {result, [], NSD}
end.
remove_nonmembers(StateData) ->
2012-09-11 15:45:59 +02:00
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)).
2012-09-11 15:45:59 +02:00
set_opts([], StateData) -> StateData;
set_opts([{Opt, Val} | Opts], StateData) ->
NSD = case Opt of
2012-09-11 15:45:59 +02:00
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}).
2012-09-11 15:45:59 +02:00
make_opts(StateData) ->
Config = StateData#state.config,
2012-09-11 15:45:59 +02:00
[?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),
2012-09-11 15:45:59 +02:00
?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),
2012-09-11 15:45:59 +02:00
?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,
2012-09-11 15:45:59 +02:00
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
{affiliations,
(?DICT):to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
2012-09-11 15:45:59 +02:00
{subject_author, StateData#state.subject_author}].
destroy_room(DEl, StateData) ->
2012-09-11 15:45:59 +02:00
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]}]},
route_stanza(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
2012-09-11 15:45:59 +02:00
true ->
mod_muc:forget_room(StateData#state.server_host,
StateData#state.host, StateData#state.room);
false -> ok
end,
{result, [], stop}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Disco
2012-09-11 15:45:59 +02:00
-define(FEATURE(Var),
#xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}],
children = []}).
-define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse),
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
{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),
2012-09-11 15:45:59 +02:00
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children = [{xmlcdata, Val}]}]}).
-define(RFIELD(Label, Var, Val),
2012-09-11 15:45:59 +02:00
#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) ->
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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.
2012-09-11 15:45:59 +02:00
process_iq_captcha(_From, get, _Lang, _SubEl,
_StateData) ->
{error, ?ERR_NOT_ALLOWED};
2012-09-11 15:45:59 +02:00
process_iq_captcha(_From, set, _Lang, SubEl,
StateData) ->
case ejabberd_captcha:process_reply(SubEl) of
2012-09-11 15:45:59 +02:00
ok -> {result, [], StateData};
_ -> {error, ?ERR_NOT_ACCEPTABLE}
end.
get_title(StateData) ->
case (StateData#state.config)#config.title of
2012-09-11 15:45:59 +02:00
<<"">> -> StateData#state.room;
Name -> Name
end.
get_roomdesc_reply(JID, StateData, Tail) ->
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
true -> <<"">>;
_ -> translate:translate(Lang, <<"private, ">>)
end,
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
#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),
2012-09-11 15:45:59 +02:00
lists:foreach(fun ({_, User}) ->
route_stanza(StateData#state.jid, User#user.jid,
prepare_request_form(From, FromNick,
<<"">>))
end,
Moderators).
is_voice_approvement(Els) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
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) ->
2012-09-11 15:45:59 +02:00
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),
2012-09-11 15:45:59 +02:00
CanInvite =
(StateData#state.config)#config.allow_user_invites
orelse
FAffiliation == admin orelse FAffiliation == owner,
InviteEl = case xml:remove_cdata(Els) of
2012-09-11 15:45:59 +02:00
[#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,
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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 ->
2012-09-11 15:45:59 +02:00
[#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]},
route_stanza(StateData#state.jid, JID, Msg),
JID
end.
2012-09-11 15:45:59 +02:00
handle_roommessage_from_nonparticipant(Packet, Lang,
StateData, From) ->
case catch check_decline_invitation(Packet) of
2012-09-11 15:45:59 +02:00
{true, Decline_data} ->
send_decline_invitation(Decline_data,
StateData#state.jid, From);
_ ->
send_error_only_occupants(Packet, Lang,
StateData#state.jid, From)
end.
check_decline_invitation(Packet) ->
2012-09-11 15:45:59 +02:00
#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}}.
2012-09-11 15:45:59 +02:00
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),
route_stanza(RoomJID, ToJID, Packet2).
2012-09-11 15:45:59 +02:00
replace_subelement(#xmlel{name = Name, attrs = Attrs,
children = SubEls},
NewSubEl) ->
{_, NameNewSubEl, _, _} = NewSubEl,
2012-09-11 15:45:59 +02:00
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)),
route_stanza(RoomJID, From, Err).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Logging
add_to_log(Type, Data, StateData)
2012-09-11 15:45:59 +02:00
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
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
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,
2012-09-11 15:45:59 +02:00
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},
2012-09-11 15:45:59 +02:00
case catch ets:select(muc_online_users,
[{#muc_online_users{us = US, _ = '_'}, [], [[]]}])
of
Res when is_list(Res) -> length(Res);
_ -> 0
end.
2012-09-11 15:45:59 +02:00
element_size(El) -> byte_size(xml:element_to_binary(El)).
route_stanza(From, To, El) ->
case mod_muc:is_broadcasted(From#jid.lserver) of
2012-09-11 15:45:59 +02:00
true ->
#jid{luser = LUser, lserver = LServer} = To,
case ejabberd_cluster:get_node({LUser, LServer}) of
Node when Node == node() ->
ejabberd_router:route(From, To, El);
_ -> ok
end;
false -> ejabberd_router:route(From, To, El)
end.
2012-09-11 15:45:59 +02:00
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Multicast
send_multiple(From, Server, Users, Packet) ->
JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).