ToNick),
From, Err);
_ ->
- ToJID = find_jid_by_nick(ToNick, StateData),
- case ToJID of
+ ToJIDs = find_jids_by_nick(ToNick, StateData),
+ case ToJIDs of
false ->
ErrText = "Recipient is not in the conference room",
Err = exmpp_stanza:reply_with_error(Packet,
From, Err);
_ ->
SrcIsVisitor = is_visitor(From, StateData),
- DstIsModerator = is_moderator(ToJID, StateData),
+ DstIsModerator = is_moderator(hd(ToJIDs), StateData),
PmFromVisitors = (StateData#state.config)#config.allow_private_messages_from_visitors,
if SrcIsVisitor == false;
PmFromVisitors == anyone;
{ok, #user{nick = FromNick}} =
?DICT:find(jlib:jid_tolower(From),
StateData#state.users),
- ejabberd_router:route(
- jid_replace_resource(
- StateData#state.jid,
- FromNick),
- ToJID, Packet);
+ FromNickJID = jid_replace_resource(StateData#state.jid, FromNick),
+ [ejabberd_router:route(FromNickJID, ToJID, Packet) || ToJID <- ToJIDs];
true ->
ErrText = "It is not allowed to send private messages",
Err = exmpp_stanza:reply_with_error(Packet,
true ->
NewState =
add_user_presence_un(From, Packet, StateData),
- send_new_presence(From, NewState),
+ case ?DICT:find(Nick, StateData#state.nicks) of
+ {ok, [_, _ | _]} -> ok;
+ _ -> send_new_presence(From, NewState)
+ end,
Reason = case exmpp_xml:get_element(Packet, 'status') of
undefined -> <<>>;
Status_el -> exmpp_xml:get_cdata(Status_el)
true ->
case is_nick_change(From, Nick, StateData) of
true ->
- case {is_nick_exists(Nick, StateData),
+ case {nick_collision(From, Nick, StateData),
mod_muc:can_use_nick(
StateData#state.host, From, Nick),
{(StateData#state.config)#config.allow_visitor_nickchange,
[]
end
end,
- Users = case Role of
- none ->
- lists:foldl(fun(J, Us) ->
- ?DICT:erase(J,
- Us)
- end, StateData#state.users, 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)
- end,
- StateData#state{users = Users}.
+ {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:short_prepd_jid(JID),
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),
tab_add_online_user(JID, StateData),
- StateData#state{users = Users}.
+ StateData#state{users = Users, nicks = Nicks}.
remove_online_user(JID, StateData) ->
remove_online_user(JID, StateData, <<>>).
add_to_log(leave, {Nick, Reason}, StateData),
tab_remove_online_user(JID, StateData),
Users = ?DICT:erase(LJID, StateData#state.users),
- StateData#state{users = 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);
+ false ->
+ StateData#state.nicks
+ end,
+ StateData#state{users = Users, nicks = Nicks}.
filter_presence(#xmlel{name = 'presence'} = Packet) ->
StateData#state{users = Users}.
-is_nick_exists(Nick, StateData) ->
- ?DICT:fold(fun(_, #user{nick = N}, B) ->
- B orelse (N == Nick)
- end, false, StateData#state.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]} ->
+ [exmpp_jid:make(User)];
+ {ok, Users} ->
+ [exmpp_jid:make(LJID) || LJID <- Users];
+ error ->
+ false
+ end.
%% @spec(Nick::binary(), StateData) -> JID::jid() | false
+%% Find and return the full JID of the user of Nick with
+%% highest-priority presence. Return jid record.
find_jid_by_nick(Nick, StateData) ->
- ?DICT:fold(fun(_, #user{jid = JID, nick = N}, R) ->
- case Nick of
- N -> JID;
- _ -> R
- end
- end, false, StateData#state.users).
+ case ?DICT:find(Nick, StateData#state.nicks) of
+ {ok, [User]} ->
+ exmpp_jid:make(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),
+ exmpp_jid:make(LJID);
+ error ->
+ false
+ end.
+
+higher_presence(Pres1, Pres2) ->
+ Pri1 = exmpp_presence:get_priority(Pres1),
+ Pri2 = exmpp_presence:get_priority(Pres2),
+ Pri1 > Pri2.
is_nick_change(JID, Nick, StateData) ->
LJID = jlib:short_prepd_jid(JID),
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
+ not exmpp_jid:bare_compare(UserOfNick, User).
+
add_new_user(From, Nick, Packet, StateData) ->
Lang = exmpp_stanza:get_lang(Packet),
MaxUsers = get_max_users(StateData),
MaxConferences = gen_mod:get_module_opt(
StateData#state.server_host,
mod_muc, max_user_conferences, 10),
+ Collision = nick_collision(From, Nick, StateData),
case {(ServiceAffiliation == owner orelse
MaxUsers < 0 orelse
((Affiliation == admin orelse Affiliation == owner) andalso
NUsers < MaxAdminUsers) orelse
NUsers < MaxUsers) andalso
NConferences < MaxConferences,
- is_nick_exists(Nick, StateData),
+ Collision,
mod_muc:can_use_nick(StateData#state.host, From, Nick),
get_default_role(Affiliation, StateData)} of
{false, _, _, _} ->
%% @spec(NJID::jid(), Reason::binary(), StateData) ->
send_new_presence({U, S, R}, Reason, StateData) ->
send_new_presence(exmpp_jid:make(U, S, R), Reason, StateData);
-send_new_presence(NJID, Reason, StateData) ->
+send_new_presence(NJID1, Reason, StateData) ->
+ NJID = {exmpp_jid:node(NJID1), exmpp_jid:domain(NJID1), exmpp_jid:resource(NJID1)},
+ %% First, find the nick associated with this JID.
+ #user{nick = Nick} = ?DICT:fetch(NJID, StateData#state.users),
+ %% Then find the JID using this nick with highest priority.
+ LJID1 = find_jid_by_nick(Nick, StateData),
+ LJID = {exmpp_jid:node(LJID1), exmpp_jid:domain(LJID1), exmpp_jid:resource(LJID1)},
+ %% Then we get the presence data we're supposed to send.
{ok, #user{jid = RealJID,
- nick = Nick,
role = Role,
last_presence = Presence}} =
- ?DICT:find(jlib:short_prepd_jid(NJID), StateData#state.users),
- Affiliation = get_affiliation(NJID, StateData),
+ ?DICT:find(LJID, StateData#state.users),
+ Affiliation = get_affiliation(LJID1, StateData),
SAffiliation = affiliation_to_binary(Affiliation),
SRole = role_to_binary(Role),
lists:foreach(
send_existing_presences(ToJID, StateData) ->
- LToJID = jlib:short_prepd_jid(ToJID),
+ LToJID = {exmpp_jid:node(ToJID), exmpp_jid:domain(ToJID), exmpp_jid:resource(ToJID)},
{ok, #user{jid = RealToJID,
role = Role}} =
?DICT:find(LToJID, StateData#state.users),
lists:foreach(
- fun({LJID, #user{jid = FromJID,
- nick = FromNick,
- role = FromRole,
- last_presence = Presence
- }}) ->
+ fun({FromNick, _Users}) ->
+ LJID1 = find_jid_by_nick(FromNick, StateData),
+ LJID = {exmpp_jid:node(LJID1), exmpp_jid:domain(LJID1), exmpp_jid:resource(LJID1)},
+ #user{jid = FromJID,
+ role = FromRole,
+ last_presence = Presence
+ } = ?DICT:fetch(LJID, StateData#state.users),
case RealToJID of
FromJID ->
ok;
_ ->
- {N,D,R} = LJID,
+ {N,D,R} = LJID,
FromAffiliation = get_affiliation(exmpp_jid:make(N,D,R),
StateData),
ItemAttrs =
RealToJID,
Packet)
end
- end, ?DICT:to_list(StateData#state.users)).
+ end, ?DICT:to_list(StateData#state.nicks)).
fun(#user{} = User) ->
User#user{nick = Nick}
end, StateData#state.users),
- NewStateData = StateData#state{users = Users},
- send_nick_changing(JID, OldNick, NewStateData),
+ 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),
add_to_log(nickchange, {OldNick, Nick}, StateData),
NewStateData.
-send_nick_changing(JID, OldNick, StateData) ->
+send_nick_changing(JID, OldNick, StateData,
+ SendOldUnavailable, SendNewAvailable) ->
{ok, #user{jid = RealJID,
nick = Nick,
role = Role,
children =[#xmlel{ns = ?NS_MUC_USER,
name = 'item',
attrs = ItemAttrs2}]}),
-
- ejabberd_router:route(
- jid_replace_resource(StateData#state.jid, OldNick),
- Info#user.jid,
- Packet1),
- ejabberd_router:route(
- jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet2)
+ if SendOldUnavailable ->
+ ejabberd_router:route(
+ jid_replace_resource(StateData#state.jid, OldNick),
+ Info#user.jid,
+ Packet1);
+ true ->
+ ok
+ end,
+ if SendNewAvailable ->
+ ejabberd_router:route(
+ jid_replace_resource(StateData#state.jid, Nick),
+ Info#user.jid,
+ Packet2);
+ true ->
+ ok
+ end
end, ?DICT:to_list(StateData#state.users)).
#xmlcdata{cdata = list_to_binary(Val)}]}]}).
iq_disco_info_extras(Lang, StateData) ->
- Len = length(?DICT:to_list(StateData#state.users)),
+ Len = ?DICT:size(StateData#state.users),
RoomDescription = (StateData#state.config)#config.description,
[#xmlel{ns = ?NS_DATA_FORMS, name = 'x',
attrs = [?XMLATTR(<<"type">>, <<"result">>)],
tab_add_online_user(JID, StateData) ->
LUser = exmpp_jid:prep_node(JID),
LServer = exmpp_jid:prep_domain(JID),
+ LResource = exmpp_jid:prep_resource(JID),
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
catch ets:insert(
muc_online_users,
- #muc_online_users{us = US, room = Room, host = Host}).
+ #muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
tab_remove_online_user(JID, StateData) when ?IS_JID(JID) ->
LUser = exmpp_jid:prep_node(JID),
LServer = exmpp_jid:prep_domain(JID),
- tab_remove_online_user({LUser, LServer, none},StateData);
+ LResource = exmpp_jid:prep_resource(JID),
+ tab_remove_online_user({LUser, LServer, LResource},StateData);
-tab_remove_online_user({LUser, LServer,_}, StateData) ->
+tab_remove_online_user({LUser, LServer, LResource}, StateData) ->
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
catch ets:delete_object(
muc_online_users,
- #muc_online_users{us = US, room = Room, host = Host}).
+ #muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
tab_count_user(JID) ->
LUser = exmpp_jid:prep_node(JID),