diff --git a/src/mod_muc.erl b/src/mod_muc.erl index c77977387c8..aadd77a357b 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -51,6 +51,7 @@ process_disco_items/1, process_vcard/1, process_register/1, + process_iq_register/1, process_muc_unique/1, process_mucsub/1, broadcast_service_message/3, @@ -675,29 +676,33 @@ process_vcard(#iq{lang = Lang} = IQ) -> xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec process_register(iq()) -> iq(). -process_register(#iq{type = Type, from = From, to = To, lang = Lang, - sub_els = [El = #register{}]} = IQ) -> +process_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, + RegisterDestination = jid:encode(To), ServerHost = ejabberd_router:host_of_route(Host), AccessRegister = mod_muc_opt:access_register(ServerHost), case acl:match_rule(ServerHost, AccessRegister, From) of allow -> case Type of get -> - xmpp:make_iq_result( - IQ, iq_get_register_info(ServerHost, Host, From, Lang)); + {result, iq_get_register_info(ServerHost, RegisterDestination, From, Lang)}; set -> - case process_iq_register_set(ServerHost, Host, From, El, Lang) of - {result, Result} -> - xmpp:make_iq_result(IQ, Result); - {error, Err} -> - xmpp:make_error(IQ, Err) - end + process_iq_register_set(ServerHost, RegisterDestination, From, El, Lang) end; deny -> ErrText = ?T("Access denied by service policy"), Err = xmpp:err_forbidden(ErrText, Lang), - xmpp:make_error(IQ, Err) + {error, Err} end. -spec process_disco_info(iq()) -> iq(). @@ -1416,6 +1421,11 @@ mod_doc() -> "nobody else can use that nickname in any room in the MUC " "service. To register a nickname, open the Service Discovery in " "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 " "can be started per cluster node. Rooms are distributed at " "creation time on all available MUC module instances. The " @@ -1461,11 +1471,12 @@ mod_doc() -> "modify that option.")}}, {access_register, #{value => ?T("AccessName"), + note => "improved in 23.xx", desc => ?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 " - "to register any free nick.")}}, + "to register any free nick in the MUC service and in the rooms.")}}, {db_type, #{value => "mnesia | sql", desc => diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl index 4b422f998b6..d994110ce4c 100644 --- a/src/mod_muc_mnesia.erl +++ b/src/mod_muc_mnesia.erl @@ -82,13 +82,19 @@ forget_room(_LServer, Host, Name) -> end, mnesia:transaction(F). -can_use_nick(_LServer, Host, JID, Nick) -> +can_use_nick(_LServer, ServiceOrRoom, JID, Nick) -> {LUser, LServer, _} = jid:tolower(JID), 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, [{#muc_registered{us_host = '$1', nick = Nick, _ = '_'}, - [{'==', {element, 2, '$1'}, Host}], + MatchSpec, ['$_']}]) of {'EXIT', _Reason} -> true; @@ -110,31 +116,46 @@ get_nick(_LServer, Host, From) -> [#muc_registered{nick = Nick}] -> Nick end. -set_nick(_LServer, Host, From, Nick) -> +set_nick(_LServer, ServiceOrRoom, From, Nick) -> {LUser, LServer, _} = jid:tolower(From), LUS = {LUser, LServer}, F = fun () -> case Nick of <<"">> -> - mnesia:delete({muc_registered, {LUS, Host}}), + mnesia:delete({muc_registered, {LUS, ServiceOrRoom}}), 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( muc_registered, - [{#muc_registered{us_host = - '$1', - nick = Nick, - _ = '_'}, - [{'==', {element, 2, '$1'}, - Host}], + [{#muc_registered{us_host = '$1', nick = Nick, _ = '_'}, + MatchSpec, ['$_']}]) 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; + [#muc_registered{us_host = {_U, Host}}] + when (Host == Service) and (ServiceOrRoom /= Service) -> + false; [#muc_registered{us_host = {U, _Host}}] -> U == LUS end, if Allow -> mnesia:write(#muc_registered{ - us_host = {LUS, Host}, + us_host = {LUS, ServiceOrRoom}, nick = Nick}), ok; true -> diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 88e2c0c0a67..30df7dbaad4 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -501,6 +501,8 @@ normal_state({route, <<"">>, process_iq_captcha(From, IQ, StateData); #adhoc_command{} -> process_iq_adhoc(From, IQ, StateData); + #register{} -> + mod_muc:process_iq_register(IQ); #fasten_apply_to{} = ApplyTo -> case xmpp:get_subtag(ApplyTo, #message_moderate{}) of #message_moderate{} = Moderate -> @@ -1406,7 +1408,7 @@ do_process_presence(Nick, #presence{from = From, type = available, lang = Lang} true -> case {nick_collision(From, Nick, StateData), mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, + jid:encode(StateData#state.jid), From, Nick), {(StateData#state.config)#config.allow_visitor_nickchange, is_visitor(From, StateData)}} of @@ -2290,7 +2292,7 @@ add_new_user(From, Nick, Packet, StateData) -> andalso NConferences < MaxConferences), Collision, 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)} of {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers -> @@ -4385,8 +4387,10 @@ maybe_forget_room(StateData) -> end). -spec make_disco_info(jid(), state()) -> disco_info(). -make_disco_info(_From, StateData) -> +make_disco_info(From, StateData) -> 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, ?NS_COMMANDS, ?NS_MESSAGE_MODERATE, ?NS_MESSAGE_RETRACT, ?CONFIG_OPT_TO_FEATURE((Config#config.public), @@ -4401,6 +4405,10 @@ make_disco_info(_From, StateData) -> <<"muc_moderated">>, <<"muc_unmoderated">>), ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected), <<"muc_passwordprotected">>, <<"muc_unsecured">>)] + ++ case acl:match_rule(ServerHost, AccessRegister, From) of + allow -> [?NS_REGISTER]; + deny -> [] + end ++ case Config#config.allow_subscription of true -> [?NS_MUCSUB]; false -> [] @@ -4703,7 +4711,7 @@ process_iq_mucsub(From, {error, xmpp:err_conflict(ErrText, Lang)}; false -> case mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, + jid:encode(StateData#state.jid), From, Nick) of false -> Err = case Nick of diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl index 1c72a5bd232..51a42c32a40 100644 --- a/src/mod_muc_sql.erl +++ b/src/mod_muc_sql.erl @@ -159,13 +159,19 @@ forget_room(LServer, Host, Name) -> end, 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))), - case catch ejabberd_sql:sql_query( - LServer, - ?SQL("select @(jid)s from muc_registered " - "where nick=%(Nick)s" - " and host=%(Host)s")) of + SqlQuery = case (jid:decode(ServiceOrRoom))#jid.lserver of + ServiceOrRoom -> + ?SQL("select @(jid)s from muc_registered " + "where nick=%(Nick)s" + " 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; _ -> true end. @@ -258,28 +264,57 @@ get_nick(LServer, Host, From) -> _ -> error end. -set_nick(LServer, Host, From, Nick) -> +set_nick(LServer, ServiceOrRoom, From, Nick) -> JID = jid:encode(jid:tolower(jid:remove_resource(From))), F = fun () -> case Nick of <<"">> -> ejabberd_sql:sql_query_t( ?SQL("delete from muc_registered where" - " jid=%(JID)s and host=%(Host)s")), + " jid=%(JID)s and host=%(ServiceOrRoom)s")), ok; _ -> - Allow = case ejabberd_sql:sql_query_t( - ?SQL("select @(jid)s from muc_registered" - " where nick=%(Nick)s" - " and host=%(Host)s")) of - {selected, [{J}]} -> J == JID; - _ -> true + Service = (jid:decode(ServiceOrRoom))#jid.lserver, + SqlQuery = case (ServiceOrRoom == Service) of + true -> + ?SQL("select @(jid)s, @(host)s from muc_registered " + "where nick=%(Nick)s" + " 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, if Allow -> ?SQL_UPSERT_T( "muc_registered", ["!jid=%(JID)s", - "!host=%(Host)s", + "!host=%(ServiceOrRoom)s", "server_host=%(LServer)s", "nick=%(Nick)s"]), ok;