]> granicus.if.org Git - ejabberd/commitdiff
Add more tests for privacy lists and blocking command
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sat, 22 Oct 2016 10:01:45 +0000 (13:01 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sat, 22 Oct 2016 10:01:45 +0000 (13:01 +0300)
src/ejabberd_c2s.erl
src/ejabberd_piefxis.erl
src/ejabberd_socket.erl
src/mod_admin_extra.erl
src/mod_blocking.erl
src/mod_privacy.erl
src/mod_roster.erl
test/ejabberd_SUITE.erl
test/muc_tests.erl
test/privacy_tests.erl [new file with mode: 0644]
test/suite.erl

index f7d8e9dbb579b566c139f6b4c54788f80a0bf5c6..986310546d659225069771e98d72aa478f54a6b2 100644 (file)
@@ -881,18 +881,20 @@ decode_element(#xmlel{} = El, StateName, StateData) ->
        end
     catch error:{xmpp_codec, Why} ->
            NS = xmpp:get_ns(El),
-           case xmpp:is_stanza(El) of
-               true ->
-                   Lang = xmpp:get_lang(El),
-                   Txt = xmpp:format_error(Why),
-                   send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
-               false when NS == ?NS_STREAM_MGMT_2; NS == ?NS_STREAM_MGMT_3 ->
-                   Err = #sm_failed{reason = 'bad-request', xmlns = NS},
-                   send_element(StateData, Err);
-               false ->
-                   ok
-           end,
-           fsm_next_state(StateName, StateData)
+           fsm_next_state(
+             StateName,
+             case xmpp:is_stanza(El) of
+                 true ->
+                     Lang = xmpp:get_lang(El),
+                     Txt = xmpp:format_error(Why),
+                     send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
+                 false when NS == ?NS_STREAM_MGMT_2; NS == ?NS_STREAM_MGMT_3 ->
+                     Err = #sm_failed{reason = 'bad-request', xmlns = NS},
+                     send_element(StateData, Err),
+                     StateData;
+                 false ->
+                     StateData
+             end)
     end.
 
 wait_for_bind({xmlstreamelement, El}, StateData) ->
@@ -957,13 +959,14 @@ wait_for_bind(closed, StateData) ->
 wait_for_bind(stop, StateData) ->
     {stop, normal, StateData};
 wait_for_bind(Pkt, StateData) ->
-    case xmpp:is_stanza(Pkt) of
-       true ->
-           send_error(StateData, Pkt, xmpp:err_not_acceptable());
-       false ->
-           ok
-    end,
-    fsm_next_state(wait_for_bind, StateData).
+    fsm_next_state(
+      wait_for_bind,
+      case xmpp:is_stanza(Pkt) of
+         true ->
+             send_error(StateData, Pkt, xmpp:err_not_acceptable());
+         false ->
+             StateData
+      end).
 
 -spec open_session(state()) -> {ok, state()} | {error, stanza_error()}.
 open_session(StateData) ->
@@ -1315,27 +1318,23 @@ handle_info({route, From, To, Packet}, StateName, StateData) when ?is_stanza(Pac
                                    allow ->
                                        {true, StateData};
                                    deny ->
-                                       Err = xmpp:make_error(
-                                               Packet,
-                                               xmpp:err_service_unavailable()),
-                                       ejabberd_router:route(To, From, Err),
+                                       ejabberd_router:route_error(
+                                         To, From, Packet,
+                                         xmpp:err_service_unavailable()),
                                        {false, StateData}
                                end;
                            _ ->
-                               Err = xmpp:make_error(Packet, xmpp:err_forbidden()),
-                               ejabberd_router:route(To, From, Err),
+                               ejabberd_router:route_error(
+                                 To, From, Packet, xmpp:err_forbidden()),
                                {false, StateData}
                        end;
                    _ ->
                        case privacy_check_packet(StateData, From, To, Packet, in) of
                            allow ->
                                {true, StateData};
-                           deny when T == get; T == set ->
-                               Err = xmpp:make_error(
-                                       Packet, xmpp:err_service_unavailable()),
-                               ejabberd_router:route(To, From, Err),
-                               {false, StateData};
                            deny ->
+                               ejabberd_router:route_error(
+                                 To, From, Packet, xmpp:err_service_unavailable()),
                                {false, StateData}
                        end
                end;
@@ -1345,13 +1344,11 @@ handle_info({route, From, To, Packet}, StateName, StateData) when ?is_stanza(Pac
                        {true, StateData};
                    deny ->
                        case T of
-                           error -> ok;
                            groupchat -> ok;
                            headline -> ok;
                            _ ->
-                               Err = xmpp:make_error(
-                                       Packet, xmpp:err_service_unavailable()),
-                               ejabberd_router:route(To, From, Err)
+                               ejabberd_router:route_error(
+                                 To, From, Packet, xmpp:err_service_unavailable())
                        end,
                        {false, StateData}
                end
@@ -1572,14 +1569,14 @@ send_element(StateData, #xmlel{} = El) ->
 send_element(StateData, Pkt) ->
     send_element(StateData, xmpp:encode(Pkt, ?NS_CLIENT)).
 
--spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
+-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> state().
 send_error(StateData, Stanza, Error) ->
     Type = xmpp:get_type(Stanza),
     if Type == error; Type == result;
        Type == <<"error">>; Type == <<"result">> ->
-           ok;
+           StateData;
        true ->
-           send_element(StateData, xmpp:make_error(Stanza, Error))
+           send_stanza(StateData, xmpp:make_error(Stanza, Error))
     end.
 
 -spec send_stanza(state(), xmpp_element()) -> state().
@@ -1754,47 +1751,56 @@ presence_track(From, To, Packet, StateData) ->
     LTo = jid:tolower(To),
     User = StateData#state.user,
     Server = StateData#state.server,
-    case Type of
-      unavailable ->
-         A = ?SETS:del_element(LTo, StateData#state.pres_a),
-         check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet);
-      subscribe ->
-         try_roster_subscribe(subscribe, User, Server, From, To, Packet, StateData);
-      subscribed ->
-         ejabberd_hooks:run(roster_out_subscription, Server,
-                            [User, Server, To, subscribed]),
-         check_privacy_route(From, StateData,
-                             jid:remove_resource(From), To, Packet);
-      unsubscribe ->
-         try_roster_subscribe(unsubscribe, User, Server, From, To, Packet, StateData);
-      unsubscribed ->
-         ejabberd_hooks:run(roster_out_subscription, Server,
-                            [User, Server, To, unsubscribed]),
-         check_privacy_route(From, StateData,
-                             jid:remove_resource(From), To, Packet);
-      error ->
-         check_privacy_route(From, StateData, From, To, Packet);
-      probe ->
-         check_privacy_route(From, StateData, From, To, Packet);
-      _ ->
-         A = (?SETS):add_element(LTo, StateData#state.pres_a),
-         check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet)
+    Lang = StateData#state.lang,
+    case privacy_check_packet(StateData, From, To, Packet, out) of
+       deny ->
+            ErrText = <<"Your active privacy list has denied "
+                       "the routing of this stanza.">>,
+           Err = xmpp:err_not_acceptable(ErrText, Lang),
+           send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
+       allow when Type == subscribe; Type == subscribed;
+                  Type == unsubscribe; Type == unsubscribed ->
+           Access = gen_mod:get_module_opt(Server, mod_roster, access,
+                                           fun(A) when is_atom(A) -> A end,
+                                           all),
+           MyBareJID = jid:make(User, Server, <<"">>),
+           case acl:match_rule(Server, Access, MyBareJID) of
+               deny ->
+                   ErrText = <<"Denied by ACL">>,
+                   Err = xmpp:err_forbidden(ErrText, Lang),
+                   send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
+               allow ->
+                   ejabberd_hooks:run(roster_out_subscription,
+                                      Server,
+                                      [User, Server, To, Type]),
+                   ejabberd_router:route(jid:remove_resource(From), To, Packet),
+                   StateData
+           end;
+       allow when Type == error; Type == probe ->
+           ejabberd_router:route(From, To, Packet),
+           StateData;
+       allow ->
+           ejabberd_router:route(From, To, Packet),
+           A = case Type of
+                   available ->
+                       ?SETS:add_element(LTo, StateData#state.pres_a);
+                   unavailable ->
+                       ?SETS:del_element(LTo, StateData#state.pres_a)
+               end,
+           StateData#state{pres_a = A}
     end.
 
 -spec check_privacy_route(jid(), state(), jid(), jid(), stanza()) -> state().
 check_privacy_route(From, StateData, FromRoute, To,
                    Packet) ->
     case privacy_check_packet(StateData, From, To, Packet,
-                             out)
-       of
+                             out) of
         deny ->
             Lang = StateData#state.lang,
             ErrText = <<"Your active privacy list has denied "
-                       "the routing of this stanza.">>,
-           Err = xmpp:make_error(
-                   xmpp:set_from_to(Packet, From, To),
-                   xmpp:err_not_acceptable(ErrText, Lang)),
-            send_stanza(StateData, Err);
+                       "the routing of this stanza.">>,
+           Err = xmpp:err_not_acceptable(ErrText, Lang),
+           send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
         allow ->
            ejabberd_router:route(FromRoute, To, Packet),
             StateData
@@ -1815,24 +1821,6 @@ is_privacy_allow(StateData, From, To, Packet, Dir) ->
     allow ==
       privacy_check_packet(StateData, From, To, Packet, Dir).
 
-%%% Check ACL before allowing to send a subscription stanza
--spec try_roster_subscribe(subscribe | unsubscribe, binary(), binary(),
-                          jid(), jid(), presence(), state()) -> state().
-try_roster_subscribe(Type, User, Server, From, To, Packet, StateData) ->
-    JID1 = jid:make(User, Server, <<"">>),
-    Access = gen_mod:get_module_opt(Server, mod_roster, access, fun(A) when is_atom(A) -> A end, all),
-    case acl:match_rule(Server, Access, JID1) of
-       deny ->
-           %% Silently drop this (un)subscription request
-           StateData;
-       allow ->
-           ejabberd_hooks:run(roster_out_subscription,
-                              Server,
-                              [User, Server, To, Type]),
-           check_privacy_route(From, StateData, jid:remove_resource(From),
-                               To, Packet)
-    end.
-
 %% Send presence when disconnecting
 -spec presence_broadcast(state(), jid(), ?SETS:set(), presence()) -> ok.
 presence_broadcast(StateData, From, JIDSet, Packet) ->
@@ -1980,7 +1968,7 @@ process_privacy_iq(#iq{from = From, to = To,
                       privacy_iq_set,
                       StateData#state.server,
                       {error, xmpp:err_feature_not_implemented(Txt, Lang)},
-                      [IQ])
+                      [IQ, StateData#state.privacy_list])
                of
                    {result, R, NewPrivList} ->
                        {{result, R},
@@ -2522,9 +2510,8 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
                false ->
                    fun(From, To, El, _Time) ->
                            Txt = <<"User session terminated">>,
-                           Err = xmpp:make_error(
-                                   El, xmpp:err_service_unavailable(Txt, Lang)),
-                           ejabberd_router:route(To, From, Err)
+                           ejabberd_router:route_error(
+                             To, From, El, xmpp:err_service_unavailable(Txt, Lang))
                    end
              end,
     F = fun(From, _To, #presence{}, _Time) ->
@@ -2532,9 +2519,8 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
                       [jid:to_string(From)]);
           (From, To, #iq{} = El, _Time) ->
                Txt = <<"User session terminated">>,
-               Err = xmpp:make_error(
-                       El, xmpp:err_service_unavailable(Txt, Lang)),
-               ejabberd_router:route(To, From, Err);
+               ejabberd_router:route_error(
+                 To, From, El, xmpp:err_service_unavailable(Txt, Lang));
           (From, To, El, Time) ->
                %% We'll drop the stanza if it was <forwarded/> by some
                %% encapsulating protocol as per XEP-0297.  One such protocol is
index 5e6e1bf5828b2943e9f3e1de9d828f8cab02581c..0e79c991315396fcc515d6eb19478ae50cecc509 100644 (file)
@@ -486,7 +486,7 @@ process_privacy(#privacy_query{lists = Lists,
             from = JID, to = JID, sub_els = [PrivacyQuery]},
     Txt = <<"No module is handling this query">>,
     Error = {error, xmpp:err_feature_not_implemented(Txt, ?MYLANG)},
-    case mod_privacy:process_iq_set(Error, IQ) of
+    case mod_privacy:process_iq_set(Error, IQ, #userlist{}) of
         {error, #stanza_error{reason = Reason}} = Err ->
            if Reason == 'item-not-found', Lists == [],
               Active == undefined, Default /= undefined ->
index f8dc84630ed594036e2abd9c92de264646b6675c..b5fa52ded7344549428ce323129adfce9ef6a192 100644 (file)
@@ -31,6 +31,7 @@
 -export([start/4,
         connect/3,
         connect/4,
+        connect/5,
         starttls/2,
         starttls/3,
         compress/1,
@@ -125,19 +126,21 @@ start(Module, SockMod, Socket, Opts) ->
     end.
 
 connect(Addr, Port, Opts) ->
-    connect(Addr, Port, Opts, infinity).
+    connect(Addr, Port, Opts, infinity, self()).
 
 connect(Addr, Port, Opts, Timeout) ->
+    connect(Addr, Port, Opts, Timeout, self()).
+
+connect(Addr, Port, Opts, Timeout, Owner) ->
     case gen_tcp:connect(Addr, Port, Opts, Timeout) of
       {ok, Socket} ->
          Receiver = ejabberd_receiver:start(Socket, gen_tcp,
                                             none),
          SocketData = #socket_state{sockmod = gen_tcp,
                                     socket = Socket, receiver = Receiver},
-         Pid = self(),
          case gen_tcp:controlling_process(Socket, Receiver) of
            ok ->
-               ejabberd_receiver:become_controller(Receiver, Pid),
+               ejabberd_receiver:become_controller(Receiver, Owner),
                {ok, SocketData};
            {error, _Reason} = Error -> gen_tcp:close(Socket), Error
          end;
index 627b5b58fffb5b716fcdb831e8ffb296caf4f935..4598805c272d9daaef3643817a2bd0f4df86ebb8 100644 (file)
@@ -53,6 +53,7 @@
 -include("ejabberd.hrl").
 -include("ejabberd_commands.hrl").
 -include("mod_roster.hrl").
+-include("mod_privacy.hrl").
 -include("ejabberd_sm.hrl").
 -include("xmpp.hrl").
 
@@ -1380,11 +1381,12 @@ privacy_set(Username, Host, QueryS) ->
     To = jid:make(Host),
     QueryEl = fxml_stream:parse_element(QueryS),
     SubEl = xmpp:decode(QueryEl),
-    IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl]},
+    IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
+            from = From, to = To},
     ejabberd_hooks:run_fold(privacy_iq_set,
                            Host,
                            {error, xmpp:err_feature_not_implemented()},
-                           [From, To, IQ]),
+                           [IQ, #userlist{}]),
     ok.
 
 %%%
index b3bbff96eaabb5b168b206b15c966be9cab0026b..d2b187d260a62d5ef2b6d486f4c7510bccb03c0c 100644 (file)
@@ -30,7 +30,7 @@
 -protocol({xep, 191, '1.2'}).
 
 -export([start/2, stop/1, process_iq/1,
-        process_iq_set/2, process_iq_get/3, mod_opt_type/1, depends/2]).
+        process_iq_set/3, process_iq_get/3, mod_opt_type/1, depends/2]).
 
 -include("ejabberd.hrl").
 -include("logger.hrl").
@@ -85,10 +85,11 @@ process_iq_get(Acc, _, _) -> Acc.
 -spec process_iq_set({error, stanza_error()} |
                     {result, xmpp_element() | undefined} |
                     {result, xmpp_element() | undefined, userlist()},
-                    iq()) -> {error, stanza_error()} |
-                             {result, xmpp_element() | undefined} |
-                             {result, xmpp_element() | undefined, userlist()}.
-process_iq_set(Acc, #iq{from = From, lang = Lang, sub_els = [SubEl]}) ->
+                    iq(), userlist()) ->
+                           {error, stanza_error()} |
+                           {result, xmpp_element() | undefined} |
+                           {result, xmpp_element() | undefined, userlist()}.
+process_iq_set(Acc, #iq{from = From, lang = Lang, sub_els = [SubEl]}, _) ->
     #jid{luser = LUser, lserver = LServer} = From,
     case SubEl of
        #block{items = []} ->
@@ -105,7 +106,7 @@ process_iq_set(Acc, #iq{from = From, lang = Lang, sub_els = [SubEl]}) ->
        _ ->
            Acc
     end;
-process_iq_set(Acc, _) -> Acc.
+process_iq_set(Acc, _, _) -> Acc.
 
 -spec list_to_blocklist_jids([listitem()], [ljid()]) -> [ljid()].
 list_to_blocklist_jids([], JIDs) -> JIDs;
index 2f318deec4437c3012769baf1ba59935e2b2bf4b..d4c8464f1d4612c6b8ee9e0a5804d90ac9adc0e1 100644 (file)
@@ -32,7 +32,7 @@
 -behaviour(gen_mod).
 
 -export([start/2, stop/1, process_iq/1, export/1, import/1,
-        process_iq_set/2, process_iq_get/3, get_user_list/3,
+        process_iq_set/3, process_iq_get/3, get_user_list/3,
         check_packet/6, remove_user/2, encode_list_item/1,
         is_list_needdb/1, updated_list/3,
          item_to_xml/1, get_user_lists/2, import/3,
@@ -103,6 +103,12 @@ process_iq(IQ) ->
 -spec process_iq_get({error, stanza_error()} | {result, xmpp_element() | undefined},
                     iq(), userlist()) -> {error, stanza_error()} |
                                          {result, xmpp_element() | undefined}.
+process_iq_get(_, #iq{lang = Lang,
+                     sub_els = [#privacy_query{default = Default,
+                                               active = Active}]},
+              _) when Default /= undefined; Active /= undefined ->
+    Txt = <<"Only <list/> element is allowed in this query">>,
+    {error, xmpp:err_bad_request(Txt, Lang)};
 process_iq_get(_, #iq{from = From, lang = Lang,
                      sub_els = [#privacy_query{lists = Lists}]},
               #userlist{name = Active}) ->
@@ -205,7 +211,7 @@ encode_value(Type, Val) ->
                          listitem_value().
 decode_value(Type, Value) ->
     case Type of
-       jid -> jid:from_string(Value);
+       jid -> jid:tolower(jid:from_string(Value));
        subscription ->
            case Value of
                <<"from">> -> from;
@@ -213,35 +219,37 @@ decode_value(Type, Value) ->
                <<"both">> -> both;
                <<"none">> -> none
            end;
-       group -> Value;
+       group when Value /= <<"">> -> Value;
        undefined -> none
     end.
 
 -spec process_iq_set({error, stanza_error()} |
                     {result, xmpp_element() | undefined} |
                     {result, xmpp_element() | undefined, userlist()},
-                    iq()) -> {error, stanza_error()} |
-                             {result, xmpp_element() | undefined} |
-                             {result, xmpp_element() | undefined, userlist()}.
+                    iq(), #userlist{}) ->
+                           {error, stanza_error()} |
+                           {result, xmpp_element() | undefined} |
+                           {result, xmpp_element() | undefined, userlist()}.
 process_iq_set(_, #iq{from = From, lang = Lang,
                      sub_els = [#privacy_query{default = Default,
                                                active = Active,
-                                               lists = Lists}]}) ->
+                                               lists = Lists}]},
+             #userlist{} = UserList) ->
     #jid{luser = LUser, lserver = LServer} = From,
     case Lists of
        [#privacy_list{items = Items, name = ListName}]
          when Default == undefined, Active == undefined ->
-           process_lists_set(LUser, LServer, ListName, Items, Lang);
+           process_lists_set(LUser, LServer, ListName, Items, UserList, Lang);
        [] when Default == undefined, Active /= undefined ->
            process_active_set(LUser, LServer, Active, Lang);
        [] when Active == undefined, Default /= undefined ->
            process_default_set(LUser, LServer, Default, Lang);
        _ ->
-           Txt = <<"There should be exactly one element in this query: "
-                   "<list/>, <active/> or <default/>">>,
+           Txt = <<"The stanza MUST contain only one <active/> element, "
+                   "one <default/> element, or one <list/> element">>,
            {error, xmpp:err_bad_request(Txt, Lang)}
     end;
-process_iq_set(Acc, _) ->
+process_iq_set(Acc, _, _) ->
     Acc.
 
 -spec process_default_set(binary(), binary(), none | binary(),
@@ -286,13 +294,20 @@ set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
     Mod:set_privacy_list(Privacy).
 
 -spec process_lists_set(binary(), binary(), binary(), [privacy_item()],
-                       binary()) -> {error, stanza_error()} | {result, undefined}.
-process_lists_set(LUser, LServer, Name, [], Lang) ->
+                       #userlist{}, binary()) -> {error, stanza_error()} |
+                                                 {result, undefined}.
+process_lists_set(_LUser, _LServer, Name, [], #userlist{name = Name}, Lang) ->
+    Txt = <<"Cannot remove active list">>,
+    {error, xmpp:err_conflict(Txt, Lang)};
+process_lists_set(LUser, LServer, Name, [], _UserList, Lang) ->
     Mod = gen_mod:db_mod(LServer, ?MODULE),
     case Mod:remove_privacy_list(LUser, LServer, Name) of
        {atomic, conflict} ->
            Txt = <<"Cannot remove default list">>,
            {error, xmpp:err_conflict(Txt, Lang)};
+       {atomic, not_found} ->
+           Txt = <<"No privacy list with this name found">>,
+           {error, xmpp:err_item_not_found(Txt, Lang)};
        {atomic, ok} ->
            ejabberd_sm:route(jid:make(LUser, LServer,
                                       <<"">>),
@@ -308,7 +323,7 @@ process_lists_set(LUser, LServer, Name, [], Lang) ->
            Txt = <<"Database failure">>,
            {error, xmpp:err_internal_server_error(Txt, Lang)}
     end;
-process_lists_set(LUser, LServer, Name, Items, Lang) ->
+process_lists_set(LUser, LServer, Name, Items, _UserList, Lang) ->
     case catch lists:map(fun decode_item/1, Items) of
        {error, Why} ->
            Txt = xmpp:format_error(Why),
@@ -358,9 +373,7 @@ decode_item(#privacy_item{order = Order,
                         action = Action,
                         type = Type,
                         value = Value},
-    if MatchMessage and MatchIQ and MatchPresenceIn and MatchPresenceOut ->
-           ListItem#listitem{match_all = true};
-       not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) ->
+    if not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) ->
            ListItem#listitem{match_all = true};
        true ->
            ListItem#listitem{match_iq = MatchIQ,
@@ -468,17 +481,11 @@ check_packet_aux([Item | List], PType, JID,
        Item,
     case is_ptype_match(Item, PType) of
       true ->
-         case Type of
-           none -> Action;
-           _ ->
-               case is_type_match(Type, Value, JID, Subscription,
-                                  Groups)
-                   of
-                 true -> Action;
-                 false ->
-                     check_packet_aux(List, PType, JID, Subscription, Groups)
-               end
-         end;
+           case is_type_match(Type, Value, JID, Subscription, Groups) of
+               true -> Action;
+               false ->
+                   check_packet_aux(List, PType, JID, Subscription, Groups)
+           end;
       false ->
          check_packet_aux(List, PType, JID, Subscription, Groups)
     end.
@@ -499,8 +506,10 @@ is_ptype_match(Item, PType) ->
          end
     end.
 
--spec is_type_match(jid | subscription | group, listitem_value(),
+-spec is_type_match(none | jid | subscription | group, listitem_value(),
                    ljid(), none | both | from | to, [binary()]) -> boolean().
+is_type_match(none, _Value, _JID, _Subscription, _Groups) ->
+    true;
 is_type_match(Type, Value, JID, Subscription, Groups) ->
     case Type of
       jid ->
index feebd3945ded3c57e604fe49be9fd575ff6860b4..423fe9e0eba12dc0b41c21a70f8d7ce0e57848f7 100644 (file)
@@ -49,7 +49,7 @@
         get_jid_info/4, encode_item/1, webadmin_page/3,
         webadmin_user/4, get_versioning_feature/2,
         roster_versioning_enabled/1, roster_version/2,
-        mod_opt_type/1, set_roster/1, depends/2]).
+        mod_opt_type/1, set_roster/1, del_roster/3, depends/2]).
 
 -include("ejabberd.hrl").
 -include("logger.hrl").
@@ -297,6 +297,13 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
              roster_subscribe_t(LUser, LServer, LJID, Item)
       end).
 
+del_roster(LUser, LServer, LJID) ->
+    transaction(
+      LServer,
+      fun() ->
+             del_roster_t(LUser, LServer, LJID)
+      end).
+
 encode_item(Item) ->
     #roster_item{jid = jid:make(Item#roster.jid),
                 name = Item#roster.name,
index 1a5c890768bfb00f5dce0b0bda8d322036174755..59936352b4699ccb66655596d90d54b384e85254 100644 (file)
                 stop_event_relay/1, put_event/2, get_event/1,
                 bind/1, auth/1, auth/2, open_session/1, open_session/2,
                zlib/1, starttls/1, starttls/2, close_socket/1, init_stream/1,
-               auth_legacy/2, auth_legacy/3, tcp_connect/1, send_text/2]).
+               auth_legacy/2, auth_legacy/3, tcp_connect/1, send_text/2,
+               set_roster/3, del_roster/1]).
 -include("suite.hrl").
 
 suite() ->
-    [{timetrap, {seconds, 30}}].
+    [{timetrap, {seconds, 120}}].
 
 init_per_suite(Config) ->
     NewConfig = init_config(Config),
@@ -183,9 +184,18 @@ end_per_group(_GroupName, Config) ->
     set_opt(anonymous, false, Config).
 
 init_per_testcase(stop_ejabberd, Config) ->
-    open_session(bind(auth(connect(Config))));
+    NewConfig = set_opt(resource, <<"">>,
+                       set_opt(anonymous, true, Config)),
+    open_session(bind(auth(connect(NewConfig))));
 init_per_testcase(TestCase, OrigConfig) ->
-    subscribe_to_events(OrigConfig),
+    Test = atom_to_list(TestCase),
+    IsMaster = lists:suffix("_master", Test),
+    IsSlave = lists:suffix("_slave", Test),
+    if IsMaster or IsSlave ->
+           subscribe_to_events(OrigConfig);
+       true ->
+           ok
+    end,
     TestGroup = proplists:get_value(
                  name, ?config(tc_group_properties, OrigConfig)),
     Server = ?config(server, OrigConfig),
@@ -199,9 +209,6 @@ init_per_testcase(TestCase, OrigConfig) ->
               end,
     MasterResource = ?config(master_resource, OrigConfig),
     SlaveResource = ?config(slave_resource, OrigConfig),
-    Test = atom_to_list(TestCase),
-    IsMaster = lists:suffix("_master", Test),
-    IsSlave = lists:suffix("_slave", Test),
     Mode = if IsSlave -> slave;
              IsMaster -> master;
              true -> single
@@ -389,12 +396,12 @@ db_tests(riak) ->
        last,
        roster_get,
        private,
-       privacy,
-       blocking,
+       privacy_tests:single_cases(),
        vcard,
        muc_tests:single_cases(),
        test_unregister]},
      muc_tests:master_slave_cases(),
+     privacy_tests:master_slave_cases(),
      {test_roster_subscribe, [parallel],
       [roster_subscribe_master,
        roster_subscribe_slave]},
@@ -420,13 +427,13 @@ db_tests(DB) when DB == mnesia; DB == redis ->
        roster_get,
        roster_ver,
        private,
-       privacy,
-       blocking,
+       privacy_tests:single_cases(),
        vcard,
        pubsub_single_tests(),
        muc_tests:single_cases(),
        test_unregister]},
      muc_tests:master_slave_cases(),
+     privacy_tests:master_slave_cases(),
      pubsub_multiple_tests(),
      {test_mix, [parallel],
       [mix_master, mix_slave]},
@@ -466,13 +473,13 @@ db_tests(_) ->
        roster_get,
        roster_ver,
        private,
-       privacy,
-       blocking,
+       privacy_tests:single_cases(),
        vcard,
        pubsub_single_tests(),
        muc_tests:single_cases(),
        test_unregister]},
      muc_tests:master_slave_cases(),
+     privacy_tests:master_slave_cases(),
      pubsub_multiple_tests(),
      {test_mix, [parallel],
       [mix_master, mix_slave]},
@@ -797,7 +804,7 @@ s2s_ping(Config) ->
     To = jid:make(?MNESIA_VHOST),
     ID = randoms:get_string(),
     ejabberd_s2s:route(From, To, #iq{id = ID, type = get, sub_els = [#ping{}]}),
-    ?recv1(#iq{type = result, id = ID, sub_els = []}),
+    #iq{type = result, id = ID, sub_els = []} = recv_iq(Config),
     disconnect(Config).
 
 auth_md5(Config) ->
@@ -872,7 +879,7 @@ roster_ver(Config) ->
     %% Attempting to subscribe to server's JID
     send(Config, #presence{type = subscribe, to = server_jid(Config)}),
     %% Receive a single roster push with the new "ver"
-    #iq{type = set, sub_els = [#roster_query{ver = Ver2}]} = recv(Config),
+    #iq{type = set, sub_els = [#roster_query{ver = Ver2}]} = recv_iq(Config),
     %% Requesting roster with the previous "ver". Should receive Ver2 again
     #iq{type = result, sub_els = [#roster_query{ver = Ver2}]} =
         send_recv(Config, #iq{type = get,
@@ -901,9 +908,8 @@ unsupported_query(Config) ->
     disconnect(Config).
 
 presence(Config) ->
-    send(Config, #presence{}),
     JID = my_jid(Config),
-    ?recv1(#presence{from = JID, to = JID}),
+    #presence{from = JID, to = JID} = send_recv(Config, #presence{}),
     disconnect(Config).
 
 presence_broadcast(Config) ->
@@ -927,11 +933,11 @@ presence_broadcast(Config) ->
     %% 1) disco#info iq request for CAPS
     %% 2) welcome message
     %% 3) presence broadcast
-    {IQ, _, _} = ?recv3(#iq{type = get,
-                           from = ServerJID,
-                           sub_els = [#disco_info{node = Node}]},
-                       #message{type = normal},
-                       #presence{from = JID, to = JID}),
+    IQ = #iq{type = get,
+            from = ServerJID,
+            sub_els = [#disco_info{node = Node}]} = recv_iq(Config),
+    #message{type = normal} = recv_message(Config),
+    #presence{from = JID, to = JID} = recv_presence(Config),
     send(Config, #iq{type = result, id = IQ#iq.id,
                     to = ServerJID, sub_els = [Info]}),
     %% We're trying to read our feature from ejabberd database
@@ -1045,7 +1051,7 @@ sm_resume(Config) ->
     ejabberd_router:route(ServerJID, MyJID, Msg),
     send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}),
     ?recv1(#sm_resumed{previd = ID, h = 3}),
-    ?recv1(#message{from = ServerJID, to = MyJID, body = [Txt]}),
+    #message{from = ServerJID, to = MyJID, body = [Txt]} = recv_message(Config),
     ?recv1(#sm_r{}),
     send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}),
     %% Send another stanza to increment the server's 'h' for sm_resume_failed.
@@ -1095,90 +1101,97 @@ last(Config) ->
                               to = server_jid(Config)}),
     disconnect(Config).
 
-privacy(Config) ->
-    true = is_feature_advertised(Config, ?NS_PRIVACY),
-    #iq{type = result, sub_els = [#privacy_query{}]} =
-        send_recv(Config, #iq{type = get, sub_els = [#privacy_query{}]}),
-    JID = <<"tybalt@example.com">>,
-    I1 = send(Config,
-              #iq{type = set,
-                  sub_els = [#privacy_query{
-                                lists = [#privacy_list{
-                                            name = <<"public">>,
-                                            items =
-                                                [#privacy_item{
-                                                    type = jid,
-                                                    order = 3,
-                                                    action = deny,
-                                                   presence_in = true,
-                                                    value = JID}]}]}]}),
-    {Push1, _} =
-        ?recv2(
-           #iq{type = set,
-               sub_els = [#privacy_query{
-                             lists = [#privacy_list{
-                                         name = <<"public">>}]}]},
-           #iq{type = result, id = I1, sub_els = []}),
-    send(Config, make_iq_result(Push1)),
-    #iq{type = result, sub_els = []} =
-        send_recv(Config, #iq{type = set,
-                              sub_els = [#privacy_query{active = <<"public">>}]}),
-    #iq{type = result, sub_els = []} =
-        send_recv(Config, #iq{type = set,
-                              sub_els = [#privacy_query{default = <<"public">>}]}),
-    #iq{type = result,
-        sub_els = [#privacy_query{default = <<"public">>,
-                            active = <<"public">>,
-                            lists = [#privacy_list{name = <<"public">>}]}]} =
-        send_recv(Config, #iq{type = get, sub_els = [#privacy_query{}]}),
-    #iq{type = result, sub_els = []} =
-        send_recv(Config,
-                  #iq{type = set, sub_els = [#privacy_query{default = none}]}),
-    #iq{type = result, sub_els = []} =
-        send_recv(Config, #iq{type = set, sub_els = [#privacy_query{active = none}]}),
-    I2 = send(Config, #iq{type = set,
-                          sub_els = [#privacy_query{
-                                        lists =
-                                            [#privacy_list{
-                                                name = <<"public">>}]}]}),
-    {Push2, _} =
-        ?recv2(
-           #iq{type = set,
-               sub_els = [#privacy_query{
-                             lists = [#privacy_list{
-                                         name = <<"public">>}]}]},
-           #iq{type = result, id = I2, sub_els = []}),
-    send(Config, make_iq_result(Push2)),
-    disconnect(Config).
-
-blocking(Config) ->
-    true = is_feature_advertised(Config, ?NS_BLOCKING),
-    JID = jid:make(<<"romeo">>, <<"montague.net">>, <<>>),
-    #iq{type = result, sub_els = [#block_list{}]} =
-        send_recv(Config, #iq{type = get, sub_els = [#block_list{}]}),
-    I1 = send(Config, #iq{type = set,
-                          sub_els = [#block{items = [JID]}]}),
-    {Push1, Push2, _} =
-        ?recv3(
-           #iq{type = set,
-               sub_els = [#privacy_query{lists = [#privacy_list{}]}]},
-           #iq{type = set,
-               sub_els = [#block{items = [JID]}]},
-           #iq{type = result, id = I1, sub_els = []}),
-    send(Config, make_iq_result(Push1)),
-    send(Config, make_iq_result(Push2)),
-    I2 = send(Config, #iq{type = set,
-                          sub_els = [#unblock{items = [JID]}]}),
-    {Push3, Push4, _} =
-        ?recv3(
-           #iq{type = set,
-               sub_els = [#privacy_query{lists = [#privacy_list{}]}]},
-           #iq{type = set,
-               sub_els = [#unblock{items = [JID]}]},
-           #iq{type = result, id = I2, sub_els = []}),
-    send(Config, make_iq_result(Push3)),
-    send(Config, make_iq_result(Push4)),
-    disconnect(Config).
+privacy_feature_enabled(Config) ->
+    privacy_tests:feature_enabled(Config).
+privacy_set_get_list(Config) ->
+    privacy_tests:set_get_list(Config).
+privacy_get_list_non_existent(Config) ->
+    privacy_tests:get_list_non_existent(Config).
+privacy_set_default(Config) ->
+    privacy_tests:set_default(Config).
+privacy_del_default(Config) ->
+    privacy_tests:del_default(Config).
+privacy_set_default_non_existent(Config) ->
+    privacy_tests:set_default_non_existent(Config).
+privacy_set_active(Config) ->
+    privacy_tests:set_active(Config).
+privacy_del_active(Config) ->
+    privacy_tests:del_active(Config).
+privacy_set_active_non_existent(Config) ->
+    privacy_tests:set_active_non_existent(Config).
+privacy_remove_list(Config) ->
+    privacy_tests:remove_list(Config).
+privacy_remove_active_list(Config) ->
+    privacy_tests:remove_active_list(Config).
+privacy_remove_default_list(Config) ->
+    privacy_tests:remove_default_list(Config).
+privacy_remove_list_non_existent(Config) ->
+    privacy_tests:remove_list_non_existent(Config).
+privacy_allow_local_server(Config) ->
+    privacy_tests:allow_local_server(Config).
+privacy_malformed_iq_query(Config) ->
+    privacy_tests:malformed_iq_query(Config).
+privacy_malformed_get(Config) ->
+    privacy_tests:malformed_get(Config).
+privacy_malformed_set(Config) ->
+    privacy_tests:malformed_set(Config).
+privacy_malformed_type_value(Config) ->
+    privacy_tests:malformed_type_value(Config).
+privacy_set_get_block(Config) ->
+    privacy_tests:set_get_block(Config).
+
+privacy_deny_bare_jid_master(Config) ->
+    privacy_tests:deny_bare_jid_master(Config).
+privacy_deny_bare_jid_slave(Config) ->
+    privacy_tests:deny_bare_jid_slave(Config).
+privacy_deny_full_jid_master(Config) ->
+    privacy_tests:deny_full_jid_master(Config).
+privacy_deny_full_jid_slave(Config) ->
+    privacy_tests:deny_full_jid_slave(Config).
+privacy_deny_server_jid_master(Config) ->
+    privacy_tests:deny_server_jid_master(Config).
+privacy_deny_server_jid_slave(Config) ->
+    privacy_tests:deny_server_jid_slave(Config).
+privacy_deny_group_master(Config) ->
+    privacy_tests:deny_group_master(Config).
+privacy_deny_group_slave(Config) ->
+    privacy_tests:deny_group_slave(Config).
+privacy_deny_sub_both_master(Config) ->
+    privacy_tests:deny_sub_both_master(Config).
+privacy_deny_sub_both_slave(Config) ->
+    privacy_tests:deny_sub_both_slave(Config).
+privacy_deny_sub_from_master(Config) ->
+    privacy_tests:deny_sub_from_master(Config).
+privacy_deny_sub_from_slave(Config) ->
+    privacy_tests:deny_sub_from_slave(Config).
+privacy_deny_sub_to_master(Config) ->
+    privacy_tests:deny_sub_to_master(Config).
+privacy_deny_sub_to_slave(Config) ->
+    privacy_tests:deny_sub_to_slave(Config).
+privacy_deny_sub_none_master(Config) ->
+    privacy_tests:deny_sub_none_master(Config).
+privacy_deny_sub_none_slave(Config) ->
+    privacy_tests:deny_sub_none_slave(Config).
+privacy_deny_all_master(Config) ->
+    privacy_tests:deny_all_master(Config).
+privacy_deny_all_slave(Config) ->
+    privacy_tests:deny_all_slave(Config).
+privacy_deny_offline_master(Config) ->
+    privacy_tests:deny_offline_master(Config).
+privacy_deny_offline_slave(Config) ->
+    privacy_tests:deny_offline_slave(Config).
+privacy_block_master(Config) ->
+    privacy_tests:block_master(Config).
+privacy_block_slave(Config) ->
+    privacy_tests:block_slave(Config).
+privacy_unblock_master(Config) ->
+    privacy_tests:unblock_master(Config).
+privacy_unblock_slave(Config) ->
+    privacy_tests:unblock_slave(Config).
+privacy_unblock_all_master(Config) ->
+    privacy_tests:unblock_all_master(Config).
+privacy_unblock_all_slave(Config) ->
+    privacy_tests:unblock_all_slave(Config).
 
 vcard(Config) ->
     true = is_feature_advertised(Config, ?NS_VCARD),
@@ -1239,17 +1252,16 @@ vcard_xupdate_master(Config) ->
     MyJID = my_jid(Config),
     Peer = ?config(slave, Config),
     wait_for_slave(Config),
-    send(Config, #presence{}),
-    ?recv2(#presence{from = MyJID, type = available},
-           #presence{from = Peer, type = available}),
+    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
+    #presence{from = Peer, type = available} = recv_presence(Config),
     VCard = #vcard_temp{photo = #vcard_photo{type = <<"image/png">>, binval = Img}},
-    I1 = send(Config, #iq{type = set, sub_els = [VCard]}),
-    ?recv2(#iq{type = result, sub_els = [], id = I1},
-          #presence{from = MyJID, type = available,
-                    sub_els = [#vcard_xupdate{hash = ImgHash}]}),
-    I2 = send(Config, #iq{type = set, sub_els = [#vcard_temp{}]}),
-    ?recv3(#iq{type = result, sub_els = [], id = I2},
-          #presence{from = MyJID, type = available,
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set, sub_els = [VCard]}),
+    #presence{from = MyJID, type = available,
+             sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config),
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set, sub_els = [#vcard_temp{}]}),
+    ?recv2(#presence{from = MyJID, type = available,
                     sub_els = [#vcard_xupdate{hash = undefined}]},
           #presence{from = Peer, type = unavailable}),
     disconnect(Config).
@@ -1259,14 +1271,13 @@ vcard_xupdate_slave(Config) ->
     ImgHash = p1_sha:sha(Img),
     MyJID = my_jid(Config),
     Peer = ?config(master, Config),
-    send(Config, #presence{}),
-    ?recv1(#presence{from = MyJID, type = available}),
+    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
     wait_for_master(Config),
-    ?recv1(#presence{from = Peer, type = available}),
-    ?recv1(#presence{from = Peer, type = available,
-             sub_els = [#vcard_xupdate{hash = ImgHash}]}),
-    ?recv1(#presence{from = Peer, type = available,
-             sub_els = [#vcard_xupdate{hash = undefined}]}),
+    #presence{from = Peer, type = available} = recv_presence(Config),
+    #presence{from = Peer, type = available,
+             sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config),
+    #presence{from = Peer, type = available,
+             sub_els = [#vcard_xupdate{hash = undefined}]} = recv_presence(Config),
     disconnect(Config).
 
 stats(Config) ->
@@ -1439,7 +1450,7 @@ pubsub_publish_slave(Config) ->
        sub_els =
           [#ps_event{
               items = #ps_items{node = Node,
-                                items = [Item]}}]} = recv(Config),
+                                items = [Item]}}]} = recv_message(Config),
     put_event(Config, Item),
     disconnect(Config).
 
@@ -1478,13 +1489,14 @@ pubsub_subscriptions_slave(Config) ->
                                                  type = Type}}]},
                     #message{sub_els = [#ps_event{}]});
         (Type) ->
-             ?recv1(#message{
-                       sub_els =
-                           [#ps_event{
-                               subscription = #ps_subscription{
-                                                 node = Node,
-                                                 jid = MyJID,
-                                                 type = Type}}]})
+             #message{
+                sub_els =
+                    [#ps_event{
+                        subscription = #ps_subscription{
+                                          node = Node,
+                                          jid = MyJID,
+                                          type = Type}}]} =
+                 recv_message(Config)
       end, [subscribed, unconfigured, pending, none]),
     disconnect(Config).
 
@@ -1644,7 +1656,7 @@ pubsub_affiliations_slave(Config, disconnect) ->
 
 pubsub_authorize_master(Config) ->
     send(Config, #presence{}),
-    ?recv1(#presence{}),
+    #presence{} = recv_presence(Config),
     Peer = ?config(slave, Config),
     PJID = pubsub_jid(Config),
     NodeConfig = set_opts(default_node_config(Config),
@@ -1652,7 +1664,7 @@ pubsub_authorize_master(Config) ->
     Node = ?config(pubsub_node, Config),
     Node = create_node(Config, Node, NodeConfig),
     wait_for_slave(Config),
-    #message{sub_els = [#xdata{fields = F1}]} = recv(Config),
+    #message{sub_els = [#xdata{fields = F1}]} = recv_message(Config),
     C1 = pubsub_subscribe_authorization:decode(F1),
     Node = proplists:get_value(node, C1),
     Peer = proplists:get_value(subscriber_jid, C1),
@@ -1666,7 +1678,7 @@ pubsub_authorize_master(Config) ->
     %% We should not have any subscriptions
     [] = get_subscriptions(Config, Node),
     wait_for_slave(Config),
-    #message{sub_els = [#xdata{fields = F2}]} = recv(Config),
+    #message{sub_els = [#xdata{fields = F2}]} = recv_message(Config),
     C2 = pubsub_subscribe_authorization:decode(F2),
     Node = proplists:get_value(node, C2),
     Peer = proplists:get_value(subscriber_jid, C2),
@@ -1687,19 +1699,21 @@ pubsub_authorize_slave(Config) ->
     wait_for_master(Config),
     #ps_subscription{type = pending} = subscribe_node(Config, Node),
     %% We're denied at first
-    ?recv1(#message{
-             sub_els =
-                 [#ps_event{
-                     subscription = #ps_subscription{type = none,
-                                                     jid = MyJID}}]}),
+    #message{
+       sub_els =
+          [#ps_event{
+              subscription = #ps_subscription{type = none,
+                                              jid = MyJID}}]} =
+       recv_message(Config),
     wait_for_master(Config),
     #ps_subscription{type = pending} = subscribe_node(Config, Node),
     %% Now much better!
-    ?recv1(#message{
-             sub_els =
-                 [#ps_event{
-                     subscription = #ps_subscription{type = subscribed,
-                                                     jid = MyJID}}]}),
+    #message{
+       sub_els =
+          [#ps_event{
+              subscription = #ps_subscription{type = subscribed,
+                                              jid = MyJID}}]} =
+       recv_message(Config),
     wait_for_master(Config),
     disconnect(Config).
 
@@ -1961,46 +1975,48 @@ mix_master(Config) ->
     Nodes = [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PRESENCE,
             ?NS_MIX_NODES_PARTICIPANTS, ?NS_MIX_NODES_SUBJECT,
             ?NS_MIX_NODES_CONFIG],
-    I0 = send(Config, #iq{type = set, to = Room,
-                         sub_els = [#mix_join{subscribe = Nodes}]}),
-    {_, #message{sub_els =
-                    [#ps_event{
-                        items = #ps_items{
-                                   node = ?NS_MIX_NODES_PARTICIPANTS,
-                                   items = [#ps_item{
-                                               id = ParticipantID,
-                                               xml_els = [PXML]}]}}]}} =
-       ?recv2(#iq{type = result, id = I0,
-                  sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]},
-              #message{from = Room}),
+    #iq{type = result,
+       sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]} =
+       send_recv(Config, #iq{type = set, to = Room,
+                             sub_els = [#mix_join{subscribe = Nodes}]}),
+    #message{from = Room,
+            sub_els =
+                [#ps_event{
+                    items = #ps_items{
+                               node = ?NS_MIX_NODES_PARTICIPANTS,
+                               items = [#ps_item{
+                                           id = ParticipantID,
+                                           xml_els = [PXML]}]}}]} =
+       recv_message(Config),
     #mix_participant{jid = MyBareJID} = xmpp:decode(PXML),
     %% Coming online
     PresenceID = randoms:get_string(),
     Presence = xmpp:encode(#presence{}),
-    I1 = send(
-          Config,
-          #iq{type = set, to = Room,
-              sub_els =
-                  [#pubsub{
-                      publish = #ps_publish{
-                                   node = ?NS_MIX_NODES_PRESENCE,
-                                   items = [#ps_item{
-                                               id = PresenceID,
-                                               xml_els = [Presence]}]}}]}),
-    ?recv2(#iq{type = result, id = I1,
-              sub_els =
-                  [#pubsub{
-                      publish = #ps_publish{
-                                   node = ?NS_MIX_NODES_PRESENCE,
-                                   items = [#ps_item{id = PresenceID}]}}]},
-          #message{from = Room,
-                   sub_els =
-                       [#ps_event{
-                           items = #ps_items{
-                                      node = ?NS_MIX_NODES_PRESENCE,
-                                      items = [#ps_item{
-                                                   id = PresenceID,
-                                                  xml_els = [Presence]}]}}]}),
+    #iq{type = result,
+       sub_els =
+           [#pubsub{
+               publish = #ps_publish{
+                            node = ?NS_MIX_NODES_PRESENCE,
+                            items = [#ps_item{id = PresenceID}]}}]} =
+       send_recv(
+         Config,
+         #iq{type = set, to = Room,
+             sub_els =
+                 [#pubsub{
+                     publish = #ps_publish{
+                                  node = ?NS_MIX_NODES_PRESENCE,
+                                  items = [#ps_item{
+                                              id = PresenceID,
+                                              xml_els = [Presence]}]}}]}),
+    #message{from = Room,
+            sub_els =
+                [#ps_event{
+                    items = #ps_items{
+                               node = ?NS_MIX_NODES_PRESENCE,
+                               items = [#ps_item{
+                                           id = PresenceID,
+                                           xml_els = [Presence]}]}}]} =
+       recv_message(Config),
     %% Coming offline
     send(Config, #presence{type = unavailable, to = Room}),
     %% Receiving presence retract event
@@ -2008,101 +2024,109 @@ mix_master(Config) ->
             sub_els = [#ps_event{
                           items = #ps_items{
                                      node = ?NS_MIX_NODES_PRESENCE,
-                                     retract = PresenceID}}]} = recv(Config),
+                                     retract = PresenceID}}]} =
+       recv_message(Config),
     %% Leaving
-    I2 = send(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}),
-    ?recv2(#iq{type = result, id = I2, sub_els = []},
-          #message{from = Room,
-                   sub_els =
-                       [#ps_event{
-                           items = #ps_items{
-                                      node = ?NS_MIX_NODES_PARTICIPANTS,
-                                      retract = ParticipantID}}]}),
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}),
+    #message{from = Room,
+            sub_els =
+                [#ps_event{
+                    items = #ps_items{
+                               node = ?NS_MIX_NODES_PARTICIPANTS,
+                               retract = ParticipantID}}]} =
+       recv_message(Config),
+    put_event(Config, disconnect),
     disconnect(Config).
 
 mix_slave(Config) ->
+    disconnect = get_event(Config),
     disconnect(Config).
 
 roster_subscribe_master(Config) ->
-    send(Config, #presence{}),
-    ?recv1(#presence{}),
+    #presence{} = send_recv(Config, #presence{}),
     wait_for_slave(Config),
-    Peer = ?config(slave, Config),
+    Peer = ?config(peer, Config),
     LPeer = jid:remove_resource(Peer),
     send(Config, #presence{type = subscribe, to = LPeer}),
-    Push1 = ?recv1(#iq{type = set,
+    Push1 = #iq{type = set,
                 sub_els = [#roster_query{items = [#roster_item{
-                                               ask = subscribe,
-                                               subscription = none,
-                                               jid = LPeer}]}]}),
+                                                    ask = subscribe,
+                                                    subscription = none,
+                                                    jid = LPeer}]}]} =
+       recv_iq(Config),
     send(Config, make_iq_result(Push1)),
-    {Push2, _} = ?recv2(
-                    #iq{type = set,
-                        sub_els = [#roster_query{items = [#roster_item{
-                                                       subscription = to,
-                                                       jid = LPeer}]}]},
-                    #presence{type = subscribed, from = LPeer}),
+    #presence{type = subscribed, from = LPeer} = recv_presence(Config),
+    Push2 = #iq{type = set,
+               sub_els = [#roster_query{items = [#roster_item{
+                                                    subscription = to,
+                                                    jid = LPeer}]}]} =
+       recv_iq(Config),
     send(Config, make_iq_result(Push2)),
-    ?recv1(#presence{type = available, from = Peer}),
+    #presence{type = available, from = Peer} = recv_presence(Config),
     %% BUG: ejabberd sends previous push again. Is it ok?
-    Push3 = ?recv1(#iq{type = set,
+    Push3 = #iq{type = set,
                 sub_els = [#roster_query{items = [#roster_item{
-                                               subscription = to,
-                                               jid = LPeer}]}]}),
+                                                    subscription = to,
+                                                    jid = LPeer}]}]} =
+       recv_iq(Config),
     send(Config, make_iq_result(Push3)),
-    ?recv1(#presence{type = subscribe, from = LPeer}),
+    #presence{type = subscribe, from = LPeer} = recv_presence(Config),
     send(Config, #presence{type = subscribed, to = LPeer}),
-    Push4 = ?recv1(#iq{type = set,
+    Push4 = #iq{type = set,
                 sub_els = [#roster_query{items = [#roster_item{
-                                               subscription = both,
-                                               jid = LPeer}]}]}),
+                                                    subscription = both,
+                                                    jid = LPeer}]}]} =
+       recv_iq(Config),
     send(Config, make_iq_result(Push4)),
     %% Move into a group
     Groups = [<<"A">>, <<"B">>],
     Item = #roster_item{jid = LPeer, groups = Groups},
-    I1 = send(Config, #iq{type = set, sub_els = [#roster_query{items = [Item]}]}),
-    {Push5, _} = ?recv2(
-                   #iq{type = set,
-                       sub_els =
-                           [#roster_query{items = [#roster_item{
-                                                jid = LPeer,
-                                                subscription = both}]}]},
-                   #iq{type = result, id = I1, sub_els = []}),
+    #iq{type = result, sub_els = []} =
+       send_recv(Config,
+                 #iq{type = set, sub_els = [#roster_query{items = [Item]}]}),
+    Push5 = #iq{type = set,
+               sub_els =
+                   [#roster_query{items = [#roster_item{
+                                              jid = LPeer,
+                                              subscription = both}]}]} =
+       recv_iq(Config),
     send(Config, make_iq_result(Push5)),
     #iq{sub_els = [#roster_query{items = [#roster_item{groups = G1}]}]} = Push5,
     Groups = lists:sort(G1),
     wait_for_slave(Config),
-    ?recv1(#presence{type = unavailable, from = Peer}),
+    #presence{type = unavailable, from = Peer} = recv_presence(Config),
     disconnect(Config).
 
 roster_subscribe_slave(Config) ->
-    send(Config, #presence{}),
-    ?recv1(#presence{}),
+    #presence{} = send_recv(Config, #presence{}),
     wait_for_master(Config),
     Peer = ?config(master, Config),
     LPeer = jid:remove_resource(Peer),
-    ?recv1(#presence{type = subscribe, from = LPeer}),
+    #presence{type = subscribe, from = LPeer} = recv_presence(Config),
     send(Config, #presence{type = subscribed, to = LPeer}),
-    Push1 = ?recv1(#iq{type = set,
+    Push1 = #iq{type = set,
                 sub_els = [#roster_query{items = [#roster_item{
-                                               subscription = from,
-                                               jid = LPeer}]}]}),
+                                                    subscription = from,
+                                                    jid = LPeer}]}]} =
+       recv_iq(Config),
     send(Config, make_iq_result(Push1)),
     send(Config, #presence{type = subscribe, to = LPeer}),
-    Push2 = ?recv1(#iq{type = set,
+    Push2 = #iq{type = set,
                 sub_els = [#roster_query{items = [#roster_item{
-                                               ask = subscribe,
-                                               subscription = from,
-                                               jid = LPeer}]}]}),
+                                                    ask = subscribe,
+                                                    subscription = from,
+                                                    jid = LPeer}]}]} =
+       recv_iq(Config),
     send(Config, make_iq_result(Push2)),
-    {Push3, _} = ?recv2(
-                    #iq{type = set,
-                        sub_els = [#roster_query{items = [#roster_item{
-                                                       subscription = both,
-                                                       jid = LPeer}]}]},
-                    #presence{type = subscribed, from = LPeer}),
+    #presence{type = subscribed, from = LPeer} = recv_presence(Config),
+    Push3 = #iq{type = set,
+               sub_els = [#roster_query{items = [#roster_item{
+                                                    subscription = both,
+                                                    jid = LPeer}]}]} =
+       recv_iq(Config),
     send(Config, make_iq_result(Push3)),
-    ?recv1(#presence{type = available, from = Peer}),
+    #presence{type = available, from = Peer} = recv_presence(Config),
     wait_for_master(Config),
     disconnect(Config).
 
@@ -2112,9 +2136,8 @@ roster_remove_master(Config) ->
     LPeer = jid:remove_resource(Peer),
     Groups = [<<"A">>, <<"B">>],
     wait_for_slave(Config),
-    send(Config, #presence{}),
-    ?recv2(#presence{from = MyJID, type = available},
-           #presence{from = Peer, type = available}),
+    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
+    #presence{from = Peer, type = available} = recv_presence(Config),
     %% The peer removed us from its roster.
     {Push1, Push2, _, _, _} =
         ?recv5(
@@ -2144,21 +2167,21 @@ roster_remove_slave(Config) ->
     MyJID = my_jid(Config),
     Peer = ?config(master, Config),
     LPeer = jid:remove_resource(Peer),
-    send(Config, #presence{}),
-    ?recv1(#presence{from = MyJID, type = available}),
+    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
     wait_for_master(Config),
-    ?recv1(#presence{from = Peer, type = available}),
+    #presence{from = Peer, type = available} = recv_presence(Config),
     %% Remove the peer from roster.
     Item = #roster_item{jid = LPeer, subscription = remove},
-    I = send(Config, #iq{type = set, sub_els = [#roster_query{items = [Item]}]}),
-    {Push, _, _} = ?recv3(
-                   #iq{type = set,
-                       sub_els =
-                           [#roster_query{items = [#roster_item{
-                                                jid = LPeer,
-                                                subscription = remove}]}]},
-                   #iq{type = result, id = I, sub_els = []},
-                   #presence{type = unavailable, from = Peer}),
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set,
+                             sub_els = [#roster_query{items = [Item]}]}),
+    Push = #iq{type = set,
+              sub_els =
+                  [#roster_query{items = [#roster_item{
+                                             jid = LPeer,
+                                             subscription = remove}]}]} =
+       recv_iq(Config),
+    #presence{type = unavailable, from = Peer} = recv_presence(Config),
     send(Config, make_iq_result(Push)),
     disconnect(Config).
 
@@ -2168,7 +2191,7 @@ proxy65_master(Config) ->
     Peer = ?config(slave, Config),
     wait_for_slave(Config),
     send(Config, #presence{}),
-    ?recv1(#presence{from = MyJID, type = available}),
+    #presence{from = MyJID, type = available} = recv_presence(Config),
     true = is_feature_advertised(Config, ?NS_BYTESTREAMS, Proxy),
     #iq{type = result, sub_els = [#bytestreams{hosts = [StreamHost]}]} =
         send_recv(
@@ -2191,7 +2214,7 @@ proxy65_slave(Config) ->
     MyJID = my_jid(Config),
     Peer = ?config(master, Config),
     send(Config, #presence{}),
-    ?recv1(#presence{from = MyJID, type = available}),
+    #presence{from = MyJID, type = available} = recv_presence(Config),
     wait_for_master(Config),
     {StreamHost, SID, Data} = get_event(Config),
     Socks5 = socks5_connect(StreamHost, {SID, Peer, MyJID}),
@@ -2206,11 +2229,11 @@ send_messages_to_room(Config, Range) ->
     lists:foreach(
       fun(N) ->
               Text = #text{data = integer_to_binary(N)},
-              I = send(Config, #message{to = Room, body = [Text],
-                                       type = groupchat}),
-             ?recv1(#message{from = MyNickJID, id = I,
-                             type = groupchat,
-                             body = [Text]})
+             #message{from = MyNickJID, id = I,
+                      type = groupchat,
+                      body = [Text]} =
+                 send_recv(Config, #message{to = Room, body = [Text],
+                                            type = groupchat})
       end, Range).
 
 retrieve_messages_from_room_via_mam(Config, Range) ->
@@ -2225,34 +2248,33 @@ retrieve_messages_from_room_via_mam(Config, Range) ->
     lists:foreach(
       fun(N) ->
              Text = #text{data = integer_to_binary(N)},
-             ?recv1(#message{
-                       to = MyJID, from = Room,
-                       sub_els =
-                           [#mam_result{
-                               xmlns = ?NS_MAM_1,
-                               queryid = QID,
-                               sub_els =
-                                   [#forwarded{
-                                       delay = #delay{},
-                                       sub_els = [#message{
-                                                     from = MyNickJID,
-                                                     type = groupchat,
-                                                     body = [Text]}]}]}]})
+             #message{
+                to = MyJID, from = Room,
+                sub_els =
+                    [#mam_result{
+                        xmlns = ?NS_MAM_1,
+                        queryid = QID,
+                        sub_els =
+                            [#forwarded{
+                                delay = #delay{},
+                                sub_els = [#message{
+                                              from = MyNickJID,
+                                              type = groupchat,
+                                              body = [Text]}]}]}]} =
+                 recv_message(Config)
       end, Range),
-    ?recv1(#iq{from = Room, id = I, type = result,
-              sub_els = [#mam_fin{xmlns = ?NS_MAM_1,
-                                  id = QID,
-                                  rsm = #rsm_set{count = Count},
-                                  complete = true}]}).
+    #iq{from = Room, id = I, type = result,
+       sub_els = [#mam_fin{xmlns = ?NS_MAM_1,
+                           id = QID,
+                           rsm = #rsm_set{count = Count},
+                           complete = true}]} = recv_iq(Config).
 
 muc_mam_master(Config) ->
     MyNick = ?config(master_nick, Config),
     Room = muc_room_jid(Config),
     MyNickJID = jid:replace_resource(Room, MyNick),
     %% Joining
-    send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
-    %% Receive self-presence
-    ?recv1(#presence{from = MyNickJID}),
+    ok = muc_tests:muc_join_new(Config),
     %% MAM feature should not be advertised at this point,
     %% because MAM is not enabled so far
     false = is_feature_advertised(Config, ?NS_MAM_1, Room),
@@ -2274,20 +2296,22 @@ muc_mam_master(Config) ->
                          []
                  end, RoomCfg#xdata.fields),
     NewRoomCfg = #xdata{type = submit, fields = NewFields},
-    I1 = send(Config, #iq{type = set, to = Room,
-                         sub_els = [#muc_owner{config = NewRoomCfg}]}),
-    ?recv2(#iq{type = result, id = I1},
-          #message{from = Room, type = groupchat,
-                   sub_els = [#muc_user{status_codes = [104]}]}),
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set, to = Room,
+                             sub_els = [#muc_owner{config = NewRoomCfg}]}),
+    #message{from = Room, type = groupchat,
+            sub_els = [#muc_user{status_codes = [104]}]} = recv_message(Config),
     %% Check if MAM has been enabled
     true = is_feature_advertised(Config, ?NS_MAM_1, Room),
     %% We now sending some messages again
     send_messages_to_room(Config, lists:seq(1, 5)),
     %% And retrieve them via MAM again.
     retrieve_messages_from_room_via_mam(Config, lists:seq(1, 5)),
+    put_event(Config, disconnect),
     disconnect(Config).
 
 muc_mam_slave(Config) ->
+    disconnect = get_event(Config),
     disconnect(Config).
 
 %% OK, I know this is retarded, but I didn't find a better way to
@@ -2441,12 +2465,11 @@ announce_master(Config) ->
     ServerJID = server_jid(Config),
     MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>),
     MotdText = #text{data = <<"motd">>},
-    send(Config, #presence{}),
-    ?recv1(#presence{from = MyJID}),
+    #presence{from = MyJID} = send_recv(Config, #presence{}),
     %% Set message of the day
     send(Config, #message{to = MotdJID, body = [MotdText]}),
     %% Receive this message back
-    ?recv1(#message{from = ServerJID, body = [MotdText]}),
+    #message{from = ServerJID, body = [MotdText]} = recv_message(Config),
     disconnect(Config).
 
 announce_slave(Config) ->
@@ -2454,9 +2477,8 @@ announce_slave(Config) ->
     ServerJID = server_jid(Config),
     MotdDelJID = jid:replace_resource(ServerJID, <<"announce/motd/delete">>),
     MotdText = #text{data = <<"motd">>},
-    send(Config, #presence{}),
-    ?recv2(#presence{from = MyJID},
-          #message{from = ServerJID, body = [MotdText]}),
+    #presence{from = MyJID} = send_recv(Config, #presence{}),
+    #message{from = ServerJID, body = [MotdText]} = recv_message(Config),
     %% Delete message of the day
     send(Config, #message{to = MotdDelJID}),
     disconnect(Config).
@@ -2520,39 +2542,39 @@ flex_offline_slave(Config) ->
                end, DiscoItems)),
     %% Since headers are received we can send initial presence without a risk
     %% of getting offline messages flood
-    send(Config, #presence{}),
-    ?recv1(#presence{from = MyJID}),
+    #presence{from = MyJID} = send_recv(Config, #presence{}),
     %% Check full fetch
-    I0 = send(Config, #iq{type = get, sub_els = [#offline{fetch = true}]}),
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = get, sub_els = [#offline{fetch = true}]}),
     lists:foreach(
       fun({I, N}) ->
              Text = integer_to_binary(I),
-             #message{body = Body, sub_els = SubEls} = recv(Config),
+             #message{body = Body, sub_els = SubEls} = recv_message(Config),
              [#text{data = Text}] = Body,
              #offline{items = [#offline_item{node = N}]} =
                  lists:keyfind(offline, 1, SubEls),
              #delay{} = lists:keyfind(delay, 1, SubEls)
       end, lists:zip(lists:seq(1, 5), Nodes)),
-    ?recv1(#iq{type = result, id = I0, sub_els = []}),
     %% Fetch 2nd and 4th message
-    I1 = send(Config,
-             #iq{type = get,
-                 sub_els = [#offline{
-                               items = [#offline_item{
-                                           action = view,
-                                           node = lists:nth(2, Nodes)},
-                                        #offline_item{
-                                           action = view,
-                                           node = lists:nth(4, Nodes)}]}]}),
+    #iq{type = result, sub_els = []} =
+       send_recv(
+         Config,
+         #iq{type = get,
+             sub_els = [#offline{
+                           items = [#offline_item{
+                                       action = view,
+                                       node = lists:nth(2, Nodes)},
+                                    #offline_item{
+                                       action = view,
+                                       node = lists:nth(4, Nodes)}]}]}),
     lists:foreach(
       fun({I, N}) ->
              Text = integer_to_binary(I),
              #message{body = [#text{data = Text}],
-                      sub_els = SubEls} = recv(Config),
+                      sub_els = SubEls} = recv_message(Config),
              #offline{items = [#offline_item{node = N}]} =
                  lists:keyfind(offline, 1, SubEls)
       end, lists:zip([2, 4], [lists:nth(2, Nodes), lists:nth(4, Nodes)])),
-    ?recv1(#iq{type = result, id = I1, sub_els = []}),
     %% Delete 2nd and 4th message
     #iq{type = result, sub_els = []} =
        send_recv(
@@ -2601,12 +2623,12 @@ offline_master(Config) ->
 
 offline_slave(Config) ->
     Peer = ?config(master, Config),
-    send(Config, #presence{}),
-    {_, #message{sub_els = SubEls}} =
-        ?recv2(#presence{},
-               #message{from = Peer,
-                        body = [#text{data = <<"body">>}],
-                        subject = [#text{data = <<"subject">>}]}),
+    #presence{} = send_recv(Config, #presence{}),
+    #message{sub_els = SubEls,
+            from = Peer,
+            body = [#text{data = <<"body">>}],
+            subject = [#text{data = <<"subject">>}]} =
+       recv_message(Config),
     true = lists:keymember(delay, 1, SubEls),
     disconnect(Config).
 
@@ -2616,10 +2638,9 @@ carbons_master(Config) ->
     Peer = ?config(slave, Config),
     Txt = #text{data = <<"body">>},
     true = is_feature_advertised(Config, ?NS_CARBONS_2),
-    send(Config, #presence{priority = 10}),
-    ?recv1(#presence{from = MyJID}),
+    #presence{from = MyJID} = send_recv(Config, #presence{priority = 10}),
     wait_for_slave(Config),
-    ?recv1(#presence{from = Peer}),
+    #presence{from = Peer} = recv_presence(Config),
     %% Enable carbons
     #iq{type = result, sub_els = []} =
        send_recv(Config,
@@ -2670,8 +2691,8 @@ carbons_slave(Config) ->
     Peer = ?config(master, Config),
     Txt = #text{data = <<"body">>},
     wait_for_master(Config),
-    send(Config, #presence{priority = 5}),
-    ?recv2(#presence{from = MyJID}, #presence{from = Peer}),
+    #presence{from = MyJID} = send_recv(Config, #presence{priority = 5}),
+    #presence{from = Peer} = recv_presence(Config),
     %% Enable carbons
     #iq{type = result, sub_els = []} =
        send_recv(Config,
@@ -2722,7 +2743,7 @@ carbons_slave(Config) ->
                      sub_els = [#carbons_disable{}]}),
     wait_for_master(Config),
     %% Now we should receive nothing but presence unavailable from the peer
-    ?recv1(#presence{from = Peer, type = unavailable}),
+    #presence{from = Peer, type = unavailable} = recv_presence(Config),
     disconnect(Config).
 
 mam_old_master(Config) ->
@@ -2736,10 +2757,9 @@ mam_master(Config, NS) ->
     MyJID = my_jid(Config),
     BareMyJID = jid:remove_resource(MyJID),
     Peer = ?config(slave, Config),
-    send(Config, #presence{}),
-    ?recv1(#presence{}),
+    #presence{} = send_recv(Config, #presence{}),
     wait_for_slave(Config),
-    ?recv1(#presence{from = Peer}),
+    #presence{from = Peer} = recv_presence(Config),
     #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = roster}]} =
         send_recv(Config,
                   #iq{type = set,
@@ -2747,18 +2767,18 @@ mam_master(Config, NS) ->
                                            default = roster,
                                             never = [MyJID]}]}),
     if NS == ?NS_MAM_TMP ->
-           FakeArchived = #mam_archived{id = randoms:get_string(),
-                                        by = server_jid(Config)},
-           send(Config, #message{to = MyJID,
-                                 sub_els = [FakeArchived],
-                                 body = [#text{data = <<"a">>}]}),
-           send(Config, #message{to = BareMyJID,
-                                 sub_els = [FakeArchived],
-                                 body = [#text{data = <<"b">>}]}),
            %% NOTE: The server should strip fake archived tags,
            %% i.e. the sub_els received should be [].
-           ?recv2(#message{body = [#text{data = <<"a">>}], sub_els = []},
-                  #message{body = [#text{data = <<"b">>}], sub_els = []});
+           FakeArchived = #mam_archived{id = randoms:get_string(),
+                                        by = server_jid(Config)},
+           #message{body = [#text{data = <<"a">>}], sub_els = []} =
+               send_recv(Config, #message{to = MyJID,
+                                          sub_els = [FakeArchived],
+                                          body = [#text{data = <<"a">>}]}),
+           #message{body = [#text{data = <<"b">>}], sub_els = []} =
+               send_recv(Config, #message{to = BareMyJID,
+                                          sub_els = [FakeArchived],
+                                          body = [#text{data = <<"b">>}]});
        true ->
            ok
     end,
@@ -2766,10 +2786,9 @@ mam_master(Config, NS) ->
     lists:foreach(
       fun(N) ->
               Text = #text{data = integer_to_binary(N)},
-              send(Config,
-                   #message{to = Peer, body = [Text]})
+              send(Config, #message{to = Peer, body = [Text]})
       end, lists:seq(1, 5)),
-    ?recv1(#presence{type = unavailable, from = Peer}),
+    #presence{type = unavailable, from = Peer} = recv_presence(Config),
     mam_query_all(Config, NS),
     mam_query_with(Config, Peer, NS),
     %% mam_query_with(Config, jid:remove_resource(Peer)),
@@ -2791,8 +2810,8 @@ mam_slave(Config, NS) ->
     MyJID = my_jid(Config),
     ServerJID = server_jid(Config),
     wait_for_master(Config),
-    send(Config, #presence{}),
-    ?recv2(#presence{from = MyJID}, #presence{from = Peer}),
+    #presence{from = MyJID} = send_recv(Config, #presence{}),
+    #presence{from = Peer} = recv_presence(Config),
     #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = always}]} =
         send_recv(Config,
                   #iq{type = set,
@@ -2801,7 +2820,7 @@ mam_slave(Config, NS) ->
     lists:foreach(
       fun(N) ->
               Text = #text{data = integer_to_binary(N)},
-             Msg = ?recv1(#message{from = Peer, body = [Text]}),
+             Msg = #message{from = Peer, body = [Text]} = recv_message(Config),
              #mam_archived{by = ServerJID} =
                  xmpp:get_subtag(Msg, #mam_archived{}),
              #stanza_id{by = ServerJID} =
@@ -2828,7 +2847,7 @@ mam_query_all(Config, NS) ->
     lists:foreach(
       fun(N) ->
               Text = #text{data = integer_to_binary(N)},
-              ?recv1(#message{to = MyJID,
+              #message{to = MyJID,
                        sub_els =
                            [#mam_result{
                                queryid = QID,
@@ -2838,13 +2857,15 @@ mam_query_all(Config, NS) ->
                                        sub_els =
                                            [#message{
                                                from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]})
+                                               body = [Text]}]}]}]} =
+                 recv_message(Config)
       end, Iter),
     if NS == ?NS_MAM_TMP ->
-           ?recv1(#iq{type = result, id = I,
-                      sub_els = [#mam_query{xmlns = NS, id = QID}]});
+           #iq{type = result, id = I,
+               sub_els = [#mam_query{xmlns = NS, id = QID}]} = recv_iq(Config);
        true ->
-           ?recv1(#message{sub_els = [#mam_fin{complete = true, id = QID}]})
+           #message{sub_els = [#mam_fin{complete = true, id = QID}]} =
+               recv_message(Config)
     end.
 
 mam_query_with(Config, JID, NS) ->
@@ -2866,7 +2887,7 @@ mam_query_with(Config, JID, NS) ->
     lists:foreach(
       fun(N) ->
               Text = #text{data = integer_to_binary(N)},
-              ?recv1(#message{to = MyJID,
+              #message{to = MyJID,
                        sub_els =
                            [#mam_result{
                                sub_els =
@@ -2875,17 +2896,19 @@ mam_query_with(Config, JID, NS) ->
                                        sub_els =
                                            [#message{
                                                from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]})
+                                               body = [Text]}]}]}]} =
+                 recv_message(Config)
       end, Iter),
     if NS == ?NS_MAM_TMP ->
-           ?recv1(#iq{type = result, id = I,
-                      sub_els = [#mam_query{xmlns = NS}]});
+           #iq{type = result, id = I,
+               sub_els = [#mam_query{xmlns = NS}]} = recv_iq(Config);
        true ->
-           ?recv1(#message{sub_els = [#mam_fin{complete = true}]})
+           #message{sub_els = [#mam_fin{complete = true}]} =
+               recv_message(Config)
     end.
 
 maybe_recv_iq_result(Config, ?NS_MAM_0, I1) ->
-    ?recv1(#iq{type = result, id = I1});
+    #iq{type = result, id = I1} = recv_iq(Config);
 maybe_recv_iq_result(_, _, _) ->
     ok.
 
@@ -2904,7 +2927,7 @@ mam_query_rsm(Config, NS) ->
     lists:foreach(
       fun(N) ->
               Text = #text{data = integer_to_binary(N)},
-              ?recv1(#message{to = MyJID,
+              #message{to = MyJID,
                        sub_els =
                            [#mam_result{
                               xmlns = NS,
@@ -2914,20 +2937,21 @@ mam_query_rsm(Config, NS) ->
                                        sub_els =
                                            [#message{
                                                from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]})
+                                               body = [Text]}]}]}]} =
+                 recv_message(Config)
       end, lists:seq(1, 3)),
     if NS == ?NS_MAM_TMP ->
            #iq{type = result, id = I1,
                sub_els = [#mam_query{xmlns = NS,
                                      rsm = #rsm_set{last = Last,
                                                     count = 5}}]} =
-               recv(Config);
+               recv_iq(Config);
        true ->
            #message{sub_els = [#mam_fin{
                                   complete = false,
                                   rsm = #rsm_set{last = Last,
                                                  count = 10}}]} =
-               recv(Config)
+               recv_message(Config)
     end,
     %% Get the next items starting from the `Last`.
     %% Limit the response to 2 items.
@@ -2940,7 +2964,7 @@ mam_query_rsm(Config, NS) ->
     lists:foreach(
       fun(N) ->
               Text = #text{data = integer_to_binary(N)},
-              ?recv1(#message{to = MyJID,
+              #message{to = MyJID,
                        sub_els =
                            [#mam_result{
                               xmlns = NS,
@@ -2950,7 +2974,8 @@ mam_query_rsm(Config, NS) ->
                                        sub_els =
                                            [#message{
                                                from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]})
+                                               body = [Text]}]}]}]} =
+                 recv_message(Config)
       end, lists:seq(4, 5)),
     if NS == ?NS_MAM_TMP ->
            #iq{type = result, id = I2,
@@ -2959,7 +2984,7 @@ mam_query_rsm(Config, NS) ->
                              rsm = #rsm_set{
                                       count = 5,
                                       first = #rsm_first{data = First}}}]} =
-               recv(Config);
+               recv_iq(Config);
        true ->
            #message{
               sub_els = [#mam_fin{
@@ -2967,7 +2992,7 @@ mam_query_rsm(Config, NS) ->
                             rsm = #rsm_set{
                                      count = 10,
                                      first = #rsm_first{data = First}}}]} =
-               recv(Config)
+               recv_message(Config)
     end,
     %% Paging back. Should receive 3 elements: 1, 2, 3.
     I3 = send(Config,
@@ -2979,7 +3004,7 @@ mam_query_rsm(Config, NS) ->
     lists:foreach(
       fun(N) ->
               Text = #text{data = integer_to_binary(N)},
-              ?recv1(#message{to = MyJID,
+              #message{to = MyJID,
                        sub_els =
                            [#mam_result{
                               xmlns = NS,
@@ -2989,15 +3014,18 @@ mam_query_rsm(Config, NS) ->
                                        sub_els =
                                            [#message{
                                                from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]})
+                                               body = [Text]}]}]}]} =
+                 recv_message(Config)
       end, lists:seq(1, 3)),
     if NS == ?NS_MAM_TMP ->
-           ?recv1(#iq{type = result, id = I3,
-                      sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]});
+           #iq{type = result, id = I3,
+               sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]} =
+               recv_iq(Config);
        true ->
-           ?recv1(#message{
-                     sub_els = [#mam_fin{complete = true,
-                                         rsm = #rsm_set{count = 10}}]})
+           #message{
+              sub_els = [#mam_fin{complete = true,
+                                  rsm = #rsm_set{count = 10}}]} =
+               recv_message(Config)
     end,
     %% Getting the item count. Should be 5 (or 10).
     I4 = send(Config,
@@ -3006,19 +3034,21 @@ mam_query_rsm(Config, NS) ->
                                        rsm = #rsm_set{max = 0}}]}),
     maybe_recv_iq_result(Config, NS, I4),
     if NS == ?NS_MAM_TMP ->
-           ?recv1(#iq{type = result, id = I4,
-                      sub_els = [#mam_query{
-                                    xmlns = NS,
-                                    rsm = #rsm_set{count = 5,
-                                                   first = undefined,
-                                                   last = undefined}}]});
+           #iq{type = result, id = I4,
+               sub_els = [#mam_query{
+                             xmlns = NS,
+                             rsm = #rsm_set{count = 5,
+                                            first = undefined,
+                                            last = undefined}}]} =
+               recv_iq(Config);
        true ->
-           ?recv1(#message{
-                     sub_els = [#mam_fin{
-                                   complete = false,
-                                   rsm = #rsm_set{count = 10,
-                                                  first = undefined,
-                                                  last = undefined}}]})
+           #message{
+              sub_els = [#mam_fin{
+                            complete = false,
+                            rsm = #rsm_set{count = 10,
+                                           first = undefined,
+                                           last = undefined}}]} =
+               recv_message(Config)
     end,
     %% Should receive 2 last messages
     I5 = send(Config,
@@ -3030,25 +3060,28 @@ mam_query_rsm(Config, NS) ->
     lists:foreach(
       fun(N) ->
              Text = #text{data = integer_to_binary(N)},
-             ?recv1(#message{to = MyJID,
-                             sub_els =
-                                 [#mam_result{
-                                     xmlns = NS,
-                                     sub_els =
-                                         [#forwarded{
-                                             delay = #delay{},
-                                             sub_els =
-                                                 [#message{
-                                                     from = MyJID, to = Peer,
-                                                     body = [Text]}]}]}]})
+             #message{to = MyJID,
+                      sub_els =
+                          [#mam_result{
+                              xmlns = NS,
+                              sub_els =
+                                  [#forwarded{
+                                      delay = #delay{},
+                                      sub_els =
+                                          [#message{
+                                              from = MyJID, to = Peer,
+                                              body = [Text]}]}]}]} =
+                 recv_message(Config)
       end, lists:seq(4, 5)),
     if NS == ?NS_MAM_TMP ->
-           ?recv1(#iq{type = result, id = I5,
-                      sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]});
+           #iq{type = result, id = I5,
+               sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]} =
+               recv_iq(Config);
        true ->
-           ?recv1(#message{
-                     sub_els = [#mam_fin{complete = false,
-                                         rsm = #rsm_set{count = 10}}]})
+           #message{
+              sub_els = [#mam_fin{complete = false,
+                                  rsm = #rsm_set{count = 10}}]} =
+               recv_message(Config)
     end.
 
 client_state_master(Config) ->
@@ -3109,8 +3142,8 @@ client_state_slave(Config) ->
     Peer = ?config(master, Config),
     change_client_state(Config, inactive),
     wait_for_master(Config),
-    ?recv1(#presence{from = Peer, type = unavailable,
-                    sub_els = [#delay{}]}),
+    #presence{from = Peer, type = unavailable, sub_els = [#delay{}]} =
+       recv_presence(Config),
     #message{
        from = Peer,
        sub_els =
@@ -3121,7 +3154,7 @@ client_state_slave(Config) ->
                      items =
                          [#ps_item{
                              id = <<"pep-1">>}]}},
-           #delay{}]} = recv(Config),
+           #delay{}]} = recv_message(Config),
     #message{
        from = Peer,
        sub_els =
@@ -3132,17 +3165,17 @@ client_state_slave(Config) ->
                      items =
                          [#ps_item{
                              id = <<"pep-2">>}]}},
-           #delay{}]} = recv(Config),
-    ?recv1(#message{from = Peer, thread = <<"1">>,
-                   sub_els = [#chatstate{type = composing},
-                              #delay{}]}),
-    ?recv1(#message{from = Peer, thread = <<"1">>,
-                   body = [#text{data = <<"body">>}],
-                   sub_els = [#chatstate{type = active}]}),
+           #delay{}]} = recv_message(Config),
+    #message{from = Peer, thread = <<"1">>,
+            sub_els = [#chatstate{type = composing},
+                       #delay{}]} = recv_message(Config),
+    #message{from = Peer, thread = <<"1">>,
+            body = [#text{data = <<"body">>}],
+            sub_els = [#chatstate{type = active}]} = recv_message(Config),
     change_client_state(Config, active),
     wait_for_master(Config),
-    ?recv1(#message{from = Peer, thread = <<"1">>,
-                   sub_els = [#chatstate{type = active}]}),
+    #message{from = Peer, thread = <<"1">>,
+            sub_els = [#chatstate{type = active}]} = recv_message(Config),
     disconnect(Config).
 
 %%%===================================================================
index c89b977425faf1b6b2db73e50d6ba28c03f50ae4..709a82a22f648975b9cb16946cc7ec2892d76cca 100644 (file)
@@ -425,6 +425,7 @@ muc_history_master(Config) ->
     ServerHost = ?config(server_host, Config),
     MyNick = ?config(nick, Config),
     MyNickJID = jid:replace_resource(Room, MyNick),
+    PeerNickJID = peer_muc_jid(Config),
     Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size,
                                  fun(I) when is_integer(I), I>=0 -> I end,
                                  20),
@@ -439,9 +440,14 @@ muc_history_master(Config) ->
              #message{type = groupchat, from = MyNickJID,
                       body = Body} = recv_message(Config)
       end, lists:seq(0, Size)),
-    wait_for_slave(Config),
-    wait_for_slave(Config),
-    flush(Config),
+    put_event(Config, join),
+    lists:foreach(
+      fun(Type) ->
+             recv_muc_presence(Config, PeerNickJID, Type)
+      end, [available, unavailable,
+           available, unavailable,
+           available, unavailable,
+           available, unavailable]),
     ok = muc_leave(Config),
     disconnect(Config).
 
@@ -453,7 +459,9 @@ muc_history_slave(Config) ->
     Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size,
                                  fun(I) when is_integer(I), I>=0 -> I end,
                                  20),
-    {History, _, _} = muc_slave_join(Config),
+    ct:comment("Waiting for 'join' command from the master"),
+    join = get_event(Config),
+    {History, _, _} = muc_join(Config),
     ct:comment("Checking ordering of history events"),
     BodyList = [binary_to_integer(xmpp:get_text(Body))
                || #message{type = groupchat, from = From,
@@ -486,7 +494,6 @@ muc_history_slave(Config) ->
                               From == PeerNickJID],
     BodyListWithoutFirst = lists:nthtail(1, lists:seq(1, Size)),
     ok = muc_leave(Config),
-    wait_for_master(Config),
     disconnect(Config).
 
 muc_invite_master(Config) ->
@@ -663,8 +670,7 @@ muc_change_role_master(Config) ->
                         nick = PeerNick}|_] = muc_get_role(Config, Role)
       end, [visitor, participant, moderator]),
     put_event(Config, disconnect),
-    wait_for_slave(Config),
-    flush(Config),
+    recv_muc_presence(Config, PeerNickJID, unavailable),
     ok = muc_leave(Config),
     disconnect(Config).
 
@@ -687,7 +693,6 @@ muc_change_role_slave(Config, {Role, Reason}) ->
     muc_change_role_slave(Config, get_event(Config));
 muc_change_role_slave(Config, disconnect) ->
     ok = muc_leave(Config),
-    wait_for_master(Config),
     disconnect(Config).
 
 muc_change_affiliation_master(Config) ->
@@ -1522,7 +1527,7 @@ muc_join_new(Config, Room) ->
        items = [#muc_item{role = moderator,
                          jid = MyJID,
                          affiliation = owner}]} =
-       xmpp:get_subtag(?recv1(#presence{from = MyNickJID}), #muc_user{}),
+       recv_muc_presence(Config, MyNickJID, available),
     ct:comment("Checking if codes '110' (self-presence) and "
               "'201' (new room) is set"),
     true = lists:member(110, Codes),
@@ -1623,8 +1628,7 @@ muc_leave(Config, Room) ->
     #muc_user{
        status_codes = Codes,
        items = [#muc_item{role = none, jid = MyJID}]} =
-       xmpp:get_subtag(?recv1(#presence{from = MyNickJID,
-                                        type = unavailable}), #muc_user{}),
+       recv_muc_presence(Config, MyNickJID, unavailable),
     ct:comment("Checking if code '110' (self-presence) is set"),
     true = lists:member(110, Codes),
     ok.
diff --git a/test/privacy_tests.erl b/test/privacy_tests.erl
new file mode 100644 (file)
index 0000000..2ee945f
--- /dev/null
@@ -0,0 +1,821 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 18 Oct 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(privacy_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [disconnect/1, send_recv/2, get_event/1, put_event/2,
+               recv_iq/1, recv_presence/1, recv_message/1, recv/1,
+               send/2, my_jid/1, server_jid/1, get_features/1,
+               set_roster/3, del_roster/1]).
+-include("suite.hrl").
+-include("mod_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single cases
+%%%===================================================================
+single_cases() ->
+    {privacy_single, [sequence],
+     [single_test(feature_enabled),
+      single_test(set_get_list),
+      single_test(get_list_non_existent),
+      single_test(set_default),
+      single_test(del_default),
+      single_test(set_default_non_existent),
+      single_test(set_active),
+      single_test(del_active),
+      single_test(set_active_non_existent),
+      single_test(remove_list),
+      single_test(remove_default_list),
+      single_test(remove_active_list),
+      %% TODO: this should be fixed
+      %% single_test(remove_list_non_existent),
+      single_test(allow_local_server),
+      single_test(malformed_iq_query),
+      single_test(malformed_get),
+      single_test(malformed_set),
+      single_test(malformed_type_value),
+      single_test(set_get_block)]}.
+
+feature_enabled(Config) ->
+    Features = get_features(Config),
+    true = lists:member(?NS_PRIVACY, Features),
+    true = lists:member(?NS_BLOCKING, Features),
+    disconnect(Config).
+
+set_get_list(Config) ->
+    ListName = <<"set-get-list">>,
+    Items = [#privacy_item{order = 0, action = deny,
+                          type = jid, value = <<"user@jabber.org">>,
+                          iq = true},
+            #privacy_item{order = 1, action = allow,
+                          type = group, value = <<"group">>,
+                          message = true},
+            #privacy_item{order = 2, action = allow,
+                          type = subscription, value = <<"both">>,
+                          presence_in = true},
+            #privacy_item{order = 3, action = deny,
+                          type = subscription, value = <<"from">>,
+                          presence_out = true},
+            #privacy_item{order = 4, action = deny,
+                          type = subscription, value = <<"to">>,
+                          iq = true, message = true},
+            #privacy_item{order = 5, action = deny,
+                          type = subscription, value = <<"none">>,
+                          _ = true},
+            #privacy_item{order = 6, action = deny}],
+    ok = set_items(Config, ListName, Items),
+    #privacy_list{name = ListName, items = Items1} = get_list(Config, ListName),
+    Items = lists:keysort(#privacy_item.order, Items1),
+    del_privacy(disconnect(Config)).
+
+get_list_non_existent(Config) ->
+    ListName = <<"get-list-non-existent">>,
+    #stanza_error{reason = 'item-not-found'} = get_list(Config, ListName),
+    disconnect(Config).
+
+set_default(Config) ->
+    ListName = <<"set-default">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = set_default(Config, ListName),
+    #privacy_query{default = ListName} = get_lists(Config),
+    del_privacy(disconnect(Config)).
+
+del_default(Config) ->
+    ListName = <<"del-default">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = set_default(Config, ListName),
+    #privacy_query{default = ListName} = get_lists(Config),
+    ok = set_default(Config, none),
+    #privacy_query{default = none} = get_lists(Config),
+    del_privacy(disconnect(Config)).
+
+set_default_non_existent(Config) ->
+    ListName = <<"set-default-non-existent">>,
+    #stanza_error{reason = 'item-not-found'} = set_default(Config, ListName),
+    disconnect(Config).
+
+set_active(Config) ->
+    ListName = <<"set-active">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = set_active(Config, ListName),
+    #privacy_query{active = ListName} = get_lists(Config),
+    del_privacy(disconnect(Config)).
+
+del_active(Config) ->
+    ListName = <<"del-active">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = set_active(Config, ListName),
+    #privacy_query{active = ListName} = get_lists(Config),
+    ok = set_active(Config, none),
+    #privacy_query{active = none} = get_lists(Config),
+    del_privacy(disconnect(Config)).
+
+set_active_non_existent(Config) ->
+    ListName = <<"set-active-non-existent">>,
+    #stanza_error{reason = 'item-not-found'} = set_active(Config, ListName),
+    disconnect(Config).
+
+remove_list(Config) ->
+    ListName = <<"remove-list">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = del_list(Config, ListName),
+    #privacy_query{lists = []} = get_lists(Config),
+    del_privacy(disconnect(Config)).
+
+remove_active_list(Config) ->
+    ListName = <<"remove-active-list">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = set_active(Config, ListName),
+    #stanza_error{reason = 'conflict'} = del_list(Config, ListName),
+    del_privacy(disconnect(Config)).
+
+remove_default_list(Config) ->
+    ListName = <<"remove-default-list">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = set_default(Config, ListName),
+    #stanza_error{reason = 'conflict'} = del_list(Config, ListName),
+    del_privacy(disconnect(Config)).
+
+remove_list_non_existent(Config) ->
+    ListName = <<"remove-list-non-existent">>,
+    #stanza_error{reason = 'item-not-found'} = del_list(Config, ListName),
+    disconnect(Config).
+
+allow_local_server(Config) ->
+    ListName = <<"allow-local-server">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = set_active(Config, ListName),
+    %% Whatever privacy rules are set, we should always communicate
+    %% with our home server
+    server_send_iqs(Config),
+    server_recv_iqs(Config),
+    send_stanzas_to_server_resource(Config),
+    del_privacy(disconnect(Config)).
+
+malformed_iq_query(Config) ->
+    lists:foreach(
+      fun(Type) ->
+             #iq{type = error} =
+                 send_recv(Config,
+                           #iq{type = Type,
+                               sub_els = [#privacy_list{name = <<"foo">>}]})
+      end, [get, set]),
+    disconnect(Config).
+
+malformed_get(Config) ->
+    JID = jid:make(randoms:get_string()),
+    lists:foreach(
+      fun(SubEl) ->
+             #iq{type = error} =
+                 send_recv(Config, #iq{type = get, sub_els = [SubEl]})
+      end, [#privacy_query{active = none},
+           #privacy_query{default = none},
+           #privacy_query{lists = [#privacy_list{name = <<"1">>},
+                                   #privacy_list{name = <<"2">>}]},
+           #block{items = [JID]}, #unblock{items = [JID]},
+           #block{}, #unblock{}]),
+    disconnect(Config).
+
+malformed_set(Config) ->
+    lists:foreach(
+      fun(SubEl) ->
+             #iq{type = error} =
+                 send_recv(Config, #iq{type = set, sub_els = [SubEl]})
+      end, [#privacy_query{active = none, default = none},
+           #privacy_query{lists = [#privacy_list{name = <<"1">>},
+                                   #privacy_list{name = <<"2">>}]},
+           #block{},
+           #block_list{},
+           #block_list{items = [jid:make(randoms:get_string())]}]).
+
+malformed_type_value(Config) ->
+    Item = #privacy_item{order = 0, action = deny},
+    #stanza_error{reason = 'bad-request'} =
+       set_items(Config, <<"malformed-jid">>,
+                 [Item#privacy_item{type = jid, value = <<"@bad">>}]),
+    #stanza_error{reason = 'bad-request'} =
+       set_items(Config, <<"malformed-group">>,
+                 [Item#privacy_item{type = group, value = <<"">>}]),
+    #stanza_error{reason = 'bad-request'} =
+       set_items(Config, <<"malformed-subscription">>,
+                 [Item#privacy_item{type = subscription, value = <<"bad">>}]),
+    disconnect(Config).
+
+set_get_block(Config) ->
+    J1 = jid:make(randoms:get_string(), randoms:get_string()),
+    J2 = jid:make(randoms:get_string(), randoms:get_string()),
+    {ok, ListName} = set_block(Config, [J1, J2]),
+    JIDs = get_block(Config),
+    JIDs = lists:sort([J1, J2]),
+    {ok, ListName} = set_unblock(Config, [J2, J1]),
+    [] = get_block(Config),
+    del_privacy(disconnect(Config)).
+
+%%%===================================================================
+%%% Master-slave cases
+%%%===================================================================
+master_slave_cases() ->
+    {privacy_master_slave, [parallel],
+     [master_slave_test(deny_bare_jid),
+      master_slave_test(deny_full_jid),
+      master_slave_test(deny_server_jid),
+      master_slave_test(deny_group),
+      master_slave_test(deny_sub_both),
+      master_slave_test(deny_sub_from),
+      master_slave_test(deny_sub_to),
+      master_slave_test(deny_sub_none),
+      master_slave_test(deny_all),
+      master_slave_test(deny_offline),
+      master_slave_test(block),
+      master_slave_test(unblock),
+      master_slave_test(unblock_all)]}.
+
+deny_bare_jid_master(Config) ->
+    PeerJID = ?config(peer, Config),
+    PeerBareJID = jid:remove_resource(PeerJID),
+    deny_master(Config, {jid, jid:to_string(PeerBareJID)}).
+
+deny_bare_jid_slave(Config) ->
+    deny_slave(Config).
+
+deny_full_jid_master(Config) ->
+    PeerJID = ?config(peer, Config),
+    deny_master(Config, {jid, jid:to_string(PeerJID)}).
+
+deny_full_jid_slave(Config) ->
+    deny_slave(Config).
+
+deny_server_jid_master(Config) ->
+    {_, Server, _} = jid:tolower(?config(peer, Config)),
+    deny_master(Config, {jid, Server}).
+
+deny_server_jid_slave(Config) ->
+    deny_slave(Config).
+
+deny_group_master(Config) ->
+    Group = randoms:get_string(),
+    deny_master(Config, {group, Group}).
+
+deny_group_slave(Config) ->
+    deny_slave(Config).
+
+deny_sub_both_master(Config) ->
+    deny_master(Config, {subscription, <<"both">>}).
+
+deny_sub_both_slave(Config) ->
+    deny_slave(Config).
+
+deny_sub_from_master(Config) ->
+    deny_master(Config, {subscription, <<"from">>}).
+
+deny_sub_from_slave(Config) ->
+    deny_slave(Config).
+
+deny_sub_to_master(Config) ->
+    deny_master(Config, {subscription, <<"to">>}).
+
+deny_sub_to_slave(Config) ->
+    deny_slave(Config).
+
+deny_sub_none_master(Config) ->
+    deny_master(Config, {subscription, <<"none">>}).
+
+deny_sub_none_slave(Config) ->
+    deny_slave(Config).
+
+deny_all_master(Config) ->
+    deny_master(Config, {undefined, <<"">>}).
+
+deny_all_slave(Config) ->
+    deny_slave(Config).
+
+deny_master(Config, {Type, Value}) ->
+    Sub = if Type == subscription ->
+                 erlang:binary_to_atom(Value, utf8);
+            true ->
+                 both
+         end,
+    Groups = if Type == group -> [Value];
+               true -> []
+            end,
+    set_roster(Config, Sub, Groups),
+    lists:foreach(
+      fun(Opts) ->
+             ListName = str:format("deny-~s-~s-~p", [Type, Value, Opts]),
+             Item = #privacy_item{order = 0,
+                                  action = deny,
+                                  iq = proplists:get_bool(iq, Opts),
+                                  message = proplists:get_bool(message, Opts),
+                                  presence_in = proplists:get_bool(presence_in, Opts),
+                                  presence_out = proplists:get_bool(presence_out, Opts),
+                                  type = Type,
+                                  value = Value},
+             ok = set_items(Config, ListName, [Item]),
+             ok = set_active(Config, ListName),
+             put_event(Config, Opts),
+             case is_presence_in_blocked(Opts) of
+                 true -> ok;
+                 false -> recv_presences(Config)
+             end,
+             case is_iq_in_blocked(Opts) of
+                 true -> ok;
+                 false -> recv_iqs(Config)
+             end,
+             case is_message_in_blocked(Opts) of
+                 true -> ok;
+                 false -> recv_messages(Config)
+             end,
+             ct:comment("Waiting for 'send' command from the slave"),
+             send = get_event(Config),
+             case is_presence_out_blocked(Opts) of
+                 true -> check_presence_blocked(Config, 'not-acceptable');
+                 false -> ok
+             end,
+             case is_iq_out_blocked(Opts) of
+                 true -> check_iq_blocked(Config, 'not-acceptable');
+                 false -> send_iqs(Config)
+             end,
+             case is_message_out_blocked(Opts) of
+                 true -> check_message_blocked(Config, 'not-acceptable');
+                 false -> send_messages(Config)
+             end,
+             case is_other_blocked(Opts) of
+                 true -> check_other_blocked(Config, 'not-acceptable');
+                 false -> ok
+             end,
+             ct:comment("Waiting for slave to finish processing our stanzas"),
+             done = get_event(Config)
+      end,
+      [[iq], [message], [presence_in], [presence_out],
+       [iq, message, presence_in, presence_out], []]),
+    put_event(Config, disconnect),
+    clean_up(disconnect(Config)).
+
+deny_slave(Config) ->
+    set_roster(Config, both, []),
+    deny_slave(Config, get_event(Config)).
+
+deny_slave(Config, disconnect) ->
+    clean_up(disconnect(Config));
+deny_slave(Config, Opts) ->
+    send_presences(Config),
+    case is_iq_in_blocked(Opts) of
+       true -> check_iq_blocked(Config, 'service-unavailable');
+       false -> send_iqs(Config)
+    end,
+    case is_message_in_blocked(Opts) of
+       true -> check_message_blocked(Config, 'service-unavailable');
+       false -> send_messages(Config)
+    end,
+    put_event(Config, send),
+    case is_iq_out_blocked(Opts) of
+       true -> ok;
+       false -> recv_iqs(Config)
+    end,
+    case is_message_out_blocked(Opts) of
+       true -> ok;
+       false -> recv_messages(Config)
+    end,
+    put_event(Config, done),
+    deny_slave(Config, get_event(Config)).
+
+deny_offline_master(Config) ->
+    set_roster(Config, both, []),
+    ListName = <<"deny-offline">>,
+    Item = #privacy_item{order = 0, action = deny},
+    ok = set_items(Config, ListName, [Item]),
+    ok = set_default(Config, ListName),
+    NewConfig = disconnect(Config),
+    put_event(NewConfig, send),
+    ct:comment("Waiting for the slave to finish"),
+    done = get_event(NewConfig),
+    clean_up(NewConfig).
+
+deny_offline_slave(Config) ->
+    set_roster(Config, both, []),
+    ct:comment("Waiting for 'send' command from the master"),
+    send = get_event(Config),
+    send_presences(Config),
+    check_iq_blocked(Config, 'service-unavailable'),
+    check_message_blocked(Config, 'service-unavailable'),
+    put_event(Config, done),
+    clean_up(disconnect(Config)).
+
+block_master(Config) ->
+    PeerJID = ?config(peer, Config),
+    set_roster(Config, both, []),
+    {ok, _} = set_block(Config, [PeerJID]),
+    check_presence_blocked(Config, 'not-acceptable'),
+    check_iq_blocked(Config, 'not-acceptable'),
+    check_message_blocked(Config, 'not-acceptable'),
+    check_other_blocked(Config, 'not-acceptable'),
+    %% We should always be able to communicate with our home server
+    server_send_iqs(Config),
+    server_recv_iqs(Config),
+    send_stanzas_to_server_resource(Config),
+    put_event(Config, send),
+    done = get_event(Config),
+    clean_up(disconnect(Config)).
+
+block_slave(Config) ->
+    set_roster(Config, both, []),
+    ct:comment("Waiting for 'send' command from master"),
+    send = get_event(Config),
+    send_presences(Config),
+    check_iq_blocked(Config, 'service-unavailable'),
+    check_message_blocked(Config, 'service-unavailable'),
+    put_event(Config, done),
+    clean_up(disconnect(Config)).
+
+unblock_master(Config) ->
+    PeerJID = ?config(peer, Config),
+    set_roster(Config, both, []),
+    {ok, ListName} = set_block(Config, [PeerJID]),
+    {ok, ListName} = set_unblock(Config, [PeerJID]),
+    put_event(Config, send),
+    recv_presences(Config),
+    recv_iqs(Config),
+    recv_messages(Config),
+    clean_up(disconnect(Config)).
+
+unblock_slave(Config) ->
+    set_roster(Config, both, []),
+    ct:comment("Waiting for 'send' command from master"),
+    send = get_event(Config),
+    send_presences(Config),
+    send_iqs(Config),
+    send_messages(Config),
+    clean_up(disconnect(Config)).
+
+unblock_all_master(Config) ->
+    PeerJID = ?config(peer, Config),
+    set_roster(Config, both, []),
+    {ok, ListName} = set_block(Config, [PeerJID]),
+    {ok, ListName} = set_unblock(Config, []),
+    put_event(Config, send),
+    recv_presences(Config),
+    recv_iqs(Config),
+    recv_messages(Config),
+    clean_up(disconnect(Config)).
+
+unblock_all_slave(Config) ->
+    set_roster(Config, both, []),
+    ct:comment("Waiting for 'send' command from master"),
+    send = get_event(Config),
+    send_presences(Config),
+    send_iqs(Config),
+    send_messages(Config),
+    clean_up(disconnect(Config)).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("privacy_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("privacy_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("privacy_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("privacy_" ++ atom_to_list(T) ++ "_slave")]}.
+
+set_items(Config, Name, Items) ->
+    ct:comment("Setting privacy list ~s with items = ~p", [Name, Items]),
+    case send_recv(
+          Config,
+          #iq{type = set, sub_els = [#privacy_query{
+                                        lists = [#privacy_list{
+                                                    name = Name,
+                                                    items = Items}]}]}) of
+       #iq{type = result, sub_els = []} ->
+           ct:comment("Receiving privacy list push"),
+           #iq{type = set, id = ID,
+               sub_els = [#privacy_query{lists = [#privacy_list{
+                                                     name = Name}]}]} =
+               recv_iq(Config),
+           send(Config, #iq{type = result, id = ID}),
+           ok;
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+get_list(Config, Name) ->
+    ct:comment("Requesting privacy list ~s", [Name]),
+    case send_recv(Config,
+                  #iq{type = get,
+                      sub_els = [#privacy_query{
+                                    lists = [#privacy_list{name = Name}]}]}) of
+       #iq{type = result, sub_els = [#privacy_query{lists = [List]}]} ->
+           List;
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+get_lists(Config) ->
+    ct:comment("Requesting privacy lists"),
+    case send_recv(Config, #iq{type = get, sub_els = [#privacy_query{}]}) of
+       #iq{type = result, sub_els = [SubEl]} ->
+           SubEl;
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+del_list(Config, Name) ->
+    case send_recv(
+          Config,
+          #iq{type = set, sub_els = [#privacy_query{
+                                        lists = [#privacy_list{
+                                                    name = Name}]}]}) of
+       #iq{type = result, sub_els = []} ->
+           ct:comment("Receiving privacy list push"),
+           #iq{type = set, id = ID,
+               sub_els = [#privacy_query{lists = [#privacy_list{
+                                                     name = Name}]}]} =
+               recv_iq(Config),
+           send(Config, #iq{type = result, id = ID}),
+           ok;
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+set_active(Config, Name) ->
+    ct:comment("Setting active privacy list ~s", [Name]),
+    case send_recv(
+          Config,
+          #iq{type = set, sub_els = [#privacy_query{active = Name}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+set_default(Config, Name) ->
+    ct:comment("Setting default privacy list ~s", [Name]),
+    case send_recv(
+          Config,
+          #iq{type = set, sub_els = [#privacy_query{default = Name}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+get_block(Config) ->
+    case send_recv(Config, #iq{type = get, sub_els = [#block_list{}]}) of
+       #iq{type = result, sub_els = [#block_list{items = JIDs}]} ->
+           lists:sort(JIDs);
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+set_block(Config, JIDs) ->
+    case send_recv(Config, #iq{type = set,
+                              sub_els = [#block{items = JIDs}]}) of
+       #iq{type = result, sub_els = []} ->
+           {#iq{id = I1, sub_els = [#block{items = Items}]},
+            #iq{id = I2, sub_els = [#privacy_query{lists = Lists}]}} =
+               ?recv2(#iq{type = set, sub_els = [#block{}]},
+                      #iq{type = set, sub_els = [#privacy_query{}]}),
+           send(Config, #iq{type = result, id = I1}),
+           send(Config, #iq{type = result, id = I2}),
+           ct:comment("Checking if all JIDs present in the push"),
+           true = lists:sort(JIDs) == lists:sort(Items),
+           ct:comment("Getting name of the corresponding privacy list"),
+           [#privacy_list{name = Name}] = Lists,
+           {ok, Name};
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+set_unblock(Config, JIDs) ->
+    ct:comment("Unblocking ~p", [JIDs]),
+    case send_recv(Config, #iq{type = set,
+                              sub_els = [#unblock{items = JIDs}]}) of
+       #iq{type = result, sub_els = []} ->
+           {#iq{id = I1, sub_els = [#unblock{items = Items}]},
+            #iq{id = I2, sub_els = [#privacy_query{lists = Lists}]}} =
+               ?recv2(#iq{type = set, sub_els = [#unblock{}]},
+                      #iq{type = set, sub_els = [#privacy_query{}]}),
+           send(Config, #iq{type = result, id = I1}),
+           send(Config, #iq{type = result, id = I2}),
+           ct:comment("Checking if all JIDs present in the push"),
+           true = lists:sort(JIDs) == lists:sort(Items),
+           ct:comment("Getting name of the corresponding privacy list"),
+           [#privacy_list{name = Name}] = Lists,
+           {ok, Name};
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+del_privacy(Config) ->
+    {U, S, _} = jid:tolower(my_jid(Config)),
+    ct:comment("Removing all privacy data"),
+    mod_privacy:remove_user(U, S),
+    Config.
+
+clean_up(Config) ->
+    del_privacy(del_roster(Config)).
+
+check_iq_blocked(Config, Reason) ->
+    PeerJID = ?config(peer, Config),
+    ct:comment("Checking if all IQs are blocked"),
+    lists:foreach(
+      fun(Type) ->
+             send(Config, #iq{type = Type, to = PeerJID})
+      end, [error, result]),
+    lists:foreach(
+      fun(Type) ->
+             #iq{type = error} = Err =
+                 send_recv(Config, #iq{type = Type, to = PeerJID,
+                                       sub_els = [#ping{}]}),
+             #stanza_error{reason = Reason} = xmpp:get_error(Err)
+      end, [set, get]).
+
+check_message_blocked(Config, Reason) ->
+    PeerJID = ?config(peer, Config),
+    ct:comment("Checking if all messages are blocked"),
+    %% TODO: do something with headline and groupchat.
+    %% The hack from 64d96778b452aad72349b21d2ac94e744617b07a
+    %% screws this up.
+    lists:foreach(
+      fun(Type) ->
+             send(Config, #message{type = Type, to = PeerJID})
+      end, [error]),
+    lists:foreach(
+      fun(Type) ->
+             #message{type = error} = Err =
+                 send_recv(Config, #message{type = Type, to = PeerJID}),
+             #stanza_error{reason = Reason} = xmpp:get_error(Err)
+      end, [chat, normal]).
+
+check_presence_blocked(Config, Reason) ->
+    PeerJID = ?config(peer, Config),
+    ct:comment("Checking if all presences are blocked"),
+    lists:foreach(
+      fun(Type) ->
+             #presence{type = error} = Err =
+                 send_recv(Config, #presence{type = Type, to = PeerJID}),
+             #stanza_error{reason = Reason} = xmpp:get_error(Err)
+      end, [available, unavailable]).
+
+check_other_blocked(Config, Reason) ->
+    PeerJID = ?config(peer, Config),
+    ct:comment("Checking if subscriptions and presence-errors are blocked"),
+    send(Config, #presence{type = error, to = PeerJID}),
+    lists:foreach(
+      fun(Type) ->
+             #presence{type = error} = Err =
+                 send_recv(Config, #presence{type = Type, to = PeerJID}),
+             #stanza_error{reason = Reason} = xmpp:get_error(Err)
+      end, [subscribe, subscribed, unsubscribe, unsubscribed]).
+
+send_presences(Config) ->
+    PeerJID = ?config(peer, Config),
+    ct:comment("Sending all types of presences to the peer"),
+    lists:foreach(
+      fun(Type) ->
+             send(Config, #presence{type = Type, to = PeerJID})
+      end, [available, unavailable]).
+
+send_iqs(Config) ->
+    PeerJID = ?config(peer, Config),
+    ct:comment("Sending all types of IQs to the peer"),
+    lists:foreach(
+      fun(Type) ->
+             send(Config, #iq{type = Type, to = PeerJID})
+      end, [set, get, error, result]).
+
+send_messages(Config) ->
+    PeerJID = ?config(peer, Config),
+    ct:comment("Sending all types of messages to the peer"),
+    lists:foreach(
+      fun(Type) ->
+             send(Config, #message{type = Type, to = PeerJID})
+      end, [chat, error, groupchat, headline, normal]).
+
+recv_presences(Config) ->
+    PeerJID = ?config(peer, Config),
+    lists:foreach(
+      fun(Type) ->
+             #presence{type = Type, from = PeerJID} =
+                 recv_presence(Config)
+      end, [available, unavailable]).
+
+recv_iqs(Config) ->
+    PeerJID = ?config(peer, Config),
+    lists:foreach(
+      fun(Type) ->
+             #iq{type = Type, from = PeerJID} = recv_iq(Config)
+      end, [set, get, error, result]).
+
+recv_messages(Config) ->
+    PeerJID = ?config(peer, Config),
+    lists:foreach(
+      fun(Type) ->
+             #message{type = Type, from = PeerJID} = recv_message(Config)
+      end, [chat, error, groupchat, headline, normal]).
+
+match_all(Opts) ->
+    IQ = proplists:get_bool(iq, Opts),
+    Message = proplists:get_bool(message, Opts),
+    PresenceIn = proplists:get_bool(presence_in, Opts),
+    PresenceOut = proplists:get_bool(presence_out, Opts),
+    not (IQ or Message or PresenceIn or PresenceOut).
+
+is_message_in_blocked(Opts) ->
+    proplists:get_bool(message, Opts) or match_all(Opts).
+
+is_message_out_blocked(Opts) ->
+    match_all(Opts).
+
+is_iq_in_blocked(Opts) ->    
+    proplists:get_bool(iq, Opts) or match_all(Opts).
+
+is_iq_out_blocked(Opts) ->
+    match_all(Opts).
+
+is_presence_in_blocked(Opts) ->
+    proplists:get_bool(presence_in, Opts) or match_all(Opts).
+
+is_presence_out_blocked(Opts) ->
+    proplists:get_bool(presence_out, Opts) or match_all(Opts).
+
+is_other_blocked(Opts) ->
+    %% 'other' means subscriptions and presence-errors
+    match_all(Opts).
+
+server_send_iqs(Config) ->
+    ServerJID = server_jid(Config),
+    MyJID = my_jid(Config),
+    ct:comment("Sending IQs from ~s to ~s",
+              [jid:to_string(ServerJID), jid:to_string(MyJID)]),
+    lists:foreach(
+      fun(Type) ->
+             ejabberd_router:route(
+               ServerJID, MyJID, #iq{type = Type})
+      end, [error, result]),
+    lists:foreach(
+      fun(Type) ->
+             ejabberd_local:route_iq(
+               ServerJID, MyJID, #iq{type = Type},
+               fun(#iq{type = result, sub_els = []}) -> ok;
+                  (IQ) -> ct:fail({unexpected_iq_result, IQ})
+               end)
+      end, [set, get]).
+
+server_recv_iqs(Config) ->
+    ServerJID = server_jid(Config),
+    ct:comment("Receiving IQs from ~s", [jid:to_string(ServerJID)]),
+    lists:foreach(
+      fun(Type) ->
+             #iq{type = Type, from = ServerJID} = recv_iq(Config)
+      end, [error, result]),
+    lists:foreach(
+      fun(Type) ->
+             #iq{type = Type, from = ServerJID, id = I} = recv_iq(Config),
+             send(Config, #iq{to = ServerJID, type = result, id = I})
+      end, [set, get]).
+
+send_stanzas_to_server_resource(Config) ->
+    ServerJID = server_jid(Config),
+    ServerJIDResource = jid:replace_resource(ServerJID, <<"resource">>),
+    %% All stanzas sent should be handled by local_send_to_resource_hook
+    %% and should be bounced with item-not-found error
+    ct:comment("Sending IQs to ~s", [jid:to_string(ServerJIDResource)]),
+    lists:foreach(
+      fun(Type) ->
+             #iq{type = error} = Err =
+                 send_recv(Config, #iq{type = Type, to = ServerJIDResource}),
+                 #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
+      end, [set, get]),
+    ct:comment("Sending messages to ~s", [jid:to_string(ServerJIDResource)]),
+    lists:foreach(
+      fun(Type) ->
+             #message{type = error} = Err =
+                 send_recv(Config, #message{type = Type, to = ServerJIDResource}),
+             #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
+      end, [normal, chat, groupchat, headline]),
+    ct:comment("Sending presences to ~s", [jid:to_string(ServerJIDResource)]),
+    lists:foreach(
+      fun(Type) ->
+             #presence{type = error} = Err =
+                 send_recv(Config, #presence{type = Type, to = ServerJIDResource}),
+             #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
+      end, [available, unavailable]).
index 7a823844bafcaaa0d6c365cb9c9570720d1bb33d..3bed6256271cb5ed6df42e75d63fef9ff7daccd9 100644 (file)
@@ -13,6 +13,7 @@
 
 -include("suite.hrl").
 -include_lib("kernel/include/file.hrl").
+-include("mod_roster.hrl").
 
 %%%===================================================================
 %%% API
@@ -171,10 +172,18 @@ connect(Config) ->
 tcp_connect(Config) ->
     case ?config(socket, Config) of
        undefined ->
+           Owner = self(),
+           NS = case ?config(type, Config) of
+                    client -> ?NS_CLIENT;
+                    server -> ?NS_SERVER;
+                    component -> ?NS_COMPONENT
+                end,
+           ReceiverPid = spawn(fun() -> receiver(NS, Owner) end),
            {ok, Sock} = ejabberd_socket:connect(
                           ?config(server_host, Config),
                           ?config(server_port, Config),
-                          [binary, {packet, 0}, {active, false}]),
+                          [binary, {packet, 0}, {active, false}],
+                          infinity, ReceiverPid),
            set_opt(socket, Sock, Config);
        _ ->
            Config
@@ -219,9 +228,11 @@ disconnect(Config) ->
     catch exit:normal ->
            ok
     end,
-    {xmlstreamend, <<"stream:stream">>} = recv(Config),
+    receive {xmlstreamend, <<"stream:stream">>} -> ok end,
+    flush(Config),
     ejabberd_socket:close(Socket),
-    Config.
+    ct:comment("Disconnected"),
+    set_opt(socket, undefined, Config).
 
 close_socket(Config) ->
     Socket = ?config(socket, Config),
@@ -435,40 +446,25 @@ match_failure(Received, [Match]) when is_list(Match)->
 match_failure(Received, Matches) ->
     ct:fail("Received input:~n~n~p~n~ndon't match expected patterns:~n~n~p", [Received, Matches]).
 
-recv(Config) ->
+recv(_Config) ->
     receive
-        {'$gen_event', {xmlstreamelement, El}} ->
-           decode_stream_element(Config, El);
-       {'$gen_event', {xmlstreamstart, Name, Attrs}} ->
-           decode(#xmlel{name = Name, attrs = Attrs}, <<>>, []);
-       {'$gen_event', Event} ->
-            Event
+       {fail, El, Why} ->
+           ct:fail("recv failed: ~p->~n~s",
+                   [El, xmpp:format_error(Why)]);
+       Event ->
+           Event
     end.
 
-recv_iq(Config) ->
-    receive
-       {'$gen_event', {xmlstreamelement, #xmlel{name = <<"iq">>} = El}} ->
-           decode_stream_element(Config, El)
-    end.
+recv_iq(_Config) ->
+    receive #iq{} = IQ -> IQ end.
 
-recv_presence(Config) ->
-    receive
-       {'$gen_event', {xmlstreamelement, #xmlel{name = <<"presence">>} = El}} ->
-           decode_stream_element(Config, El)
-    end.
+recv_presence(_Config) ->
+    receive #presence{} = Pres -> Pres end.
 
-recv_message(Config) ->
-    receive
-       {'$gen_event', {xmlstreamelement, #xmlel{name = <<"message">>} = El}} ->
-           decode_stream_element(Config, El)
-    end.
+recv_message(_Config) ->
+    receive #message{} = Msg -> Msg end.
 
-decode_stream_element(Config, El) ->
-    NS = case ?config(type, Config) of
-            client -> ?NS_CLIENT;
-            server -> ?NS_SERVER;
-            component -> ?NS_COMPONENT
-        end,
+decode_stream_element(NS, El) ->
     decode(El, NS, []).
 
 format_element(El) ->
@@ -517,13 +513,13 @@ send(State, Pkt) ->
 
 send_recv(State, #message{} = Msg) ->
     ID = send(State, Msg),
-    #message{id = ID} = recv_message(State);
+    receive #message{id = ID} = Result -> Result end;
 send_recv(State, #presence{} = Pres) ->
     ID = send(State, Pres),
-    #presence{id = ID} = recv_presence(State);
+    receive #presence{id = ID} = Result -> Result end;
 send_recv(State, #iq{} = IQ) ->
     ID = send(State, IQ),
-    #iq{id = ID} = recv_iq(State).
+    receive #iq{id = ID} = Result -> Result end.
 
 sasl_new(<<"PLAIN">>, User, Server, Password) ->
     {<<User/binary, $@, Server/binary, 0, User/binary, 0, Password/binary>>,
@@ -698,6 +694,50 @@ wait_for_slave(Config) ->
 make_iq_result(#iq{from = From} = IQ) ->
     IQ#iq{type = result, to = From, from = undefined, sub_els = []}.
 
+set_roster(Config, Subscription, Groups) ->
+    MyJID = my_jid(Config),
+    {U, S, _} = jid:tolower(MyJID),
+    PeerJID = ?config(peer, Config),
+    PeerBareJID = jid:remove_resource(PeerJID),
+    PeerLJID = jid:tolower(PeerBareJID),
+    ct:comment("Adding ~s to roster with subscription '~s' in groups ~p",
+              [jid:to_string(PeerBareJID), Subscription, Groups]),
+    {atomic, _} = mod_roster:set_roster(#roster{usj = {U, S, PeerLJID},
+                                               us = {U, S},
+                                               jid = PeerLJID,
+                                               subscription = Subscription,
+                                               groups = Groups}),
+    Config.
+
+del_roster(Config) ->
+    MyJID = my_jid(Config),
+    {U, S, _} = jid:tolower(MyJID),
+    PeerJID = ?config(peer, Config),
+    PeerBareJID = jid:remove_resource(PeerJID),
+    PeerLJID = jid:tolower(PeerBareJID),
+    ct:comment("Removing ~s from roster", [jid:to_string(PeerBareJID)]),
+    {atomic, _} = mod_roster:del_roster(U, S, PeerLJID),
+    Config.
+
+receiver(NS, Owner) ->
+    MRef = erlang:monitor(process, Owner),
+    receiver(NS, Owner, MRef).
+
+receiver(NS, Owner, MRef) ->
+    receive
+        {'$gen_event', {xmlstreamelement, El}} ->
+           Owner ! decode_stream_element(NS, El),
+           receiver(NS, Owner, MRef);
+       {'$gen_event', {xmlstreamstart, Name, Attrs}} ->
+           Owner ! decode(#xmlel{name = Name, attrs = Attrs}, <<>>, []),
+           receiver(NS, Owner, MRef);
+       {'$gen_event', Event} ->
+            Owner ! Event,
+           receiver(NS, Owner, MRef);
+       {'DOWN', MRef, process, Owner, _} ->
+           ok
+    end.
+
 %%%===================================================================
 %%% Clients puts and gets events via this relay.
 %%%===================================================================
@@ -730,12 +770,17 @@ event_relay(Events, Subscribers) ->
               end, Subscribers),
             event_relay([Event|Events], Subscribers);
        {'DOWN', _MRef, process, Pid, _Info} ->
-           NewSubscribers = lists:delete(Pid, Subscribers),
-           lists:foreach(
-             fun(Subscriber) ->
-                     Subscriber ! {event, peer_down, self()}
-             end, NewSubscribers),
-           event_relay(Events, NewSubscribers)
+           case lists:member(Pid, Subscribers) of
+               true ->
+                   NewSubscribers = lists:delete(Pid, Subscribers),
+                   lists:foreach(
+                     fun(Subscriber) ->
+                             Subscriber ! {event, peer_down, self()}
+                     end, NewSubscribers),
+                   event_relay(Events, NewSubscribers);
+               false ->
+                   event_relay(Events, Subscribers)
+           end
     end.
 
 subscribe_to_events(Config) ->
@@ -762,8 +807,10 @@ get_event(Config) ->
     end.
 
 flush(Config) ->
-    flush(Config, []).
-
-flush(Config, Msgs) ->
-    receive Msg -> flush(Config, [Msg|Msgs])
-    after 1000 -> lists:reverse(Msgs) end.
+    receive
+       {event, peer_down, _} -> flush(Config);
+       closed -> flush(Config);
+       Msg -> ct:fail({unexpected_msg, Msg})
+    after 0 ->
+           ok
+    end.