diff options
Diffstat (limited to 'src/mod_muc_room.erl')
-rw-r--r-- | src/mod_muc_room.erl | 933 |
1 files changed, 283 insertions, 650 deletions
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 339b85ecb..52401f835 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -754,100 +754,131 @@ process_groupchat_message(From, #message{lang = Lang} = Packet, StateData) -> -spec process_normal_message(jid(), message(), state()) -> state(). process_normal_message(From, #message{lang = Lang} = Pkt, StateData) -> - IsInvitation = is_invitation(Pkt), - IsVoiceRequest = is_voice_request(Pkt) and - is_visitor(From, StateData), - IsVoiceApprovement = is_voice_approvement(Pkt) and - not is_visitor(From, StateData), - if IsInvitation -> - case check_invitation(From, Pkt, StateData) of - {error, Error} -> - ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error), - 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), - send_affiliation(IJID, member, StateData), - store_room(NSD), - NSD; - _ -> - StateData - end; - false -> - StateData - end - end; - IsVoiceRequest -> - case (StateData#state.config)#config.allow_voice_requests of + Action = lists:foldl( + fun(_, {error, _} = Err) -> + Err; + (#muc_user{invites = [#muc_invite{to = undefined}]}, _) -> + Txt = <<"No 'to' attribute found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + (#muc_user{invites = [I]}, _) -> + {ok, I}; + (#muc_user{invites = [_|_]}, _) -> + Txt = <<"Multiple <invite/> elements are not allowed">>, + {error, xmpp:err_resource_constraint(Txt, Lang)}; + (#xdata{type = submit, fields = Fs}, _) -> + try {ok, muc_request:decode(Fs)} + catch _:{muc_request, Why} -> + Txt = muc_request:format_error(Why), + {error, xmpp:err_bad_request(Txt, Lang)} + end; + (_, Acc) -> + Acc + end, ok, xmpp:get_els(Pkt)), + case Action of + {ok, #muc_invite{} = Invitation} -> + process_invitation(From, Pkt, Invitation, StateData); + {ok, [{role, participant}]} -> + process_voice_request(From, Pkt, StateData); + {ok, VoiceApproval} -> + process_voice_approval(From, Pkt, VoiceApproval, StateData); + {error, Err} -> + ejabberd_router:route_error(StateData#state.jid, From, Pkt, Err), + StateData; + ok -> + StateData + end. + +-spec process_invitation(jid(), message(), muc_invite(), state()) -> state(). +process_invitation(From, Pkt, Invitation, StateData) -> + Lang = xmpp:get_lang(Pkt), + case check_invitation(From, Invitation, Lang, StateData) of + {error, Error} -> + ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error), + StateData; + IJID -> + Config = StateData#state.config, + case Config#config.members_only of true -> - MinInterval = (StateData#state.config)#config.voice_request_min_interval, - BareFrom = jid:remove_resource(jid:tolower(From)), - NowPriority = -p1_time_compat:system_time(micro_seconds), - 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, Lang, NSD), + case get_affiliation(IJID, StateData) of + none -> + NSD = set_affiliation(IJID, member, StateData), + send_affiliation(IJID, member, StateData), + store_room(NSD), NSD; - {ok, _, _} -> - ErrText = <<"Please, wait for a while before sending " - "new voice request">>, - Err = xmpp:err_not_acceptable(ErrText, Lang), - ejabberd_router:route_error( - StateData#state.jid, From, Pkt, Err), - StateData#state{last_voice_request_time = Times} + _ -> + StateData end; false -> - ErrText = <<"Voice requests are disabled in this conference">>, - Err = xmpp:err_forbidden(ErrText, Lang), + StateData + end + end. + +-spec process_voice_request(jid(), message(), state()) -> state(). +process_voice_request(From, Pkt, StateData) -> + Lang = xmpp:get_lang(Pkt), + case (StateData#state.config)#config.allow_voice_requests of + true -> + MinInterval = (StateData#state.config)#config.voice_request_min_interval, + BareFrom = jid:remove_resource(jid:tolower(From)), + NowPriority = -p1_time_compat:system_time(micro_seconds), + 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, Lang, NSD), + NSD; + {ok, _, _} -> + ErrText = <<"Please, wait for a while before sending " + "new voice request">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error( StateData#state.jid, From, Pkt, Err), - StateData + StateData#state{last_voice_request_time = Times} end; - IsVoiceApprovement -> - case is_moderator(From, StateData) of - true -> - case extract_jid_from_voice_approvement(Pkt) of - error -> - ErrText = <<"Failed to extract JID from your voice " - "request approval">>, - Err = xmpp:err_bad_request(ErrText, Lang), - ejabberd_router:route_error( - StateData#state.jid, From, Pkt, Err), - StateData; - TargetJid -> - case is_visitor(TargetJid, StateData) of - true -> - Reason = <<>>, - NSD = set_role(TargetJid, - participant, - StateData), - catch send_new_presence(TargetJid, - Reason, - NSD, - StateData), - NSD; - _ -> - StateData - end + false -> + ErrText = <<"Voice requests are disabled in this conference">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData + end. + +-spec process_voice_approval(jid(), message(), [muc_request:property()], state()) -> state(). +process_voice_approval(From, Pkt, VoiceApproval, StateData) -> + Lang = xmpp:get_lang(Pkt), + case is_moderator(From, StateData) of + true -> + case lists:keyfind(jid, 1, VoiceApproval) of + {_, TargetJid} -> + Allow = proplists:get_bool(request_allow, VoiceApproval), + case is_visitor(TargetJid, StateData) of + true when Allow -> + Reason = <<>>, + NSD = set_role(TargetJid, participant, StateData), + catch send_new_presence( + TargetJid, Reason, NSD, StateData), + NSD; + _ -> + StateData end; - _ -> - ErrText = <<"Only moderators can approve voice requests">>, - Err = xmpp:err_not_allowed(ErrText, Lang), + false -> + ErrText = <<"Failed to extract JID from your voice " + "request approval">>, + Err = xmpp:err_bad_request(ErrText, Lang), ejabberd_router:route_error( StateData#state.jid, From, Pkt, Err), StateData end; - true -> + false -> + ErrText = <<"Only moderators can approve voice requests">>, + Err = xmpp:err_not_allowed(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), StateData end. @@ -3090,34 +3121,6 @@ is_password_settings_correct(XData, StateData) -> _ -> true end. --define(XFIELD(Type, Label, Var, Vals), - #xdata_field{type = Type, - label = translate:translate(Lang, Label), - var = Var, - values = Vals}). - --define(BOOLXFIELD(Label, Var, Val), - ?XFIELD(boolean, Label, Var, - case Val of - true -> [<<"1">>]; - _ -> [<<"0">>] - end)). - --define(STRINGXFIELD(Label, Var, Val), - ?XFIELD('text-single', Label, Var, [Val])). - --define(PRIVATEXFIELD(Label, Var, Val), - ?XFIELD('text-private', Label, Var, [Val])). - --define(JIDMULTIXFIELD(Label, Var, JIDList), - ?XFIELD('jid-multi', Label, Var, - [jid:to_string(JID) || JID <- JIDList])). - --spec make_options([{binary(), binary()}], binary()) -> [xdata_option()]. -make_options(Options, Lang) -> - [#xdata_option{label = translate:translate(Lang, Label), - value = Value} || {Label, Value} <- Options]. - -spec get_default_room_maxusers(state()) -> non_neg_integer(). get_default_room_maxusers(RoomState) -> DefRoomOpts = @@ -3138,424 +3141,156 @@ get_config(Lang, StateData, From) -> {MaxUsersRoomInteger, MaxUsersRoomString} = case get_max_users(StateData) of N when is_integer(N) -> - {N, integer_to_binary(N)}; - _ -> {0, <<"none">>} + {N, N}; + _ -> {0, none} end, Title = iolist_to_binary( io_lib:format( translate:translate(Lang, <<"Configuration of room ~s">>), [jid:to_string(StateData#state.jid)])), - Fs = [#xdata_field{type = hidden, - var = <<"FORM_TYPE">>, - values = [<<"http://jabber.org/protocol/muc#roomconfig">>]}, - ?STRINGXFIELD(<<"Room title">>, - <<"muc#roomconfig_roomname">>, (Config#config.title)), - ?STRINGXFIELD(<<"Room description">>, - <<"muc#roomconfig_roomdesc">>, - (Config#config.description))] ++ + Fs = [{roomname, Config#config.title}, + {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))]; + allow -> [{persistentroom, Config#config.persistent}]; deny -> [] 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), - #xdata_field{type = 'list-single', - label = translate:translate( - Lang, <<"Maximum Number of Occupants">>), - var = <<"muc#roomconfig_maxusers">>, - values = [MaxUsersRoomString], - options = - if is_integer(ServiceMaxUsers) -> []; - true -> make_options( - [{<<"No limit">>, <<"none">>}], - Lang) - end ++ - make_options( - [{integer_to_binary(N), integer_to_binary(N)} - || N <- lists:usort([ServiceMaxUsers, - DefaultRoomMaxUsers, - MaxUsersRoomInteger - | ?MAX_USERS_DEFAULT_LIST]), - N =< ServiceMaxUsers], - Lang)}, - #xdata_field{type = 'list-single', - label = translate:translate( - Lang, <<"Present real Jabber IDs to">>), - var = <<"muc#roomconfig_whois">>, - values = [if Config#config.anonymous -> <<"moderators">>; - true -> <<"anyone">> - end], - options = make_options( - [{<<"moderators only">>, <<"moderators">>}, - {<<"anyone">>, <<"anyone">>}], - Lang)}, - #xdata_field{type = 'list-multi', - label = translate:translate( - Lang, - <<"Roles for which Presence is Broadcasted">>), - var = <<"muc#roomconfig_presencebroadcast">>, - values = [atom_to_binary(Role, utf8) - || Role <- Config#config.presence_broadcast], - options = make_options( - [{<<"Moderator">>, <<"moderator">>}, - {<<"Participant">>, <<"participant">>}, - {<<"Visitor">>, <<"visitor">>}], - Lang)}, - ?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)), - #xdata_field{type = 'list-single', - label = translate:translate( - Lang, - <<"Allow visitors to send private messages to">>), - var = <<"allow_private_messages_from_visitors">>, - values = [case Config#config.allow_private_messages_from_visitors of - anyone -> <<"anyone">>; - moderators -> <<"moderators">>; - nobody -> <<"nobody">> - end], - options = make_options( - [{<<"nobody">>, <<"nobody">>}, - {<<"moderators only">>, <<"moderators">>}, - {<<"anyone">>, <<"anyone">>}], - Lang)}, - ?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)), - ?BOOLXFIELD(<<"Allow subscription">>, - <<"muc#roomconfig_allow_subscription">>, - (Config#config.allow_subscription)), - ?STRINGXFIELD(<<"Minimum interval between voice requests " - "(in seconds)">>, - <<"muc#roomconfig_voicerequestmininterval">>, - integer_to_binary(Config#config.voice_request_min_interval))] + [{publicroom, Config#config.public}, + {public_list, Config#config.public_list}, + {passwordprotectedroom, Config#config.password_protected}, + {roomsecret, case Config#config.password_protected of + true -> Config#config.password; + false -> <<"">> + end}, + {maxusers, MaxUsersRoomString, + [if is_integer(ServiceMaxUsers) -> []; + true -> [{<<"No limit">>, <<"none">>}] + end] ++ [{integer_to_binary(N), N} + || N <- lists:usort([ServiceMaxUsers, + DefaultRoomMaxUsers, + MaxUsersRoomInteger + | ?MAX_USERS_DEFAULT_LIST]), + N =< ServiceMaxUsers]}, + {whois, if Config#config.anonymous -> moderators; + true -> anyone + end}, + {presencebroadcast, Config#config.presence_broadcast}, + {membersonly, Config#config.members_only}, + {moderatedroom, Config#config.moderated}, + {members_by_default, Config#config.members_by_default}, + {changesubject, Config#config.allow_change_subj}, + {allow_private_messages, Config#config.allow_private_messages}, + {allow_private_messages_from_visitors, + Config#config.allow_private_messages_from_visitors}, + {allow_query_users, Config#config.allow_query_users}, + {allowinvites, Config#config.allow_user_invites}, + {allow_visitor_status, Config#config.allow_visitor_status}, + {allow_visitor_nickchange, Config#config.allow_visitor_nickchange}, + {allow_voice_requests, Config#config.allow_voice_requests}, + {allow_subscription, Config#config.allow_subscription}, + {voice_request_min_interval, 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))]; + true -> [{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)))] + [{captcha_whitelist, + lists:map(fun jid:make/1, ?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))]; + allow -> [{enablelogging, Config#config.logging}]; deny -> [] end, Fields = ejabberd_hooks:run_fold(get_room_config, StateData#state.server_host, Fs, [StateData, From, Lang]), - #xdata{type = form, title = Title, fields = Fields}. + #xdata{type = form, title = Title, + fields = muc_roomconfig:encode( + Fields, fun(T) -> translate:translate(Lang, T) end)}. -spec set_config(xdata(), state(), binary()) -> {error, stanza_error()} | {result, undefined, state()}. set_config(#xdata{fields = Fields}, StateData, Lang) -> - Options = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- Fields], - case set_xoption(Options, StateData#state.config, - StateData#state.server_host, Lang) 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 + try + Options = muc_roomconfig:decode(Fields), + #config{} = Config = set_config(Options, StateData#state.config, + StateData#state.server_host, Lang), + {result, _, NSD} = Res = change_config(Config, StateData), + 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 + catch _:{muc_roomconfig, Why} -> + Txt = muc_roomconfig:format_error(Why), + {error, xmpp:err_bad_request(Txt, Lang)}; + _:{badmatch, {error, #stanza_error{}} = Err} -> + Err end. get_config_opt_name(Pos) -> Fs = [config|record_info(fields, config)], lists:nth(Pos, Fs). --define(SET_BOOL_XOPT(Opt, Val), - case Val of - <<"0">> -> - set_xoption(Opts, setelement(Opt, Config, false), ServerHost, Lang); - <<"false">> -> - set_xoption(Opts, setelement(Opt, Config, false), ServerHost, Lang); - <<"1">> -> set_xoption(Opts, setelement(Opt, Config, true), ServerHost, Lang); - <<"true">> -> - set_xoption(Opts, setelement(Opt, Config, true), ServerHost, Lang); - _ -> - Txt = <<"Value of '~s' should be boolean">>, - OptName = get_config_opt_name(Opt), - ErrTxt = iolist_to_binary(io_lib:format(Txt, [OptName])), - {error, xmpp:err_bad_request(ErrTxt, Lang)} - end). - --define(SET_NAT_XOPT(Opt, Val), - case catch binary_to_integer(Val) of - I when is_integer(I), I > 0 -> - set_xoption(Opts, setelement(Opt, Config, I), ServerHost, Lang); - _ -> - Txt = <<"Value of '~s' should be integer">>, - OptName = get_config_opt_name(Opt), - ErrTxt = iolist_to_binary(io_lib:format(Txt, [OptName])), - {error, xmpp:err_bad_request(ErrTxt, Lang)} - end). - --define(SET_STRING_XOPT(Opt, Vals), - try - V = case Vals of - [] -> <<"">>; - [Val] -> Val; - _ when is_atom(Vals) -> Vals - end, - set_xoption(Opts, setelement(Opt, Config, V), ServerHost, Lang) - catch _:_ -> - Txt = <<"Incorrect value of option '~s'">>, - OptName = get_config_opt_name(Opt), - ErrTxt = iolist_to_binary(io_lib:format(Txt, [OptName])), - {error, xmpp:err_bad_request(ErrTxt, Lang)} - end). - --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, setelement(Opt, Config, Set), ServerHost, Lang) - end). - --spec set_xoption([{binary(), [binary()]}], #config{}, +-spec set_config([muc_roomconfig:property()], #config{}, binary(), binary()) -> #config{} | {error, stanza_error()}. -set_xoption([], Config, _ServerHost, _Lang) -> Config; -set_xoption([{<<"muc#roomconfig_roomname">>, Vals} - | Opts], - Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(#config.title, Vals); -set_xoption([{<<"muc#roomconfig_roomdesc">>, Vals} - | Opts], - Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(#config.description, Vals); -set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.allow_change_subj, Val); -set_xoption([{<<"allow_query_users">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.allow_query_users, Val); -set_xoption([{<<"allow_private_messages">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.allow_private_messages, Val); -set_xoption([{<<"allow_private_messages_from_visitors">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - case Val of - <<"anyone">> -> - ?SET_STRING_XOPT(#config.allow_private_messages_from_visitors, - anyone); - <<"moderators">> -> - ?SET_STRING_XOPT(#config.allow_private_messages_from_visitors, - moderators); - <<"nobody">> -> - ?SET_STRING_XOPT(#config.allow_private_messages_from_visitors, - nobody); - _ -> - Txt = <<"Value of 'allow_private_messages_from_visitors' " - "should be anyone|moderators|nobody">>, - {error, xmpp:err_bad_request(Txt, Lang)} - end; -set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.allow_visitor_status, Val); -set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.allow_visitor_nickchange, Val); -set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.public, Val); -set_xoption([{<<"public_list">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.public_list, Val); -set_xoption([{<<"muc#roomconfig_persistentroom">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.persistent, Val); -set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.moderated, Val); -set_xoption([{<<"members_by_default">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.members_by_default, Val); -set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.members_only, Val); -set_xoption([{<<"captcha_protected">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.captcha_protected, Val); -set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.allow_user_invites, Val); -set_xoption([{<<"muc#roomconfig_allow_subscription">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.allow_subscription, Val); -set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.password_protected, Val); -set_xoption([{<<"muc#roomconfig_roomsecret">>, Vals} - | Opts], - Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(#config.password, Vals); -set_xoption([{<<"anonymous">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.anonymous, Val); -set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts], - Config, ServerHost, Lang) -> - Roles = - lists:foldl( - fun(_S, error) -> error; - (S, {M, P, V}) -> - case S of - <<"moderator">> -> {true, P, V}; - <<"participant">> -> {M, true, V}; - <<"visitor">> -> {M, P, true}; - _ -> error - end - end, {false, false, false}, Vals), - case Roles of - error -> - Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should " - "be moderator|participant|visitor">>, - {error, xmpp:err_bad_request(Txt, Lang)}; - {M, P, V} -> - Res = - if M -> [moderator]; true -> [] end ++ - if P -> [participant]; true -> [] end ++ - if V -> [visitor]; true -> [] end, - set_xoption(Opts, Config#config{presence_broadcast = Res}, - ServerHost, Lang) - end; -set_xoption([{<<"muc#roomconfig_allowvoicerequests">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.allow_voice_requests, Val); -set_xoption([{<<"muc#roomconfig_voicerequestmininterval">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_NAT_XOPT(#config.voice_request_min_interval, Val); -set_xoption([{<<"muc#roomconfig_whois">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - case Val of - <<"moderators">> -> - ?SET_BOOL_XOPT(#config.anonymous, - (iolist_to_binary(integer_to_list(1)))); - <<"anyone">> -> - ?SET_BOOL_XOPT(#config.anonymous, - (iolist_to_binary(integer_to_list(0)))); - _ -> - Txt = <<"Value of 'muc#roomconfig_whois' should be " - "moderators|anyone">>, - {error, xmpp:err_bad_request(Txt, Lang)} - end; -set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - case Val of - <<"none">> -> ?SET_STRING_XOPT(#config.max_users, none); - _ -> ?SET_NAT_XOPT(#config.max_users, Val) - end; -set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(#config.logging, Val); -set_xoption([{<<"muc#roomconfig_captcha_whitelist">>, - Vals} - | Opts], - Config, ServerHost, Lang) -> - JIDs = [jid:from_string(Val) || Val <- Vals], - ?SET_JIDMULTI_XOPT(#config.captcha_whitelist, JIDs); -set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config, ServerHost, Lang) -> - set_xoption(Opts, Config, ServerHost, Lang); -set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) -> - Txt = <<"Unknown option '~s'">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - Err = {error, xmpp:err_bad_request(ErrTxt, Lang)}, - case ejabberd_hooks:run_fold(set_room_option, - ServerHost, - Err, - [Opt, Vals, Lang]) of - {error, Reason} -> - {error, Reason}; - {Pos, Val} -> - set_xoption(Opts, setelement(Pos, Config, Val), ServerHost, Lang) - end. +set_config(Opts, Config, ServerHost, Lang) -> + lists:foldl( + fun(_, {error, _} = Err) -> Err; + ({roomname, Title}, C) -> C#config{title = Title}; + ({roomdesc, Desc}, C) -> C#config{description = Desc}; + ({changesubject, V}, C) -> C#config{allow_change_subj = V}; + ({allow_query_users, V}, C) -> C#config{allow_query_users = V}; + ({allow_private_messages, V}, C) -> + C#config{allow_private_messages = V}; + ({allow_private_messages_from_visitors, V}, C) -> + C#config{allow_private_messages_from_visitors = V}; + ({allow_visitor_status, V}, C) -> C#config{allow_visitor_status = V}; + ({allow_visitor_nickchange, V}, C) -> + C#config{allow_visitor_nickchange = V}; + ({publicroom, V}, C) -> C#config{public = V}; + ({public_list, V}, C) -> C#config{public_list = V}; + ({persistentroom, V}, C) -> C#config{persistent = V}; + ({moderatedroom, V}, C) -> C#config{moderated = V}; + ({members_by_default, V}, C) -> C#config{members_by_default = V}; + ({membersonly, V}, C) -> C#config{members_only = V}; + ({captcha_protected, V}, C) -> C#config{captcha_protected = V}; + ({allowinvites, V}, C) -> C#config{allow_user_invites = V}; + ({allow_subscription, V}, C) -> C#config{allow_subscription = V}; + ({passwordprotectedroom, V}, C) -> C#config{password_protected = V}; + ({roomsecret, V}, C) -> C#config{password = V}; + ({anonymous, V}, C) -> C#config{anonymous = V}; + ({presencebroadcast, V}, C) -> C#config{presence_broadcast = V}; + ({allow_voice_requests, V}, C) -> C#config{allow_voice_requests = V}; + ({voice_request_min_interval, V}, C) -> + C#config{voice_request_min_interval = V}; + ({whois, moderators}, C) -> C#config{anonymous = true}; + ({whois, anyone}, C) -> C#config{anonymous = false}; + ({maxusers, V}, C) -> C#config{max_users = V}; + ({enablelogging, V}, C) -> C#config{logging = V}; + ({captcha_whitelist, Js}, C) -> + LJIDs = [jid:tolower(J) || J <- Js], + C#config{captcha_whitelist = ?SETS:from_list(LJIDs)}; + ({O, V} = Opt, C) -> + case ejabberd_hooks:run_fold(set_room_option, + ServerHost, + {0, undefined}, + [Opt, Lang]) of + {0, undefined} -> + ?ERROR_MSG("set_room_option hook failed for " + "option '~s' with value ~p", [O, V]), + Txt = <<"Failed to process option '", O/binary, "'">>, + {error, xmpp:err_internal_server_error(Txt, Lang)}; + {Pos, Val} -> + setelement(Pos, C, Val) + end + end, Config, Opts). -spec change_config(#config{}, state()) -> {result, undefined, state()}. change_config(Config, StateData) -> @@ -3872,33 +3607,13 @@ process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) -> name = get_title(StateData)}], features = Feats}}. --spec mk_rfieldt('boolean' | 'fixed' | 'hidden' | - 'jid-multi' | 'jid-single' | 'list-multi' | - 'list-single' | 'text-multi' | 'text-private' | - 'text-single', binary(), binary()) -> xdata_field(). -mk_rfieldt(Type, Var, Val) -> - #xdata_field{type = Type, var = Var, values = [Val]}. - --spec mk_rfield(binary(), binary(), binary(), binary()) -> xdata_field(). -mk_rfield(Label, Var, Val, Lang) -> - #xdata_field{type = 'text-single', - label = translate:translate(Lang, Label), - var = Var, - values = [Val]}. - -spec iq_disco_info_extras(binary(), state()) -> xdata(). iq_disco_info_extras(Lang, StateData) -> - Len = (?DICT):size(StateData#state.users), - RoomDescription = (StateData#state.config)#config.description, + Fs = [{description, (StateData#state.config)#config.description}, + {occupants, ?DICT:size(StateData#state.users)}], #xdata{type = result, - fields = [mk_rfieldt(hidden, <<"FORM_TYPE">>, - "http://jabber.org/protocol/muc#roominfo"), - mk_rfield(<<"Room description">>, - <<"muc#roominfo_description">>, - RoomDescription, Lang), - mk_rfield(<<"Number of occupants">>, - <<"muc#roominfo_occupants">>, - integer_to_binary(Len), Lang)]}. + fields = muc_roominfo:encode( + Fs, fun(T) -> translate:translate(Lang, T) end)}. -spec process_iq_disco_items(jid(), iq(), state()) -> {error, stanza_error()} | {result, disco_items()}. @@ -4105,39 +3820,16 @@ get_mucroom_disco_items(StateData) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Voice request support --spec is_voice_request(message()) -> boolean(). -is_voice_request(Packet) -> - Els = xmpp:get_els(Packet), - lists:any( - fun(#xdata{} = X) -> - case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X), - xmpp_util:get_xdata_values(<<"muc#role">>, X)} of - {[<<"http://jabber.org/protocol/muc#request">>], - [<<"participant">>]} -> - true; - _ -> - false - end; - (_) -> - false - end, Els). - -spec prepare_request_form(jid(), binary(), binary()) -> message(). prepare_request_form(Requester, Nick, Lang) -> Title = translate:translate(Lang, <<"Voice request">>), Instruction = translate:translate( Lang, <<"Either approve or decline the voice request.">>), - Fs = [#xdata_field{var = <<"FORM_TYPE">>, - type = hidden, - values = [<<"http://jabber.org/protocol/muc#request">>]}, - #xdata_field{var = <<"muc#role">>, - type = hidden, - values = [<<"participant">>]}, - ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>, - jid:to_string(Requester)), - ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, Nick), - ?BOOLXFIELD(<<"Grant voice to this person?">>, - <<"muc#request_allow">>, false)], + Fs = muc_request:encode([{role, participant}, + {jid, Requester}, + {roomnick, Nick}, + {request_allow, false}], + fun(T) -> translate:translate(Lang, T) end), #message{type = normal, sub_els = [#xdata{type = form, title = Title, @@ -4155,59 +3847,11 @@ send_voice_request(From, Lang, StateData) -> end, Moderators). --spec is_voice_approvement(message()) -> boolean(). -is_voice_approvement(Packet) -> - Els = xmpp:get_els(Packet), - lists:any( - fun(#xdata{} = X) -> - case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X), - xmpp_util:get_xdata_values(<<"muc#role">>, X), - xmpp_util:get_xdata_values(<<"muc#request_allow">>, X)} of - {[<<"http://jabber.org/protocol/muc#request">>], - [<<"participant">>], [Flag]} when Flag == <<"true">>; - Flag == <<"1">> -> - true; - _ -> - false - end; - (_) -> - false - end, Els). - --spec extract_jid_from_voice_approvement(message()) -> jid() | error. -extract_jid_from_voice_approvement(Packet) -> - Els = xmpp:get_els(Packet), - lists:foldl( - fun(#xdata{} = X, error) -> - case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X), - xmpp_util:get_xdata_values(<<"muc#role">>, X), - xmpp_util:get_xdata_values(<<"muc#request_allow">>, X), - xmpp_util:get_xdata_values(<<"muc#jid">>, X)} of - {[<<"http://jabber.org/protocol/muc#request">>], - [<<"participant">>], [Flag], [J]} when Flag == <<"true">>; - Flag == <<"1">> -> - jid:from_string(J); - _ -> - error - end; - (_, Acc) -> - Acc - end, error, Els). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Invitation support --spec is_invitation(message()) -> boolean(). -is_invitation(Packet) -> - Els = xmpp:get_els(Packet), - lists:any( - fun(#muc_user{invites = [_|_]}) -> true; - (_) -> false - end, Els). - --spec check_invitation(jid(), message(), state()) -> {error, stanza_error()} | jid(). -check_invitation(From, Packet, StateData) -> - Lang = xmpp:get_lang(Packet), +-spec check_invitation(jid(), muc_invite(), binary(), state()) -> {error, stanza_error()} | jid(). +check_invitation(From, Invitation, Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), CanInvite = (StateData#state.config)#config.allow_user_invites orelse @@ -4217,57 +3861,46 @@ check_invitation(From, Packet, StateData) -> Txt = <<"Invitations are not allowed in this conference">>, {error, xmpp:err_not_allowed(Txt, Lang)}; true -> - case xmpp:get_subtag(Packet, #muc_user{}) of - #muc_user{invites = [#muc_invite{to = undefined}]} -> - Txt = <<"No 'to' attribute found">>, - {error, xmpp:err_bad_request(Txt, Lang)}; - #muc_user{invites = [#muc_invite{to = JID, reason = Reason} = I]} -> - Invite = I#muc_invite{to = undefined, from = From}, - Password = case (StateData#state.config)#config.password_protected of - true -> - (StateData#state.config)#config.password; - false -> - undefined - end, - XUser = #muc_user{password = Password, invites = [Invite]}, - XConference = #x_conference{jid = jid:make(StateData#state.room, - StateData#state.host), - reason = Reason}, - Body = iolist_to_binary( - [io_lib:format( - translate:translate( - Lang, - <<"~s invites you to the room ~s">>), - [jid:to_string(From), - 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 = #message{type = normal, - body = xmpp:mk_text(Body), - sub_els = [XUser, XConference]}, - ejabberd_router:route(StateData#state.jid, JID, Msg), - JID; - #muc_user{invites = [_|_]} -> - Txt = <<"Multiple <invite/> elements are not allowed">>, - {error, xmpp:err_forbidden(Txt, Lang)}; - _ -> - Txt = <<"No <invite/> element found">>, - {error, xmpp:err_bad_request(Txt, Lang)} - end + #muc_invite{to = JID, reason = Reason} = Invitation, + Invite = Invitation#muc_invite{to = undefined, from = From}, + Password = case (StateData#state.config)#config.password_protected of + true -> + (StateData#state.config)#config.password; + false -> + undefined + end, + XUser = #muc_user{password = Password, invites = [Invite]}, + XConference = #x_conference{jid = jid:make(StateData#state.room, + StateData#state.host), + reason = Reason}, + Body = iolist_to_binary( + [io_lib:format( + translate:translate( + Lang, + <<"~s invites you to the room ~s">>), + [jid:to_string(From), + 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 = #message{type = normal, + body = xmpp:mk_text(Body), + sub_els = [XUser, XConference]}, + ejabberd_router:route(StateData#state.jid, JID, Msg), + JID end. %% Handle a message sent to the room by a non-participant. |