diff options
Diffstat (limited to 'test/suite.erl')
-rw-r--r-- | test/suite.erl | 784 |
1 files changed, 620 insertions, 164 deletions
diff --git a/test/suite.erl b/test/suite.erl index c5593c4cf..a82f06efb 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -1,11 +1,26 @@ %%%------------------------------------------------------------------- -%%% @author Evgeniy Khramtsov <> -%%% @copyright (C) 2013-2016, Evgeniy Khramtsov -%%% @doc +%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net> +%%% Created : 27 Jun 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net> %%% -%%% @end -%%% Created : 27 Jun 2013 by Evgeniy Khramtsov <> -%%%------------------------------------------------------------------- +%%% +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- + -module(suite). %% API @@ -13,6 +28,7 @@ -include("suite.hrl"). -include_lib("kernel/include/file.hrl"). +-include("mod_roster.hrl"). %%%=================================================================== %%% API @@ -22,56 +38,120 @@ init_config(Config) -> PrivDir = proplists:get_value(priv_dir, Config), [_, _|Tail] = lists:reverse(filename:split(DataDir)), BaseDir = filename:join(lists:reverse(Tail)), - ConfigPathTpl = filename:join([DataDir, "ejabberd.yml"]), + MacrosPathTpl = filename:join([DataDir, "macros.yml"]), + ConfigPath = filename:join([DataDir, "ejabberd.yml"]), LogPath = filename:join([PrivDir, "ejabberd.log"]), SASLPath = filename:join([PrivDir, "sasl.log"]), MnesiaDir = filename:join([PrivDir, "mnesia"]), CertFile = filename:join([DataDir, "cert.pem"]), + SelfSignedCertFile = filename:join([DataDir, "self-signed-cert.pem"]), + CAFile = filename:join([DataDir, "ca.pem"]), {ok, CWD} = file:get_cwd(), {ok, _} = file:copy(CertFile, filename:join([CWD, "cert.pem"])), - {ok, CfgContentTpl} = file:read_file(ConfigPathTpl), - CfgContent = process_config_tpl(CfgContentTpl, [ - {c2s_port, 5222}, - {loglevel, 4}, - {s2s_port, 5269}, - {web_port, 5280}, - {mysql_server, <<"localhost">>}, - {mysql_port, 3306}, - {mysql_db, <<"ejabberd_test">>}, - {mysql_user, <<"ejabberd_test">>}, - {mysql_pass, <<"ejabberd_test">>}, - {pgsql_server, <<"localhost">>}, - {pgsql_port, 5432}, - {pgsql_db, <<"ejabberd_test">>}, - {pgsql_user, <<"ejabberd_test">>}, - {pgsql_pass, <<"ejabberd_test">>} - ]), - ConfigPath = filename:join([CWD, "ejabberd.yml"]), - ok = file:write_file(ConfigPath, CfgContent), + {ok, _} = file:copy(SelfSignedCertFile, + filename:join([CWD, "self-signed-cert.pem"])), + {ok, _} = file:copy(CAFile, filename:join([CWD, "ca.pem"])), + {ok, MacrosContentTpl} = file:read_file(MacrosPathTpl), + Password = <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>, + Backends = get_config_backends(), + MacrosContent = process_config_tpl( + MacrosContentTpl, + [{c2s_port, 5222}, + {loglevel, 4}, + {new_schema, false}, + {s2s_port, 5269}, + {component_port, 5270}, + {web_port, 5280}, + {proxy_port, 7777}, + {password, Password}, + {mysql_server, <<"localhost">>}, + {mysql_port, 3306}, + {mysql_db, <<"ejabberd_test">>}, + {mysql_user, <<"ejabberd_test">>}, + {mysql_pass, <<"ejabberd_test">>}, + {pgsql_server, <<"localhost">>}, + {pgsql_port, 5432}, + {pgsql_db, <<"ejabberd_test">>}, + {pgsql_user, <<"ejabberd_test">>}, + {pgsql_pass, <<"ejabberd_test">>}, + {priv_dir, PrivDir}]), + MacrosPath = filename:join([CWD, "macros.yml"]), + ok = file:write_file(MacrosPath, MacrosContent), + copy_backend_configs(DataDir, CWD, Backends), setup_ejabberd_lib_path(Config), - ok = application:load(sasl), - ok = application:load(mnesia), - ok = application:load(ejabberd), + case application:load(sasl) of + ok -> ok; + {error, {already_loaded, _}} -> ok + end, + case application:load(mnesia) of + ok -> ok; + {error, {already_loaded, _}} -> ok + end, + case application:load(ejabberd) of + ok -> ok; + {error, {already_loaded, _}} -> ok + end, application:set_env(ejabberd, config, ConfigPath), application:set_env(ejabberd, log_path, LogPath), application:set_env(sasl, sasl_error_logger, {file, SASLPath}), application:set_env(mnesia, dir, MnesiaDir), [{server_port, ct:get_config(c2s_port, 5222)}, {server_host, "localhost"}, + {component_port, ct:get_config(component_port, 5270)}, + {s2s_port, ct:get_config(s2s_port, 5269)}, {server, ?COMMON_VHOST}, {user, <<"test_single!#$%^*()`~+-;_=[]{}|\\">>}, + {nick, <<"nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {master_nick, <<"master_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {slave_nick, <<"slave_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {room_subject, <<"hello, world!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {certfile, CertFile}, + {persistent_room, true}, + {anonymous, false}, + {type, client}, + {xmlns, ?NS_CLIENT}, + {ns_stream, ?NS_STREAM}, + {stream_version, {1, 0}}, + {stream_id, <<"">>}, + {stream_from, <<"">>}, + {db_xmlns, <<"">>}, + {mechs, []}, + {rosterver, false}, + {lang, <<"en">>}, {base_dir, BaseDir}, + {receiver, undefined}, + {pubsub_node, <<"node!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, + {pubsub_node_title, <<"title!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {resource, <<"resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {master_resource, <<"master_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {slave_resource, <<"slave_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, - {password, <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, - {backends, get_config_backends()} + {password, Password}, + {backends, Backends} |Config]. +copy_backend_configs(DataDir, CWD, Backends) -> + Files = filelib:wildcard(filename:join([DataDir, "ejabberd.*.yml"])), + lists:foreach( + fun(Src) -> + File = filename:basename(Src), + case string:tokens(File, ".") of + ["ejabberd", SBackend, "yml"] -> + Backend = list_to_atom(SBackend), + Macro = list_to_atom(string:to_upper(SBackend) ++ "_CONFIG"), + Dst = filename:join([CWD, File]), + case lists:member(Backend, Backends) of + true -> + {ok, _} = file:copy(Src, Dst); + false -> + ok = file:write_file( + Dst, fast_yaml:encode( + [{define_macro, [{Macro, []}]}])) + end; + _ -> + ok + end + end, Files). + find_top_dir(Dir) -> case file:read_file_info(filename:join([Dir, ebin])) of {ok, #file_info{type = directory}} -> @@ -93,146 +173,335 @@ setup_ejabberd_lib_path(Config) -> ok end. -%% Read environment variable CT_DB=riak,mysql to limit the backends to test. +%% Read environment variable CT_DB=mysql to limit the backends to test. %% You can thus limit the backend you want to test with: -%% CT_BACKENDS=riak,mysql rebar ct suites=ejabberd +%% CT_BACKENDS=mysql rebar ct suites=ejabberd get_config_backends() -> - case os:getenv("CT_BACKENDS") of - false -> all; - String -> - Backends0 = string:tokens(String, ","), - lists:map(fun(Backend) -> string:strip(Backend, both, $ ) end, Backends0) - end. + EnvBackends = case os:getenv("CT_BACKENDS") of + false -> ?BACKENDS; + String -> + Backends0 = string:tokens(String, ","), + lists:map( + fun(Backend) -> + list_to_atom(string:strip(Backend, both, $ )) + end, Backends0) + end, + application:load(ejabberd), + EnabledBackends = application:get_env(ejabberd, enabled_backends, EnvBackends), + misc:intersection(EnvBackends, [mnesia, ldap, extauth|EnabledBackends]). process_config_tpl(Content, []) -> Content; process_config_tpl(Content, [{Name, DefaultValue} | Rest]) -> Val = case ct:get_config(Name, DefaultValue) of - V1 when is_integer(V1) -> - integer_to_binary(V1); - V2 when is_atom(V2) -> - atom_to_binary(V2, latin1); - V3 -> - V3 + V when is_integer(V) -> + integer_to_binary(V); + V when is_atom(V) -> + atom_to_binary(V, latin1); + V -> + iolist_to_binary(V) end, - NewContent = binary:replace(Content, <<"@@",(atom_to_binary(Name, latin1))/binary, "@@">>, Val), + NewContent = binary:replace(Content, + <<"@@",(atom_to_binary(Name,latin1))/binary, "@@">>, + Val, [global]), process_config_tpl(NewContent, Rest). +stream_header(Config) -> + To = case ?config(server, Config) of + <<"">> -> undefined; + Server -> jid:make(Server) + end, + From = case ?config(stream_from, Config) of + <<"">> -> undefined; + Frm -> jid:make(Frm) + end, + #stream_start{to = To, + from = From, + lang = ?config(lang, Config), + version = ?config(stream_version, Config), + xmlns = ?config(xmlns, Config), + db_xmlns = ?config(db_xmlns, Config), + stream_xmlns = ?config(ns_stream, Config)}. connect(Config) -> - {ok, Sock} = ejabberd_socket:connect( - ?config(server_host, Config), - ?config(server_port, Config), - [binary, {packet, 0}, {active, false}]), - init_stream(set_opt(socket, Sock, Config)). + NewConfig = init_stream(Config), + case ?config(type, NewConfig) of + client -> process_stream_features(NewConfig); + server -> process_stream_features(NewConfig); + component -> NewConfig + end. + +tcp_connect(Config) -> + case ?config(receiver, Config) of + undefined -> + Owner = self(), + NS = case ?config(type, Config) of + client -> ?NS_CLIENT; + server -> ?NS_SERVER; + component -> ?NS_COMPONENT + end, + Server = ?config(server_host, Config), + Port = ?config(server_port, Config), + ReceiverPid = spawn(fun() -> + start_receiver(NS, Owner, Server, Port) + end), + set_opt(receiver, ReceiverPid, Config); + _ -> + Config + end. init_stream(Config) -> - ok = send_text(Config, io_lib:format(?STREAM_HEADER, - [?config(server, Config)])), - {xmlstreamstart, <<"stream:stream">>, Attrs} = recv(), - <<"jabber:client">> = fxml:get_attr_s(<<"xmlns">>, Attrs), - <<"1.0">> = fxml:get_attr_s(<<"version">>, Attrs), - #stream_features{sub_els = Fs} = recv(), - Mechs = lists:flatmap( - fun(#sasl_mechanisms{list = Ms}) -> - Ms; - (_) -> - [] - end, Fs), - lists:foldl( - fun(#feature_register{}, Acc) -> - set_opt(register, true, Acc); - (#starttls{}, Acc) -> - set_opt(starttls, true, Acc); - (#compression{methods = Ms}, Acc) -> - set_opt(compression, Ms, Acc); - (_, Acc) -> - Acc - end, set_opt(mechs, Mechs, Config), Fs). + Version = ?config(stream_version, Config), + NewConfig = tcp_connect(Config), + send(NewConfig, stream_header(NewConfig)), + XMLNS = case ?config(type, Config) of + client -> ?NS_CLIENT; + component -> ?NS_COMPONENT; + server -> ?NS_SERVER + end, + receive + #stream_start{id = ID, xmlns = XMLNS, version = Version} -> + set_opt(stream_id, ID, NewConfig) + end. + +process_stream_features(Config) -> + receive + #stream_features{sub_els = Fs} -> + Mechs = lists:flatmap( + fun(#sasl_mechanisms{list = Ms}) -> + Ms; + (_) -> + [] + end, Fs), + lists:foldl( + fun(#feature_register{}, Acc) -> + set_opt(register, true, Acc); + (#starttls{}, Acc) -> + set_opt(starttls, true, Acc); + (#legacy_auth_feature{}, Acc) -> + set_opt(legacy_auth, true, Acc); + (#compression{methods = Ms}, Acc) -> + set_opt(compression, Ms, Acc); + (_, Acc) -> + Acc + end, set_opt(mechs, Mechs, Config), Fs) + end. disconnect(Config) -> - Socket = ?config(socket, Config), - ok = ejabberd_socket:send(Socket, ?STREAM_TRAILER), - {xmlstreamend, <<"stream:stream">>} = recv(), - ejabberd_socket:close(Socket), - Config. + ct:comment("Disconnecting"), + try + send_text(Config, ?STREAM_TRAILER) + catch exit:normal -> + ok + end, + receive {xmlstreamend, <<"stream:stream">>} -> ok end, + flush(Config), + ok = recv_call(Config, close), + ct:comment("Disconnected"), + set_opt(receiver, undefined, Config). close_socket(Config) -> - Socket = ?config(socket, Config), - ejabberd_socket:close(Socket), + ok = recv_call(Config, close), Config. starttls(Config) -> + starttls(Config, false). + +starttls(Config, ShouldFail) -> send(Config, #starttls{}), - #starttls_proceed{} = recv(), - TLSSocket = ejabberd_socket:starttls( - ?config(socket, Config), - [{certfile, ?config(certfile, Config)}, - connect]), - init_stream(set_opt(socket, TLSSocket, Config)). + receive + #starttls_proceed{} when ShouldFail -> + ct:fail(starttls_should_have_failed); + #starttls_failure{} when ShouldFail -> + Config; + #starttls_failure{} -> + ct:fail(starttls_failed); + #starttls_proceed{} -> + ok = recv_call(Config, {starttls, ?config(certfile, Config)}), + Config + end. zlib(Config) -> send(Config, #compress{methods = [<<"zlib">>]}), - #compressed{} = recv(), - ZlibSocket = ejabberd_socket:compress(?config(socket, Config)), - init_stream(set_opt(socket, ZlibSocket, Config)). + receive #compressed{} -> ok end, + ok = recv_call(Config, compress), + process_stream_features(init_stream(Config)). auth(Config) -> + auth(Config, false). + +auth(Config, ShouldFail) -> + Type = ?config(type, Config), + IsAnonymous = ?config(anonymous, Config), Mechs = ?config(mechs, Config), HaveMD5 = lists:member(<<"DIGEST-MD5">>, Mechs), HavePLAIN = lists:member(<<"PLAIN">>, Mechs), - if HavePLAIN -> - auth_SASL(<<"PLAIN">>, Config); + HaveExternal = lists:member(<<"EXTERNAL">>, Mechs), + HaveAnonymous = lists:member(<<"ANONYMOUS">>, Mechs), + if HaveAnonymous and IsAnonymous -> + auth_SASL(<<"ANONYMOUS">>, Config, ShouldFail); + HavePLAIN -> + auth_SASL(<<"PLAIN">>, Config, ShouldFail); HaveMD5 -> - auth_SASL(<<"DIGEST-MD5">>, Config); + auth_SASL(<<"DIGEST-MD5">>, Config, ShouldFail); + HaveExternal -> + auth_SASL(<<"EXTERNAL">>, Config, ShouldFail); + Type == client -> + auth_legacy(Config, false, ShouldFail); + Type == component -> + auth_component(Config, ShouldFail); true -> - ct:fail(no_sasl_mechanisms_available) + ct:fail(no_known_sasl_mechanism_available) end. bind(Config) -> - #iq{type = result, sub_els = [#bind{}]} = - send_recv( - Config, - #iq{type = set, - sub_els = [#bind{resource = ?config(resource, Config)}]}), - Config. + U = ?config(user, Config), + S = ?config(server, Config), + R = ?config(resource, Config), + case ?config(type, Config) of + client -> + #iq{type = result, sub_els = [#bind{jid = JID}]} = + send_recv( + Config, #iq{type = set, sub_els = [#bind{resource = R}]}), + case ?config(anonymous, Config) of + false -> + {U, S, R} = jid:tolower(JID), + Config; + true -> + {User, S, Resource} = jid:tolower(JID), + set_opt(user, User, set_opt(resource, Resource, Config)) + end; + component -> + Config + end. open_session(Config) -> - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, sub_els = [#session{}]}), + open_session(Config, false). + +open_session(Config, Force) -> + if Force -> + #iq{type = result, sub_els = []} = + send_recv(Config, #iq{type = set, sub_els = [#xmpp_session{}]}); + true -> + ok + end, Config. +auth_legacy(Config, IsDigest) -> + auth_legacy(Config, IsDigest, false). + +auth_legacy(Config, IsDigest, ShouldFail) -> + ServerJID = server_jid(Config), + U = ?config(user, Config), + R = ?config(resource, Config), + P = ?config(password, Config), + #iq{type = result, + from = ServerJID, + sub_els = [#legacy_auth{username = <<"">>, + password = <<"">>, + resource = <<"">>} = Auth]} = + send_recv(Config, + #iq{to = ServerJID, type = get, + sub_els = [#legacy_auth{}]}), + Res = case Auth#legacy_auth.digest of + <<"">> when IsDigest -> + StreamID = ?config(stream_id, Config), + D = p1_sha:sha(<<StreamID/binary, P/binary>>), + send_recv(Config, #iq{to = ServerJID, type = set, + sub_els = [#legacy_auth{username = U, + resource = R, + digest = D}]}); + _ when not IsDigest -> + send_recv(Config, #iq{to = ServerJID, type = set, + sub_els = [#legacy_auth{username = U, + resource = R, + password = P}]}) + end, + case Res of + #iq{from = ServerJID, type = result, sub_els = []} -> + if ShouldFail -> + ct:fail(legacy_auth_should_have_failed); + true -> + Config + end; + #iq{from = ServerJID, type = error} -> + if ShouldFail -> + Config; + true -> + ct:fail(legacy_auth_failed) + end + end. + +auth_component(Config, ShouldFail) -> + StreamID = ?config(stream_id, Config), + Password = ?config(password, Config), + Digest = p1_sha:sha(<<StreamID/binary, Password/binary>>), + send(Config, #handshake{data = Digest}), + receive + #handshake{} when ShouldFail -> + ct:fail(component_auth_should_have_failed); + #handshake{} -> + Config; + #stream_error{reason = 'not-authorized'} when ShouldFail -> + Config; + #stream_error{reason = 'not-authorized'} -> + ct:fail(component_auth_failed) + end. + auth_SASL(Mech, Config) -> - {Response, SASL} = sasl_new(Mech, - ?config(user, Config), - ?config(server, Config), - ?config(password, Config)), + auth_SASL(Mech, Config, false). + +auth_SASL(Mech, Config, ShouldFail) -> + Creds = {?config(user, Config), + ?config(server, Config), + ?config(password, Config)}, + auth_SASL(Mech, Config, ShouldFail, Creds). + +auth_SASL(Mech, Config, ShouldFail, Creds) -> + {Response, SASL} = sasl_new(Mech, Creds), send(Config, #sasl_auth{mechanism = Mech, text = Response}), - wait_auth_SASL_result(set_opt(sasl, SASL, Config)). + wait_auth_SASL_result(set_opt(sasl, SASL, Config), ShouldFail). -wait_auth_SASL_result(Config) -> - case recv() of +wait_auth_SASL_result(Config, ShouldFail) -> + receive + #sasl_success{} when ShouldFail -> + ct:fail(sasl_auth_should_have_failed); #sasl_success{} -> - ejabberd_socket:reset_stream(?config(socket, Config)), - send_text(Config, - io_lib:format(?STREAM_HEADER, - [?config(server, Config)])), - {xmlstreamstart, <<"stream:stream">>, Attrs} = recv(), - <<"jabber:client">> = fxml:get_attr_s(<<"xmlns">>, Attrs), - <<"1.0">> = fxml:get_attr_s(<<"version">>, Attrs), - #stream_features{sub_els = Fs} = recv(), - lists:foldl( - fun(#feature_sm{}, ConfigAcc) -> - set_opt(sm, true, ConfigAcc); - (#feature_csi{}, ConfigAcc) -> - set_opt(csi, true, ConfigAcc); - (_, ConfigAcc) -> - ConfigAcc - end, Config, Fs); + ok = recv_call(Config, reset_stream), + send(Config, stream_header(Config)), + Type = ?config(type, Config), + NS = if Type == client -> ?NS_CLIENT; + Type == server -> ?NS_SERVER + end, + Config2 = receive #stream_start{id = ID, xmlns = NS, version = {1,0}} -> + set_opt(stream_id, ID, Config) + end, + receive #stream_features{sub_els = Fs} -> + if Type == client -> + #xmpp_session{optional = true} = + lists:keyfind(xmpp_session, 1, Fs); + true -> + ok + end, + lists:foldl( + fun(#feature_sm{}, ConfigAcc) -> + set_opt(sm, true, ConfigAcc); + (#feature_csi{}, ConfigAcc) -> + set_opt(csi, true, ConfigAcc); + (#rosterver_feature{}, ConfigAcc) -> + set_opt(rosterver, true, ConfigAcc); + (#compression{methods = Ms}, ConfigAcc) -> + set_opt(compression, Ms, ConfigAcc); + (_, ConfigAcc) -> + ConfigAcc + end, Config2, Fs) + end; #sasl_challenge{text = ClientIn} -> {Response, SASL} = (?config(sasl, Config))(ClientIn), send(Config, #sasl_response{text = Response}), - wait_auth_SASL_result(set_opt(sasl, SASL, Config)); + wait_auth_SASL_result(set_opt(sasl, SASL, Config), ShouldFail); + #sasl_failure{} when ShouldFail -> + Config; #sasl_failure{} -> ct:fail(sasl_auth_failed) end. @@ -241,39 +510,54 @@ re_register(Config) -> User = ?config(user, Config), Server = ?config(server, Config), Pass = ?config(password, Config), - {atomic, ok} = ejabberd_auth:try_register(User, Server, Pass), - ok. + ok = ejabberd_auth:try_register(User, Server, Pass). match_failure(Received, [Match]) when is_list(Match)-> ct:fail("Received input:~n~n~p~n~ndon't match expected patterns:~n~n~s", [Received, Match]); match_failure(Received, Matches) -> ct:fail("Received input:~n~n~p~n~ndon't match expected patterns:~n~n~p", [Received, Matches]). -recv() -> +recv(_Config) -> receive - {'$gen_event', {xmlstreamelement, El}} -> - Pkt = xmpp_codec:decode(fix_ns(El)), - ct:pal("recv: ~p ->~n~s", [El, xmpp_codec:pp(Pkt)]), - Pkt; - {'$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 #iq{} = IQ -> IQ end. + +recv_presence(_Config) -> + receive #presence{} = Pres -> Pres end. + +recv_message(_Config) -> + receive #message{} = Msg -> Msg end. + +decode_stream_element(NS, El) -> + decode(El, NS, []). + +format_element(El) -> + case erlang:function_exported(ct, log, 5) of + true -> ejabberd_web_admin:pretty_print_xml(El); + false -> io_lib:format("~p~n", [El]) end. -fix_ns(#xmlel{name = Tag, attrs = Attrs} = El) - when Tag == <<"stream:features">>; Tag == <<"stream:error">> -> - NewAttrs = [{<<"xmlns">>, <<"http://etherx.jabber.org/streams">>} - |lists:keydelete(<<"xmlns">>, 1, Attrs)], - El#xmlel{attrs = NewAttrs}; -fix_ns(#xmlel{name = Tag, attrs = Attrs} = El) - when Tag == <<"message">>; Tag == <<"iq">>; Tag == <<"presence">> -> - NewAttrs = [{<<"xmlns">>, <<"jabber:client">>} - |lists:keydelete(<<"xmlns">>, 1, Attrs)], - El#xmlel{attrs = NewAttrs}; -fix_ns(El) -> - El. +decode(El, NS, Opts) -> + try + Pkt = xmpp:decode(El, NS, Opts), + ct:pal("RECV:~n~s~n~s", + [format_element(El), xmpp:pp(Pkt)]), + Pkt + catch _:{xmpp_codec, Why} -> + ct:pal("recv failed: ~p->~n~s", + [El, xmpp:format_error(Why)]), + erlang:error({xmpp_codec, Why}) + end. send_text(Config, Text) -> - ejabberd_socket:send(?config(socket, Config), Text). + recv_call(Config, {send_text, Text}). send(State, Pkt) -> {NewID, NewPkt} = case Pkt of @@ -289,22 +573,39 @@ send(State, Pkt) -> _ -> {undefined, Pkt} end, - El = xmpp_codec:encode(NewPkt), - ct:pal("sent: ~p <-~n~s", [El, xmpp_codec:pp(NewPkt)]), - ok = send_text(State, fxml:element_to_binary(El)), + El = xmpp:encode(NewPkt), + ct:pal("SENT:~n~s~n~s", + [format_element(El), xmpp:pp(NewPkt)]), + Data = case NewPkt of + #stream_start{} -> fxml:element_to_header(El); + _ -> fxml:element_to_binary(El) + end, + ok = send_text(State, Data), NewID. -send_recv(State, IQ) -> +send_recv(State, #message{} = Msg) -> + ID = send(State, Msg), + receive #message{id = ID} = Result -> Result end; +send_recv(State, #presence{} = Pres) -> + ID = send(State, Pres), + receive #presence{id = ID} = Result -> Result end; +send_recv(State, #iq{} = IQ) -> ID = send(State, IQ), - #iq{id = ID} = recv(). + receive #iq{id = ID} = Result -> Result end. -sasl_new(<<"PLAIN">>, User, Server, Password) -> +sasl_new(<<"PLAIN">>, {User, Server, Password}) -> {<<User/binary, $@, Server/binary, 0, User/binary, 0, Password/binary>>, fun (_) -> {error, <<"Invalid SASL challenge">>} end}; -sasl_new(<<"DIGEST-MD5">>, User, Server, Password) -> +sasl_new(<<"EXTERNAL">>, {User, Server, _Password}) -> + {jid:encode(jid:make(User, Server)), + fun(_) -> ct:fail(sasl_challenge_is_not_expected) end}; +sasl_new(<<"ANONYMOUS">>, _) -> + {<<"">>, + fun(_) -> ct:fail(sasl_challenge_is_not_expected) end}; +sasl_new(<<"DIGEST-MD5">>, {User, Server, Password}) -> {<<"">>, fun (ServerIn) -> - case cyrsasl_digest:parse(ServerIn) of + case xmpp_sasl_digest:parse(ServerIn) of bad -> {error, <<"Invalid SASL challenge">>}; KeyVals -> Nonce = fxml:get_attr_s(<<"nonce">>, KeyVals), @@ -330,7 +631,7 @@ sasl_new(<<"DIGEST-MD5">>, User, Server, Password) -> MyResponse/binary, "\"">>, {Resp, fun (ServerIn2) -> - case cyrsasl_digest:parse(ServerIn2) of + case xmpp_sasl_digest:parse(ServerIn2) of bad -> {error, <<"Invalid SASL challenge">>}; _KeyVals2 -> {<<"">>, @@ -387,6 +688,10 @@ proxy_jid(Config) -> Server = ?config(server, Config), jid:make(<<>>, <<"proxy.", Server/binary>>, <<>>). +upload_jid(Config) -> + Server = ?config(server, Config), + jid:make(<<>>, <<"upload.", Server/binary>>, <<>>). + muc_jid(Config) -> Server = ?config(server, Config), jid:make(<<>>, <<"conference.", Server/binary>>, <<>>). @@ -395,6 +700,20 @@ muc_room_jid(Config) -> Server = ?config(server, Config), jid:make(<<"test">>, <<"conference.", Server/binary>>, <<>>). +my_muc_jid(Config) -> + Nick = ?config(nick, Config), + RoomJID = muc_room_jid(Config), + jid:replace_resource(RoomJID, Nick). + +peer_muc_jid(Config) -> + PeerNick = ?config(peer_nick, Config), + RoomJID = muc_room_jid(Config), + jid:replace_resource(RoomJID, PeerNick). + +alt_room_jid(Config) -> + Server = ?config(server, Config), + jid:make(<<"alt">>, <<"conference.", Server/binary>>, <<>>). + mix_jid(Config) -> Server = ?config(server, Config), jid:make(<<>>, <<"mix.", Server/binary>>, <<>>). @@ -404,10 +723,10 @@ mix_room_jid(Config) -> jid:make(<<"test">>, <<"mix.", Server/binary>>, <<>>). id() -> - id(undefined). + id(<<>>). -id(undefined) -> - randoms:get_string(); +id(<<>>) -> + p1_rand:get_string(); id(ID) -> ID. @@ -415,6 +734,7 @@ get_features(Config) -> get_features(Config, server_jid(Config)). get_features(Config, To) -> + ct:comment("Getting features of ~s", [jid:encode(To)]), #iq{type = result, sub_els = [#disco_info{features = Features}]} = send_recv(Config, #iq{type = get, sub_els = [#disco_info{}], to = To}), Features. @@ -430,16 +750,130 @@ set_opt(Opt, Val, Config) -> [{Opt, Val}|lists:keydelete(Opt, 1, Config)]. wait_for_master(Config) -> - put_event(Config, slave_ready), - master_ready = get_event(Config). + put_event(Config, peer_ready), + case get_event(Config) of + peer_ready -> + ok; + Other -> + suite:match_failure(Other, peer_ready) + end. wait_for_slave(Config) -> - put_event(Config, master_ready), - slave_ready = get_event(Config). + put_event(Config, peer_ready), + case get_event(Config) of + peer_ready -> + ok; + Other -> + suite:match_failure(Other, peer_ready) + end. make_iq_result(#iq{from = From} = IQ) -> IQ#iq{type = result, to = From, from = undefined, sub_els = []}. +self_presence(Config, Type) -> + MyJID = my_jid(Config), + ct:comment("Sending self-presence"), + #presence{type = Type, from = MyJID} = + send_recv(Config, #presence{type = Type}). + +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:encode(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) -> + del_roster(Config, ?config(peer, Config)). + +del_roster(Config, PeerJID) -> + MyJID = my_jid(Config), + {U, S, _} = jid:tolower(MyJID), + PeerBareJID = jid:remove_resource(PeerJID), + PeerLJID = jid:tolower(PeerBareJID), + ct:comment("Removing ~s from roster", [jid:encode(PeerBareJID)]), + {atomic, _} = mod_roster:del_roster(U, S, PeerLJID), + Config. + +get_roster(Config) -> + {LUser, LServer, _} = jid:tolower(my_jid(Config)), + mod_roster:get_roster(LUser, LServer). + +recv_call(Config, Msg) -> + Receiver = ?config(receiver, Config), + Ref = make_ref(), + Receiver ! {Ref, Msg}, + receive + {Ref, Reply} -> + Reply + end. + +start_receiver(NS, Owner, Server, Port) -> + MRef = erlang:monitor(process, Owner), + {ok, Socket} = xmpp_socket:connect( + Server, Port, + [binary, {packet, 0}, {active, false}], infinity), + receiver(NS, Owner, Socket, MRef). + +receiver(NS, Owner, Socket, MRef) -> + receive + {Ref, reset_stream} -> + Socket1 = xmpp_socket:reset_stream(Socket), + Owner ! {Ref, ok}, + receiver(NS, Owner, Socket1, MRef); + {Ref, {starttls, Certfile}} -> + {ok, TLSSocket} = xmpp_socket:starttls( + Socket, + [{certfile, Certfile}, connect]), + Owner ! {Ref, ok}, + receiver(NS, Owner, TLSSocket, MRef); + {Ref, compress} -> + {ok, ZlibSocket} = xmpp_socket:compress(Socket), + Owner ! {Ref, ok}, + receiver(NS, Owner, ZlibSocket, MRef); + {Ref, {send_text, Text}} -> + Ret = xmpp_socket:send(Socket, Text), + Owner ! {Ref, Ret}, + receiver(NS, Owner, Socket, MRef); + {Ref, close} -> + xmpp_socket:close(Socket), + Owner ! {Ref, ok}, + receiver(NS, Owner, Socket, MRef); + {'$gen_event', {xmlstreamelement, El}} -> + Owner ! decode_stream_element(NS, El), + receiver(NS, Owner, Socket, MRef); + {'$gen_event', {xmlstreamstart, Name, Attrs}} -> + Owner ! decode(#xmlel{name = Name, attrs = Attrs}, <<>>, []), + receiver(NS, Owner, Socket, MRef); + {'$gen_event', Event} -> + Owner ! Event, + receiver(NS, Owner, Socket, MRef); + {'DOWN', MRef, process, Owner, _} -> + ok; + {tcp, _, Data} -> + case xmpp_socket:recv(Socket, Data) of + {ok, Socket1} -> + receiver(NS, Owner, Socket1, MRef); + {error, _} -> + Owner ! closed, + receiver(NS, Owner, Socket, MRef) + end; + {tcp_error, _, _} -> + Owner ! closed, + receiver(NS, Owner, Socket, MRef); + {tcp_closed, _} -> + Owner ! closed, + receiver(NS, Owner, Socket, MRef) + end. + %%%=================================================================== %%% Clients puts and gets events via this relay. %%%=================================================================== @@ -456,6 +890,7 @@ event_relay() -> event_relay(Events, Subscribers) -> receive {subscribe, From} -> + erlang:monitor(process, From), From ! {ok, self()}, lists:foreach( fun(Event) -> From ! {event, Event, self()} @@ -469,7 +904,19 @@ event_relay(Events, Subscribers) -> (_) -> ok end, Subscribers), - event_relay([Event|Events], Subscribers) + event_relay([Event|Events], Subscribers); + {'DOWN', _MRef, process, Pid, _Info} -> + 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) -> @@ -494,3 +941,12 @@ get_event(Config) -> {event, Event, Relay} -> Event end. + +flush(Config) -> + receive + {event, peer_down, _} -> flush(Config); + closed -> flush(Config); + Msg -> ct:fail({unexpected_msg, Msg}) + after 0 -> + ok + end. |