Add support to register nick in a room (#3455)
Registering a nick in the MUC service or in a room is mutually exclusive: - A nick that is registered in the service cannot be registered in any room, not even the original owner can register it. - Similarly, a nick registered in any room cannot be registered in the service.
This commit is contained in:
parent
9534ca2da1
commit
10245b40ee
|
@ -51,6 +51,7 @@
|
||||||
process_disco_items/1,
|
process_disco_items/1,
|
||||||
process_vcard/1,
|
process_vcard/1,
|
||||||
process_register/1,
|
process_register/1,
|
||||||
|
process_iq_register/1,
|
||||||
process_muc_unique/1,
|
process_muc_unique/1,
|
||||||
process_mucsub/1,
|
process_mucsub/1,
|
||||||
broadcast_service_message/3,
|
broadcast_service_message/3,
|
||||||
|
@ -675,29 +676,33 @@ process_vcard(#iq{lang = Lang} = IQ) ->
|
||||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
||||||
|
|
||||||
-spec process_register(iq()) -> iq().
|
-spec process_register(iq()) -> iq().
|
||||||
process_register(#iq{type = Type, from = From, to = To, lang = Lang,
|
process_register(IQ) ->
|
||||||
sub_els = [El = #register{}]} = IQ) ->
|
case process_iq_register(IQ) of
|
||||||
|
{result, Result} ->
|
||||||
|
xmpp:make_iq_result(IQ, Result);
|
||||||
|
{error, Err} ->
|
||||||
|
xmpp:make_error(IQ, Err)
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec process_iq_register(iq()) -> {result, register()} | {error, stanza_error()}.
|
||||||
|
process_iq_register(#iq{type = Type, from = From, to = To, lang = Lang,
|
||||||
|
sub_els = [El = #register{}]}) ->
|
||||||
Host = To#jid.lserver,
|
Host = To#jid.lserver,
|
||||||
|
RegisterDestination = jid:encode(To),
|
||||||
ServerHost = ejabberd_router:host_of_route(Host),
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
AccessRegister = mod_muc_opt:access_register(ServerHost),
|
AccessRegister = mod_muc_opt:access_register(ServerHost),
|
||||||
case acl:match_rule(ServerHost, AccessRegister, From) of
|
case acl:match_rule(ServerHost, AccessRegister, From) of
|
||||||
allow ->
|
allow ->
|
||||||
case Type of
|
case Type of
|
||||||
get ->
|
get ->
|
||||||
xmpp:make_iq_result(
|
{result, iq_get_register_info(ServerHost, RegisterDestination, From, Lang)};
|
||||||
IQ, iq_get_register_info(ServerHost, Host, From, Lang));
|
|
||||||
set ->
|
set ->
|
||||||
case process_iq_register_set(ServerHost, Host, From, El, Lang) of
|
process_iq_register_set(ServerHost, RegisterDestination, From, El, Lang)
|
||||||
{result, Result} ->
|
|
||||||
xmpp:make_iq_result(IQ, Result);
|
|
||||||
{error, Err} ->
|
|
||||||
xmpp:make_error(IQ, Err)
|
|
||||||
end
|
|
||||||
end;
|
end;
|
||||||
deny ->
|
deny ->
|
||||||
ErrText = ?T("Access denied by service policy"),
|
ErrText = ?T("Access denied by service policy"),
|
||||||
Err = xmpp:err_forbidden(ErrText, Lang),
|
Err = xmpp:err_forbidden(ErrText, Lang),
|
||||||
xmpp:make_error(IQ, Err)
|
{error, Err}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec process_disco_info(iq()) -> iq().
|
-spec process_disco_info(iq()) -> iq().
|
||||||
|
@ -1416,6 +1421,11 @@ mod_doc() ->
|
||||||
"nobody else can use that nickname in any room in the MUC "
|
"nobody else can use that nickname in any room in the MUC "
|
||||||
"service. To register a nickname, open the Service Discovery in "
|
"service. To register a nickname, open the Service Discovery in "
|
||||||
"your XMPP client and register in the MUC service."), "",
|
"your XMPP client and register in the MUC service."), "",
|
||||||
|
?T("It is also possible to register a nickname in a room, so "
|
||||||
|
"nobody else can use that nickname in that room. If a nick is "
|
||||||
|
"registered in the MUC service, that nick cannot be registered in "
|
||||||
|
"any room, and vice versa: a nick that is registered in a room "
|
||||||
|
"cannot be registered at the MUC service."), "",
|
||||||
?T("This module supports clustering and load balancing. One module "
|
?T("This module supports clustering and load balancing. One module "
|
||||||
"can be started per cluster node. Rooms are distributed at "
|
"can be started per cluster node. Rooms are distributed at "
|
||||||
"creation time on all available MUC module instances. The "
|
"creation time on all available MUC module instances. The "
|
||||||
|
@ -1461,11 +1471,12 @@ mod_doc() ->
|
||||||
"modify that option.")}},
|
"modify that option.")}},
|
||||||
{access_register,
|
{access_register,
|
||||||
#{value => ?T("AccessName"),
|
#{value => ?T("AccessName"),
|
||||||
|
note => "improved in 23.xx",
|
||||||
desc =>
|
desc =>
|
||||||
?T("This option specifies who is allowed to register nickname "
|
?T("This option specifies who is allowed to register nickname "
|
||||||
"within the Multi-User Chat service. The default is 'all' for "
|
"within the Multi-User Chat service and rooms. The default is 'all' for "
|
||||||
"backward compatibility, which means that any user is allowed "
|
"backward compatibility, which means that any user is allowed "
|
||||||
"to register any free nick.")}},
|
"to register any free nick in the MUC service and in the rooms.")}},
|
||||||
{db_type,
|
{db_type,
|
||||||
#{value => "mnesia | sql",
|
#{value => "mnesia | sql",
|
||||||
desc =>
|
desc =>
|
||||||
|
|
|
@ -82,13 +82,19 @@ forget_room(_LServer, Host, Name) ->
|
||||||
end,
|
end,
|
||||||
mnesia:transaction(F).
|
mnesia:transaction(F).
|
||||||
|
|
||||||
can_use_nick(_LServer, Host, JID, Nick) ->
|
can_use_nick(_LServer, ServiceOrRoom, JID, Nick) ->
|
||||||
{LUser, LServer, _} = jid:tolower(JID),
|
{LUser, LServer, _} = jid:tolower(JID),
|
||||||
LUS = {LUser, LServer},
|
LUS = {LUser, LServer},
|
||||||
|
MatchSpec = case (jid:decode(ServiceOrRoom))#jid.lserver of
|
||||||
|
ServiceOrRoom -> [{'==', {element, 2, '$1'}, ServiceOrRoom}];
|
||||||
|
Service -> [{'orelse',
|
||||||
|
{'==', {element, 2, '$1'}, Service},
|
||||||
|
{'==', {element, 2, '$1'}, ServiceOrRoom} }]
|
||||||
|
end,
|
||||||
case catch mnesia:dirty_select(muc_registered,
|
case catch mnesia:dirty_select(muc_registered,
|
||||||
[{#muc_registered{us_host = '$1',
|
[{#muc_registered{us_host = '$1',
|
||||||
nick = Nick, _ = '_'},
|
nick = Nick, _ = '_'},
|
||||||
[{'==', {element, 2, '$1'}, Host}],
|
MatchSpec,
|
||||||
['$_']}])
|
['$_']}])
|
||||||
of
|
of
|
||||||
{'EXIT', _Reason} -> true;
|
{'EXIT', _Reason} -> true;
|
||||||
|
@ -110,31 +116,46 @@ get_nick(_LServer, Host, From) ->
|
||||||
[#muc_registered{nick = Nick}] -> Nick
|
[#muc_registered{nick = Nick}] -> Nick
|
||||||
end.
|
end.
|
||||||
|
|
||||||
set_nick(_LServer, Host, From, Nick) ->
|
set_nick(_LServer, ServiceOrRoom, From, Nick) ->
|
||||||
{LUser, LServer, _} = jid:tolower(From),
|
{LUser, LServer, _} = jid:tolower(From),
|
||||||
LUS = {LUser, LServer},
|
LUS = {LUser, LServer},
|
||||||
F = fun () ->
|
F = fun () ->
|
||||||
case Nick of
|
case Nick of
|
||||||
<<"">> ->
|
<<"">> ->
|
||||||
mnesia:delete({muc_registered, {LUS, Host}}),
|
mnesia:delete({muc_registered, {LUS, ServiceOrRoom}}),
|
||||||
ok;
|
ok;
|
||||||
_ ->
|
_ ->
|
||||||
|
Service = (jid:decode(ServiceOrRoom))#jid.lserver,
|
||||||
|
MatchSpec = case (ServiceOrRoom == Service) of
|
||||||
|
true -> [{'==', {element, 2, '$1'}, ServiceOrRoom}];
|
||||||
|
false -> [{'orelse',
|
||||||
|
{'==', {element, 2, '$1'}, Service},
|
||||||
|
{'==', {element, 2, '$1'}, ServiceOrRoom} }]
|
||||||
|
end,
|
||||||
Allow = case mnesia:select(
|
Allow = case mnesia:select(
|
||||||
muc_registered,
|
muc_registered,
|
||||||
[{#muc_registered{us_host =
|
[{#muc_registered{us_host = '$1', nick = Nick, _ = '_'},
|
||||||
'$1',
|
MatchSpec,
|
||||||
nick = Nick,
|
|
||||||
_ = '_'},
|
|
||||||
[{'==', {element, 2, '$1'},
|
|
||||||
Host}],
|
|
||||||
['$_']}]) of
|
['$_']}]) of
|
||||||
|
[] when (ServiceOrRoom == Service) ->
|
||||||
|
NickRegistrations = mnesia:select(
|
||||||
|
muc_registered,
|
||||||
|
[{#muc_registered{us_host = '$1', nick = Nick, _ = '_'},
|
||||||
|
[],
|
||||||
|
['$_']}]),
|
||||||
|
not lists:any(fun({_, {_NRUS, NRServiceOrRoom}, _Nick}) ->
|
||||||
|
Service == (jid:decode(NRServiceOrRoom))#jid.lserver end,
|
||||||
|
NickRegistrations);
|
||||||
[] -> true;
|
[] -> true;
|
||||||
|
[#muc_registered{us_host = {_U, Host}}]
|
||||||
|
when (Host == Service) and (ServiceOrRoom /= Service) ->
|
||||||
|
false;
|
||||||
[#muc_registered{us_host = {U, _Host}}] ->
|
[#muc_registered{us_host = {U, _Host}}] ->
|
||||||
U == LUS
|
U == LUS
|
||||||
end,
|
end,
|
||||||
if Allow ->
|
if Allow ->
|
||||||
mnesia:write(#muc_registered{
|
mnesia:write(#muc_registered{
|
||||||
us_host = {LUS, Host},
|
us_host = {LUS, ServiceOrRoom},
|
||||||
nick = Nick}),
|
nick = Nick}),
|
||||||
ok;
|
ok;
|
||||||
true ->
|
true ->
|
||||||
|
|
|
@ -501,6 +501,8 @@ normal_state({route, <<"">>,
|
||||||
process_iq_captcha(From, IQ, StateData);
|
process_iq_captcha(From, IQ, StateData);
|
||||||
#adhoc_command{} ->
|
#adhoc_command{} ->
|
||||||
process_iq_adhoc(From, IQ, StateData);
|
process_iq_adhoc(From, IQ, StateData);
|
||||||
|
#register{} ->
|
||||||
|
mod_muc:process_iq_register(IQ);
|
||||||
#fasten_apply_to{} = ApplyTo ->
|
#fasten_apply_to{} = ApplyTo ->
|
||||||
case xmpp:get_subtag(ApplyTo, #message_moderate{}) of
|
case xmpp:get_subtag(ApplyTo, #message_moderate{}) of
|
||||||
#message_moderate{} = Moderate ->
|
#message_moderate{} = Moderate ->
|
||||||
|
@ -1406,7 +1408,7 @@ do_process_presence(Nick, #presence{from = From, type = available, lang = Lang}
|
||||||
true ->
|
true ->
|
||||||
case {nick_collision(From, Nick, StateData),
|
case {nick_collision(From, Nick, StateData),
|
||||||
mod_muc:can_use_nick(StateData#state.server_host,
|
mod_muc:can_use_nick(StateData#state.server_host,
|
||||||
StateData#state.host,
|
jid:encode(StateData#state.jid),
|
||||||
From, Nick),
|
From, Nick),
|
||||||
{(StateData#state.config)#config.allow_visitor_nickchange,
|
{(StateData#state.config)#config.allow_visitor_nickchange,
|
||||||
is_visitor(From, StateData)}} of
|
is_visitor(From, StateData)}} of
|
||||||
|
@ -2290,7 +2292,7 @@ add_new_user(From, Nick, Packet, StateData) ->
|
||||||
andalso NConferences < MaxConferences),
|
andalso NConferences < MaxConferences),
|
||||||
Collision,
|
Collision,
|
||||||
mod_muc:can_use_nick(StateData#state.server_host,
|
mod_muc:can_use_nick(StateData#state.server_host,
|
||||||
StateData#state.host, From, Nick),
|
jid:encode(StateData#state.jid), From, Nick),
|
||||||
get_occupant_initial_role(From, Affiliation, StateData)}
|
get_occupant_initial_role(From, Affiliation, StateData)}
|
||||||
of
|
of
|
||||||
{false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers ->
|
{false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers ->
|
||||||
|
@ -4385,8 +4387,10 @@ maybe_forget_room(StateData) ->
|
||||||
end).
|
end).
|
||||||
|
|
||||||
-spec make_disco_info(jid(), state()) -> disco_info().
|
-spec make_disco_info(jid(), state()) -> disco_info().
|
||||||
make_disco_info(_From, StateData) ->
|
make_disco_info(From, StateData) ->
|
||||||
Config = StateData#state.config,
|
Config = StateData#state.config,
|
||||||
|
ServerHost = StateData#state.server_host,
|
||||||
|
AccessRegister = mod_muc_opt:access_register(ServerHost),
|
||||||
Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||||
?NS_COMMANDS, ?NS_MESSAGE_MODERATE, ?NS_MESSAGE_RETRACT,
|
?NS_COMMANDS, ?NS_MESSAGE_MODERATE, ?NS_MESSAGE_RETRACT,
|
||||||
?CONFIG_OPT_TO_FEATURE((Config#config.public),
|
?CONFIG_OPT_TO_FEATURE((Config#config.public),
|
||||||
|
@ -4401,6 +4405,10 @@ make_disco_info(_From, StateData) ->
|
||||||
<<"muc_moderated">>, <<"muc_unmoderated">>),
|
<<"muc_moderated">>, <<"muc_unmoderated">>),
|
||||||
?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
|
?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
|
||||||
<<"muc_passwordprotected">>, <<"muc_unsecured">>)]
|
<<"muc_passwordprotected">>, <<"muc_unsecured">>)]
|
||||||
|
++ case acl:match_rule(ServerHost, AccessRegister, From) of
|
||||||
|
allow -> [?NS_REGISTER];
|
||||||
|
deny -> []
|
||||||
|
end
|
||||||
++ case Config#config.allow_subscription of
|
++ case Config#config.allow_subscription of
|
||||||
true -> [?NS_MUCSUB];
|
true -> [?NS_MUCSUB];
|
||||||
false -> []
|
false -> []
|
||||||
|
@ -4703,7 +4711,7 @@ process_iq_mucsub(From,
|
||||||
{error, xmpp:err_conflict(ErrText, Lang)};
|
{error, xmpp:err_conflict(ErrText, Lang)};
|
||||||
false ->
|
false ->
|
||||||
case mod_muc:can_use_nick(StateData#state.server_host,
|
case mod_muc:can_use_nick(StateData#state.server_host,
|
||||||
StateData#state.host,
|
jid:encode(StateData#state.jid),
|
||||||
From, Nick) of
|
From, Nick) of
|
||||||
false ->
|
false ->
|
||||||
Err = case Nick of
|
Err = case Nick of
|
||||||
|
|
|
@ -159,13 +159,19 @@ forget_room(LServer, Host, Name) ->
|
||||||
end,
|
end,
|
||||||
ejabberd_sql:sql_transaction(LServer, F).
|
ejabberd_sql:sql_transaction(LServer, F).
|
||||||
|
|
||||||
can_use_nick(LServer, Host, JID, Nick) ->
|
can_use_nick(LServer, ServiceOrRoom, JID, Nick) ->
|
||||||
SJID = jid:encode(jid:tolower(jid:remove_resource(JID))),
|
SJID = jid:encode(jid:tolower(jid:remove_resource(JID))),
|
||||||
case catch ejabberd_sql:sql_query(
|
SqlQuery = case (jid:decode(ServiceOrRoom))#jid.lserver of
|
||||||
LServer,
|
ServiceOrRoom ->
|
||||||
?SQL("select @(jid)s from muc_registered "
|
?SQL("select @(jid)s from muc_registered "
|
||||||
"where nick=%(Nick)s"
|
"where nick=%(Nick)s"
|
||||||
" and host=%(Host)s")) of
|
" and host=%(ServiceOrRoom)s");
|
||||||
|
Service ->
|
||||||
|
?SQL("select @(jid)s from muc_registered "
|
||||||
|
"where nick=%(Nick)s"
|
||||||
|
" and (host=%(ServiceOrRoom)s or host=%(Service)s)")
|
||||||
|
end,
|
||||||
|
case catch ejabberd_sql:sql_query(LServer, SqlQuery) of
|
||||||
{selected, [{SJID1}]} -> SJID == SJID1;
|
{selected, [{SJID1}]} -> SJID == SJID1;
|
||||||
_ -> true
|
_ -> true
|
||||||
end.
|
end.
|
||||||
|
@ -258,28 +264,57 @@ get_nick(LServer, Host, From) ->
|
||||||
_ -> error
|
_ -> error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
set_nick(LServer, Host, From, Nick) ->
|
set_nick(LServer, ServiceOrRoom, From, Nick) ->
|
||||||
JID = jid:encode(jid:tolower(jid:remove_resource(From))),
|
JID = jid:encode(jid:tolower(jid:remove_resource(From))),
|
||||||
F = fun () ->
|
F = fun () ->
|
||||||
case Nick of
|
case Nick of
|
||||||
<<"">> ->
|
<<"">> ->
|
||||||
ejabberd_sql:sql_query_t(
|
ejabberd_sql:sql_query_t(
|
||||||
?SQL("delete from muc_registered where"
|
?SQL("delete from muc_registered where"
|
||||||
" jid=%(JID)s and host=%(Host)s")),
|
" jid=%(JID)s and host=%(ServiceOrRoom)s")),
|
||||||
ok;
|
ok;
|
||||||
_ ->
|
_ ->
|
||||||
Allow = case ejabberd_sql:sql_query_t(
|
Service = (jid:decode(ServiceOrRoom))#jid.lserver,
|
||||||
?SQL("select @(jid)s from muc_registered"
|
SqlQuery = case (ServiceOrRoom == Service) of
|
||||||
" where nick=%(Nick)s"
|
true ->
|
||||||
" and host=%(Host)s")) of
|
?SQL("select @(jid)s, @(host)s from muc_registered "
|
||||||
{selected, [{J}]} -> J == JID;
|
"where nick=%(Nick)s"
|
||||||
_ -> true
|
" and host=%(ServiceOrRoom)s");
|
||||||
|
false ->
|
||||||
|
?SQL("select @(jid)s, @(host)s from muc_registered "
|
||||||
|
"where nick=%(Nick)s"
|
||||||
|
" and (host=%(ServiceOrRoom)s or host=%(Service)s)")
|
||||||
|
end,
|
||||||
|
Allow = case ejabberd_sql:sql_query_t(SqlQuery) of
|
||||||
|
{selected, []}
|
||||||
|
when (ServiceOrRoom == Service) ->
|
||||||
|
%% Registering in the service...
|
||||||
|
%% check if nick is registered for some room in this service
|
||||||
|
{selected, NickRegistrations} =
|
||||||
|
ejabberd_sql:sql_query_t(
|
||||||
|
?SQL("select @(jid)s, @(host)s from muc_registered "
|
||||||
|
"where nick=%(Nick)s")),
|
||||||
|
not lists:any(fun({_NRJid, NRServiceOrRoom}) ->
|
||||||
|
Service == (jid:decode(NRServiceOrRoom))#jid.lserver end,
|
||||||
|
NickRegistrations);
|
||||||
|
{selected, []} ->
|
||||||
|
%% Nick not registered in any service or room
|
||||||
|
true;
|
||||||
|
{selected, [{_J, Host}]}
|
||||||
|
when (Host == Service) and (ServiceOrRoom /= Service) ->
|
||||||
|
%% Registering in a room, but the nick is already registered in the service
|
||||||
|
false;
|
||||||
|
{selected, [{J, _Host}]} ->
|
||||||
|
%% Registering in room (or service) a nick that is
|
||||||
|
%% already registered in this room (or service)
|
||||||
|
%% Only the owner of this registration can use the nick
|
||||||
|
J == JID
|
||||||
end,
|
end,
|
||||||
if Allow ->
|
if Allow ->
|
||||||
?SQL_UPSERT_T(
|
?SQL_UPSERT_T(
|
||||||
"muc_registered",
|
"muc_registered",
|
||||||
["!jid=%(JID)s",
|
["!jid=%(JID)s",
|
||||||
"!host=%(Host)s",
|
"!host=%(ServiceOrRoom)s",
|
||||||
"server_host=%(LServer)s",
|
"server_host=%(LServer)s",
|
||||||
"nick=%(Nick)s"]),
|
"nick=%(Nick)s"]),
|
||||||
ok;
|
ok;
|
||||||
|
|
Loading…
Reference in New Issue