]> granicus.if.org Git - ejabberd/commitdiff
Link MUC subscription to bare JID
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Wed, 7 Sep 2016 07:33:37 +0000 (10:33 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Wed, 7 Sep 2016 07:33:37 +0000 (10:33 +0300)
include/mod_muc_room.hrl
src/mod_muc_room.erl

index d985f3f3b3a0c2448e4576b5e3be577ee1af8549..47489f3d01100472ae26ede02575f6f0f278d257 100644 (file)
     jid :: jid(),
     nick :: binary(),
     role :: role(),
-    is_subscriber = false :: boolean(),
-    subscriptions = [] :: [binary()],
+    %%is_subscriber = false :: boolean(),
+    %%subscriptions = [] :: [binary()],
     last_presence :: xmlel()
 }).
 
+-record(subscriber, {jid :: jid(),
+                    nick = <<>> :: binary(),
+                    nodes = [] :: [binary()]}).
+
 -record(activity,
 {
     message_time    = 0 :: integer(),
     jid                     = #jid{} :: jid(),
     config                  = #config{} :: config(),
     users                   = (?DICT):new() :: ?TDICT,
+    subscribers             = (?DICT):new() :: ?TDICT,
     last_voice_request_time = treap:empty() :: treap:treap(),
     robots                  = (?DICT):new() :: ?TDICT,
     nicks                   = (?DICT):new() :: ?TDICT,
index f86b990d31c4659063708ac52c9f873cb75de308..11cc1d5f5a2a3d2f9a8f53acb8afe226b85db7c4 100644 (file)
@@ -678,7 +678,7 @@ handle_event({service_message, Msg}, _StateName,
                                    children = [{xmlcdata, Msg}]}]},
     send_wrapped_multiple(
       StateData#state.jid,
-      StateData#state.users,
+      get_users_and_subscribers(StateData),
       MessagePkt,
       ?NS_MUCSUB_NODES_MESSAGES,
       StateData),
@@ -750,12 +750,8 @@ handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData
     NSD = process_item_change(Item, StateData, UJID),
     {reply, {ok, NSD}, StateName, NSD};
 handle_sync_event(get_subscribers, _From, StateName, StateData) ->
-    JIDs = dict:fold(
-            fun(_, #user{is_subscriber = true, jid = J}, Acc) ->
-                    [J|Acc];
-               (_, _, Acc) ->
-                    Acc
-            end, [], StateData#state.users),
+    JIDs = lists:map(fun jid:make/1,
+                    ?DICT:fetch_keys(StateData#state.subscribers)),
     {reply, {ok, JIDs}, StateName, StateData};
 handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
                  StateName, StateData) ->
@@ -936,7 +932,7 @@ terminate(Reason, _StateName, StateData) ->
                         end,
                         tab_remove_online_user(LJID, StateData)
                 end,
-                [], StateData#state.users),
+                [], get_users_and_subscribers(StateData)),
     add_to_log(room_existence, stopped, StateData),
     mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(),
                           StateData#state.server_host),
@@ -953,11 +949,12 @@ process_groupchat_message(From,
                          #xmlel{name = <<"message">>, attrs = Attrs} = Packet,
                          StateData) ->
     Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
-    case is_user_online(From, StateData) orelse
+    IsSubscriber = is_subscriber(From, StateData),
+    case is_user_online(From, StateData) orelse IsSubscriber orelse
           is_user_allowed_message_nonparticipant(From, StateData)
        of
       true ->
-         {FromNick, Role, IsSubscriber} = get_participant_data(From, StateData),
+         {FromNick, Role} = get_participant_data(From, StateData),
          if (Role == moderator) or (Role == participant) or IsSubscriber or
               ((StateData#state.config)#config.moderated == false) ->
                 Subject = check_subject(Packet),
@@ -1000,7 +997,7 @@ process_groupchat_message(From,
                                    end,
                             send_wrapped_multiple(
                               jid:replace_resource(StateData#state.jid, FromNick),
-                              StateData#state.users,
+                              get_users_and_subscribers(StateData),
                               NewPacket, Node, NewStateData1),
                             NewStateData2 = case has_body_or_subject(NewPacket) of
                                               true ->
@@ -1068,9 +1065,9 @@ get_participant_data(From, StateData) ->
     case (?DICT):find(jid:tolower(From),
                      StateData#state.users)
        of
-      {ok, #user{nick = FromNick, role = Role, is_subscriber = IsSubscriber}} ->
-         {FromNick, Role, IsSubscriber};
-      error -> {<<"">>, moderator, false}
+      {ok, #user{nick = FromNick, role = Role}} ->
+         {FromNick, Role};
+      error -> {<<"">>, moderator}
     end.
 
 process_presence(From, Nick,
@@ -1078,7 +1075,6 @@ process_presence(From, Nick,
                 StateData) ->
     Type0 = fxml:get_attr_s(<<"type">>, Attrs0),
     IsOnline = is_user_online(From, StateData),
-    IsSubscriber = is_subscriber(From, StateData),
     if Type0 == <<"">>;
        IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) ->
           case ejabberd_hooks:run_fold(muc_filter_presence,
@@ -1115,7 +1111,7 @@ process_presence(From, Nick,
                                               Status_el ->
                                                   fxml:get_tag_cdata(Status_el)
                                             end,
-                                   remove_online_user(From, NewState, IsSubscriber, Reason);
+                                   remove_online_user(From, NewState, Reason);
                                <<"error">> ->
                                    ErrorText = <<"It is not allowed to send error messages to the"
                                        " room. The participant (~s) has sent an error "
@@ -1172,27 +1168,15 @@ process_presence(From, Nick,
                                                                   From, Err),
                                                       StateData;
                                                   _ ->
-                                                      case is_initial_presence(From, StateData) of
-                                                          true ->
-                                                              subscriber_becomes_available(
-                                                                From, Nick, Packet, StateData);
-                                                          false ->
-                                                              change_nick(From, Nick, StateData)
-                                                      end
+                                                      change_nick(From, Nick, StateData)
                                                 end;
                                             _NotNickChange ->
-                                                case is_initial_presence(From, StateData) of
-                                                    true ->
-                                                        subscriber_becomes_available(
-                                                          From, Nick, Packet, StateData);
-                                                    false ->
-                                                        Stanza = maybe_strip_status_from_presence(
-                                                                   From, Packet, StateData),
-                                                        NewState = add_user_presence(From, Stanza,
-                                                                                     StateData),
-                                                        send_new_presence(From, NewState, StateData),
-                                                        NewState
-                                                end
+                                                  Stanza = maybe_strip_status_from_presence(
+                                                             From, Packet, StateData),
+                                                  NewState = add_user_presence(From, Stanza,
+                                                                               StateData),
+                                                  send_new_presence(From, NewState, StateData),
+                                                  NewState
                                           end
                                    end
                              end,
@@ -1210,22 +1194,10 @@ maybe_strip_status_from_presence(From, Packet, StateData) ->
        _Allowed -> Packet
     end.
 
-subscriber_becomes_available(From, Nick, Packet, StateData) ->
-    Stanza = maybe_strip_status_from_presence(From, Packet, StateData),
-    State1 = add_user_presence(From, Stanza, StateData),
-    Aff = get_affiliation(From, State1),
-    Role = get_default_role(Aff, State1),
-    State2 = set_role(From, Role, State1),
-    State3 = set_nick(From, Nick, State2),
-    tab_add_online_user(From, StateData),
-    send_existing_presences(From, State3),
-    send_initial_presence(From, State3, StateData),
-    State3.
-
 close_room_if_temporary_and_empty(StateData1) ->
     case not (StateData1#state.config)#config.persistent
-          andalso (?DICT):to_list(StateData1#state.users) == []
-       of
+       andalso (?DICT):size(StateData1#state.users) == 0
+       andalso (?DICT):size(StateData1#state.subscribers) == 0 of
       true ->
          ?INFO_MSG("Destroyed MUC room ~s because it's temporary "
                    "and empty",
@@ -1235,18 +1207,39 @@ close_room_if_temporary_and_empty(StateData1) ->
       _ -> {next_state, normal_state, StateData1}
     end.
 
+get_users_and_subscribers(StateData) ->
+    OnlineSubscribers = ?DICT:fold(
+                          fun(LJID, _, Acc) ->
+                                  LBareJID = jid:remove_resource(LJID),
+                                  case is_subscriber(LBareJID, StateData) of
+                                      true ->
+                                          ?SETS:add_element(LBareJID, Acc);
+                                      false ->
+                                          Acc
+                                  end
+                          end, ?SETS:new(), StateData#state.users),
+    ?DICT:fold(
+       fun(LBareJID, #subscriber{nick = Nick}, Acc) ->
+              case ?SETS:is_element(LBareJID, OnlineSubscribers) of
+                  false ->
+                      ?DICT:store(LBareJID,
+                                  #user{jid = jid:make(LBareJID),
+                                        nick = Nick,
+                                        role = none,
+                                        last_presence = undefined},
+                                  Acc);
+                  true ->
+                      Acc
+              end
+       end, StateData#state.users, StateData#state.subscribers).
+
 is_user_online(JID, StateData) ->
     LJID = jid:tolower(JID),
     (?DICT):is_key(LJID, StateData#state.users).
 
 is_subscriber(JID, StateData) ->
-    LJID = jid:tolower(JID),
-    case (?DICT):find(LJID, StateData#state.users) of
-       {ok, #user{is_subscriber = IsSubscriber}} ->
-           IsSubscriber;
-       _ ->
-           false
-    end.
+    LJID = jid:tolower(jid:remove_resource(JID)),
+    (?DICT):is_key(LJID, StateData#state.subscribers).
 
 %% Check if the user is occupant of the room, or at least is an admin or owner.
 is_occupant_or_admin(JID, StateData) ->
@@ -1423,7 +1416,6 @@ make_reason(Packet, From, StateData, Reason1) ->
     iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])).
 
 expulse_participant(Packet, From, StateData, Reason1) ->
-    IsSubscriber = is_subscriber(From, StateData),
     Reason2 = make_reason(Packet, From, StateData, Reason1),
     NewState = add_user_presence_un(From,
                                    #xmlel{name = <<"presence">>,
@@ -1438,7 +1430,7 @@ expulse_participant(Packet, From, StateData, Reason1) ->
                                                             Reason2}]}]},
                                    StateData),
     send_new_presence(From, NewState, StateData),
-    remove_online_user(From, NewState, IsSubscriber).
+    remove_online_user(From, NewState).
 
 set_affiliation(JID, Affiliation, StateData) ->
     set_affiliation(JID, Affiliation, StateData, <<"">>).
@@ -1718,8 +1710,7 @@ prepare_room_queue(StateData) ->
       {empty, _} -> StateData
     end.
 
-update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
-                             is_subscriber = IsSubscriber} = User, StateData) ->
+update_online_user(JID, #user{nick = Nick} = User, StateData) ->
     LJID = jid:tolower(JID),
     Nicks1 = case (?DICT):find(LJID, StateData#state.users) of
                 {ok, #user{nick = OldNick}} ->
@@ -1738,9 +1729,7 @@ update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
                           [LJID], Nicks1),
     Users = (?DICT):update(LJID,
                           fun(U) ->
-                                  U#user{nick = Nick,
-                                         subscriptions = Nodes,
-                                         is_subscriber = IsSubscriber}
+                                  U#user{nick = Nick}
                           end, User, StateData#state.users),
     NewStateData = StateData#state{users = Users, nicks = Nicks},
     case {?DICT:find(LJID, StateData#state.users),
@@ -1752,32 +1741,26 @@ update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
     end,
     NewStateData.
 
-add_online_user(JID, Nick, Role, IsSubscriber, Nodes, StateData) ->
-    User = #user{jid = JID, nick = Nick, role = Role,
-                is_subscriber = IsSubscriber, subscriptions = Nodes},
-    StateData1 = update_online_user(JID, User, StateData),
-    if IsSubscriber ->
-           store_room(StateData1);
-       true ->
-           ok
-    end,
-    StateData1.
+set_subscriber(JID, Nick, Nodes, StateData) ->
+    BareJID = jid:remove_resource(JID),
+    Subscribers = ?DICT:store(jid:tolower(BareJID),
+                             #subscriber{jid = BareJID,
+                                         nick = Nick,
+                                         nodes = Nodes},
+                             StateData#state.subscribers),
+    NewStateData = StateData#state{subscribers = Subscribers},
+    store_room(NewStateData),
+    NewStateData.
 
-remove_online_user(JID, StateData, IsSubscriber) ->
-    remove_online_user(JID, StateData, IsSubscriber, <<"">>).
+add_online_user(JID, Nick, Role, StateData) ->
+    tab_add_online_user(JID, StateData),
+    User = #user{jid = JID, nick = Nick, role = Role},
+    update_online_user(JID, User, StateData).
 
-remove_online_user(JID, StateData, _IsSubscriber = true, _Reason) ->
-    LJID = jid:tolower(JID),
-    Users = case (?DICT):find(LJID, StateData#state.users) of
-               {ok, U} ->
-                   (?DICT):store(LJID, U#user{last_presence = undefined},
-                                 StateData#state.users);
-               error ->
-                   StateData#state.users
-           end,
-    tab_remove_online_user(JID, StateData),
-    StateData#state{users = Users};
-remove_online_user(JID, StateData, _IsSubscriber, Reason) ->
+remove_online_user(JID, StateData) ->
+    remove_online_user(JID, StateData, <<"">>).
+
+remove_online_user(JID, StateData, Reason) ->
     LJID = jid:tolower(JID),
     {ok, #user{nick = Nick}} = (?DICT):find(LJID,
                                            StateData#state.users),
@@ -2032,9 +2015,7 @@ add_new_user(From, Nick,
                              NewState = add_user_presence(
                                           From, Packet,
                                           add_online_user(From, Nick, Role,
-                                                          IsSubscribeRequest,
-                                                          Nodes, StateData)),
-                             tab_add_online_user(From, NewState),
+                                                          StateData)),
                              send_existing_presences(From, NewState),
                              send_initial_presence(From, NewState, StateData),
                              Shift = count_stanza_shift(Nick, Els, NewState),
@@ -2044,9 +2025,7 @@ add_new_user(From, Nick,
                              end,
                              NewState;
                         true ->
-                             add_online_user(From, Nick, none,
-                                             IsSubscribeRequest,
-                                             Nodes, StateData)
+                             set_subscriber(From, Nick, Nodes, StateData)
                      end,
                  ResultState =
                      case NewStateData#state.just_created of
@@ -2286,15 +2265,6 @@ presence_broadcast_allowed(JID, StateData) ->
     Role = get_role(JID, StateData),
     lists:member(Role, (StateData#state.config)#config.presence_broadcast).
 
-is_initial_presence(From, StateData) ->
-    LJID = jid:tolower(From),
-    case (?DICT):find(LJID, StateData#state.users) of
-       {ok, #user{last_presence = Pres}} when Pres /= undefined ->
-           false;
-       _ ->
-           true
-    end.
-
 send_initial_presence(NJID, StateData, OldStateData) ->
     send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
 
@@ -2388,7 +2358,7 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
             true ->
                 [{LNJID, UserInfo}];
             false ->
-                (?DICT):to_list(StateData#state.users)
+                (?DICT):to_list(get_users_and_subscribers(StateData))
         end,
     lists:foreach(
       fun({LUJID, Info}) ->
@@ -2444,7 +2414,7 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
              send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
                           Info#user.jid, Packet, Node1, StateData),
              Type = fxml:get_tag_attr_s(<<"type">>, Packet),
-             IsSubscriber = Info#user.is_subscriber,
+             IsSubscriber = is_subscriber(Info#user.jid, StateData),
              IsOccupant = Info#user.last_presence /= undefined,
              if (IsSubscriber and not IsOccupant) and
                 (IsInitialPresence or (Type == <<"unavailable">>)) ->
@@ -2668,19 +2638,20 @@ send_nick_changing(JID, OldNick, StateData,
                      (_) ->
                          ok
                  end,
-                 (?DICT):to_list(StateData#state.users)).
+                 ?DICT:to_list(get_users_and_subscribers(StateData))).
 
 maybe_send_affiliation(JID, Affiliation, StateData) ->
     LJID = jid:tolower(JID),
+    Users = get_users_and_subscribers(StateData),
     IsOccupant = case LJID of
                   {LUser, LServer, <<"">>} ->
                       not (?DICT):is_empty(
                             (?DICT):filter(fun({U, S, _}, _) ->
                                                    U == LUser andalso
                                                      S == LServer
-                                           end, StateData#state.users));
+                                           end, Users));
                   {_LUser, _LServer, _LResource} ->
-                      (?DICT):is_key(LJID, StateData#state.users)
+                      (?DICT):is_key(LJID, Users)
                 end,
     case IsOccupant of
       true ->
@@ -2701,19 +2672,19 @@ send_affiliation(LJID, Affiliation, StateData) ->
                                 children =
                                     [#xmlel{name = <<"item">>,
                                             attrs = ItemAttrs}]}]},
+    Users = get_users_and_subscribers(StateData),
     Recipients = case (StateData#state.config)#config.anonymous of
                   true ->
                       (?DICT):filter(fun(_, #user{role = moderator}) ->
                                              true;
                                         (_, _) ->
                                              false
-                                     end, StateData#state.users);
+                                     end, Users);
                   false ->
-                      StateData#state.users
+                      Users
                 end,
-    send_multiple(StateData#state.jid,
-                 StateData#state.server_host,
-                 Recipients, Message).
+    send_wrapped_multiple(StateData#state.jid, Recipients, Message,
+                         ?NS_MUCSUB_NODES_AFFILIATIONS, StateData).
 
 status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
     Status = case IsInitialPresence of
@@ -3420,7 +3391,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
                                          StateData#state.jid, Nick),
                          send_wrapped(RoomJIDNick, Info#user.jid, Packet,
                                       ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
-                         IsSubscriber = Info#user.is_subscriber,
+                         IsSubscriber = is_subscriber(Info#user.jid, StateData),
                          IsOccupant = Info#user.last_presence /= undefined,
                          if (IsSubscriber and not IsOccupant) ->
                                  send_wrapped(RoomJIDNick, Info#user.jid, Packet,
@@ -3429,7 +3400,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
                                  ok
                          end
                  end,
-                 (?DICT):to_list(StateData#state.users)).
+                 (?DICT):to_list(get_users_and_subscribers(StateData))).
 
 get_actor_nick(<<"">>, _StateData) ->
     <<"">>;
@@ -4259,7 +4230,7 @@ send_config_change_info(New, #state{config = Old} = StateData) ->
                                        attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
                                        children = StatusEls}]},
     send_wrapped_multiple(StateData#state.jid,
-                         StateData#state.users,
+                         get_users_and_subscribers(StateData),
                          Message,
                          ?NS_MUCSUB_NODES_CONFIG,
                          StateData).
@@ -4275,7 +4246,7 @@ remove_nonmembers(StateData) ->
                          _ -> SD
                        end
                end,
-               StateData, (?DICT):to_list(StateData#state.users)).
+               StateData, (?DICT):to_list(get_users_and_subscribers(StateData))).
 
 set_opts([], StateData) -> StateData;
 set_opts([{Opt, Val} | Opts], StateData) ->
@@ -4396,14 +4367,17 @@ set_opts([{Opt, Val} | Opts], StateData) ->
                StateData#state{config =
                                    (StateData#state.config)#config{allow_subscription = Val}};
            subscribers ->
-               lists:foldl(
-                 fun({JID, Nick, Nodes}, State) ->
-                         User = #user{jid = JID, nick = Nick,
-                                      subscriptions = Nodes,
-                                      is_subscriber = true,
-                                      role = none},
-                         update_online_user(JID, User, State)
-                 end, StateData, Val);
+               Subscribers = lists:foldl(
+                               fun({JID, Nick, Nodes}, Acc) ->
+                                       BareJID = jid:remove_resource(JID),
+                                       ?DICT:store(
+                                          jid:tolower(BareJID),
+                                          #subscriber{jid = BareJID,
+                                                      nick = Nick,
+                                                      nodes = Nodes},
+                                          Acc)
+                               end, ?DICT:new(), Val),
+               StateData#state{subscribers = Subscribers};
            affiliations ->
                StateData#state{affiliations = (?DICT):from_list(Val)};
            subject -> StateData#state{subject = Val};
@@ -4418,12 +4392,11 @@ set_opts([{Opt, Val} | Opts], StateData) ->
 make_opts(StateData) ->
     Config = StateData#state.config,
     Subscribers = (?DICT):fold(
-                   fun(_LJID, #user{is_subscriber = true} = User, Acc) ->
-                           [{User#user.jid, User#user.nick,
-                             User#user.subscriptions}|Acc];
-                      (_, _, Acc) ->
-                           Acc
-                   end, [], StateData#state.users),
+                   fun(_LJID, Sub, Acc) ->
+                           [{Sub#subscriber.jid,
+                             Sub#subscriber.nick,
+                             Sub#subscriber.nodes}|Acc]
+                   end, [], StateData#state.subscribers),
     [?MAKE_CONFIG_OPT(title), ?MAKE_CONFIG_OPT(description),
      ?MAKE_CONFIG_OPT(allow_change_subj),
      ?MAKE_CONFIG_OPT(allow_query_users),
@@ -4479,7 +4452,7 @@ destroy_room(DEl, StateData) ->
                                       Info#user.jid, Packet,
                                       ?NS_MUCSUB_NODES_CONFIG, StateData)
                  end,
-                 (?DICT):to_list(StateData#state.users)),
+                 (?DICT):to_list(get_users_and_subscribers(StateData))),
     case (StateData#state.config)#config.persistent of
       true ->
          mod_muc:forget_room(StateData#state.server_host,
@@ -4639,9 +4612,9 @@ process_iq_mucsub(From, Packet,
            Err = ?ERRT_BAD_REQUEST(Lang, <<"Missing 'nick' attribute">>),
            {error, Err};
        Nick when Config#config.allow_subscription ->
-           LJID = jid:tolower(From),
-           case (?DICT):find(LJID, StateData#state.users) of
-               {ok, #user{role = Role, nick = Nick1}} when Nick1 /= Nick ->
+           LBareJID = jid:tolower(jid:remove_resource(From)),
+           case (?DICT):find(LBareJID, StateData#state.subscribers) of
+               {ok, #subscriber{nick = Nick1}} when Nick1 /= Nick ->
                    Nodes = get_subscription_nodes(Packet),
                    case {nick_collision(From, Nick, StateData),
                          mod_muc:can_use_nick(StateData#state.server_host,
@@ -4654,14 +4627,12 @@ process_iq_mucsub(From, Packet,
                            ErrText = <<"That nickname is registered by another person">>,
                            {error, ?ERRT_CONFLICT(Lang, ErrText)};
                        _ ->
-                           NewStateData = add_online_user(
-                                            From, Nick, Role, true, Nodes, StateData),
+                           NewStateData = set_subscriber(From, Nick, Nodes, StateData),
                            {result, subscription_nodes_to_events(Nodes), NewStateData}
                    end;
-               {ok, #user{role = Role}} ->
+               {ok, #subscriber{}} ->
                    Nodes = get_subscription_nodes(Packet),
-                   NewStateData = add_online_user(
-                                    From, Nick, Role, true, Nodes, StateData),
+                   NewStateData = set_subscriber(From, Nick, Nodes, StateData),
                    {result, subscription_nodes_to_events(Nodes), NewStateData};
                error ->
                    add_new_user(From, Nick, Packet, StateData)
@@ -4674,27 +4645,11 @@ process_iq_mucsub(From, _Packet,
                  #iq{type = set,
                      sub_el = #xmlel{name = <<"unsubscribe">>}},
                  StateData) ->
-    LJID = jid:tolower(From),
-    case ?DICT:find(LJID, StateData#state.users) of
-       {ok, #user{is_subscriber = true} = User} ->
-           NewStateData = remove_subscription(From, User, StateData),
-           store_room(NewStateData),
-           {result, [], NewStateData};
-       error when From#jid.lresource == <<"">> ->
-           {LUser, LServer, _} = LJID,
-           NewStateData =
-               dict:fold(
-                 fun({U, S, _}, #user{jid = J, is_subscriber = true} = User,
-                     AccState) when U == LUser, S == LServer ->
-                         remove_subscription(J, User, AccState);
-                    (_, _, AccState) ->
-                         AccState
-                 end, StateData, StateData#state.users),
-           store_room(NewStateData),
-           {result, [], NewStateData};
-       _ ->
-           {result, [], StateData}
-    end;
+    LBareJID = jid:tolower(jid:remove_resource(From)),
+    Subscribers = ?DICT:erase(LBareJID, StateData#state.subscribers),
+    NewStateData = StateData#state{subscribers = Subscribers},
+    store_room(NewStateData),
+    {result, [], NewStateData};
 process_iq_mucsub(From, _Packet,
                  #iq{type = get, lang = Lang,
                      sub_el = #xmlel{name = <<"subscriptions">>}},
@@ -4703,13 +4658,11 @@ process_iq_mucsub(From, _Packet,
     FRole = get_role(From, StateData),
     if FRole == moderator; FAffiliation == owner; FAffiliation == admin ->
            Subs = dict:fold(
-                    fun(_, #user{is_subscriber = true, jid = J}, Acc) ->
-                            SJID = jid:to_string(jid:remove_resource(J)),
+                    fun(_, #subscriber{jid = J}, Acc) ->
+                            SJID = jid:to_string(J),
                             [#xmlel{name = <<"subscription">>,
-                                    attrs = [{<<"jid">>, SJID}]}|Acc];
-                       (_, _, Acc) ->
-                            Acc
-                    end, [], StateData#state.users),
+                                    attrs = [{<<"jid">>, SJID}]}|Acc]
+                    end, [], StateData#state.subscribers),
            {result, Subs, StateData};
        true ->
            Txt = <<"Moderator privileges required">>,
@@ -4719,25 +4672,9 @@ process_iq_mucsub(_From, _Packet, #iq{lang = Lang}, _StateData) ->
     Txt = <<"Unrecognized subscription command">>,
     {error, ?ERRT_BAD_REQUEST(Lang, Txt)}.
 
-remove_subscription(JID, #user{is_subscriber = true} = User, StateData) ->
-    case User#user.last_presence of
-       undefined ->
-           remove_online_user(JID, StateData, false);
-       _ ->
-           LJID = jid:tolower(JID),
-           Users = ?DICT:store(LJID, User#user{is_subscriber = false},
-                               StateData#state.users),
-           StateData#state{users = Users}
-    end;
-remove_subscription(_JID, #user{}, StateData) ->
-    StateData.
-
 remove_subscriptions(StateData) ->
     if not (StateData#state.config)#config.allow_subscription ->
-           dict:fold(
-             fun(_LJID, User, State) ->
-                     remove_subscription(User#user.jid, User, State)
-             end, StateData, StateData#state.users);
+           StateData#state{subscribers = ?DICT:new()};
        true ->
            StateData
     end.
@@ -5198,18 +5135,26 @@ store_room(StateData) ->
 
 send_wrapped(From, To, Packet, Node, State) ->
     LTo = jid:tolower(To),
-    case ?DICT:find(LTo, State#state.users) of
-       {ok, #user{is_subscriber = true,
-                  subscriptions = Nodes,
-                  last_presence = undefined}} ->
-           case lists:member(Node, Nodes) of
-               true ->
-                   NewPacket = wrap(From, To, Packet, Node),
-                   ejabberd_router:route(State#state.jid, To, NewPacket);
-               false ->
+    LBareTo = jid:tolower(jid:remove_resource(To)),
+    IsOffline = case ?DICT:find(LTo, State#state.users) of
+                   {ok, #user{last_presence = undefined}} -> true;
+                   error -> true;
+                   _ -> false
+               end,
+    if IsOffline ->
+           case ?DICT:find(LBareTo, State#state.subscribers) of
+               {ok, #subscriber{nodes = Nodes, jid = JID}} ->
+                   case lists:member(Node, Nodes) of
+                       true ->
+                           NewPacket = wrap(From, JID, Packet, Node),
+                           ejabberd_router:route(State#state.jid, JID, NewPacket);
+                       false ->
+                           ok
+                   end;
+               _ ->
                    ok
            end;
-       _ ->
+       true ->
            ejabberd_router:route(From, To, Packet)
     end.
 
@@ -5230,9 +5175,9 @@ wrap(From, To, Packet, Node) ->
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Multicast
 
-send_multiple(From, Server, Users, Packet) ->
-    JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
-    ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
+%% send_multiple(From, Server, Users, Packet) ->
+%%     JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
+%%     ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
 
 send_wrapped_multiple(From, Users, Packet, Node, State) ->
     lists:foreach(