diff options
Diffstat (limited to 'src/mod_muc/mod_muc_room.erl')
-rw-r--r-- | src/mod_muc/mod_muc_room.erl | 7043 |
1 files changed, 3668 insertions, 3375 deletions
diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index ecf9c9581..6c139d5c5 100644 --- a/src/mod_muc/mod_muc_room.erl +++ b/src/mod_muc/mod_muc_room.erl @@ -25,37 +25,27 @@ %%%---------------------------------------------------------------------- -module(mod_muc_room). + -author('alexey@process-one.net'). -define(GEN_FSM, p1_fsm). -behaviour(?GEN_FSM). - %% External exports --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]). +-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 --export([init/1, - normal_state/2, - handle_event/3, - handle_sync_event/4, - handle_info/3, - terminate/3, - print_state/1, - code_change/4]). +-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"). + -include("jlib.hrl"). + -include("mod_muc_room.hrl"). -define(MAX_USERS_DEFAULT_LIST, @@ -64,865 +54,866 @@ %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. %% Module start with or without supervisor: -ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START(Args), - ?GEN_FSM:start(?MODULE, Args, ?FSMOPTS)). + +-define(SUPERVISOR_START(Args), + (?GEN_FSM):start(?MODULE, Args, ?FSMOPTS)). + -else. --define(SUPERVISOR_START(Args), - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup), + +-define(SUPERVISOR_START(Args), + Supervisor = gen_mod:get_module_proc(ServerHost, + ejabberd_mod_muc_sup), supervisor:start_child(Supervisor, Args)). --endif. -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -start(Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper, - Creator, Nick, DefRoomOpts) -> - ?SUPERVISOR_START([Host, ServerHost, Access, Room, HistorySize, PersistHistory, - RoomShaper, Creator, Nick, DefRoomOpts]). +-endif. -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(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]). -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(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) -> - ?GEN_FSM:start_link(?MODULE, [StateName, StateData], ?FSMOPTS). + (?GEN_FSM):start_link(?MODULE, [StateName, StateData], + ?FSMOPTS). migrate(FsmRef, Node, After) -> erlang:send_after(After, FsmRef, {migrate, Node}). moderate_room_history(FsmRef, Nick) -> - ?GEN_FSM:sync_send_all_state_event(FsmRef, {moderate_room_history, Nick}). + (?GEN_FSM):sync_send_all_state_event(FsmRef, + {moderate_room_history, Nick}). persist_recent_messages(FsmRef) -> - ?GEN_FSM:sync_send_all_state_event(FsmRef, persist_recent_messages). -%%%---------------------------------------------------------------------- -%%% Callback functions from gen_fsm -%%%---------------------------------------------------------------------- + (?GEN_FSM):sync_send_all_state_event(FsmRef, + persist_recent_messages). -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- -init([Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper, Creator, _Nick, DefRoomOpts]) -> +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, - #state{host = Host, - server_host = ServerHost, - access = Access, - room = Room, + #state{host = Host, server_host = ServerHost, + access = Access, room = Room, history = lqueue_new(HistorySize), persist_history = PersistHistory, - jid = jlib:make_jid(Room, Host, ""), - just_created = true, - room_shaper = Shaper}), + jid = jlib:make_jid(Room, Host, <<"">>), + just_created = true, room_shaper = Shaper}), State1 = set_opts(DefRoomOpts, State), - %% this will trigger a write of the muc to disc if it is persistent. - %% we need to do this because otherwise if muc are persistent by default, - %% but never configured in any way by the client, we were never - %% storing it on disc to be recreated on startup. - if - (State1#state.config)#config.persistent -> - mod_muc:store_room(State1#state.host, State1#state.room, make_opts(State1)); - true -> - ok + 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, - ?INFO_MSG("Created MUC room ~s@~s by ~s", + ?INFO_MSG("Created MUC room ~s@~s by ~s", [Room, Host, jlib:jid_to_string(Creator)]), add_to_log(room_existence, created, State1), add_to_log(room_existence, started, State1), {ok, normal_state, State1}; -init([Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper, Opts]) -> +init([Host, ServerHost, Access, Room, HistorySize, + PersistHistory, RoomShaper, Opts]) -> process_flag(trap_exit, true), Shaper = shaper:new(RoomShaper), - State = set_opts(Opts, #state{host = Host, - server_host = ServerHost, - access = Access, - room = Room, - history = load_history(ServerHost, Room, PersistHistory, lqueue_new(HistorySize)), - persist_history = PersistHistory, - jid = jlib:make_jid(Room, Host, ""), - room_shaper = Shaper}), + 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}; -init([StateName, #state{room = Room, host = Host} = StateData]) -> +init([StateName, + #state{room = Room, host = Host} = StateData]) -> process_flag(trap_exit, true), mod_muc:register_room(Host, Room, self()), {ok, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: StateName/2 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -normal_state({route, From, "", - {xmlelement, "message", Attrs, Els} = Packet}, +normal_state({route, From, <<"">>, + #xmlel{name = <<"message">>, attrs = Attrs, + children = Els} = + Packet}, StateData) -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), case is_user_online(From, StateData) orelse - is_user_allowed_message_nonparticipant(From, StateData) of - true -> - case xml:get_attr_s("type", Attrs) of - "groupchat" -> - Activity = get_user_activity(From, StateData), - Now = now_to_usec(now()), - MinMessageInterval = - trunc(gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, min_message_interval, 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 + 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 -> - 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; + 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 -> - {next_state, normal_state, StateData} - end - end; - IsVoiceRequest -> - NewStateData = - case (StateData#state.config)#config.allow_voice_requests of - true -> - MinInterval = (StateData#state.config) - #config.voice_request_min_interval, - BareFrom = jlib:jid_remove_resource( - jlib:jid_tolower(From)), - NowPriority = -now_to_usec(now()), - CleanPriority = - NowPriority + MinInterval*1000000, - Times = clean_treap( - StateData#state.last_voice_request_time, - CleanPriority), - case treap:lookup(BareFrom, Times) of - error -> - Times1 = treap:insert( - BareFrom, - NowPriority, - true, Times), - NSD = StateData#state{ - last_voice_request_time = - Times1}, - send_voice_request(From, NSD), - NSD; - {ok, _, _} -> - ErrText = "Please, wait for " - "a while before sending " - "new voice request", - Err = jlib:make_error_reply( - Packet, - ?ERRT_NOT_ACCEPTABLE( - Lang, ErrText)), - route_stanza( - StateData#state.jid, - From, Err), - StateData#state{ - last_voice_request_time = - Times} - end; - false -> - ErrText = "Voice requests are " - "disabled in this conference", - Err = jlib:make_error_reply( - Packet, - ?ERRT_FORBIDDEN( - Lang, ErrText)), - 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), + 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; - _ -> - 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} + _ -> 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; - -normal_state({route, From, "", - {xmlelement, "iq", _Attrs, _Els} = Packet}, +normal_state({route, From, <<"">>, + #xmlel{name = <<"iq">>} = Packet}, StateData) -> case jlib:iq_query_info(Packet) of - #iq{type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ when - (XMLNS == ?NS_MUC_ADMIN) or - (XMLNS == ?NS_MUC_OWNER) or - (XMLNS == ?NS_DISCO_INFO) or - (XMLNS == ?NS_DISCO_ITEMS) or - (XMLNS == ?NS_CAPTCHA) -> - Res1 = case XMLNS of - ?NS_MUC_ADMIN -> - process_iq_admin(From, Type, Lang, SubEl, StateData); - ?NS_MUC_OWNER -> - process_iq_owner(From, Type, Lang, SubEl, StateData); - ?NS_DISCO_INFO -> - process_iq_disco_info(From, Type, Lang, StateData); - ?NS_DISCO_ITEMS -> - process_iq_disco_items(From, Type, Lang, StateData); - ?NS_CAPTCHA -> - process_iq_captcha(From, Type, Lang, SubEl, StateData) - end, - {IQRes, NewStateData} = - case Res1 of - {result, Res, SD} -> - {IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - 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} + #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, - {xmlelement, "presence", _Attrs, _Els} = Packet}, + #xmlel{name = <<"presence">>} = Packet}, StateData) -> Activity = get_user_activity(From, StateData), Now = now_to_usec(now()), MinPresenceInterval = - trunc(gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, min_presence_interval, 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} + 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, - {xmlelement, "message", Attrs, _} = Packet}, + #xmlel{name = <<"message">>, attrs = Attrs} = Packet}, StateData) -> - Type = xml:get_attr_s("type", Attrs), - Lang = xml:get_attr_s("xml:lang", Attrs), - case decide_fate_message(Type, Packet, From, StateData) of - {expulse_sender, Reason} -> - ?DEBUG(Reason, []), - ErrorText = "This participant is kicked from the room because " - "he sent an error message to another participant", - NewState = expulse_participant(Packet, From, StateData, - translate:translate(Lang, ErrorText)), - {next_state, normal_state, NewState}; - forget_message -> - {next_state, normal_state, StateData}; - continue_delivery -> - case {(StateData#state.config)#config.allow_private_messages, - is_user_online(From, StateData)} of - {true, true} -> - case Type of - "groupchat" -> - ErrText = "It is not allowed to send private " - "messages of type \"groupchat\"", - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, ErrText)), - 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 + 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 - 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 + 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, - {xmlelement, "iq", Attrs, _Els} = Packet}, + #xmlel{name = <<"iq">>, attrs = Attrs} = Packet}, StateData) -> - Lang = xml:get_attr_s("xml:lang", Attrs), - StanzaId = xml:get_attr_s("id", Attrs), + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + StanzaId = xml:get_attr_s(<<"id">>, Attrs), case {(StateData#state.config)#config.allow_query_users, - is_user_online_iq(StanzaId, From, StateData)} of - {true, {true, NewId, FromFull}} -> - case find_jid_by_nick(ToNick, StateData) of - false -> - case jlib:iq_query_info(Packet) of - reply -> - ok; - _ -> - ErrText = "Recipient is not in the conference room", - Err = jlib:make_error_reply( - Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), - 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 + 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}. - - -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_event({service_message, Msg}, _StateName, StateData) -> - MessagePkt = {xmlelement, "message", - [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg}]}]}, - lists:foreach( - fun({_LJID, Info}) -> - route_stanza( - StateData#state.jid, - Info#user.jid, - MessagePkt) - end, - ?DICT:to_list(StateData#state.users)), - NSD = add_message_to_history("", - StateData#state.jid, - MessagePkt, - StateData), +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}; - -handle_event({destroy, Reason}, _StateName, StateData) -> - {result, [], stop} = - destroy_room( - {xmlelement, "destroy", - [{"xmlns", ?NS_MUC_OWNER}], - case Reason of - none -> []; - _Else -> - [{xmlelement, "reason", - [], [{xmlcdata, Reason}]}] - end}, StateData), - ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", +handle_event({destroy, Reason}, _StateName, + StateData) -> + {result, [], stop} = destroy_room(#xmlel{name = + <<"destroy">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_OWNER}], + children = + case Reason of + none -> []; + _Else -> + [#xmlel{name = + <<"reason">>, + attrs = [], + children = + [{xmlcdata, + Reason}]}] + end}, + StateData), + ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", [jlib:jid_to_string(StateData#state.jid), Reason]), add_to_log(room_existence, destroyed, StateData), {stop, shutdown, StateData}; handle_event(destroy, StateName, StateData) -> - ?INFO_MSG("Destroyed MUC room ~s", + ?INFO_MSG("Destroyed MUC room ~s", [jlib:jid_to_string(StateData#state.jid)]), handle_event({destroy, none}, StateName, StateData); - -handle_event({set_affiliations, Affiliations}, StateName, StateData) -> - {next_state, StateName, StateData#state{affiliations = Affiliations}}; - +handle_event({set_affiliations, Affiliations}, + StateName, StateData) -> + {next_state, StateName, + StateData#state{affiliations = Affiliations}}; handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -handle_sync_event({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, 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) -> +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}; -handle_sync_event(get_config, _From, StateName, StateData) -> - {reply, {ok, StateData#state.config}, StateName, StateData}; -handle_sync_event(get_state, _From, StateName, StateData) -> +handle_sync_event(get_config, _From, StateName, + StateData) -> + {reply, {ok, StateData#state.config}, StateName, + StateData}; +handle_sync_event(get_state, _From, StateName, + StateData) -> {reply, {ok, StateData}, StateName, StateData}; -handle_sync_event({change_config, Config}, _From, StateName, StateData) -> +handle_sync_event({change_config, Config}, _From, + StateName, StateData) -> {result, [], NSD} = change_config(Config, StateData), {reply, {ok, NSD#state.config}, StateName, NSD}; -handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) -> +handle_sync_event({change_state, NewStateData}, _From, + StateName, _StateData) -> {reply, {ok, NewStateData}, StateName, NewStateData}; -handle_sync_event(_Event, _From, StateName, StateData) -> - Reply = ok, - {reply, Reply, StateName, StateData}. +handle_sync_event(_Event, _From, StateName, + StateData) -> + Reply = ok, {reply, Reply, StateName, StateData}. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. -print_state(StateData) -> - StateData. - -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_info({process_user_presence, From}, normal_state = _StateName, StateData) -> - RoomQueueEmpty = queue:is_empty(StateData#state.room_queue), - RoomQueue = queue:in({presence, From}, StateData#state.room_queue), +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}, - if - RoomQueueEmpty -> - StateData2 = prepare_room_queue(StateData1), - {next_state, normal_state, StateData2}; - true -> - {next_state, normal_state, StateData1} + if RoomQueueEmpty -> + StateData2 = prepare_room_queue(StateData1), + {next_state, normal_state, StateData2}; + true -> {next_state, normal_state, StateData1} end; -handle_info({process_user_message, From}, normal_state = _StateName, StateData) -> - RoomQueueEmpty = queue:is_empty(StateData#state.room_queue), - RoomQueue = queue:in({message, From}, StateData#state.room_queue), +handle_info({process_user_message, From}, + normal_state = _StateName, StateData) -> + RoomQueueEmpty = + queue:is_empty(StateData#state.room_queue), + RoomQueue = queue:in({message, From}, + StateData#state.room_queue), StateData1 = StateData#state{room_queue = RoomQueue}, - if - RoomQueueEmpty -> - StateData2 = prepare_room_queue(StateData1), - {next_state, normal_state, StateData2}; - true -> - {next_state, normal_state, StateData1} + if RoomQueueEmpty -> + StateData2 = prepare_room_queue(StateData1), + {next_state, normal_state, StateData2}; + true -> {next_state, normal_state, StateData1} end; -handle_info(process_room_queue, normal_state = StateName, StateData) -> +handle_info(process_room_queue, + normal_state = StateName, StateData) -> case queue:out(StateData#state.room_queue) of - {{value, {message, From}}, RoomQueue} -> - Activity = get_user_activity(From, StateData), - Packet = Activity#activity.message, - NewActivity = Activity#activity{message = undefined}, - StateData1 = - store_user_activity( - From, NewActivity, StateData), - StateData2 = - StateData1#state{ - room_queue = RoomQueue}, - StateData3 = prepare_room_queue(StateData2), - process_groupchat_message(From, Packet, StateData3); - {{value, {presence, From}}, RoomQueue} -> - Activity = get_user_activity(From, StateData), - {Nick, Packet} = Activity#activity.presence, - NewActivity = Activity#activity{presence = undefined}, - StateData1 = - store_user_activity( - From, NewActivity, StateData), - StateData2 = - StateData1#state{ - room_queue = RoomQueue}, - StateData3 = prepare_room_queue(StateData2), - process_presence(From, Nick, Packet, StateData3); - {empty, _} -> - {next_state, StateName, StateData} + {{value, {message, From}}, RoomQueue} -> + Activity = get_user_activity(From, StateData), + Packet = Activity#activity.message, + NewActivity = Activity#activity{message = undefined}, + StateData1 = store_user_activity(From, NewActivity, + StateData), + StateData2 = StateData1#state{room_queue = RoomQueue}, + StateData3 = prepare_room_queue(StateData2), + process_groupchat_message(From, Packet, StateData3); + {{value, {presence, From}}, RoomQueue} -> + Activity = get_user_activity(From, StateData), + {Nick, Packet} = Activity#activity.presence, + NewActivity = Activity#activity{presence = undefined}, + StateData1 = store_user_activity(From, NewActivity, + StateData), + StateData2 = StateData1#state{room_queue = RoomQueue}, + StateData3 = prepare_room_queue(StateData2), + process_presence(From, Nick, Packet, StateData3); + {empty, _} -> {next_state, StateName, StateData} end; -handle_info({captcha_succeed, From}, normal_state, StateData) -> - NewState = case ?DICT:find(From, StateData#state.robots) of - {ok, {Nick, Packet}} -> - Robots = ?DICT:store(From, passed, StateData#state.robots), - add_new_user(From, Nick, Packet, StateData#state{robots=Robots}); - _ -> - StateData +handle_info({captcha_succeed, From}, normal_state, + StateData) -> + NewState = case (?DICT):find(From, + StateData#state.robots) + of + {ok, {Nick, Packet}} -> + Robots = (?DICT):store(From, passed, + StateData#state.robots), + add_new_user(From, Nick, Packet, + StateData#state{robots = Robots}); + _ -> StateData end, {next_state, normal_state, NewState}; -handle_info({captcha_failed, From}, normal_state, StateData) -> - NewState = case ?DICT:find(From, StateData#state.robots) of - {ok, {Nick, Packet}} -> - Robots = ?DICT:erase(From, StateData#state.robots), - Err = jlib:make_error_reply( - Packet, ?ERR_NOT_AUTHORIZED), - route_stanza( % TODO: s/Nick/""/ - jlib:jid_replace_resource( - StateData#state.jid, Nick), - From, Err), - StateData#state{robots=Robots}; - _ -> - StateData +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() -> - {migrate, StateData, - {Node, ?MODULE, start, [StateName, StateData]}, 0}; - true -> - {next_state, StateName, StateData} + {migrate, StateData, + {Node, ?MODULE, start, [StateName, StateData]}, 0}; + true -> {next_state, StateName, StateData} end; -handle_info('shutdown', _StateName, StateData) -> - {stop, 'shutdown', StateData}; +handle_info(shutdown, _StateName, StateData) -> + {stop, shutdown, StateData}; handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- terminate({migrated, Clone}, _StateName, StateData) -> ?INFO_MSG("Migrating room ~s@~s to ~p on node ~p", - [StateData#state.room, StateData#state.host, - Clone, node(Clone)]), - mod_muc:room_destroyed(StateData#state.host, StateData#state.room, - self(), StateData#state.server_host), + [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 - shutdown -> "You are being removed from the room because" - " of a system shutdown"; - _ -> "Room terminates" + shutdown -> + <<"You are being removed from the room " + "because of a system shutdown">>; + _ -> <<"Room terminates">> end, - ItemAttrs = [{"affiliation", "none"}, {"role", "none"}], - ReasonEl = {xmlelement, "reason", [], [{xmlcdata, ReasonT}]}, - Packet = {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, [ReasonEl]}, - {xmlelement, "status", [{"code", "332"}], []} - ]}]}, - ?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), + 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), - if - Reason == 'shutdown' -> - persist_muc_history(StateData); - true -> - ok + if Reason == shutdown -> persist_muc_history(StateData); + true -> ok end, - mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(), + mod_muc:room_destroyed(StateData#state.host, + StateData#state.room, self(), StateData#state.server_host), ok. @@ -930,592 +921,596 @@ terminate(Reason, _StateName, StateData) -> %%% Internal functions %%%---------------------------------------------------------------------- -load_history(_Host, _Room, false, Queue) -> - Queue; +load_history(_Host, _Room, false, Queue) -> Queue; load_history(Host, Room, true, Queue) -> - ?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(list_to_integer(Ts)), - list_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)), - atom_to_list(HaveSubject), - integer_to_list(calendar:datetime_to_gregorian_seconds(Timestamp)), - 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}. + ?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) -> - ?GEN_FSM:send_event(Pid, {route, From, ToNick, Packet}). + (?GEN_FSM):send_event(Pid, + {route, From, ToNick, Packet}). -process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet, +process_groupchat_message(From, + #xmlel{name = <<"message">>, attrs = Attrs} = Packet, StateData) -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), case is_user_online(From, StateData) orelse - is_user_allowed_message_nonparticipant(From, StateData) of - true -> - {FromNick, Role} = get_participant_data(From, StateData), - if - (Role == moderator) or (Role == participant) - or ((StateData#state.config)#config.moderated == false) -> - {NewStateData1, IsAllowed} = - case check_subject(Packet) of - false -> - {StateData, true}; - Subject -> - case can_change_subject(Role, - StateData) of - true -> - NSD = - StateData#state{ - subject = Subject, - subject_author = - FromNick}, - case (NSD#state.config)#config.persistent of - true -> - mod_muc:store_room( - NSD#state.server_host, - NSD#state.host, - NSD#state.room, - make_opts(NSD)); - _ -> - ok - end, - {NSD, true}; - _ -> - {StateData, false} - end - end, - case IsAllowed of - true -> - lists:foreach( - fun({_LJID, Info}) -> - route_stanza( - jlib:jid_replace_resource( - StateData#state.jid, - FromNick), - Info#user.jid, - Packet) - end, - ?DICT:to_list(StateData#state.users)), - NewStateData2 = - add_message_to_history(FromNick, - From, - Packet, - NewStateData1), - {next_state, normal_state, NewStateData2}; - _ -> - Err = - case (StateData#state.config)#config.allow_change_subj of - true -> - ?ERRT_FORBIDDEN( - Lang, - "Only moderators and participants " - "are allowed to change the subject in this room"); - _ -> - ?ERRT_FORBIDDEN( - Lang, - "Only moderators " - "are allowed to change the subject in this room") - end, - 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} + 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. -%% @doc Check if this non participant can send message to room. -%% -%% XEP-0045 v1.23: -%% 7.9 Sending a Message to All Occupants -%% an implementation MAY allow users with certain privileges -%% (e.g., a room owner, room admin, or service-level admin) -%% to send messages to the room even if those users are not occupants. -is_user_allowed_message_nonparticipant(JID, StateData) -> +is_user_allowed_message_nonparticipant(JID, + StateData) -> case get_service_affiliation(JID, StateData) of - owner -> - true; - _ -> false + owner -> true; + _ -> false end. -%% @doc Get information of this participant, or default values. -%% If the JID is not a participant, return values for a service message. get_participant_data(From, StateData) -> - case ?DICT:find(jlib:jid_tolower(From), StateData#state.users) of - {ok, #user{nick = FromNick, role = Role}} -> - {FromNick, Role}; - error -> - {"", moderator} + case (?DICT):find(jlib:jid_tolower(From), + StateData#state.users) + of + {ok, #user{nick = FromNick, role = Role}} -> + {FromNick, Role}; + error -> {<<"">>, moderator} end. - -process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = Packet, +process_presence(From, Nick, + #xmlel{name = <<"presence">>, attrs = Attrs} = Packet, StateData) -> - Type = xml:get_attr_s("type", Attrs), - Lang = xml:get_attr_s("xml:lang", Attrs), - StateData1 = - case Type of - "unavailable" -> - case is_user_online(From, StateData) of - true -> - NewPacket = case {(StateData#state.config)#config.allow_visitor_status, - is_visitor(From, StateData)} of - {false, true} -> - strip_status(Packet); - _ -> - Packet - end, - NewState = - add_user_presence_un(From, NewPacket, StateData), - case ?DICT:find(Nick, StateData#state.nicks) of - {ok, [_, _ | _]} -> ok; - _ -> send_new_presence(From, NewState) - end, - Reason = case xml:get_subtag(NewPacket, "status") of - false -> ""; - Status_el -> xml:get_tag_cdata(Status_el) - end, - remove_online_user(From, NewState, Reason); - _ -> - StateData - end; - "error" -> - case is_user_online(From, StateData) of - true -> - ErrorText = "This participant is kicked from the room because " - "he sent an error presence", - expulse_participant(Packet, From, StateData, - translate:translate(Lang, ErrorText)); - _ -> - StateData - end; - "" -> - case is_user_online(From, StateData) of - true -> - case is_nick_change(From, Nick, StateData) of - true -> - case {nick_collision(From, Nick, StateData), - mod_muc:can_use_nick( - StateData#state.server_host, - StateData#state.host, From, Nick), - {(StateData#state.config)#config.allow_visitor_nickchange, - is_visitor(From, StateData)}} of - {_, _, {false, true}} -> - ErrText = "Visitors are not allowed to change their nicknames in this room", - Err = jlib:make_error_reply( - Packet, - ?ERRT_NOT_ALLOWED(Lang, ErrText)), - route_stanza( - % TODO: s/Nick/""/ - 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( - % TODO: s/Nick/""/ - 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} + 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), - ?DICT:is_key(LJID, StateData#state.users). + (?DICT):is_key(LJID, StateData#state.users). -%% Check if the user is occupant of the room, or at least is an admin or owner. is_occupant_or_admin(JID, StateData) -> FAffiliation = get_affiliation(JID, StateData), FRole = get_role(JID, StateData), - case (FRole /= none) orelse - (FAffiliation == admin) orelse - (FAffiliation == owner) of - true -> - true; - _ -> - false + case FRole /= none orelse + FAffiliation == admin orelse FAffiliation == owner + of + true -> true; + _ -> false end. -%%% -%%% Handle IQ queries of vCard -%%% -is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource /= "" -> +is_user_online_iq(StanzaId, JID, StateData) + when JID#jid.lresource /= <<"">> -> {is_user_online(JID, StateData), StanzaId, JID}; -is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == "" -> +is_user_online_iq(StanzaId, JID, StateData) + when JID#jid.lresource == <<"">> -> try stanzaid_unpack(StanzaId) of - {OriginalId, Resource} -> - JIDWithResource = jlib:jid_replace_resource(JID, Resource), - {is_user_online(JIDWithResource, StateData), - OriginalId, JIDWithResource} + {OriginalId, Resource} -> + JIDWithResource = jlib:jid_replace_resource(JID, + Resource), + {is_user_online(JIDWithResource, StateData), OriginalId, + JIDWithResource} catch - _:_ -> - {is_user_online(JID, StateData), StanzaId, JID} + _:_ -> {is_user_online(JID, StateData), StanzaId, JID} end. -handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet) -> +handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, + Packet) -> ToBareJID = jlib:jid_remove_resource(ToJID), IQ = jlib:iq_query_info(Packet), - handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, NewId, IQ, Packet). -handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, _NewId, - #iq{type = get, xmlns = ?NS_VCARD}, Packet) - when ToBareJID /= ToJID -> + handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, + NewId, IQ, Packet). + +handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, + _NewId, #iq{type = get, xmlns = ?NS_VCARD}, Packet) + when ToBareJID /= ToJID -> {ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)}; -handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, _StanzaId, NewId, _IQ, Packet) -> +handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, + _StanzaId, NewId, _IQ, Packet) -> {ToJID, change_stanzaid(NewId, Packet)}. stanzaid_pack(OriginalId, Resource) -> - "berd"++base64:encode_to_string("ejab\0" ++ OriginalId ++ "\0" ++ Resource). -stanzaid_unpack("berd"++StanzaIdBase64) -> - StanzaId = base64:decode_to_string(StanzaIdBase64), - ["ejab", OriginalId, Resource] = string:tokens(StanzaId, "\0"), + <<"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) -> - {xmlelement, Name, Attrs, Els} = jlib:remove_attr("id", Packet), - {xmlelement, Name, [{"id", NewId} | Attrs], Els}. + #xmlel{name = Name, attrs = Attrs, children = Els} = + jlib:remove_attr(<<"id">>, Packet), + #xmlel{name = Name, attrs = [{<<"id">>, NewId} | Attrs], + children = Els}. + change_stanzaid(PreviousId, ToJID, Packet) -> NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource), change_stanzaid(NewId, Packet). + %%% %%% role_to_list(Role) -> case Role of - moderator -> "moderator"; - participant -> "participant"; - visitor -> "visitor"; - none -> "none" + moderator -> <<"moderator">>; + participant -> <<"participant">>; + visitor -> <<"visitor">>; + none -> <<"none">> end. affiliation_to_list(Affiliation) -> case Affiliation of - owner -> "owner"; - admin -> "admin"; - member -> "member"; - outcast -> "outcast"; - none -> "none" + owner -> <<"owner">>; + admin -> <<"admin">>; + member -> <<"member">>; + outcast -> <<"outcast">>; + none -> <<"none">> end. list_to_role(Role) -> case Role of - "moderator" -> moderator; - "participant" -> participant; - "visitor" -> visitor; - "none" -> none + <<"moderator">> -> moderator; + <<"participant">> -> participant; + <<"visitor">> -> visitor; + <<"none">> -> none end. list_to_affiliation(Affiliation) -> case Affiliation of - "owner" -> owner; - "admin" -> admin; - "member" -> member; - "outcast" -> outcast; - "none" -> none + <<"owner">> -> owner; + <<"admin">> -> admin; + <<"member">> -> member; + <<"outcast">> -> outcast; + <<"none">> -> none end. -%% Decide the fate of the message and its sender -%% Returns: continue_delivery | forget_message | {expulse_sender, Reason} -decide_fate_message("error", Packet, From, StateData) -> - %% Make a preliminary decision +decide_fate_message(<<"error">>, Packet, From, + StateData) -> PD = case check_error_kick(Packet) of - %% If this is an error stanza and its condition matches a criteria - true -> - Reason = io_lib:format("This participant is considered a ghost and is expulsed: ~s", - [jlib:jid_to_string(From)]), - {expulse_sender, Reason}; - false -> - continue_delivery + %% If this is an error stanza and its condition matches a criteria + true -> + Reason = + io_lib:format("This participant is considered a ghost " + "and is expulsed: ~s", + [jlib:jid_to_string(From)]), + {expulse_sender, Reason}; + false -> continue_delivery end, case PD of - {expulse_sender, R} -> - case is_user_online(From, StateData) of - true -> - {expulse_sender, R}; - false -> - forget_message - end; - Other -> - Other + {expulse_sender, R} -> + case is_user_online(From, StateData) of + true -> {expulse_sender, R}; + false -> forget_message + end; + Other -> Other end; +decide_fate_message(_, _, _, _) -> continue_delivery. -decide_fate_message(_, _, _, _) -> - continue_delivery. - -%% Check if the elements of this error stanza indicate -%% that the sender is a dead participant. -%% If so, return true to kick the participant. check_error_kick(Packet) -> case get_error_condition(Packet) of - "gone" -> true; - "internal-server-error" -> true; - "item-not-found" -> true; - "jid-malformed" -> true; - "recipient-unavailable" -> true; - "redirect" -> true; - "remote-server-not-found" -> true; - "remote-server-timeout" -> true; - "service-unavailable" -> true; - _ -> false + <<"gone">> -> true; + <<"internal-server-error">> -> true; + <<"item-not-found">> -> true; + <<"jid-malformed">> -> true; + <<"recipient-unavailable">> -> true; + <<"redirect">> -> true; + <<"remote-server-not-found">> -> true; + <<"remote-server-timeout">> -> true; + <<"service-unavailable">> -> true; + _ -> false end. get_error_condition(Packet) -> - case catch get_error_condition2(Packet) of - {condition, ErrorCondition} -> - ErrorCondition; - {'EXIT', _} -> - "badformed error stanza" - end. + case catch get_error_condition2(Packet) of + {condition, ErrorCondition} -> ErrorCondition; + {'EXIT', _} -> <<"badformed error stanza">> + end. + get_error_condition2(Packet) -> - {xmlelement, _, _, EEls} = xml:get_subtag(Packet, "error"), - [Condition] = [Name || {xmlelement, Name, [{"xmlns", ?NS_STANZAS}], []} <- EEls], - {condition, Condition}. + #xmlel{children = EEls} = xml:get_subtag(Packet, + <<"error">>), + [Condition] = [Name + || #xmlel{name = Name, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], + children = []} + <- EEls], + {condition, Condition}. expulse_participant(Packet, From, StateData, Reason1) -> - ErrorCondition = get_error_condition(Packet), - Reason2 = io_lib:format(Reason1 ++ ": " ++ "~s", [ErrorCondition]), - NewState = add_user_presence_un( - From, - {xmlelement, "presence", - [{"type", "unavailable"}], - [{xmlelement, "status", [], - [{xmlcdata, Reason2}] - }]}, - StateData), - send_new_presence(From, NewState), - remove_online_user(From, NewState). - + ErrorCondition = get_error_condition(Packet), + Reason2 = iolist_to_binary( + io_lib:format(binary_to_list(Reason1) ++ ": " ++ "~s", + [ErrorCondition])), + NewState = add_user_presence_un(From, + #xmlel{name = <<"presence">>, + attrs = + [{<<"type">>, + <<"unavailable">>}], + children = + [#xmlel{name = <<"status">>, + attrs = [], + children = + [{xmlcdata, + Reason2}]}]}, + StateData), + send_new_presence(From, NewState), + remove_online_user(From, NewState). set_affiliation(JID, Affiliation, StateData) -> - set_affiliation(JID, Affiliation, StateData, ""). + set_affiliation(JID, Affiliation, StateData, <<"">>). set_affiliation(JID, Affiliation, StateData, Reason) -> LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)), Affiliations = case Affiliation of - none -> - ?DICT:erase(LJID, - StateData#state.affiliations); - _ -> - ?DICT:store(LJID, - {Affiliation, Reason}, + none -> + (?DICT):erase(LJID, StateData#state.affiliations); + _ -> + (?DICT):store(LJID, {Affiliation, Reason}, StateData#state.affiliations) end, StateData#state{affiliations = Affiliations}. get_affiliation(JID, StateData) -> - {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = StateData#state.access, - Res = - case acl:match_rule(StateData#state.server_host, AccessAdmin, JID) of - allow -> - owner; + {_AccessRoute, _AccessCreate, AccessAdmin, + _AccessPersistent} = + StateData#state.access, + Res = case acl:match_rule(StateData#state.server_host, + AccessAdmin, JID) + of + allow -> owner; _ -> LJID = jlib:jid_tolower(JID), - case ?DICT:find(LJID, StateData#state.affiliations) of - {ok, Affiliation} -> - Affiliation; - _ -> - LJID1 = jlib:jid_remove_resource(LJID), - case ?DICT:find(LJID1, StateData#state.affiliations) of - {ok, Affiliation} -> - Affiliation; - _ -> - LJID2 = setelement(1, LJID, ""), - case ?DICT:find(LJID2, StateData#state.affiliations) of - {ok, Affiliation} -> - Affiliation; - _ -> - LJID3 = jlib:jid_remove_resource(LJID2), - case ?DICT:find(LJID3, StateData#state.affiliations) of - {ok, Affiliation} -> - Affiliation; - _ -> - none - end - end - end + case (?DICT):find(LJID, StateData#state.affiliations) of + {ok, Affiliation} -> Affiliation; + _ -> + LJID1 = jlib:jid_remove_resource(LJID), + case (?DICT):find(LJID1, StateData#state.affiliations) + of + {ok, Affiliation} -> Affiliation; + _ -> + LJID2 = setelement(1, LJID, <<"">>), + case (?DICT):find(LJID2, + StateData#state.affiliations) + of + {ok, Affiliation} -> Affiliation; + _ -> + LJID3 = jlib:jid_remove_resource(LJID2), + case (?DICT):find(LJID3, + StateData#state.affiliations) + of + {ok, Affiliation} -> Affiliation; + _ -> none + end + end + end end - end, + end, case Res of - {A, _Reason} -> - A; - _ -> - Res + {A, _Reason} -> A; + _ -> Res end. get_service_affiliation(JID, StateData) -> - {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = + {_AccessRoute, _AccessCreate, AccessAdmin, + _AccessPersistent} = StateData#state.access, - case acl:match_rule(StateData#state.server_host, AccessAdmin, JID) of - allow -> - owner; - _ -> - none + case acl:match_rule(StateData#state.server_host, + AccessAdmin, JID) + of + allow -> owner; + _ -> none end. set_role(JID, Role, StateData) -> LJID = jlib:jid_tolower(JID), LJIDs = case LJID of - {U, S, ""} -> - ?DICT:fold( - fun(J, _, Js) -> - case J of - {U, S, _} -> - [J | Js]; - _ -> - Js - end - end, [], StateData#state.users); - _ -> - case ?DICT:is_key(LJID, StateData#state.users) of - true -> - [LJID]; - _ -> - [] - end - end, - {Users, Nicks} - = case Role of - none -> - lists:foldl(fun(J, {Us, Ns}) -> - NewNs = - case ?DICT:find(J, Us) of - {ok, #user{nick = Nick}} -> - ?DICT:erase(Nick, Ns); - _ -> - Ns - end, - {?DICT:erase(J, Us), NewNs} - end, - {StateData#state.users, StateData#state.nicks}, - LJIDs); + {U, S, <<"">>} -> + (?DICT):fold(fun (J, _, Js) -> + case J of + {U, S, _} -> [J | Js]; + _ -> Js + end + end, + [], StateData#state.users); _ -> - {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, + case (?DICT):is_key(LJID, StateData#state.users) of + true -> [LJID]; + _ -> [] + end + end, + {Users, Nicks} = case Role of + none -> + lists:foldl(fun (J, {Us, Ns}) -> + NewNs = case (?DICT):find(J, Us) + of + {ok, + #user{nick = Nick}} -> + (?DICT):erase(Nick, + Ns); + _ -> Ns + end, + {(?DICT):erase(J, Us), NewNs} + end, + {StateData#state.users, + StateData#state.nicks}, + LJIDs); + _ -> + {lists:foldl(fun (J, Us) -> + {ok, User} = (?DICT):find(J, + Us), + (?DICT):store(J, + User#user{role = + Role}, + Us) + end, + StateData#state.users, LJIDs), + StateData#state.nicks} + end, StateData#state{users = Users, nicks = Nicks}. get_role(JID, StateData) -> LJID = jlib:jid_tolower(JID), - case ?DICT:find(LJID, StateData#state.users) of - {ok, #user{role = Role}} -> - Role; - _ -> - none + case (?DICT):find(LJID, StateData#state.users) of + {ok, #user{role = Role}} -> Role; + _ -> none end. get_default_role(Affiliation, StateData) -> case Affiliation of - owner -> moderator; - admin -> moderator; - member -> participant; - outcast -> none; - none -> - case (StateData#state.config)#config.members_only of - true -> - none; - _ -> - case (StateData#state.config)#config.members_by_default of - true -> - participant; - _ -> - visitor - end - end + 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) -> @@ -1527,269 +1522,254 @@ is_moderator(Jid, StateData) -> get_max_users(StateData) -> MaxUsers = (StateData#state.config)#config.max_users, ServiceMaxUsers = get_service_max_users(StateData), - if - MaxUsers =< ServiceMaxUsers -> MaxUsers; - true -> ServiceMaxUsers + if MaxUsers =< ServiceMaxUsers -> MaxUsers; + true -> ServiceMaxUsers end. get_service_max_users(StateData) -> gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users, ?MAX_USERS_DEFAULT). + mod_muc, max_users, + fun(I) when is_integer(I), I>0 -> I end, + ?MAX_USERS_DEFAULT). get_max_users_admin_threshold(StateData) -> gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users_admin_threshold, 5). + mod_muc, max_users_admin_threshold, + fun(I) when is_integer(I), I>0 -> I end, + 5). get_user_activity(JID, StateData) -> case treap:lookup(jlib:jid_tolower(JID), - StateData#state.activity) of - {ok, _P, A} -> A; - error -> - MessageShaper = - shaper:new(gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, user_message_shaper, none)), - PresenceShaper = - shaper:new(gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, user_presence_shaper, none)), - #activity{message_shaper = MessageShaper, - presence_shaper = PresenceShaper} + StateData#state.activity) + of + {ok, _P, A} -> A; + error -> + MessageShaper = + shaper:new(gen_mod:get_module_opt(StateData#state.server_host, + mod_muc, user_message_shaper, + fun(A) when is_atom(A) -> A end, + none)), + PresenceShaper = + shaper:new(gen_mod:get_module_opt(StateData#state.server_host, + mod_muc, user_presence_shaper, + fun(A) when is_atom(A) -> A end, + none)), + #activity{message_shaper = MessageShaper, + presence_shaper = PresenceShaper} end. store_user_activity(JID, UserActivity, StateData) -> MinMessageInterval = - gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, min_message_interval, 0), + gen_mod:get_module_opt(StateData#state.server_host, + mod_muc, min_message_interval, + fun(I) when is_integer(I), I>=0 -> I end, + 0), MinPresenceInterval = - gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, min_presence_interval, 0), + gen_mod:get_module_opt(StateData#state.server_host, + mod_muc, min_presence_interval, + fun(I) when is_integer(I), I>=0 -> I end, + 0), Key = jlib:jid_tolower(JID), Now = now_to_usec(now()), - Activity1 = clean_treap(StateData#state.activity, {1, -Now}), - Activity = - case treap:lookup(Key, Activity1) of - {ok, _P, _A} -> - treap:delete(Key, Activity1); - error -> - Activity1 - end, - StateData1 = - case (MinMessageInterval == 0) andalso - (MinPresenceInterval == 0) andalso - (UserActivity#activity.message_shaper == none) andalso - (UserActivity#activity.presence_shaper == none) andalso - (UserActivity#activity.message == undefined) andalso - (UserActivity#activity.presence == undefined) of - true -> - StateData#state{activity = Activity}; - false -> - case (UserActivity#activity.message == undefined) andalso - (UserActivity#activity.presence == undefined) of - true -> - {_, MessageShaperInterval} = - shaper:update(UserActivity#activity.message_shaper, - 100000), - {_, PresenceShaperInterval} = - shaper:update(UserActivity#activity.presence_shaper, - 100000), - Delay = lists:max([MessageShaperInterval, - PresenceShaperInterval, - MinMessageInterval * 1000, - MinPresenceInterval * 1000]) * 1000, - Priority = {1, -(Now + Delay)}, - StateData#state{ - activity = treap:insert( - Key, - Priority, - UserActivity, - Activity)}; - false -> - Priority = {0, 0}, - StateData#state{ - activity = treap:insert( - Key, - Priority, - UserActivity, - Activity)} - end - end, + Activity1 = clean_treap(StateData#state.activity, + {1, -Now}), + Activity = case treap:lookup(Key, Activity1) of + {ok, _P, _A} -> treap:delete(Key, Activity1); + error -> Activity1 + end, + StateData1 = case MinMessageInterval == 0 andalso + MinPresenceInterval == 0 andalso + UserActivity#activity.message_shaper == none andalso + UserActivity#activity.presence_shaper == none + andalso + UserActivity#activity.message == undefined andalso + UserActivity#activity.presence == undefined + of + true -> StateData#state{activity = Activity}; + false -> + case UserActivity#activity.message == undefined andalso + UserActivity#activity.presence == undefined + of + true -> + {_, MessageShaperInterval} = + shaper:update(UserActivity#activity.message_shaper, + 100000), + {_, PresenceShaperInterval} = + shaper:update(UserActivity#activity.presence_shaper, + 100000), + Delay = lists:max([MessageShaperInterval, + PresenceShaperInterval, + MinMessageInterval * 1000, + MinPresenceInterval * 1000]) + * 1000, + Priority = {1, -(Now + Delay)}, + StateData#state{activity = + treap:insert(Key, Priority, + UserActivity, + Activity)}; + false -> + Priority = {0, 0}, + StateData#state{activity = + treap:insert(Key, Priority, + UserActivity, + Activity)} + end + end, StateData1. clean_treap(Treap, CleanPriority) -> case treap:is_empty(Treap) of - true -> - Treap; - false -> - {_Key, Priority, _Value} = treap:get_root(Treap), - if - Priority > CleanPriority -> - clean_treap(treap:delete_root(Treap), CleanPriority); - true -> - Treap - end + true -> Treap; + false -> + {_Key, Priority, _Value} = treap:get_root(Treap), + if Priority > CleanPriority -> + clean_treap(treap:delete_root(Treap), CleanPriority); + true -> Treap + end end. - prepare_room_queue(StateData) -> case queue:out(StateData#state.room_queue) of - {{value, {message, From}}, _RoomQueue} -> - Activity = get_user_activity(From, StateData), - Packet = Activity#activity.message, - Size = element_size(Packet), - {RoomShaper, RoomShaperInterval} = - shaper:update(StateData#state.room_shaper, Size), - erlang:send_after( - RoomShaperInterval, self(), - process_room_queue), - StateData#state{ - room_shaper = RoomShaper}; - {{value, {presence, From}}, _RoomQueue} -> - Activity = get_user_activity(From, StateData), - {_Nick, Packet} = Activity#activity.presence, - Size = element_size(Packet), - {RoomShaper, RoomShaperInterval} = - shaper:update(StateData#state.room_shaper, Size), - erlang:send_after( - RoomShaperInterval, self(), - process_room_queue), - StateData#state{ - room_shaper = RoomShaper}; - {empty, _} -> - StateData + {{value, {message, From}}, _RoomQueue} -> + Activity = get_user_activity(From, StateData), + Packet = Activity#activity.message, + Size = element_size(Packet), + {RoomShaper, RoomShaperInterval} = + shaper:update(StateData#state.room_shaper, Size), + erlang:send_after(RoomShaperInterval, self(), + process_room_queue), + StateData#state{room_shaper = RoomShaper}; + {{value, {presence, From}}, _RoomQueue} -> + Activity = get_user_activity(From, StateData), + {_Nick, Packet} = Activity#activity.presence, + Size = element_size(Packet), + {RoomShaper, RoomShaperInterval} = + shaper:update(StateData#state.room_shaper, Size), + erlang:send_after(RoomShaperInterval, self(), + process_room_queue), + StateData#state{room_shaper = RoomShaper}; + {empty, _} -> StateData end. - add_online_user(JID, Nick, Role, StateData) -> LJID = jlib:jid_tolower(JID), - Users = ?DICT:store(LJID, - #user{jid = JID, - nick = Nick, - role = Role}, - StateData#state.users), + Users = (?DICT):store(LJID, + #user{jid = JID, nick = Nick, role = Role}, + StateData#state.users), add_to_log(join, Nick, StateData), - Nicks = ?DICT:update(Nick, - fun(Entry) -> - case lists:member(LJID, Entry) of - true -> - Entry; - false -> - [LJID|Entry] - end - end, - [LJID], - StateData#state.nicks), + Nicks = (?DICT):update(Nick, + fun (Entry) -> + case lists:member(LJID, Entry) of + true -> Entry; + false -> [LJID | Entry] + end + end, + [LJID], StateData#state.nicks), tab_add_online_user(JID, StateData), StateData#state{users = Users, nicks = Nicks}. remove_online_user(JID, StateData) -> - remove_online_user(JID, StateData, ""). + remove_online_user(JID, StateData, <<"">>). remove_online_user(JID, StateData, Reason) -> LJID = jlib:jid_tolower(JID), - {ok, #user{nick = Nick}} = - ?DICT:find(LJID, StateData#state.users), + {ok, #user{nick = Nick}} = (?DICT):find(LJID, + StateData#state.users), add_to_log(leave, {Nick, Reason}, StateData), tab_remove_online_user(JID, StateData), - Users = ?DICT:erase(LJID, StateData#state.users), - Nicks = case ?DICT:find(Nick, StateData#state.nicks) of - {ok, [LJID]} -> - ?DICT:erase(Nick, StateData#state.nicks); - {ok, U} -> - ?DICT:store(Nick, U -- [LJID], StateData#state.nicks); - error -> - StateData#state.nicks + Users = (?DICT):erase(LJID, StateData#state.users), + Nicks = case (?DICT):find(Nick, StateData#state.nicks) + of + {ok, [LJID]} -> + (?DICT):erase(Nick, StateData#state.nicks); + {ok, U} -> + (?DICT):store(Nick, U -- [LJID], StateData#state.nicks); + error -> StateData#state.nicks end, StateData#state{users = Users, nicks = Nicks}. - -filter_presence({xmlelement, "presence", Attrs, Els}) -> - FEls = lists:filter( - fun(El) -> - case El of - {xmlcdata, _} -> - false; - {xmlelement, _Name1, Attrs1, _Els1} -> - XMLNS = xml:get_attr_s("xmlns", Attrs1), - case XMLNS of - ?NS_MUC ++ _ -> - false; - _ -> - true - end - end - end, Els), - {xmlelement, "presence", Attrs, FEls}. - -strip_status({xmlelement, "presence", Attrs, Els}) -> - FEls = lists:filter( - fun({xmlelement, "status", _Attrs1, _Els1}) -> - false; - (_) -> true - end, Els), - {xmlelement, "presence", Attrs, FEls}. +filter_presence(#xmlel{name = <<"presence">>, + attrs = Attrs, children = Els}) -> + FEls = lists:filter(fun (El) -> + case El of + {xmlcdata, _} -> false; + #xmlel{attrs = Attrs1} -> + XMLNS = xml:get_attr_s(<<"xmlns">>, + Attrs1), + NS_MUC = ?NS_MUC, + Size = byte_size(NS_MUC), + case XMLNS of + <<NS_MUC:Size/binary, _/binary>> -> + false; + _ -> + true + end + end + end, + Els), + #xmlel{name = <<"presence">>, attrs = Attrs, + children = FEls}. + +strip_status(#xmlel{name = <<"presence">>, + attrs = Attrs, children = Els}) -> + FEls = lists:filter(fun (#xmlel{name = <<"status">>}) -> + false; + (_) -> true + end, + Els), + #xmlel{name = <<"presence">>, attrs = Attrs, + children = FEls}. add_user_presence(JID, Presence, StateData) -> LJID = jlib:jid_tolower(JID), FPresence = filter_presence(Presence), - Users = - ?DICT:update( - LJID, - fun(#user{} = User) -> - User#user{last_presence = FPresence} - end, StateData#state.users), + Users = (?DICT):update(LJID, + fun (#user{} = User) -> + User#user{last_presence = FPresence} + end, + StateData#state.users), StateData#state{users = Users}. add_user_presence_un(JID, Presence, StateData) -> LJID = jlib:jid_tolower(JID), FPresence = filter_presence(Presence), - Users = - ?DICT:update( - LJID, - fun(#user{} = User) -> - User#user{last_presence = FPresence, - role = none} - end, StateData#state.users), + Users = (?DICT):update(LJID, + fun (#user{} = User) -> + User#user{last_presence = FPresence, + role = none} + end, + StateData#state.users), StateData#state{users = Users}. - -%% Find and return a list of the full JIDs of the users of Nick. -%% Return jid record. find_jids_by_nick(Nick, StateData) -> - case ?DICT:find(Nick, StateData#state.nicks) of - {ok, [User]} -> - [jlib:make_jid(User)]; - {ok, Users} -> - [jlib:make_jid(LJID) || LJID <- Users]; - error -> - false + case (?DICT):find(Nick, StateData#state.nicks) of + {ok, [User]} -> [jlib:make_jid(User)]; + {ok, Users} -> [jlib:make_jid(LJID) || LJID <- Users]; + error -> false end. -%% Find and return the full JID of the user of Nick with -%% highest-priority presence. Return jid record. find_jid_by_nick(Nick, StateData) -> - case ?DICT:find(Nick, StateData#state.nicks) of - {ok, [User]} -> - jlib:make_jid(User); - {ok, [FirstUser|Users]} -> - #user{last_presence = FirstPresence} = - ?DICT:fetch(FirstUser, StateData#state.users), - {LJID, _} = - lists:foldl(fun(Compare, {HighestUser, HighestPresence}) -> - #user{last_presence = P1} = - ?DICT:fetch(Compare, StateData#state.users), - case higher_presence(P1, HighestPresence) of - true -> - {Compare, P1}; - false -> - {HighestUser, HighestPresence} - end - end, {FirstUser, FirstPresence}, Users), - jlib:make_jid(LJID); - error -> - false + 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) -> @@ -1798,1301 +1778,1357 @@ higher_presence(Pres1, Pres2) -> Pri1 > Pri2. get_priority_from_presence(PresencePacket) -> - case xml:get_subtag(PresencePacket, "priority") of - false -> - 0; - SubEl -> - case catch list_to_integer(xml:get_tag_cdata(SubEl)) of - P when is_integer(P) -> - P; - _ -> - 0 - end + case xml:get_subtag(PresencePacket, <<"priority">>) of + false -> 0; + SubEl -> + case catch + jlib:binary_to_integer(xml:get_tag_cdata(SubEl)) + of + P when is_integer(P) -> P; + _ -> 0 + end end. find_nick_by_jid(Jid, StateData) -> - [{_, #user{nick = Nick}}] = lists:filter( - fun({_, #user{jid = FJid}}) -> FJid == Jid end, - ?DICT:to_list(StateData#state.users)), - Nick. + [{_, #user{nick = Nick}}] = lists:filter(fun ({_, + #user{jid = FJid}}) -> + FJid == Jid + end, + (?DICT):to_list(StateData#state.users)), + Nick. is_nick_change(JID, Nick, StateData) -> LJID = jlib:jid_tolower(JID), case Nick of - "" -> - false; - _ -> - {ok, #user{nick = OldNick}} = - ?DICT:find(LJID, StateData#state.users), - Nick /= OldNick + <<"">> -> 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), - %% if nick is not used, or is used by another resource of the same - %% user, it's ok. UserOfNick /= false andalso - jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick)) /= - jlib:jid_remove_resource(jlib:jid_tolower(User)). + jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick)) + /= jlib:jid_remove_resource(jlib:jid_tolower(User)). -add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> - Lang = xml:get_attr_s("xml:lang", Attrs), +add_new_user(From, Nick, + #xmlel{attrs = Attrs, children = Els} = Packet, + StateData) -> + Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), MaxUsers = get_max_users(StateData), - MaxAdminUsers = MaxUsers + get_max_users_admin_threshold(StateData), - NUsers = dict:fold(fun(_, _, Acc) -> Acc + 1 end, 0, + MaxAdminUsers = MaxUsers + + get_max_users_admin_threshold(StateData), + NUsers = dict:fold(fun (_, _, Acc) -> Acc + 1 end, 0, StateData#state.users), Affiliation = get_affiliation(From, StateData), - ServiceAffiliation = get_service_affiliation(From, StateData), + ServiceAffiliation = get_service_affiliation(From, + StateData), NConferences = tab_count_user(From), - MaxConferences = gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_user_conferences, 10), + MaxConferences = + gen_mod:get_module_opt(StateData#state.server_host, + mod_muc, max_user_conferences, + fun(I) when is_integer(I), I>0 -> I end, + 10), Collision = nick_collision(From, Nick, StateData), case {(ServiceAffiliation == owner orelse - ((Affiliation == admin orelse Affiliation == owner) andalso - NUsers < MaxAdminUsers) orelse - NUsers < MaxUsers) andalso - NConferences < MaxConferences, + (Affiliation == admin orelse Affiliation == owner) + andalso NUsers < MaxAdminUsers + orelse NUsers < MaxUsers) + andalso NConferences < MaxConferences, Collision, - mod_muc:can_use_nick( - StateData#state.server_host, - StateData#state.host, From, Nick), - get_default_role(Affiliation, StateData)} of - {false, _, _, _} -> - % max user reached and user is not admin or owner - 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( - % TODO: s/Nick/""/ - 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( - % TODO: s/Nick/""/ - 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 = {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], - [{xmlcdata, translate:translate( - Lang, - "This room is not anonymous")}]}, - {xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "status", [{"code", "100"}], []}]}]}, - 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 = {xmlelement, "message", [{"id", ID}], 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 + 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. -check_password(owner, _Affiliation, _Els, _From, _StateData) -> +check_password(owner, _Affiliation, _Els, _From, + _StateData) -> %% Don't check pass if user is owner in MUC service (access_admin option) true; -check_password(_ServiceAffiliation, Affiliation, Els, From, StateData) -> - case (StateData#state.config)#config.password_protected of - false -> - check_captcha(Affiliation, From, StateData); - true -> - Pass = extract_password(Els), - case Pass of - false -> - nopass; - _ -> - case (StateData#state.config)#config.password of - Pass -> - true; - _ -> - false - end - end +check_password(_ServiceAffiliation, Affiliation, Els, + From, StateData) -> + case (StateData#state.config)#config.password_protected + of + false -> check_captcha(Affiliation, From, StateData); + true -> + Pass = extract_password(Els), + case Pass of + false -> nopass; + _ -> + case (StateData#state.config)#config.password of + Pass -> true; + _ -> false + end + end end. check_captcha(Affiliation, From, StateData) -> case (StateData#state.config)#config.captcha_protected - andalso ejabberd_captcha:is_feature_available() of - true when Affiliation == none -> - case ?DICT:find(From, StateData#state.robots) of - {ok, passed} -> - true; - _ -> - WList = (StateData#state.config)#config.captcha_whitelist, - #jid{luser = U, lserver = S, lresource = R} = From, - case ?SETS:is_element({U, S, R}, WList) of - true -> - true; - false -> - case ?SETS:is_element({U, S, ""}, WList) of - true -> - true; - false -> - case ?SETS:is_element({"", S, ""}, WList) of - true -> - true; - false -> - captcha_required - end - end - end - end; - _ -> - true + andalso ejabberd_captcha:is_feature_available() + of + true when Affiliation == none -> + case (?DICT):find(From, StateData#state.robots) of + {ok, passed} -> true; + _ -> + WList = + (StateData#state.config)#config.captcha_whitelist, + #jid{luser = U, lserver = S, lresource = R} = From, + case (?SETS):is_element({U, S, R}, WList) of + true -> true; + false -> + case (?SETS):is_element({U, S, <<"">>}, WList) of + true -> true; + false -> + case (?SETS):is_element({<<"">>, S, <<"">>}, WList) + of + true -> true; + false -> captcha_required + end + end + end + end; + _ -> true end. -extract_password([]) -> - false; -extract_password([{xmlelement, _Name, Attrs, _SubEls} = 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) +extract_password([]) -> false; +extract_password([#xmlel{attrs = Attrs} = El | Els]) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_MUC -> + case xml:get_subtag(El, <<"password">>) of + false -> false; + SubEl -> xml:get_tag_cdata(SubEl) + end; + _ -> extract_password(Els) end; -extract_password([_ | Els]) -> - extract_password(Els). +extract_password([_ | Els]) -> extract_password(Els). count_stanza_shift(Nick, Els, StateData) -> HL = lqueue_to_list(StateData#state.history), - Since = extract_history(Els, "since"), + Since = extract_history(Els, <<"since">>), Shift0 = case Since of - false -> - 0; - _ -> - Sin = calendar:datetime_to_gregorian_seconds(Since), - count_seconds_shift(Sin, HL) + false -> 0; + _ -> + Sin = calendar:datetime_to_gregorian_seconds(Since), + count_seconds_shift(Sin, HL) end, - Seconds = extract_history(Els, "seconds"), + Seconds = extract_history(Els, <<"seconds">>), Shift1 = case Seconds of - false -> - 0; - _ -> - Sec = calendar:datetime_to_gregorian_seconds( - calendar:now_to_universal_time(now())) - Seconds, - count_seconds_shift(Sec, HL) + false -> 0; + _ -> + Sec = + calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(now())) + - Seconds, + count_seconds_shift(Sec, HL) end, - MaxStanzas = extract_history(Els, "maxstanzas"), + MaxStanzas = extract_history(Els, <<"maxstanzas">>), Shift2 = case MaxStanzas of - false -> - 0; - _ -> - count_maxstanzas_shift(MaxStanzas, HL) + false -> 0; + _ -> count_maxstanzas_shift(MaxStanzas, HL) end, - MaxChars = extract_history(Els, "maxchars"), + MaxChars = extract_history(Els, <<"maxchars">>), Shift3 = case MaxChars of - false -> - 0; - _ -> - count_maxchars_shift(Nick, MaxChars, HL) + false -> 0; + _ -> count_maxchars_shift(Nick, MaxChars, HL) end, lists:max([Shift0, Shift1, Shift2, Shift3]). count_seconds_shift(Seconds, HistoryList) -> - lists:sum( - lists:map( - fun({_Nick, _Packet, _HaveSubject, TimeStamp, _Size}) -> - T = calendar:datetime_to_gregorian_seconds(TimeStamp), - if - T < Seconds -> - 1; - true -> - 0 - end - end, HistoryList)). + lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject, + TimeStamp, _Size}) -> + T = + calendar:datetime_to_gregorian_seconds(TimeStamp), + if T < Seconds -> 1; + true -> 0 + end + end, + HistoryList)). count_maxstanzas_shift(MaxStanzas, HistoryList) -> S = length(HistoryList) - MaxStanzas, - if - S =< 0 -> - 0; - true -> - S + if S =< 0 -> 0; + true -> S end. count_maxchars_shift(Nick, MaxSize, HistoryList) -> - NLen = string:len(Nick) + 1, - Sizes = lists:map( - fun({_Nick, _Packet, _HaveSubject, _TimeStamp, Size}) -> - Size + NLen - end, HistoryList), + NLen = byte_size(Nick) + 1, + Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject, + _TimeStamp, Size}) -> + Size + NLen + end, + HistoryList), calc_shift(MaxSize, Sizes). calc_shift(MaxSize, Sizes) -> Total = lists:sum(Sizes), calc_shift(MaxSize, Total, 0, Sizes). -calc_shift(_MaxSize, _Size, Shift, []) -> - Shift; +calc_shift(_MaxSize, _Size, Shift, []) -> Shift; calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> - if - MaxSize >= Size -> - Shift; - true -> - calc_shift(MaxSize, Size - S, Shift + 1, TSizes) + if MaxSize >= Size -> Shift; + true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes) end. -extract_history([], _Type) -> - false; -extract_history([{xmlelement, _Name, Attrs, _SubEls} = 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 list_to_integer(AttrVal) of - IntVal when is_integer(IntVal) and (IntVal >= 0) -> - IntVal; - _ -> - false - end - end; - _ -> - extract_history(Els, Type) +extract_history([], _Type) -> false; +extract_history([#xmlel{attrs = Attrs} = El | Els], + Type) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_MUC -> + AttrVal = xml:get_path_s(El, + [{elem, <<"history">>}, {attr, Type}]), + case Type of + <<"since">> -> + case jlib:datetime_string_to_timestamp(AttrVal) of + undefined -> false; + TS -> calendar:now_to_universal_time(TS) + end; + _ -> + case catch jlib:binary_to_integer(AttrVal) of + IntVal when is_integer(IntVal) and (IntVal >= 0) -> + IntVal; + _ -> false + end + end; + _ -> extract_history(Els, Type) end; extract_history([_ | Els], Type) -> extract_history(Els, Type). - send_update_presence(JID, StateData) -> - send_update_presence(JID, "", StateData). + send_update_presence(JID, <<"">>, StateData). send_update_presence(JID, Reason, StateData) -> LJID = jlib:jid_tolower(JID), LJIDs = case LJID of - {U, S, ""} -> - ?DICT:fold( - fun(J, _, Js) -> - case J of - {U, S, _} -> - [J | Js]; - _ -> - Js - end - end, [], StateData#state.users); - _ -> - case ?DICT:is_key(LJID, StateData#state.users) of - true -> - [LJID]; - _ -> - [] - end + {U, S, <<"">>} -> + (?DICT):fold(fun (J, _, Js) -> + case J of + {U, S, _} -> [J | Js]; + _ -> Js + end + end, + [], StateData#state.users); + _ -> + case (?DICT):is_key(LJID, StateData#state.users) of + true -> [LJID]; + _ -> [] + end end, - lists:foreach(fun(J) -> + lists:foreach(fun (J) -> send_new_presence(J, Reason, StateData) - end, LJIDs). + end, + LJIDs). send_new_presence(NJID, StateData) -> - send_new_presence(NJID, "", StateData). + send_new_presence(NJID, <<"">>, StateData). send_new_presence(NJID, Reason, StateData) -> - %% First, find the nick associated with this JID. - #user{nick = Nick} = ?DICT:fetch(jlib:jid_tolower(NJID), StateData#state.users), - %% Then find the JID using this nick with highest priority. + #user{nick = Nick} = + (?DICT):fetch(jlib:jid_tolower(NJID), + StateData#state.users), LJID = find_jid_by_nick(Nick, StateData), - %% Then we get the presence data we're supposed to send. - {ok, #user{jid = RealJID, - role = Role, - last_presence = Presence}} = - ?DICT:find(jlib:jid_tolower(LJID), StateData#state.users), + {ok, + #user{jid = RealJID, role = Role, + last_presence = Presence}} = + (?DICT):find(jlib:jid_tolower(LJID), + StateData#state.users), Affiliation = get_affiliation(LJID, StateData), SAffiliation = affiliation_to_list(Affiliation), SRole = role_to_list(Role), - lists:foreach( - fun({_LJID, Info}) -> - ItemAttrs = - case (Info#user.role == moderator) orelse - ((StateData#state.config)#config.anonymous == false) of - true -> - [{"jid", jlib:jid_to_string(RealJID)}, - {"affiliation", SAffiliation}, - {"role", SRole}]; - _ -> - [{"affiliation", SAffiliation}, - {"role", SRole}] + 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, - ItemEls = case Reason of - "" -> - []; - _ -> - [{xmlelement, "reason", [], - [{xmlcdata, Reason}]}] - end, - Status = case StateData#state.just_created of - true -> - [{xmlelement, "status", [{"code", "201"}], []}]; - false -> - [] - end, - Status2 = case ((StateData#state.config)#config.anonymous==false) - andalso (NJID == Info#user.jid) of - true -> - [{xmlelement, "status", [{"code", "100"}], []} - | Status]; - false -> - Status - end, - Status3 = case NJID == Info#user.jid of - true -> - [{xmlelement, "status", [{"code", "110"}], []} - | Status2]; - false -> - Status2 - end, - Packet = xml:append_subtags( - Presence, - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, ItemEls} | Status3]}]), - route_stanza( - jlib:jid_replace_resource(StateData#state.jid, Nick), - Info#user.jid, - Packet) - end, ?DICT:to_list(StateData#state.users)). - + (?DICT):to_list(StateData#state.users)). send_existing_presences(ToJID, StateData) -> LToJID = jlib:jid_tolower(ToJID), - {ok, #user{jid = RealToJID, - role = Role}} = - ?DICT:find(LToJID, StateData#state.users), - lists:foreach( - fun({FromNick, _Users}) -> - LJID = find_jid_by_nick(FromNick, StateData), - #user{jid = FromJID, - role = FromRole, - last_presence = Presence - } = ?DICT:fetch(jlib:jid_tolower(LJID), StateData#state.users), - case RealToJID of - FromJID -> - ok; - _ -> - FromAffiliation = get_affiliation(LJID, StateData), - ItemAttrs = - case (Role == moderator) orelse - ((StateData#state.config)#config.anonymous == - false) of - true -> - [{"jid", jlib:jid_to_string(FromJID)}, - {"affiliation", - affiliation_to_list(FromAffiliation)}, - {"role", role_to_list(FromRole)}]; - _ -> - [{"affiliation", - affiliation_to_list(FromAffiliation)}, - {"role", role_to_list(FromRole)}] - end, - Packet = xml:append_subtags( - Presence, - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, []}]}]), - route_stanza( - jlib:jid_replace_resource( - StateData#state.jid, FromNick), - RealToJID, - Packet) - end - end, ?DICT:to_list(StateData#state.nicks)). - + {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}) -> - (MSec*1000000 + Sec)*1000000 + USec. - + (MSec * 1000000 + Sec) * 1000000 + USec. change_nick(JID, Nick, StateData) -> LJID = jlib:jid_tolower(JID), - {ok, #user{nick = OldNick}} = - ?DICT:find(LJID, StateData#state.users), - Users = - ?DICT:update( - LJID, - fun(#user{} = User) -> - User#user{nick = Nick} - end, StateData#state.users), - OldNickUsers = ?DICT:fetch(OldNick, StateData#state.nicks), - NewNickUsers = case ?DICT:find(Nick, StateData#state.nicks) of - {ok, U} -> U; - error -> [] + {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, - %% Send unavailable presence from the old nick if it's no longer - %% used. SendOldUnavailable = length(OldNickUsers) == 1, - %% If we send unavailable presence from the old nick, we should - %% probably send presence from the new nick, in order not to - %% confuse clients. Otherwise, do it only if the new nick was - %% unused. SendNewAvailable = SendOldUnavailable orelse - NewNickUsers == [], - Nicks = - case OldNickUsers of - [LJID] -> - ?DICT:store(Nick, [LJID|NewNickUsers], - ?DICT:erase(OldNick, StateData#state.nicks)); - [_|_] -> - ?DICT:store(Nick, [LJID|NewNickUsers], - ?DICT:store(OldNick, OldNickUsers -- [LJID], - StateData#state.nicks)) - end, - NewStateData = StateData#state{users = Users, nicks = Nicks}, - send_nick_changing(JID, OldNick, NewStateData, SendOldUnavailable, SendNewAvailable), + NewNickUsers == [], + Nicks = case OldNickUsers of + [LJID] -> + (?DICT):store(Nick, [LJID | NewNickUsers], + (?DICT):erase(OldNick, StateData#state.nicks)); + [_ | _] -> + (?DICT):store(Nick, [LJID | NewNickUsers], + (?DICT):store(OldNick, OldNickUsers -- [LJID], + StateData#state.nicks)) + end, + NewStateData = StateData#state{users = Users, + nicks = Nicks}, + send_nick_changing(JID, OldNick, NewStateData, + SendOldUnavailable, SendNewAvailable), add_to_log(nickchange, {OldNick, Nick}, StateData), NewStateData. send_nick_changing(JID, OldNick, StateData, - SendOldUnavailable, SendNewAvailable) -> - {ok, #user{jid = RealJID, - nick = Nick, - role = Role, - last_presence = Presence}} = - ?DICT:find(jlib:jid_tolower(JID), StateData#state.users), + SendOldUnavailable, SendNewAvailable) -> + {ok, + #user{jid = RealJID, nick = Nick, role = Role, + last_presence = Presence}} = + (?DICT):find(jlib:jid_tolower(JID), + StateData#state.users), Affiliation = get_affiliation(JID, StateData), SAffiliation = affiliation_to_list(Affiliation), SRole = role_to_list(Role), - lists:foreach( - fun({_LJID, Info}) -> - ItemAttrs1 = - case (Info#user.role == moderator) orelse - ((StateData#state.config)#config.anonymous == false) of - true -> - [{"jid", jlib:jid_to_string(RealJID)}, - {"affiliation", SAffiliation}, - {"role", SRole}, - {"nick", Nick}]; - _ -> - [{"affiliation", SAffiliation}, - {"role", SRole}, - {"nick", Nick}] - end, - ItemAttrs2 = - case (Info#user.role == moderator) orelse - ((StateData#state.config)#config.anonymous == false) of - true -> - [{"jid", jlib:jid_to_string(RealJID)}, - {"affiliation", SAffiliation}, - {"role", SRole}]; - _ -> - [{"affiliation", SAffiliation}, - {"role", SRole}] + 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, - Packet1 = - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs1, []}, - {xmlelement, "status", [{"code", "303"}], []}]}]}, - Packet2 = xml:append_subtags( - Presence, - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs2, []}]}]), - 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, ?DICT:to_list(StateData#state.users)). - + (?DICT):to_list(StateData#state.users)). lqueue_new(Max) -> - #lqueue{queue = queue:new(), - len = 0, - max = Max}. + #lqueue{queue = queue:new(), len = 0, max = Max}. -%% If the message queue limit is set to 0, do not store messages. -lqueue_in(_Item, LQ = #lqueue{max = 0}) -> - LQ; +lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ; %% Otherwise, rotate messages in the queue store. -lqueue_in(Item, #lqueue{queue = Q1, len = Len, max = Max}) -> +lqueue_in(Item, + #lqueue{queue = Q1, len = Len, max = Max}) -> Q2 = queue:in(Item, Q1), - if - Len >= Max -> - Q3 = lqueue_cut(Q2, Len - Max + 1), - #lqueue{queue = Q3, len = Max, max = Max}; - true -> - #lqueue{queue = Q2, len = Len + 1, max = Max} + if Len >= Max -> + Q3 = lqueue_cut(Q2, Len - Max + 1), + #lqueue{queue = Q3, len = Max, max = Max}; + true -> #lqueue{queue = Q2, len = Len + 1, max = Max} end. -lqueue_cut(Q, 0) -> - Q; +lqueue_cut(Q, 0) -> Q; lqueue_cut(Q, N) -> - {_, Q1} = queue:out(Q), - lqueue_cut(Q1, N - 1). + {_, 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) -> - 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 + 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()), - %% Chatroom history is stored as XMPP packets, so - %% the decision to include the original sender's JID or not is based on the - %% chatroom configuration when the message was originally sent. - %% Also, if the chatroom is anonymous, even moderators will not get the real JID - SenderJid = case ((StateData#state.config)#config.anonymous) of - true -> StateData#state.jid; - false -> FromJID - end, + SenderJid = case + (StateData#state.config)#config.anonymous + of + true -> StateData#state.jid; + false -> FromJID + end, TSPacket = xml:append_subtags(Packet, - [jlib:timestamp_to_xml(TimeStamp, utc, SenderJid, ""), - %% TODO: Delete the next line once XEP-0091 is Obsolete - jlib:timestamp_to_xml(TimeStamp)]), - SPacket = jlib:replace_from_to( - jlib:jid_replace_resource(StateData#state.jid, FromNick), - StateData#state.jid, - TSPacket), + [jlib:timestamp_to_xml(TimeStamp, utc, + SenderJid, <<"">>), + jlib:timestamp_to_xml(TimeStamp)]), + SPacket = + jlib:replace_from_to(jlib:jid_replace_resource(StateData#state.jid, + FromNick), + StateData#state.jid, TSPacket), Size = element_size(SPacket), - Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size}, + Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, + TimeStamp, Size}, StateData#state.history), add_to_log(text, {FromNick, Packet}, StateData), StateData#state{history = Q1}. send_history(JID, Shift, StateData) -> - lists:foldl( - fun({Nick, Packet, HaveSubject, _TimeStamp, _Size}, B) -> - 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))). - + 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 - "" -> - ok; - Nick -> - Subject = StateData#state.subject, - Packet = {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "subject", [], [{xmlcdata, Subject}]}, - {xmlelement, "body", [], - [{xmlcdata, - Nick ++ - translate:translate(Lang, - " has set the subject to: ") ++ - Subject}]}]}, - route_stanza( - StateData#state.jid, - JID, - Packet) + <<"">> -> 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) -> - case xml:get_subtag(Packet, "subject") of - false -> - false; - SubjEl -> - xml:get_tag_cdata(SubjEl) + case xml:get_subtag(Packet, <<"subject">>) of + false -> false; + SubjEl -> xml:get_tag_cdata(SubjEl) end. can_change_subject(Role, StateData) -> - case (StateData#state.config)#config.allow_change_subj of - true -> - (Role == moderator) orelse (Role == participant); - _ -> - Role == moderator + 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) -> - {xmlelement, _, _, Items} = SubEl, + #xmlel{children = Items} = SubEl, process_admin_items_set(From, Items, Lang, StateData); - process_iq_admin(From, get, Lang, SubEl, StateData) -> - case xml:get_subtag(SubEl, "item") of - false -> - {error, ?ERR_BAD_REQUEST}; - Item -> - FAffiliation = get_affiliation(From, StateData), - FRole = get_role(From, StateData), - case xml:get_tag_attr("role", Item) of - false -> - case xml:get_tag_attr("affiliation", Item) of - false -> - {error, ?ERR_BAD_REQUEST}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> - {error, ?ERR_BAD_REQUEST}; - SAffiliation -> - if - (FAffiliation == owner) or - (FAffiliation == admin) -> - Items = items_with_affiliation( - SAffiliation, StateData), - {result, Items, StateData}; - true -> - ErrText = "Administrator privileges required", - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end - end - end; - {value, StrRole} -> - case catch list_to_role(StrRole) of - {'EXIT', _} -> - {error, ?ERR_BAD_REQUEST}; - SRole -> - if - FRole == moderator -> - Items = items_with_role(SRole, StateData), - {result, Items, StateData}; - true -> - ErrText = "Moderator privileges required", - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + case xml:get_subtag(SubEl, <<"item">>) of + false -> {error, ?ERR_BAD_REQUEST}; + Item -> + FAffiliation = get_affiliation(From, StateData), + FRole = get_role(From, StateData), + case xml:get_tag_attr(<<"role">>, Item) of + false -> + case xml:get_tag_attr(<<"affiliation">>, Item) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, StrAffiliation} -> + case catch list_to_affiliation(StrAffiliation) of + {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; + SAffiliation -> + if (FAffiliation == owner) or + (FAffiliation == admin) -> + Items = items_with_affiliation(SAffiliation, + StateData), + {result, Items, StateData}; + true -> + ErrText = + <<"Administrator privileges required">>, + {error, ?ERRT_FORBIDDEN(Lang, ErrText)} end - end - end + end + end; + {value, StrRole} -> + case catch list_to_role(StrRole) of + {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; + SRole -> + if FRole == moderator -> + Items = items_with_role(SRole, StateData), + {result, Items, StateData}; + true -> + ErrText = <<"Moderator privileges required">>, + {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + end + end + end end. - items_with_role(SRole, StateData) -> - lists:map( - fun({_, U}) -> - user_to_item(U, StateData) - end, search_role(SRole, StateData)). + lists:map(fun ({_, U}) -> user_to_item(U, StateData) + end, + search_role(SRole, StateData)). items_with_affiliation(SAffiliation, StateData) -> - lists:map( - fun({JID, {Affiliation, Reason}}) -> - {xmlelement, "item", - [{"affiliation", affiliation_to_list(Affiliation)}, - {"jid", jlib:jid_to_string(JID)}], - [{xmlelement, "reason", [], [{xmlcdata, Reason}]}]}; - ({JID, Affiliation}) -> - {xmlelement, "item", - [{"affiliation", affiliation_to_list(Affiliation)}, - {"jid", jlib:jid_to_string(JID)}], - []} - end, search_affiliation(SAffiliation, StateData)). - -user_to_item(#user{role = Role, - nick = Nick, - jid = JID - }, StateData) -> + lists:map(fun ({JID, {Affiliation, Reason}}) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + affiliation_to_list(Affiliation)}, + {<<"jid">>, jlib:jid_to_string(JID)}], + children = + [#xmlel{name = <<"reason">>, attrs = [], + children = [{xmlcdata, Reason}]}]}; + ({JID, Affiliation}) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + affiliation_to_list(Affiliation)}, + {<<"jid">>, jlib:jid_to_string(JID)}], + children = []} + end, + search_affiliation(SAffiliation, StateData)). + +user_to_item(#user{role = Role, nick = Nick, jid = JID}, + StateData) -> Affiliation = get_affiliation(JID, StateData), - {xmlelement, "item", - [{"role", role_to_list(Role)}, - {"affiliation", affiliation_to_list(Affiliation)}, - {"nick", Nick}, - {"jid", jlib:jid_to_string(JID)}], - []}. + #xmlel{name = <<"item">>, + attrs = + [{<<"role">>, role_to_list(Role)}, + {<<"affiliation">>, affiliation_to_list(Affiliation)}, + {<<"nick">>, Nick}, + {<<"jid">>, jlib:jid_to_string(JID)}], + children = []}. search_role(Role, StateData) -> - lists:filter( - fun({_, #user{role = R}}) -> - Role == R - end, ?DICT:to_list(StateData#state.users)). + lists:filter(fun ({_, #user{role = R}}) -> Role == R + end, + (?DICT):to_list(StateData#state.users)). search_affiliation(Affiliation, StateData) -> - lists:filter( - fun({_, A}) -> - case A of - {A1, _Reason} -> - Affiliation == A1; - _ -> - Affiliation == A - end - end, ?DICT:to_list(StateData#state.affiliations)). - + lists:filter(fun ({_, A}) -> + case A of + {A1, _Reason} -> Affiliation == A1; + _ -> Affiliation == A + end + end, + (?DICT):to_list(StateData#state.affiliations)). process_admin_items_set(UJID, Items, Lang, StateData) -> UAffiliation = get_affiliation(UJID, StateData), URole = get_role(UJID, StateData), - case find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, []) of - {result, Res} -> - ?INFO_MSG("Processing MUC admin query from ~s in room ~s:~n ~p", - [jlib:jid_to_string(UJID), jlib:jid_to_string(StateData#state.jid), Res]), - NSD = - lists:foldl( - fun(E, SD) -> - case catch ( - case E of - {JID, affiliation, owner, _} - when (JID#jid.luser == "") -> - %% If the provided JID does not have username, - %% forget the affiliation completely - SD; - {JID, role, none, Reason} -> - catch send_kickban_presence( - 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 + 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. - -find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) -> +find_changed_items(_UJID, _UAffiliation, _URole, [], + _Lang, _StateData, Res) -> {result, Res}; -find_changed_items(UJID, UAffiliation, URole, [{xmlcdata, _} | Items], - Lang, StateData, Res) -> - find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res); find_changed_items(UJID, UAffiliation, URole, - [{xmlelement, "item", Attrs, _Els} = Item | Items], + [{xmlcdata, _} | Items], Lang, StateData, Res) -> + find_changed_items(UJID, UAffiliation, URole, Items, + Lang, StateData, Res); +find_changed_items(UJID, UAffiliation, URole, + [#xmlel{name = <<"item">>, attrs = Attrs} = Item + | Items], Lang, StateData, Res) -> - TJID = case xml:get_attr("jid", Attrs) of - {value, S} -> - case jlib:string_to_jid(S) of - error -> - ErrText = 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 = - 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 + TJID = case xml:get_attr(<<"jid">>, Attrs) of + {value, S} -> + case jlib:string_to_jid(S) of + error -> + ErrText = iolist_to_binary( + io_lib:format(translate:translate( + Lang, + <<"Jabber ID ~s is invalid">>), + [S])), + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; + J -> {value, [J]} + end; + _ -> + case xml:get_attr(<<"nick">>, Attrs) of + {value, N} -> + case find_jids_by_nick(N, StateData) of + false -> + ErrText = iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"Nickname ~s does not exist in the room">>), + [N])), + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; + J -> {value, J} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end end, case TJID of - {value, [JID|_]=JIDs} -> - TAffiliation = get_affiliation(JID, StateData), - TRole = get_role(JID, StateData), - case xml:get_attr("role", Attrs) of - false -> - case xml:get_attr("affiliation", Attrs) of - false -> - {error, ?ERR_BAD_REQUEST}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> - ErrText1 = - io_lib:format( - translate:translate( - Lang, - "Invalid affiliation: ~s"), - [StrAffiliation]), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)}; - SAffiliation -> - ServiceAf = get_service_affiliation(JID, StateData), - CanChangeRA = - case can_change_ra( - UAffiliation, URole, - TAffiliation, TRole, - affiliation, SAffiliation, - ServiceAf) of - nothing -> - nothing; - true -> - true; - check_owner -> - case search_affiliation( - owner, StateData) of - [{OJID, _}] -> - jlib:jid_remove_resource(OJID) /= - jlib:jid_tolower(jlib:jid_remove_resource(UJID)); - _ -> - true - end; - _ -> - false - end, - case CanChangeRA of - nothing -> - find_changed_items( - UJID, - UAffiliation, URole, - Items, Lang, StateData, - Res); - true -> - Reason = xml:get_path_s(Item, [{elem, "reason"}, cdata]), - MoreRes = [{jlib:jid_remove_resource(Jidx), affiliation, SAffiliation, Reason} || Jidx <- JIDs], - find_changed_items( - UJID, - UAffiliation, URole, - Items, Lang, StateData, - [MoreRes | Res]); - false -> - {error, ?ERR_NOT_ALLOWED} - end - end - end; - {value, StrRole} -> - case catch list_to_role(StrRole) of + {value, [JID | _] = JIDs} -> + TAffiliation = get_affiliation(JID, StateData), + TRole = get_role(JID, StateData), + case xml:get_attr(<<"role">>, Attrs) of + false -> + case xml:get_attr(<<"affiliation">>, Attrs) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, StrAffiliation} -> + case catch list_to_affiliation(StrAffiliation) of {'EXIT', _} -> - ErrText1 = - io_lib:format( - translate:translate( - Lang, - "Invalid role: ~s"), - [StrRole]), - {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)}; - SRole -> + ErrText1 = iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"Invalid affiliation: ~s">>), + [StrAffiliation])), + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)}; + SAffiliation -> ServiceAf = get_service_affiliation(JID, StateData), - CanChangeRA = - case can_change_ra( - UAffiliation, URole, - TAffiliation, TRole, - 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, + CanChangeRA = case can_change_ra(UAffiliation, + URole, + TAffiliation, + TRole, affiliation, + SAffiliation, + ServiceAf) + of + nothing -> nothing; + true -> true; + check_owner -> + case search_affiliation(owner, + StateData) + of + [{OJID, _}] -> + jlib:jid_remove_resource(OJID) + /= + jlib:jid_tolower(jlib:jid_remove_resource(UJID)); + _ -> true + end; + _ -> false + end, case CanChangeRA of - nothing -> - find_changed_items( - UJID, - UAffiliation, URole, - Items, Lang, StateData, - Res); - true -> - Reason = xml:get_path_s(Item, [{elem, "reason"}, cdata]), - MoreRes = [{Jidx, role, SRole, Reason} || Jidx <- JIDs], - find_changed_items( - UJID, - UAffiliation, URole, - Items, Lang, StateData, - [MoreRes | Res]); - _ -> - {error, ?ERR_NOT_ALLOWED} + nothing -> + find_changed_items(UJID, UAffiliation, URole, + Items, Lang, StateData, + Res); + true -> + Reason = xml:get_path_s(Item, + [{elem, <<"reason">>}, + cdata]), + MoreRes = [{jlib:jid_remove_resource(Jidx), + affiliation, SAffiliation, Reason} + || Jidx <- JIDs], + find_changed_items(UJID, UAffiliation, URole, + Items, Lang, StateData, + [MoreRes | Res]); + false -> {error, ?ERR_NOT_ALLOWED} end - end - end; - Err -> - Err + end + end; + {value, StrRole} -> + case catch list_to_role(StrRole) of + {'EXIT', _} -> + ErrText1 = iolist_to_binary( + io_lib:format(translate:translate( + Lang, + <<"Invalid role: ~s">>), + [StrRole])), + {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)}; + SRole -> + ServiceAf = get_service_affiliation(JID, StateData), + CanChangeRA = case can_change_ra(UAffiliation, URole, + TAffiliation, TRole, + role, SRole, ServiceAf) + of + nothing -> nothing; + true -> true; + check_owner -> + case search_affiliation(owner, + StateData) + of + [{OJID, _}] -> + jlib:jid_remove_resource(OJID) + /= + jlib:jid_tolower(jlib:jid_remove_resource(UJID)); + _ -> true + end; + _ -> false + end, + case CanChangeRA of + nothing -> + find_changed_items(UJID, UAffiliation, URole, Items, + Lang, StateData, Res); + true -> + Reason = xml:get_path_s(Item, + [{elem, <<"reason">>}, + cdata]), + MoreRes = [{Jidx, role, SRole, Reason} + || Jidx <- JIDs], + find_changed_items(UJID, UAffiliation, URole, Items, + Lang, StateData, + [MoreRes | Res]); + _ -> {error, ?ERR_NOT_ALLOWED} + end + end + end; + Err -> Err end; find_changed_items(_UJID, _UAffiliation, _URole, _Items, _Lang, _StateData, _Res) -> {error, ?ERR_BAD_REQUEST}. - -can_change_ra(_FAffiliation, _FRole, - owner, _TRole, +can_change_ra(_FAffiliation, _FRole, owner, _TRole, affiliation, owner, owner) -> %% A room owner tries to add as persistent owner a %% participant that is already owner because he is MUC admin true; -can_change_ra(_FAffiliation, _FRole, - _TAffiliation, _TRole, - _RoleorAffiliation, _Value, owner) -> +can_change_ra(_FAffiliation, _FRole, _TAffiliation, + _TRole, _RoleorAffiliation, _Value, owner) -> %% Nobody can decrease MUC admin's role/affiliation false; -can_change_ra(_FAffiliation, _FRole, - TAffiliation, _TRole, - affiliation, Value, _ServiceAf) - when (TAffiliation == Value) -> +can_change_ra(_FAffiliation, _FRole, TAffiliation, + _TRole, affiliation, Value, _ServiceAf) + when TAffiliation == Value -> nothing; -can_change_ra(_FAffiliation, _FRole, - _TAffiliation, TRole, - role, Value, _ServiceAf) - when (TRole == Value) -> +can_change_ra(_FAffiliation, _FRole, _TAffiliation, + TRole, role, Value, _ServiceAf) + when TRole == Value -> nothing; -can_change_ra(FAffiliation, _FRole, - outcast, _TRole, +can_change_ra(FAffiliation, _FRole, outcast, _TRole, affiliation, none, _ServiceAf) - when (FAffiliation == owner) or (FAffiliation == admin) -> + when (FAffiliation == owner) or + (FAffiliation == admin) -> true; -can_change_ra(FAffiliation, _FRole, - outcast, _TRole, +can_change_ra(FAffiliation, _FRole, outcast, _TRole, affiliation, member, _ServiceAf) - when (FAffiliation == owner) or (FAffiliation == admin) -> + when (FAffiliation == owner) or + (FAffiliation == admin) -> true; -can_change_ra(owner, _FRole, - outcast, _TRole, +can_change_ra(owner, _FRole, outcast, _TRole, affiliation, admin, _ServiceAf) -> true; -can_change_ra(owner, _FRole, - outcast, _TRole, +can_change_ra(owner, _FRole, outcast, _TRole, affiliation, owner, _ServiceAf) -> true; -can_change_ra(FAffiliation, _FRole, - none, _TRole, +can_change_ra(FAffiliation, _FRole, none, _TRole, affiliation, outcast, _ServiceAf) - when (FAffiliation == owner) or (FAffiliation == admin) -> + when (FAffiliation == owner) or + (FAffiliation == admin) -> true; -can_change_ra(FAffiliation, _FRole, - none, _TRole, +can_change_ra(FAffiliation, _FRole, none, _TRole, affiliation, member, _ServiceAf) - when (FAffiliation == owner) or (FAffiliation == admin) -> + when (FAffiliation == owner) or + (FAffiliation == admin) -> true; -can_change_ra(owner, _FRole, - none, _TRole, - affiliation, admin, _ServiceAf) -> +can_change_ra(owner, _FRole, none, _TRole, affiliation, + admin, _ServiceAf) -> true; -can_change_ra(owner, _FRole, - none, _TRole, - affiliation, owner, _ServiceAf) -> +can_change_ra(owner, _FRole, none, _TRole, affiliation, + owner, _ServiceAf) -> true; -can_change_ra(FAffiliation, _FRole, - member, _TRole, +can_change_ra(FAffiliation, _FRole, member, _TRole, affiliation, outcast, _ServiceAf) - when (FAffiliation == owner) or (FAffiliation == admin) -> + when (FAffiliation == owner) or + (FAffiliation == admin) -> true; -can_change_ra(FAffiliation, _FRole, - member, _TRole, +can_change_ra(FAffiliation, _FRole, member, _TRole, affiliation, none, _ServiceAf) - when (FAffiliation == owner) or (FAffiliation == admin) -> + when (FAffiliation == owner) or + (FAffiliation == admin) -> true; -can_change_ra(owner, _FRole, - member, _TRole, +can_change_ra(owner, _FRole, member, _TRole, affiliation, admin, _ServiceAf) -> true; -can_change_ra(owner, _FRole, - member, _TRole, +can_change_ra(owner, _FRole, member, _TRole, affiliation, owner, _ServiceAf) -> true; -can_change_ra(owner, _FRole, - admin, _TRole, - affiliation, _Affiliation, _ServiceAf) -> +can_change_ra(owner, _FRole, admin, _TRole, affiliation, + _Affiliation, _ServiceAf) -> true; -can_change_ra(owner, _FRole, - owner, _TRole, - affiliation, _Affiliation, _ServiceAf) -> +can_change_ra(owner, _FRole, owner, _TRole, affiliation, + _Affiliation, _ServiceAf) -> check_owner; -can_change_ra(_FAffiliation, _FRole, - _TAffiliation, _TRole, - affiliation, _Value, _ServiceAf) -> +can_change_ra(_FAffiliation, _FRole, _TAffiliation, + _TRole, affiliation, _Value, _ServiceAf) -> false; -can_change_ra(_FAffiliation, moderator, - _TAffiliation, visitor, - role, none, _ServiceAf) -> +can_change_ra(_FAffiliation, moderator, _TAffiliation, + visitor, role, none, _ServiceAf) -> true; -can_change_ra(_FAffiliation, moderator, - _TAffiliation, visitor, - role, participant, _ServiceAf) -> +can_change_ra(_FAffiliation, moderator, _TAffiliation, + visitor, role, participant, _ServiceAf) -> true; -can_change_ra(FAffiliation, _FRole, - _TAffiliation, visitor, - role, moderator, _ServiceAf) - when (FAffiliation == owner) or (FAffiliation == admin) -> +can_change_ra(FAffiliation, _FRole, _TAffiliation, + visitor, role, moderator, _ServiceAf) + when (FAffiliation == owner) or + (FAffiliation == admin) -> true; -can_change_ra(_FAffiliation, moderator, - _TAffiliation, participant, - role, none, _ServiceAf) -> +can_change_ra(_FAffiliation, moderator, _TAffiliation, + participant, role, none, _ServiceAf) -> true; -can_change_ra(_FAffiliation, moderator, - _TAffiliation, participant, - role, visitor, _ServiceAf) -> +can_change_ra(_FAffiliation, moderator, _TAffiliation, + participant, role, visitor, _ServiceAf) -> true; -can_change_ra(FAffiliation, _FRole, - _TAffiliation, participant, - role, moderator, _ServiceAf) - when (FAffiliation == owner) or (FAffiliation == admin) -> +can_change_ra(FAffiliation, _FRole, _TAffiliation, + participant, role, moderator, _ServiceAf) + when (FAffiliation == owner) or + (FAffiliation == admin) -> true; -can_change_ra(_FAffiliation, _FRole, - owner, moderator, +can_change_ra(_FAffiliation, _FRole, owner, moderator, role, visitor, _ServiceAf) -> false; -can_change_ra(owner, _FRole, - _TAffiliation, moderator, +can_change_ra(owner, _FRole, _TAffiliation, moderator, role, visitor, _ServiceAf) -> true; -can_change_ra(_FAffiliation, _FRole, - admin, moderator, +can_change_ra(_FAffiliation, _FRole, admin, moderator, role, visitor, _ServiceAf) -> false; -can_change_ra(admin, _FRole, - _TAffiliation, moderator, +can_change_ra(admin, _FRole, _TAffiliation, moderator, role, visitor, _ServiceAf) -> true; -can_change_ra(_FAffiliation, _FRole, - owner, moderator, +can_change_ra(_FAffiliation, _FRole, owner, moderator, role, participant, _ServiceAf) -> false; -can_change_ra(owner, _FRole, - _TAffiliation, moderator, +can_change_ra(owner, _FRole, _TAffiliation, moderator, role, participant, _ServiceAf) -> true; -can_change_ra(_FAffiliation, _FRole, - admin, moderator, +can_change_ra(_FAffiliation, _FRole, admin, moderator, role, participant, _ServiceAf) -> false; -can_change_ra(admin, _FRole, - _TAffiliation, moderator, +can_change_ra(admin, _FRole, _TAffiliation, moderator, role, participant, _ServiceAf) -> true; -can_change_ra(_FAffiliation, _FRole, - _TAffiliation, _TRole, - role, _Value, _ServiceAf) -> +can_change_ra(_FAffiliation, _FRole, _TAffiliation, + _TRole, role, _Value, _ServiceAf) -> false. - send_kickban_presence(JID, Reason, Code, StateData) -> NewAffiliation = get_affiliation(JID, StateData), - send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData). + send_kickban_presence(JID, Reason, Code, NewAffiliation, + StateData). -send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData) -> +send_kickban_presence(JID, Reason, Code, NewAffiliation, + StateData) -> LJID = jlib:jid_tolower(JID), LJIDs = case LJID of - {U, S, ""} -> - ?DICT:fold( - fun(J, _, Js) -> - case J of - {U, S, _} -> - [J | Js]; - _ -> - Js - end - end, [], StateData#state.users); - _ -> - case ?DICT:is_key(LJID, StateData#state.users) of - true -> - [LJID]; - _ -> - [] - end + {U, S, <<"">>} -> + (?DICT):fold(fun (J, _, Js) -> + case J of + {U, S, _} -> [J | Js]; + _ -> Js + end + end, + [], StateData#state.users); + _ -> + case (?DICT):is_key(LJID, StateData#state.users) of + true -> [LJID]; + _ -> [] + end end, - lists:foreach(fun(J) -> - {ok, #user{nick = Nick}} = - ?DICT:find(J, StateData#state.users), + lists:foreach(fun (J) -> + {ok, #user{nick = Nick}} = (?DICT):find(J, + StateData#state.users), add_to_log(kickban, {Nick, Reason, Code}, StateData), tab_remove_online_user(J, StateData), - send_kickban_presence1(J, Reason, Code, NewAffiliation, StateData) - end, LJIDs). + send_kickban_presence1(J, Reason, Code, + NewAffiliation, StateData) + end, + LJIDs). -send_kickban_presence1(UJID, Reason, Code, Affiliation, StateData) -> - {ok, #user{jid = RealJID, - nick = Nick}} = - ?DICT:find(jlib:jid_tolower(UJID), StateData#state.users), +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), - 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 - "" -> - []; - _ -> - [{xmlelement, "reason", [], - [{xmlcdata, Reason}]}] - end, - Packet = {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, ItemEls}, - {xmlelement, "status", [{"code", Code}], []}]}]}, - route_stanza( - jlib:jid_replace_resource(StateData#state.jid, Nick), - Info#user.jid, - Packet) - end, ?DICT:to_list(StateData#state.users)). - - + 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 @@ -3100,601 +3136,813 @@ send_kickban_presence1(UJID, Reason, Code, Affiliation, StateData) -> process_iq_owner(From, set, Lang, SubEl, StateData) -> FAffiliation = get_affiliation(From, StateData), case FAffiliation of - owner -> - {xmlelement, _Name, _Attrs, Els} = SubEl, - case xml:remove_cdata(Els) of - [{xmlelement, "x", _Attrs1, _Els1} = 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; - [{xmlelement, "destroy", _Attrs1, _Els1} = 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)} + owner -> + #xmlel{children = Els} = SubEl, + case xml:remove_cdata(Els) of + [#xmlel{name = <<"x">>} = XEl] -> + case {xml:get_tag_attr_s(<<"xmlns">>, XEl), + xml:get_tag_attr_s(<<"type">>, XEl)} + of + {?NS_XDATA, <<"cancel">>} -> {result, [], StateData}; + {?NS_XDATA, <<"submit">>} -> + case is_allowed_log_change(XEl, StateData, From) andalso + is_allowed_persistent_change(XEl, StateData, From) + andalso + is_allowed_room_name_desc_limits(XEl, StateData) + andalso + is_password_settings_correct(XEl, StateData) + of + true -> set_config(XEl, StateData); + false -> {error, ?ERR_NOT_ACCEPTABLE} + end; + _ -> {error, ?ERR_BAD_REQUEST} + end; + [#xmlel{name = <<"destroy">>} = SubEl1] -> + ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", + [jlib:jid_to_string(StateData#state.jid), + jlib:jid_to_string(From)]), + add_to_log(room_existence, destroyed, StateData), + destroy_room(SubEl1, StateData); + Items -> + process_admin_items_set(From, Items, Lang, StateData) + end; + _ -> + ErrText = <<"Owner privileges required">>, + {error, ?ERRT_FORBIDDEN(Lang, ErrText)} end; - process_iq_owner(From, get, Lang, SubEl, StateData) -> FAffiliation = get_affiliation(From, StateData), case FAffiliation of - owner -> - {xmlelement, _Name, _Attrs, 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 = - 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)} + owner -> + #xmlel{children = Els} = SubEl, + case xml:remove_cdata(Els) of + [] -> get_config(Lang, StateData, From); + [Item] -> + case xml:get_tag_attr(<<"affiliation">>, Item) of + false -> {error, ?ERR_BAD_REQUEST}; + {value, StrAffiliation} -> + case catch list_to_affiliation(StrAffiliation) of + {'EXIT', _} -> + ErrText = iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"Invalid affiliation: ~s">>), + [StrAffiliation])), + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; + SAffiliation -> + Items = items_with_affiliation(SAffiliation, + StateData), + {result, Items, StateData} + end + end; + _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} + end; + _ -> + ErrText = <<"Owner privileges required">>, + {error, ?ERRT_FORBIDDEN(Lang, ErrText)} end. is_allowed_log_change(XEl, StateData, From) -> - case lists:keymember("muc#roomconfig_enablelogging", 1, - jlib:parse_xdata_submit(XEl)) of - false -> - true; - true -> - (allow == mod_muc_log:check_access_log( - StateData#state.server_host, From)) + case lists:keymember(<<"muc#roomconfig_enablelogging">>, + 1, jlib:parse_xdata_submit(XEl)) + of + false -> true; + true -> + allow == + mod_muc_log:check_access_log(StateData#state.server_host, + From) end. is_allowed_persistent_change(XEl, StateData, From) -> - case lists:keymember("muc#roomconfig_persistentroom", 1, - jlib:parse_xdata_submit(XEl)) of - false -> - true; - true -> - {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access, - (allow == acl:match_rule(StateData#state.server_host, AccessPersistent, From)) + case + lists:keymember(<<"muc#roomconfig_persistentroom">>, 1, + jlib:parse_xdata_submit(XEl)) + of + false -> true; + true -> + {_AccessRoute, _AccessCreate, _AccessAdmin, + AccessPersistent} = + StateData#state.access, + allow == + acl:match_rule(StateData#state.server_host, + AccessPersistent, From) end. -%% Check if the Room Name and Room Description defined in the Data Form -%% are conformant to the configured limits is_allowed_room_name_desc_limits(XEl, StateData) -> - IsNameAccepted = - case lists:keysearch("muc#roomconfig_roomname", 1, - jlib:parse_xdata_submit(XEl)) of - {value, {_, [N]}} -> - length(N) =< gen_mod:get_module_opt(StateData#state.server_host, + 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, - infinite); - _ -> - true - end, - IsDescAccepted = - case lists:keysearch("muc#roomconfig_roomdesc", 1, - jlib:parse_xdata_submit(XEl)) of - {value, {_, [D]}} -> - length(D) =< gen_mod:get_module_opt(StateData#state.server_host, + 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, - infinite); - _ -> - true - end, + fun(infinity) -> infinity; + (I) when is_integer(I), + I>0 -> + I + end, infinity); + _ -> true + end, IsNameAccepted and IsDescAccepted. -%% Return false if: -%% "the password for a password-protected room is blank" is_password_settings_correct(XEl, StateData) -> Config = StateData#state.config, OldProtected = Config#config.password_protected, OldPassword = Config#config.password, - NewProtected = - case lists:keysearch("muc#roomconfig_passwordprotectedroom", 1, - jlib:parse_xdata_submit(XEl)) of - {value, {_, ["1"]}} -> - true; - {value, {_, ["0"]}} -> - false; - _ -> - undefined - end, - NewPassword = - case lists:keysearch("muc#roomconfig_roomsecret", 1, - jlib:parse_xdata_submit(XEl)) of - {value, {_, [P]}} -> - P; - _ -> - undefined - end, - case {OldProtected, NewProtected, OldPassword, NewPassword} of - {true, undefined, "", undefined} -> - false; - {true, undefined, _, ""} -> - false; - {_, true , "", undefined} -> - false; - {_, true, _, ""} -> - false; - _ -> - true + 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), - {xmlelement, "field", [{"type", Type}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, Type}, + {<<"label">>, translate:translate(Lang, Label)}, + {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]}]}). -define(BOOLXFIELD(Label, Var, Val), - ?XFIELD("boolean", Label, Var, + ?XFIELD(<<"boolean">>, Label, Var, case Val of - true -> "1"; - _ -> "0" + true -> <<"1">>; + _ -> <<"0">> end)). -define(STRINGXFIELD(Label, Var, Val), - ?XFIELD("text-single", Label, Var, Val)). + ?XFIELD(<<"text-single">>, Label, Var, Val)). -define(PRIVATEXFIELD(Label, Var, Val), - ?XFIELD("text-private", Label, Var, Val)). + ?XFIELD(<<"text-private">>, Label, Var, Val)). -define(JIDMULTIXFIELD(Label, Var, JIDList), - {xmlelement, "field", [{"type", "jid-multi"}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, jlib:jid_to_string(JID)}]} - || JID <- JIDList]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"jid-multi">>}, + {<<"label">>, translate:translate(Lang, Label)}, + {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, jlib:jid_to_string(JID)}]} + || JID <- JIDList]}). get_default_room_maxusers(RoomState) -> - DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, mod_muc, default_room_options, []), + DefRoomOpts = + gen_mod:get_module_opt(RoomState#state.server_host, + mod_muc, default_room_options, + fun(L) when is_list(L) -> L end, + []), RoomState2 = set_opts(DefRoomOpts, RoomState), (RoomState2#state.config)#config.max_users. get_config(Lang, StateData, From) -> - {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access, + {_AccessRoute, _AccessCreate, _AccessAdmin, + AccessPersistent} = + StateData#state.access, ServiceMaxUsers = get_service_max_users(StateData), - DefaultRoomMaxUsers = get_default_room_maxusers(StateData), + DefaultRoomMaxUsers = + get_default_room_maxusers(StateData), Config = StateData#state.config, - {MaxUsersRoomInteger, MaxUsersRoomString} = - case get_max_users(StateData) of - N when is_integer(N) -> - {N, erlang:integer_to_list(N)}; - _ -> {0, "none"} - end, - Res = - [{xmlelement, "title", [], - [{xmlcdata, io_lib:format(translate:translate(Lang, "Configuration of room ~s"), [jlib:jid_to_string(StateData#state.jid)])}]}, - {xmlelement, "field", [{"type", "hidden"}, - {"var", "FORM_TYPE"}], - [{xmlelement, "value", [], - [{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), - {xmlelement, "field", - [{"type", "list-single"}, - {"label", translate:translate(Lang, "Maximum Number of Occupants")}, - {"var", "muc#roomconfig_maxusers"}], - [{xmlelement, "value", [], [{xmlcdata, MaxUsersRoomString}]}] ++ - if - is_integer(ServiceMaxUsers) -> []; - true -> - [{xmlelement, "option", - [{"label", translate:translate(Lang, "No limit")}], - [{xmlelement, "value", [], [{xmlcdata, "none"}]}]}] - end ++ - [{xmlelement, "option", [{"label", erlang:integer_to_list(N)}], - [{xmlelement, "value", [], - [{xmlcdata, erlang:integer_to_list(N)}]}]} || - N <- lists:usort([ServiceMaxUsers, DefaultRoomMaxUsers, MaxUsersRoomInteger | - ?MAX_USERS_DEFAULT_LIST]), N =< ServiceMaxUsers] - }, - {xmlelement, "field", - [{"type", "list-single"}, - {"label", translate:translate(Lang, "Present real Jabber IDs to")}, - {"var", "muc#roomconfig_whois"}], - [{xmlelement, "value", [], [{xmlcdata, - if Config#config.anonymous -> - "moderators"; - true -> - "anyone" - end}]}, - {xmlelement, "option", [{"label", translate:translate(Lang, "moderators only")}], - [{xmlelement, "value", [], [{xmlcdata, "moderators"}]}]}, - {xmlelement, "option", [{"label", translate:translate(Lang, "anyone")}], - [{xmlelement, "value", [], [{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), - {xmlelement, "field", - [{"type", "list-single"}, - {"label", translate:translate(Lang, "Allow visitors to send private messages to")}, - {"var", "allow_private_messages_from_visitors"}], - [{xmlelement, "value", [], [{xmlcdata, - case Config#config.allow_private_messages_from_visitors of - anyone -> - "anyone"; - moderators -> - "moderators"; - nobody -> - "nobody" - end}]}, - {xmlelement, "option", [{"label", translate:translate(Lang, "nobody")}], - [{xmlelement, "value", [], [{xmlcdata, "nobody"}]}]}, - {xmlelement, "option", [{"label", translate:translate(Lang, "moderators only")}], - [{xmlelement, "value", [], [{xmlcdata, "moderators"}]}]}, - {xmlelement, "option", [{"label", translate:translate(Lang, "anyone")}], - [{xmlelement, "value", [], [{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", - erlang:integer_to_list(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, [{xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "You need an x:data capable client to configure room")}]}, - {xmlelement, "x", [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - Res}], + {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 - 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 + invalid -> {error, ?ERR_BAD_REQUEST}; + _ -> + case set_xoption(XData, StateData#state.config) of + #config{} = Config -> + Res = change_config(Config, StateData), + {result, _, NSD} = Res, + Type = case {(StateData#state.config)#config.logging, + Config#config.logging} + of + {true, false} -> roomconfig_change_disabledlogging; + {false, true} -> roomconfig_change_enabledlogging; + {_, _} -> roomconfig_change + end, + Users = [{U#user.jid, U#user.nick, U#user.role} + || {_, U} <- (?DICT):to_list(StateData#state.users)], + add_to_log(Type, Users, NSD), + Res; + Err -> Err + end end. -define(SET_BOOL_XOPT(Opt, Val), case Val of - "0" -> set_xoption(Opts, Config#config{Opt = false}); - "false" -> set_xoption(Opts, Config#config{Opt = false}); - "1" -> set_xoption(Opts, Config#config{Opt = true}); - "true" -> set_xoption(Opts, Config#config{Opt = true}); - _ -> {error, ?ERR_BAD_REQUEST} + <<"0">> -> + set_xoption(Opts, Config#config{Opt = false}); + <<"false">> -> + set_xoption(Opts, Config#config{Opt = false}); + <<"1">> -> set_xoption(Opts, Config#config{Opt = true}); + <<"true">> -> + set_xoption(Opts, Config#config{Opt = true}); + _ -> {error, ?ERR_BAD_REQUEST} end). -define(SET_NAT_XOPT(Opt, Val), - case catch list_to_integer(Val) of - I when is_integer(I), - I > 0 -> - set_xoption(Opts, Config#config{Opt = I}); - _ -> - {error, ?ERR_BAD_REQUEST} + case catch jlib:binary_to_integer(Val) of + I when is_integer(I), I > 0 -> + set_xoption(Opts, Config#config{Opt = I}); + _ -> {error, ?ERR_BAD_REQUEST} end). -define(SET_STRING_XOPT(Opt, Val), set_xoption(Opts, Config#config{Opt = Val})). -define(SET_JIDMULTI_XOPT(Opt, Vals), - begin - Set = lists:foldl( - fun({U, S, R}, Set1) -> - ?SETS:add_element({U, S, R}, Set1); - (#jid{luser = U, lserver = S, lresource = R}, Set1) -> - ?SETS:add_element({U, S, R}, Set1); - (_, Set1) -> - Set1 - end, ?SETS:empty(), Vals), - set_xoption(Opts, Config#config{Opt = Set}) - end). - -set_xoption([], Config) -> - Config; -set_xoption([{"muc#roomconfig_roomname", [Val]} | Opts], Config) -> + begin + Set = lists:foldl(fun ({U, S, R}, Set1) -> + (?SETS):add_element({U, S, R}, Set1); + (#jid{luser = U, lserver = S, lresource = R}, + Set1) -> + (?SETS):add_element({U, S, R}, Set1); + (_, Set1) -> Set1 + end, + (?SETS):empty(), Vals), + set_xoption(Opts, Config#config{Opt = Set}) + end). + +set_xoption([], Config) -> Config; +set_xoption([{<<"muc#roomconfig_roomname">>, [Val]} + | Opts], + Config) -> ?SET_STRING_XOPT(title, Val); -set_xoption([{"muc#roomconfig_roomdesc", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]} + | Opts], + Config) -> ?SET_STRING_XOPT(description, Val); -set_xoption([{"muc#roomconfig_changesubject", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(allow_change_subj, Val); -set_xoption([{"allow_query_users", [Val]} | Opts], Config) -> +set_xoption([{<<"allow_query_users">>, [Val]} | Opts], + Config) -> ?SET_BOOL_XOPT(allow_query_users, Val); -set_xoption([{"allow_private_messages", [Val]} | Opts], Config) -> +set_xoption([{<<"allow_private_messages">>, [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(allow_private_messages, Val); -set_xoption([{"allow_private_messages_from_visitors", [Val]} | Opts], Config) -> +set_xoption([{<<"allow_private_messages_from_visitors">>, + [Val]} + | Opts], + Config) -> case Val of - "anyone" -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, anyone); - "moderators" -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, moderators); - "nobody" -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, nobody); - _ -> - {error, ?ERR_BAD_REQUEST} + <<"anyone">> -> + ?SET_STRING_XOPT(allow_private_messages_from_visitors, + anyone); + <<"moderators">> -> + ?SET_STRING_XOPT(allow_private_messages_from_visitors, + moderators); + <<"nobody">> -> + ?SET_STRING_XOPT(allow_private_messages_from_visitors, + nobody); + _ -> {error, ?ERR_BAD_REQUEST} end; -set_xoption([{"muc#roomconfig_allowvisitorstatus", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>, + [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(allow_visitor_status, Val); -set_xoption([{"muc#roomconfig_allowvisitornickchange", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>, + [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(allow_visitor_nickchange, Val); -set_xoption([{"muc#roomconfig_publicroom", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(public, Val); -set_xoption([{"public_list", [Val]} | Opts], Config) -> +set_xoption([{<<"public_list">>, [Val]} | Opts], + Config) -> ?SET_BOOL_XOPT(public_list, Val); -set_xoption([{"muc#roomconfig_persistentroom", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_persistentroom">>, + [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(persistent, Val); -set_xoption([{"muc#roomconfig_moderatedroom", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(moderated, Val); -set_xoption([{"members_by_default", [Val]} | Opts], Config) -> +set_xoption([{<<"members_by_default">>, [Val]} | Opts], + Config) -> ?SET_BOOL_XOPT(members_by_default, Val); -set_xoption([{"muc#roomconfig_membersonly", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(members_only, Val); -set_xoption([{"captcha_protected", [Val]} | Opts], Config) -> +set_xoption([{<<"captcha_protected">>, [Val]} | Opts], + Config) -> ?SET_BOOL_XOPT(captcha_protected, Val); -set_xoption([{"muc#roomconfig_allowinvites", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(allow_user_invites, Val); -set_xoption([{"muc#roomconfig_passwordprotectedroom", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>, + [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(password_protected, Val); -set_xoption([{"muc#roomconfig_roomsecret", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]} + | Opts], + Config) -> ?SET_STRING_XOPT(password, Val); -set_xoption([{"anonymous", [Val]} | Opts], Config) -> +set_xoption([{<<"anonymous">>, [Val]} | Opts], + Config) -> ?SET_BOOL_XOPT(anonymous, Val); -set_xoption([{"muc#roomconfig_allowvoicerequests", [Val]} | Opts], Config) -> - ?SET_BOOL_XOPT(allow_voice_requests, Val); -set_xoption([{"muc#roomconfig_voicerequestmininterval", [Val]} | Opts], Config) -> - ?SET_NAT_XOPT(voice_request_min_interval, Val); -set_xoption([{"muc#roomconfig_whois", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_allowvoicerequests">>, + [Val]} + | Opts], + Config) -> + ?SET_BOOL_XOPT(allow_voice_requests, Val); +set_xoption([{<<"muc#roomconfig_voicerequestmininterval">>, + [Val]} + | Opts], + Config) -> + ?SET_NAT_XOPT(voice_request_min_interval, Val); +set_xoption([{<<"muc#roomconfig_whois">>, [Val]} + | Opts], + Config) -> case Val of - "moderators" -> - ?SET_BOOL_XOPT(anonymous, integer_to_list(1)); - "anyone" -> - ?SET_BOOL_XOPT(anonymous, integer_to_list(0)); - _ -> - {error, ?ERR_BAD_REQUEST} + <<"moderators">> -> + ?SET_BOOL_XOPT(anonymous, + (iolist_to_binary(integer_to_list(1)))); + <<"anyone">> -> + ?SET_BOOL_XOPT(anonymous, + (iolist_to_binary(integer_to_list(0)))); + _ -> {error, ?ERR_BAD_REQUEST} end; -set_xoption([{"muc#roomconfig_maxusers", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]} + | Opts], + Config) -> case Val of - "none" -> - ?SET_STRING_XOPT(max_users, none); - _ -> - ?SET_NAT_XOPT(max_users, Val) + <<"none">> -> ?SET_STRING_XOPT(max_users, none); + _ -> ?SET_NAT_XOPT(max_users, Val) end; -set_xoption([{"muc#roomconfig_enablelogging", [Val]} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]} + | Opts], + Config) -> ?SET_BOOL_XOPT(logging, Val); -set_xoption([{"muc#roomconfig_captcha_whitelist", Vals} | Opts], Config) -> +set_xoption([{<<"muc#roomconfig_captcha_whitelist">>, + Vals} + | Opts], + Config) -> JIDs = [jlib:string_to_jid(Val) || Val <- Vals], ?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs); -set_xoption([{"FORM_TYPE", _} | Opts], Config) -> - %% Ignore our FORM_TYPE +set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config) -> set_xoption(Opts, Config); set_xoption([_ | _Opts], _Config) -> {error, ?ERR_BAD_REQUEST}. - change_config(Config, StateData) -> NSD = StateData#state{config = Config}, case {(StateData#state.config)#config.persistent, - Config#config.persistent} of - {_, true} -> - mod_muc:store_room(NSD#state.server_host, NSD#state.host, - NSD#state.room, make_opts(NSD)); - {true, false} -> - mod_muc:forget_room(NSD#state.server_host, NSD#state.host, - NSD#state.room); - {false, false} -> - ok + Config#config.persistent} + of + {_, true} -> + mod_muc:store_room(NSD#state.server_host, + NSD#state.host, NSD#state.room, make_opts(NSD)); + {true, false} -> + mod_muc:forget_room(NSD#state.server_host, + NSD#state.host, NSD#state.room); + {false, false} -> ok end, case {(StateData#state.config)#config.members_only, - Config#config.members_only} of - {false, true} -> - NSD1 = remove_nonmembers(NSD), - {result, [], NSD1}; - _ -> - {result, [], NSD} + Config#config.members_only} + of + {false, true} -> + NSD1 = remove_nonmembers(NSD), {result, [], NSD1}; + _ -> {result, [], NSD} end. remove_nonmembers(StateData) -> - lists:foldl( - fun({_LJID, #user{jid = JID}}, SD) -> - Affiliation = get_affiliation(JID, SD), - case Affiliation of - none -> - catch send_kickban_presence( - JID, "", "322", SD), - set_role(JID, none, SD); - _ -> - SD - end - end, StateData, ?DICT:to_list(StateData#state.users)). - - --define(CASE_CONFIG_OPT(Opt), - Opt -> StateData#state{ - config = (StateData#state.config)#config{Opt = Val}}). + lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) -> + Affiliation = get_affiliation(JID, SD), + case Affiliation of + none -> + catch send_kickban_presence(JID, <<"">>, + <<"322">>, SD), + set_role(JID, none, SD); + _ -> SD + end + end, + StateData, (?DICT):to_list(StateData#state.users)). -set_opts([], StateData) -> - StateData; +set_opts([], StateData) -> StateData; set_opts([{Opt, Val} | Opts], StateData) -> NSD = case Opt of - title -> StateData#state{config = (StateData#state.config)#config{title = Val}}; - description -> StateData#state{config = (StateData#state.config)#config{description = Val}}; - allow_change_subj -> StateData#state{config = (StateData#state.config)#config{allow_change_subj = Val}}; - allow_query_users -> StateData#state{config = (StateData#state.config)#config{allow_query_users = Val}}; - allow_private_messages -> StateData#state{config = (StateData#state.config)#config{allow_private_messages = Val}}; - allow_private_messages_from_visitors -> StateData#state{config = (StateData#state.config)#config{allow_private_messages_from_visitors = Val}}; - allow_visitor_nickchange -> StateData#state{config = (StateData#state.config)#config{allow_visitor_nickchange = Val}}; - allow_visitor_status -> StateData#state{config = (StateData#state.config)#config{allow_visitor_status = Val}}; - public -> StateData#state{config = (StateData#state.config)#config{public = Val}}; - public_list -> StateData#state{config = (StateData#state.config)#config{public_list = Val}}; - persistent -> StateData#state{config = (StateData#state.config)#config{persistent = Val}}; - moderated -> StateData#state{config = (StateData#state.config)#config{moderated = Val}}; - members_by_default -> StateData#state{config = (StateData#state.config)#config{members_by_default = Val}}; - members_only -> StateData#state{config = (StateData#state.config)#config{members_only = Val}}; - allow_user_invites -> StateData#state{config = (StateData#state.config)#config{allow_user_invites = Val}}; - password_protected -> StateData#state{config = (StateData#state.config)#config{password_protected = Val}}; - captcha_protected -> StateData#state{config = (StateData#state.config)#config{captcha_protected = Val}}; - password -> StateData#state{config = (StateData#state.config)#config{password = Val}}; - anonymous -> StateData#state{config = (StateData#state.config)#config{anonymous = Val}}; - logging -> StateData#state{config = (StateData#state.config)#config{logging = Val}}; - captcha_whitelist -> StateData#state{config = (StateData#state.config)#config{captcha_whitelist = ?SETS:from_list(Val)}}; - allow_voice_requests -> StateData#state{config = (StateData#state.config)#config{allow_voice_requests = Val}}; - voice_request_min_interval -> StateData#state{config = (StateData#state.config)#config{voice_request_min_interval = Val}}; - max_users -> - ServiceMaxUsers = get_service_max_users(StateData), - MaxUsers = if - Val =< ServiceMaxUsers -> Val; - true -> ServiceMaxUsers - end, - StateData#state{ - config = (StateData#state.config)#config{ - max_users = MaxUsers}}; - affiliations -> - StateData#state{affiliations = ?DICT:from_list(Val)}; - subject -> - StateData#state{subject = Val}; - subject_author -> - StateData#state{subject_author = Val}; - _ -> StateData + title -> + StateData#state{config = + (StateData#state.config)#config{title = + Val}}; + description -> + StateData#state{config = + (StateData#state.config)#config{description + = Val}}; + allow_change_subj -> + StateData#state{config = + (StateData#state.config)#config{allow_change_subj + = Val}}; + allow_query_users -> + StateData#state{config = + (StateData#state.config)#config{allow_query_users + = Val}}; + allow_private_messages -> + StateData#state{config = + (StateData#state.config)#config{allow_private_messages + = Val}}; + allow_private_messages_from_visitors -> + StateData#state{config = + (StateData#state.config)#config{allow_private_messages_from_visitors + = Val}}; + allow_visitor_nickchange -> + StateData#state{config = + (StateData#state.config)#config{allow_visitor_nickchange + = Val}}; + allow_visitor_status -> + StateData#state{config = + (StateData#state.config)#config{allow_visitor_status + = Val}}; + public -> + StateData#state{config = + (StateData#state.config)#config{public = + Val}}; + public_list -> + StateData#state{config = + (StateData#state.config)#config{public_list + = Val}}; + persistent -> + StateData#state{config = + (StateData#state.config)#config{persistent = + Val}}; + moderated -> + StateData#state{config = + (StateData#state.config)#config{moderated = + Val}}; + members_by_default -> + StateData#state{config = + (StateData#state.config)#config{members_by_default + = Val}}; + members_only -> + StateData#state{config = + (StateData#state.config)#config{members_only + = Val}}; + allow_user_invites -> + StateData#state{config = + (StateData#state.config)#config{allow_user_invites + = Val}}; + password_protected -> + StateData#state{config = + (StateData#state.config)#config{password_protected + = Val}}; + captcha_protected -> + StateData#state{config = + (StateData#state.config)#config{captcha_protected + = Val}}; + password -> + StateData#state{config = + (StateData#state.config)#config{password = + Val}}; + anonymous -> + StateData#state{config = + (StateData#state.config)#config{anonymous = + Val}}; + logging -> + StateData#state{config = + (StateData#state.config)#config{logging = + Val}}; + captcha_whitelist -> + StateData#state{config = + (StateData#state.config)#config{captcha_whitelist + = + (?SETS):from_list(Val)}}; + allow_voice_requests -> + StateData#state{config = + (StateData#state.config)#config{allow_voice_requests + = Val}}; + voice_request_min_interval -> + StateData#state{config = + (StateData#state.config)#config{voice_request_min_interval + = Val}}; + max_users -> + ServiceMaxUsers = get_service_max_users(StateData), + MaxUsers = if Val =< ServiceMaxUsers -> Val; + true -> ServiceMaxUsers + end, + StateData#state{config = + (StateData#state.config)#config{max_users = + MaxUsers}}; + affiliations -> + StateData#state{affiliations = (?DICT):from_list(Val)}; + subject -> StateData#state{subject = Val}; + subject_author -> StateData#state{subject_author = Val}; + _ -> StateData end, set_opts(Opts, NSD). -define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}). + make_opts(StateData) -> Config = StateData#state.config, - [ - ?MAKE_CONFIG_OPT(title), - ?MAKE_CONFIG_OPT(description), + [?MAKE_CONFIG_OPT(title), ?MAKE_CONFIG_OPT(description), ?MAKE_CONFIG_OPT(allow_change_subj), ?MAKE_CONFIG_OPT(allow_query_users), ?MAKE_CONFIG_OPT(allow_private_messages), ?MAKE_CONFIG_OPT(allow_private_messages_from_visitors), ?MAKE_CONFIG_OPT(allow_visitor_status), ?MAKE_CONFIG_OPT(allow_visitor_nickchange), - ?MAKE_CONFIG_OPT(public), - ?MAKE_CONFIG_OPT(public_list), + ?MAKE_CONFIG_OPT(public), ?MAKE_CONFIG_OPT(public_list), ?MAKE_CONFIG_OPT(persistent), ?MAKE_CONFIG_OPT(moderated), ?MAKE_CONFIG_OPT(members_by_default), @@ -3702,471 +3950,515 @@ make_opts(StateData) -> ?MAKE_CONFIG_OPT(allow_user_invites), ?MAKE_CONFIG_OPT(password_protected), ?MAKE_CONFIG_OPT(captcha_protected), - ?MAKE_CONFIG_OPT(password), - ?MAKE_CONFIG_OPT(anonymous), - ?MAKE_CONFIG_OPT(logging), - ?MAKE_CONFIG_OPT(max_users), + ?MAKE_CONFIG_OPT(password), ?MAKE_CONFIG_OPT(anonymous), + ?MAKE_CONFIG_OPT(logging), ?MAKE_CONFIG_OPT(max_users), ?MAKE_CONFIG_OPT(allow_voice_requests), ?MAKE_CONFIG_OPT(voice_request_min_interval), {captcha_whitelist, - ?SETS:to_list((StateData#state.config)#config.captcha_whitelist)}, - {affiliations, ?DICT:to_list(StateData#state.affiliations)}, + (?SETS):to_list((StateData#state.config)#config.captcha_whitelist)}, + {affiliations, + (?DICT):to_list(StateData#state.affiliations)}, {subject, StateData#state.subject}, - {subject_author, StateData#state.subject_author} - ]. - - + {subject_author, StateData#state.subject_author}]. destroy_room(DEl, StateData) -> - lists:foreach( - fun({_LJID, Info}) -> - Nick = Info#user.nick, - ItemAttrs = [{"affiliation", "none"}, - {"role", "none"}], - Packet = {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, []}, DEl]}]}, - route_stanza( - jlib:jid_replace_resource(StateData#state.jid, Nick), - Info#user.jid, - Packet) - end, ?DICT:to_list(StateData#state.users)), + 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 - true -> - mod_muc:forget_room( - StateData#state.server_host, - StateData#state.host, StateData#state.room); - false -> - ok - end, + true -> + mod_muc:forget_room(StateData#state.server_host, + StateData#state.host, StateData#state.room); + false -> ok + end, {result, [], stop}. - - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Disco --define(FEATURE(Var), {xmlelement, "feature", [{"var", Var}], []}). +-define(FEATURE(Var), + #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}], + children = []}). -define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse), - case Opt of - true -> - ?FEATURE(Fiftrue); - false -> - ?FEATURE(Fiffalse) - end). + case Opt of + true -> ?FEATURE(Fiftrue); + false -> ?FEATURE(Fiffalse) + end). process_iq_disco_info(_From, set, _Lang, _StateData) -> {error, ?ERR_NOT_ALLOWED}; - process_iq_disco_info(_From, get, Lang, StateData) -> Config = StateData#state.config, - {result, [{xmlelement, "identity", - [{"category", "conference"}, - {"type", "text"}, - {"name", get_title(StateData)}], []}, - {xmlelement, "feature", - [{"var", ?NS_MUC}], []}, - ?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}. + {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), - {xmlelement, "field", [{"type", Type}, {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = <<"field">>, + attrs = [{<<"type">>, Type}, {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]}]}). -define(RFIELD(Label, Var, Val), - {xmlelement, "field", [{"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"label">>, translate:translate(Lang, Label)}, + {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]}]}). iq_disco_info_extras(Lang, StateData) -> - Len = ?DICT:size(StateData#state.users), - RoomDescription = (StateData#state.config)#config.description, - [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], - [?RFIELDT("hidden", "FORM_TYPE", - "http://jabber.org/protocol/muc#roominfo"), - ?RFIELD("Room description", "muc#roominfo_description", - RoomDescription), - ?RFIELD("Number of occupants", "muc#roominfo_occupants", - integer_to_list(Len)) - ]}]. + Len = (?DICT):size(StateData#state.users), + RoomDescription = + (StateData#state.config)#config.description, + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], + children = + [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>, + <<"http://jabber.org/protocol/muc#roominfo">>), + ?RFIELD(<<"Room description">>, + <<"muc#roominfo_description">>, RoomDescription), + ?RFIELD(<<"Number of occupants">>, + <<"muc#roominfo_occupants">>, + (iolist_to_binary(integer_to_list(Len))))]}]. process_iq_disco_items(_From, set, _Lang, _StateData) -> {error, ?ERR_NOT_ALLOWED}; - process_iq_disco_items(From, get, _Lang, StateData) -> case (StateData#state.config)#config.public_list of - true -> - {result, get_mucroom_disco_items(StateData), StateData}; - _ -> - case is_occupant_or_admin(From, StateData) of - true -> - {result, get_mucroom_disco_items(StateData), StateData}; - _ -> - {error, ?ERR_FORBIDDEN} - end + true -> + {result, get_mucroom_disco_items(StateData), StateData}; + _ -> + case is_occupant_or_admin(From, StateData) of + true -> + {result, get_mucroom_disco_items(StateData), StateData}; + _ -> {error, ?ERR_FORBIDDEN} + end end. -process_iq_captcha(_From, get, _Lang, _SubEl, _StateData) -> +process_iq_captcha(_From, get, _Lang, _SubEl, + _StateData) -> {error, ?ERR_NOT_ALLOWED}; - -process_iq_captcha(_From, set, _Lang, SubEl, StateData) -> +process_iq_captcha(_From, set, _Lang, SubEl, + StateData) -> case ejabberd_captcha:process_reply(SubEl) of - ok -> - {result, [], StateData}; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} + ok -> {result, [], StateData}; + _ -> {error, ?ERR_NOT_ACCEPTABLE} end. get_title(StateData) -> case (StateData#state.config)#config.title of - "" -> - StateData#state.room; - Name -> - Name + <<"">> -> StateData#state.room; + Name -> Name end. get_roomdesc_reply(JID, StateData, Tail) -> - IsOccupantOrAdmin = is_occupant_or_admin(JID, StateData), - if (StateData#state.config)#config.public or IsOccupantOrAdmin -> - if (StateData#state.config)#config.public_list or IsOccupantOrAdmin -> - {item, get_title(StateData) ++ Tail}; - true -> - {item, get_title(StateData)} - end; - true -> - false + IsOccupantOrAdmin = is_occupant_or_admin(JID, + StateData), + if (StateData#state.config)#config.public or + IsOccupantOrAdmin -> + if (StateData#state.config)#config.public_list or + IsOccupantOrAdmin -> + {item, <<(get_title(StateData))/binary,Tail/binary>>}; + true -> {item, get_title(StateData)} + end; + true -> false end. get_roomdesc_tail(StateData, Lang) -> Desc = case (StateData#state.config)#config.public of - true -> - ""; - _ -> - translate:translate(Lang, "private, ") + true -> <<"">>; + _ -> translate:translate(Lang, <<"private, ">>) end, - Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0, StateData#state.users), - " (" ++ Desc ++ integer_to_list(Len) ++ ")". + Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0, + StateData#state.users), + <<" (", Desc/binary, + (iolist_to_binary(integer_to_list(Len)))/binary, ")">>. get_mucroom_disco_items(StateData) -> - lists:map( - fun({_LJID, Info}) -> - Nick = Info#user.nick, - {xmlelement, "item", - [{"jid", jlib:jid_to_string({StateData#state.room, - StateData#state.host, Nick})}, - {"name", Nick}], []} - end, - ?DICT:to_list(StateData#state.users)). + lists:map(fun ({_LJID, Info}) -> + Nick = Info#user.nick, + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string({StateData#state.room, + StateData#state.host, + Nick})}, + {<<"name">>, Nick}], + children = []} + end, + (?DICT):to_list(StateData#state.users)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Voice request support is_voice_request(Els) -> - lists:foldl( - fun({xmlelement, "x", 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). + 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) -> - {xmlelement, "message", [{"type", "normal"}], - [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}], - [{xmlelement, "title", [], - [{xmlcdata, translate:translate(Lang, "Voice request")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Either approve or decline the voice request.")}]}, - {xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}], - [{xmlelement, "value", [], - [{xmlcdata, "http://jabber.org/protocol/muc#request"}]}]}, - {xmlelement, "field", [{"var", "muc#role"}, {"type", "hidden"}], - [{xmlelement, "value", [], [{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", - list_to_atom("false")) - ]}]}. + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"normal">>}], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Voice request">>)}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Either approve or decline the voice " + "request.">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"http://jabber.org/protocol/muc#request">>}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"muc#role">>}, + {<<"type">>, <<"hidden">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"participant">>}]}]}, + ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>, + (jlib:jid_to_string(Requester))), + ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, + Nick), + ?BOOLXFIELD(<<"Grant voice to this person?">>, + <<"muc#request_allow">>, + (jlib:binary_to_atom(<<"false">>)))]}]}. send_voice_request(From, StateData) -> Moderators = search_role(moderator, StateData), FromNick = find_nick_by_jid(From, StateData), - lists:foreach( - fun({_, User}) -> - route_stanza( - StateData#state.jid, - User#user.jid, - prepare_request_form(From, FromNick, "")) - end, Moderators). + lists:foreach(fun ({_, User}) -> + route_stanza(StateData#state.jid, User#user.jid, + prepare_request_form(From, FromNick, + <<"">>)) + end, + Moderators). is_voice_approvement(Els) -> - lists:foldl( - fun({xmlelement, "x", 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). + lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = + El, + false) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_XDATA -> + case jlib:parse_xdata_submit(El) of + [_ | _] = Fs -> + case {lists:keysearch(<<"FORM_TYPE">>, 1, + Fs), + lists:keysearch(<<"muc#role">>, 1, + Fs), + lists:keysearch(<<"muc#request_allow">>, + 1, Fs)} + of + {{value, + {_, + [<<"http://jabber.org/protocol/muc#request">>]}}, + {value, {_, [<<"participant">>]}}, + {value, {_, [Flag]}}} + when Flag == <<"true">>; + Flag == <<"1">> -> + true; + _ -> false + end; + _ -> false + end; + _ -> false + end; + (_, Acc) -> Acc + end, + false, Els). extract_jid_from_voice_approvement(Els) -> - lists:foldl( - fun({xmlelement, "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). + lists:foldl(fun (#xmlel{name = <<"x">>} = El, error) -> + Fields = case jlib:parse_xdata_submit(El) of + invalid -> []; + Res -> Res + end, + lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) -> + case jlib:string_to_jid(JIDStr) of + error -> error; + J -> {ok, J} + end; + (_, Acc) -> Acc + end, + error, Fields); + (_, Acc) -> Acc + end, + error, Els). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Invitation support is_invitation(Els) -> - lists:foldl( - fun({xmlelement, "x", 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). + lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = + El, + false) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_MUC_USER -> + case xml:get_subtag(El, <<"invite">>) of + false -> false; + _ -> true + end; + _ -> false + end; + (_, Acc) -> Acc + end, + false, Els). check_invitation(From, Els, Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), - CanInvite = (StateData#state.config)#config.allow_user_invites - orelse (FAffiliation == admin) orelse (FAffiliation == owner), + CanInvite = + (StateData#state.config)#config.allow_user_invites + orelse + FAffiliation == admin orelse FAffiliation == owner, InviteEl = case xml:remove_cdata(Els) of - [{xmlelement, "x", _Attrs1, 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 - [{xmlelement, "invite", _Attrs2, _Els2} = InviteEl1] -> - InviteEl1; - _ -> - throw({error, ?ERR_BAD_REQUEST}) - end; - _ -> - throw({error, ?ERR_BAD_REQUEST}) + [#xmlel{name = <<"x">>, children = Els1} = XEl] -> + case xml:get_tag_attr_s(<<"xmlns">>, XEl) of + ?NS_MUC_USER -> ok; + _ -> throw({error, ?ERR_BAD_REQUEST}) + end, + case xml:remove_cdata(Els1) of + [#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1; + _ -> throw({error, ?ERR_BAD_REQUEST}) + end; + _ -> throw({error, ?ERR_BAD_REQUEST}) end, - JID = case jlib:string_to_jid( - xml:get_tag_attr_s("to", InviteEl)) of - error -> - throw({error, ?ERR_JID_MALFORMED}); - JID1 -> - JID1 + JID = case + jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>, + InviteEl)) + of + error -> throw({error, ?ERR_JID_MALFORMED}); + JID1 -> JID1 end, case CanInvite of - false -> - throw({error, ?ERR_NOT_ALLOWED}); - true -> - Reason = - xml:get_path_s( - InviteEl, - [{elem, "reason"}, cdata]), - ContinueEl = - case xml:get_path_s( - InviteEl, - [{elem, "continue"}]) of - [] -> []; - Continue1 -> [Continue1] - end, - IEl = - [{xmlelement, "invite", - [{"from", - jlib:jid_to_string(From)}], - [{xmlelement, "reason", [], - [{xmlcdata, Reason}]}] ++ ContinueEl}], - PasswdEl = - case (StateData#state.config)#config.password_protected of - true -> - [{xmlelement, "password", [], - [{xmlcdata, (StateData#state.config)#config.password}]}]; - _ -> - [] - end, - Body = - {xmlelement, "body", [], - [{xmlcdata, - lists:flatten( - 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 + 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 -> - ", " ++ - translate:translate(Lang, "the password is") ++ - " '" ++ - (StateData#state.config)#config.password ++ "'"; - _ -> - "" - end ++ - case Reason of - "" -> ""; - _ -> " (" ++ Reason ++ ") " - end - }]}, - Msg = - {xmlelement, "message", - [{"type", "normal"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], IEl ++ PasswdEl}, - {xmlelement, "x", - [{"xmlns", ?NS_XCONFERENCE}, - {"jid", jlib:jid_to_string( - {StateData#state.room, - StateData#state.host, - ""})}], - [{xmlcdata, Reason}]}, - Body]}, - route_stanza(StateData#state.jid, JID, Msg), - JID + [#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. -%% Handle a message sent to the room by a non-participant. -%% If it is a decline, send to the inviter. -%% Otherwise, an error message is sent to the sender. -handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From) -> +handle_roommessage_from_nonparticipant(Packet, Lang, + StateData, From) -> case catch check_decline_invitation(Packet) of - {true, Decline_data} -> - send_decline_invitation(Decline_data, StateData#state.jid, From); - _ -> - send_error_only_occupants(Packet, Lang, StateData#state.jid, From) + {true, Decline_data} -> + send_decline_invitation(Decline_data, + StateData#state.jid, From); + _ -> + send_error_only_occupants(Packet, Lang, + StateData#state.jid, From) end. -%% Check in the packet is a decline. -%% If so, also returns the splitted packet. -%% This function must be catched, -%% because it crashes when the packet is not a decline message. check_decline_invitation(Packet) -> - {xmlelement, "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), + #xmlel{name = <<"message">>} = Packet, + XEl = xml:get_subtag(Packet, <<"x">>), + (?NS_MUC_USER) = xml:get_tag_attr_s(<<"xmlns">>, XEl), + DEl = xml:get_subtag(XEl, <<"decline">>), + ToString = xml:get_tag_attr_s(<<"to">>, DEl), ToJID = jlib:string_to_jid(ToString), {true, {Packet, XEl, DEl, ToJID}}. -%% Send the decline to the inviter user. -%% The original stanza must be slightly modified. -send_decline_invitation({Packet, XEl, DEl, ToJID}, RoomJID, FromJID) -> - FromString = jlib:jid_to_string(jlib:jid_remove_resource(FromJID)), - {xmlelement, "decline", DAttrs, DEls} = DEl, - DAttrs2 = lists:keydelete("to", 1, DAttrs), - DAttrs3 = [{"from", FromString} | DAttrs2], - DEl2 = {xmlelement, "decline", DAttrs3, DEls}, +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). -%% Given an element and a new subelement, -%% replace the instance of the subelement in element with the new subelement. -replace_subelement({xmlelement, Name, Attrs, SubEls}, NewSubEl) -> +replace_subelement(#xmlel{name = Name, attrs = Attrs, + children = SubEls}, + NewSubEl) -> {_, NameNewSubEl, _, _} = NewSubEl, - SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl), - {xmlelement, Name, Attrs, 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)), + 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) - when Type == roomconfig_change_disabledlogging -> - %% When logging is disabled, the config change message must be logged: - mod_muc_log:add_to_log( - StateData#state.server_host, roomconfig_change, Data, - StateData#state.jid, make_opts(StateData)); + when Type == roomconfig_change_disabledlogging -> + mod_muc_log:add_to_log(StateData#state.server_host, + roomconfig_change, Data, StateData#state.jid, + make_opts(StateData)); add_to_log(Type, Data, StateData) -> case (StateData#state.config)#config.logging of - true -> - mod_muc_log:add_to_log( - StateData#state.server_host, Type, Data, - StateData#state.jid, make_opts(StateData)); - false -> - ok + true -> + mod_muc_log:add_to_log(StateData#state.server_host, + Type, Data, StateData#state.jid, + make_opts(StateData)); + false -> ok end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -4177,45 +4469,46 @@ tab_add_online_user(JID, StateData) -> US = {LUser, LServer}, Room = StateData#state.room, Host = StateData#state.host, - catch ets:insert( - muc_online_users, - #muc_online_users{us = US, resource = LResource, room = Room, host = Host}). - + catch ets:insert(muc_online_users, + #muc_online_users{us = US, resource = LResource, + room = Room, host = Host}). tab_remove_online_user(JID, StateData) -> {LUser, LServer, LResource} = jlib:jid_tolower(JID), US = {LUser, LServer}, Room = StateData#state.room, Host = StateData#state.host, - catch ets:delete_object( - muc_online_users, - #muc_online_users{us = US, resource = LResource, room = Room, host = Host}). + catch ets:delete_object(muc_online_users, + #muc_online_users{us = US, resource = LResource, + room = Room, host = Host}). tab_count_user(JID) -> {LUser, LServer, _} = jlib:jid_tolower(JID), US = {LUser, LServer}, - case catch ets:select( - muc_online_users, - [{#muc_online_users{us = US, _ = '_'}, [], [[]]}]) of - Res when is_list(Res) -> - length(Res); - _ -> - 0 + case catch ets:select(muc_online_users, + [{#muc_online_users{us = US, _ = '_'}, [], [[]]}]) + of + Res when is_list(Res) -> length(Res); + _ -> 0 end. -element_size(El) -> - size(xml:element_to_binary(El)). +element_size(El) -> byte_size(xml:element_to_binary(El)). route_stanza(From, To, El) -> case mod_muc:is_broadcasted(From#jid.lserver) of - 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) + 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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% 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). |