Improve validation of arguments in mod_muc_admin commands

This adds validation to couple command where they were missing and catch
passing unknown hostnames.
This commit is contained in:
Paweł Chmielowski 2024-02-28 12:01:14 +01:00
parent ad67710f7e
commit 06675e4fb2
1 changed files with 141 additions and 87 deletions

View File

@ -491,7 +491,7 @@ get_commands_spec() ->
%%% %%%
muc_online_rooms(ServiceArg) -> muc_online_rooms(ServiceArg) ->
Hosts = find_services(ServiceArg), Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
lists:flatmap( lists:flatmap(
fun(Host) -> fun(Host) ->
[<<Name/binary, "@", Host/binary>> [<<Name/binary, "@", Host/binary>>
@ -500,7 +500,7 @@ muc_online_rooms(ServiceArg) ->
muc_online_rooms_by_regex(ServiceArg, Regex) -> muc_online_rooms_by_regex(ServiceArg, Regex) ->
{_, P} = re:compile(Regex), {_, P} = re:compile(Regex),
Hosts = find_services(ServiceArg), Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
lists:flatmap( lists:flatmap(
fun(Host) -> fun(Host) ->
[build_summary_room(Name, RoomHost, Pid) [build_summary_room(Name, RoomHost, Pid)
@ -537,9 +537,9 @@ muc_register_nick(Nick, FromBinary, Service) ->
end end
catch catch
error:{invalid_domain, _} -> error:{invalid_domain, _} ->
throw({error, "Invalid 'service'"}); throw({error, "Invalid value of 'service'"});
error:{unregistered_route, _} -> error:{unregistered_route, _} ->
throw({error, "Invalid 'service'"}); throw({error, "Unknown host in 'service'"});
error:{bad_jid, _} -> error:{bad_jid, _} ->
throw({error, "Invalid 'jid'"}); throw({error, "Invalid 'jid'"});
_ -> _ ->
@ -564,8 +564,10 @@ get_user_rooms(User, Server) ->
end, ejabberd_option:hosts()). end, ejabberd_option:hosts()).
get_user_subscriptions(User, Server) -> get_user_subscriptions(User, Server) ->
User2 = validate_muc(User, <<"user">>),
Server2 = validate_host(Server, <<"host">>),
Services = find_services(global), Services = find_services(global),
UserJid = jid:make(jid:nodeprep(User), jid:nodeprep(Server)), UserJid = jid:make(User2, Server2),
lists:flatmap( lists:flatmap(
fun(ServerHost) -> fun(ServerHost) ->
{ok, Rooms} = mod_muc:get_subscribed_rooms(ServerHost, UserJid), {ok, Rooms} = mod_muc:get_subscribed_rooms(ServerHost, UserJid),
@ -779,34 +781,24 @@ create_room(Name1, Host1, ServerHost) ->
create_room_with_opts(Name1, Host1, ServerHost, []). create_room_with_opts(Name1, Host1, ServerHost, []).
create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) -> create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
case {jid:nodeprep(Name1), jid:nodeprep(Host1), jid:nodeprep(ServerHost1)} of ServerHost = validate_host(ServerHost1, <<"serverhost">>),
{error, _, _} -> case get_room_pid_validate(Name1, Host1, <<"name">>, <<"host">>) of
throw({error, "Invalid 'name'"}); {room_not_found, Name, Host} ->
{_, error, _} -> %% Get the default room options from the muc configuration
throw({error, "Invalid 'host'"}); DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
{_, _, error} -> %% Change default room options as required
throw({error, "Invalid 'serverhost'"}); FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts],
{Name, Host, ServerHost} -> RoomOpts = lists:ukeymerge(1,
case get_room_pid(Name, Host) of lists:keysort(1, FormattedRoomOpts),
room_not_found -> lists:keysort(1, DefRoomOpts)),
%% Get the default room options from the muc configuration case mod_muc:create_room(Host, Name, RoomOpts) of
DefRoomOpts = mod_muc_opt:default_room_options(ServerHost), ok ->
%% Change default room options as required ok;
FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts], {error, _} ->
RoomOpts = lists:ukeymerge(1, throw({error, "Unable to start room"})
lists:keysort(1, FormattedRoomOpts), end;
lists:keysort(1, DefRoomOpts)), _ ->
case mod_muc:create_room(Host, Name, RoomOpts) of throw({error, "Room already exists"})
ok ->
ok;
{error, _} ->
throw({error, "Unable to start room"})
end;
invalid_service ->
throw({error, "Invalid 'service'"});
_ ->
throw({error, "Room already exists"})
end
end. end.
%% Create the room only in the database. %% Create the room only in the database.
@ -820,21 +812,12 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
%% If the room has participants, they are not notified that the room was destroyed; %% If the room has participants, they are not notified that the room was destroyed;
%% they will notice when they try to chat and receive an error that the room doesn't exist. %% they will notice when they try to chat and receive an error that the room doesn't exist.
destroy_room(Name1, Service1) -> destroy_room(Name1, Service1) ->
case {jid:nodeprep(Name1), jid:nodeprep(Service1)} of case get_room_pid_validate(Name1, Service1, <<"name">>, <<"service">>) of
{error, _} -> {room_not_found, _, _} ->
throw({error, "Invalid 'name'"}); throw({error, "Room doesn't exists"});
{_, error} -> {Pid, _, _} ->
throw({error, "Invalid 'service'"}); mod_muc_room:destroy(Pid),
{Name, Service} -> ok
case get_room_pid(Name, Service) of
room_not_found ->
throw({error, "Room doesn't exists"});
invalid_service ->
throw({error, "Invalid 'service'"});
Pid ->
mod_muc_room:destroy(Pid),
ok
end
end. end.
destroy_room({N, H, SH}) -> destroy_room({N, H, SH}) ->
@ -1101,8 +1084,8 @@ act_on_room(_Method, list, _) ->
%%---------------------------- %%----------------------------
get_room_occupants(Room, Host) -> get_room_occupants(Room, Host) ->
case get_room_pid(Room, Host) of case get_room_pid_validate(Room, Host, <<"name">>, <<"service">>) of
Pid when is_pid(Pid) -> get_room_occupants(Pid); {Pid, _, _} when is_pid(Pid) -> get_room_occupants(Pid);
_ -> throw({error, room_not_found}) _ -> throw({error, room_not_found})
end. end.
@ -1117,8 +1100,8 @@ get_room_occupants(Pid) ->
maps:to_list(S#state.users)). maps:to_list(S#state.users)).
get_room_occupants_number(Room, Host) -> get_room_occupants_number(Room, Host) ->
case get_room_pid(Room, Host) of case get_room_pid_validate(Room, Host, <<"name">>, <<"service">>) of
Pid when is_pid(Pid )-> {Pid, _, _} when is_pid(Pid )->
{ok, #{occupants_number := N}} = mod_muc_room:get_info(Pid), {ok, #{occupants_number := N}} = mod_muc_room:get_info(Pid),
N; N;
_ -> _ ->
@ -1194,12 +1177,10 @@ send_direct_invitation(FromJid, UserJid, Msg) ->
%% For example: %% For example:
%% `change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)' %% `change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)'
change_room_option(Name, Service, OptionString, ValueString) -> change_room_option(Name, Service, OptionString, ValueString) ->
case get_room_pid(Name, Service) of case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
room_not_found -> {room_not_found, _, _} ->
throw({error, "Room not found"}); throw({error, "Room not found"});
invalid_service -> {Pid, _, _} ->
throw({error, "Invalid 'service'"});
Pid ->
{Option, Value} = format_room_option(OptionString, ValueString), {Option, Value} = format_room_option(OptionString, ValueString),
change_room_option(Pid, Option, Value) change_room_option(Pid, Option, Value)
end. end.
@ -1293,8 +1274,20 @@ parse_nodes([<<"subscribers">> | Rest], Acc) ->
parse_nodes(_, _) -> parse_nodes(_, _) ->
throw({error, "Invalid 'subscribers' - unknown node name used"}). throw({error, "Invalid 'subscribers' - unknown node name used"}).
-spec get_room_pid_validate(binary(), binary(), binary(), binary()) ->
{pid() | room_not_found, binary(), binary()}.
get_room_pid_validate(Name, Service, NameArg, ServiceArg) ->
Name2 = validate_room(Name, NameArg),
{ServerHost, Service2} = validate_muc2(Service, ServiceArg),
case mod_muc:unhibernate_room(ServerHost, Service2, Name2) of
error ->
{room_not_found, Name2, Service2};
{ok, Pid} ->
{Pid, Name2, Service2}
end.
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'. %% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
-spec get_room_pid(binary(), binary()) -> pid() | room_not_found | invalid_service. -spec get_room_pid(binary(), binary()) -> pid() | room_not_found | invalid_service | unknown_service.
get_room_pid(Name, Service) -> get_room_pid(Name, Service) ->
try get_room_serverhost(Service) of try get_room_serverhost(Service) of
ServerHost -> ServerHost ->
@ -1308,7 +1301,7 @@ get_room_pid(Name, Service) ->
error:{invalid_domain, _} -> error:{invalid_domain, _} ->
invalid_service; invalid_service;
error:{unregistered_route, _} -> error:{unregistered_route, _} ->
invalid_service unknown_service
end. end.
room_diagnostics(Name, Service) -> room_diagnostics(Name, Service) ->
@ -1330,7 +1323,7 @@ room_diagnostics(Name, Service) ->
error:{invalid_domain, _} -> error:{invalid_domain, _} ->
invalid_service; invalid_service;
error:{unregistered_route, _} -> error:{unregistered_route, _} ->
invalid_service unknown_service
end. end.
%% It is required to put explicitly all the options because %% It is required to put explicitly all the options because
@ -1375,8 +1368,8 @@ change_option(Option, Value, Config) ->
%%---------------------------- %%----------------------------
get_room_options(Name, Service) -> get_room_options(Name, Service) ->
case get_room_pid(Name, Service) of case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
Pid when is_pid(Pid) -> get_room_options(Pid); {Pid, _, _} when is_pid(Pid) -> get_room_options(Pid);
_ -> [] _ -> []
end. end.
@ -1401,8 +1394,8 @@ get_options(Config) ->
%% [{JID::string(), Domain::string(), Role::string(), Reason::string()}] %% [{JID::string(), Domain::string(), Role::string(), Reason::string()}]
%% @doc Get the affiliations of the room Name@Service. %% @doc Get the affiliations of the room Name@Service.
get_room_affiliations(Name, Service) -> get_room_affiliations(Name, Service) ->
case get_room_pid(Name, Service) of case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
Pid when is_pid(Pid) -> {Pid, _, _} when is_pid(Pid) ->
%% Get the PID of the online room, then request its state %% Get the PID of the online room, then request its state
{ok, StateData} = mod_muc_room:get_state(Pid), {ok, StateData} = mod_muc_room:get_state(Pid),
Affiliations = maps:to_list(StateData#state.affiliations), Affiliations = maps:to_list(StateData#state.affiliations),
@ -1417,8 +1410,8 @@ get_room_affiliations(Name, Service) ->
end. end.
get_room_history(Name, Service) -> get_room_history(Name, Service) ->
case get_room_pid(Name, Service) of case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
Pid when is_pid(Pid) -> {Pid, _, _} when is_pid(Pid) ->
case mod_muc_room:get_state(Pid) of case mod_muc_room:get_state(Pid) of
{ok, StateData} -> {ok, StateData} ->
History = p1_queue:to_list((StateData#state.history)#lqueue.queue), History = p1_queue:to_list((StateData#state.history)#lqueue.queue),
@ -1442,15 +1435,15 @@ get_room_history(Name, Service) ->
%% @doc Get affiliation of a user in the room Name@Service. %% @doc Get affiliation of a user in the room Name@Service.
get_room_affiliation(Name, Service, JID) -> get_room_affiliation(Name, Service, JID) ->
case get_room_pid(Name, Service) of case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
Pid when is_pid(Pid) -> {Pid, _, _} when is_pid(Pid) ->
%% Get the PID of the online room, then request its state %% Get the PID of the online room, then request its state
{ok, StateData} = mod_muc_room:get_state(Pid), {ok, StateData} = mod_muc_room:get_state(Pid),
UserJID = jid:decode(JID), UserJID = jid:decode(JID),
mod_muc_room:get_affiliation(UserJID, StateData); mod_muc_room:get_affiliation(UserJID, StateData);
_ -> _ ->
throw({error, "The room does not exist."}) throw({error, "The room does not exist."})
end. end.
%%---------------------------- %%----------------------------
%% Change Room Affiliation %% Change Room Affiliation
@ -1474,8 +1467,8 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
_ -> _ ->
throw({error, "Invalid affiliation"}) throw({error, "Invalid affiliation"})
end, end,
case get_room_pid(Name, Service) of case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
Pid when is_pid(Pid) -> {Pid, _, _} when is_pid(Pid) ->
%% Get the PID for the online room so we can get the state of the room %% Get the PID for the online room so we can get the state of the room
case mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>) of case mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>) of
{ok, _} -> {ok, _} ->
@ -1485,10 +1478,8 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
{error, _} -> {error, _} ->
throw({error, "Unable to perform change"}) throw({error, "Unable to perform change"})
end; end;
room_not_found -> _ ->
throw({error, "Room doesn't exists"}); throw({error, "Room doesn't exists"})
invalid_service ->
throw({error, "Invalid 'service'"})
end. end.
%%% %%%
@ -1506,8 +1497,8 @@ subscribe_room(User, Nick, Room, NodeList) ->
try jid:decode(User) of try jid:decode(User) of
UserJID1 -> UserJID1 ->
UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>), UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>),
case get_room_pid(Name, Host) of case get_room_pid_validate(Name, Host, <<"name">>, <<"room">>) of
Pid when is_pid(Pid) -> {Pid, _, _} when is_pid(Pid) ->
case mod_muc_room:subscribe( case mod_muc_room:subscribe(
Pid, UserJID, Nick, NodeList) of Pid, UserJID, Nick, NodeList) of
{ok, SubscribedNodes} -> {ok, SubscribedNodes} ->
@ -1544,8 +1535,8 @@ unsubscribe_room(User, Room) ->
#jid{luser = Name, lserver = Host} when Name /= <<"">> -> #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
try jid:decode(User) of try jid:decode(User) of
UserJID -> UserJID ->
case get_room_pid(Name, Host) of case get_room_pid_validate(Name, Host, <<"name">>, <<"room">>) of
Pid when is_pid(Pid) -> {Pid, _, _} when is_pid(Pid) ->
case mod_muc_room:unsubscribe(Pid, UserJID) of case mod_muc_room:unsubscribe(Pid, UserJID) of
ok -> ok ->
ok; ok;
@ -1565,8 +1556,8 @@ unsubscribe_room(User, Room) ->
end. end.
get_subscribers(Name, Host) -> get_subscribers(Name, Host) ->
case get_room_pid(Name, Host) of case get_room_pid_validate(Name, Host, <<"name">>, <<"service">>) of
Pid when is_pid(Pid) -> {Pid, _, _} when is_pid(Pid) ->
{ok, JIDList} = mod_muc_room:get_subscribers(Pid), {ok, JIDList} = mod_muc_room:get_subscribers(Pid),
[jid:encode(jid:remove_resource(J)) || J <- JIDList]; [jid:encode(jid:remove_resource(J)) || J <- JIDList];
_ -> _ ->
@ -1577,11 +1568,74 @@ get_subscribers(Name, Host) ->
%% Utils %% Utils
%%---------------------------- %%----------------------------
-spec validate_host(Name :: binary(), ArgName::binary()) -> binary().
validate_host(Name, ArgName) ->
case jid:nameprep(Name) of
error ->
throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
Name2 ->
case lists:member(Name2, ejabberd_config:get_myhosts()) of
false ->
throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
_ ->
Name2
end
end.
-spec validate_muc(Name :: binary(), ArgName::binary()) -> binary().
validate_muc(Name, ArgName) ->
case jid:nameprep(Name) of
error ->
throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
Name2 ->
try get_room_serverhost(Name2) of
_ -> Name2
catch
error:{invalid_domain, _} ->
throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
error:{unregistered_route, _} ->
throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
end
end.
-spec validate_muc2(Name :: binary(), ArgName::binary()) -> {binary(), binary()}.
validate_muc2(Name, ArgName) ->
case jid:nameprep(Name) of
error ->
throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
Name2 ->
try get_room_serverhost(Name2) of
Host -> {Host, Name2}
catch
error:{invalid_domain, _} ->
throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
error:{unregistered_route, _} ->
throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
end
end.
-spec validate_room(Name :: binary(), ArgName :: binary()) -> binary().
validate_room(Name, ArgName) ->
case jid:nodeprep(Name) of
error ->
throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
Name2 ->
Name2
end.
find_service(global) -> find_service(global) ->
global; global;
find_service(ServerHost) -> find_service(ServerHost) ->
hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)). hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
find_services_validate(Global, _Name) when Global == global;
Global == <<"global">> ->
find_services(Global);
find_services_validate(Service, Name) ->
case validate_muc(Service, Name) of
Service2 -> find_services(Service2)
end.
find_services(Global) when Global == global; find_services(Global) when Global == global;
Global == <<"global">> -> Global == <<"global">> ->
lists:flatmap( lists:flatmap(