From 42a9e4f4cfb932772a3af8cecaca8e0b34227567 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 27 Jun 2013 03:55:29 +1000 Subject: [PATCH] Split the test suite into modules --- test/ejabberd_SUITE.erl | 507 +++------------------------------------- test/suite.erl | 398 +++++++++++++++++++++++++++++++ test/suite.hrl | 66 ++++++ 3 files changed, 501 insertions(+), 470 deletions(-) create mode 100644 test/suite.erl create mode 100644 test/suite.hrl diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index ba9580fb6..632633a57 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -10,111 +10,38 @@ -compile(export_all). --include_lib("common_test/include/ct.hrl"). --include("xml.hrl"). --include("ns.hrl"). --include("ejabberd.hrl"). --include("mod_proxy65.hrl"). --include("xmpp_codec.hrl"). +-import(suite, [init_config/1, connect/1, disconnect/1, + recv/0, send/2, send_recv/2, my_jid/1, server_jid/1, + pubsub_jid/1, proxy_jid/1, muc_jid/1, + muc_room_jid/1, get_features/2, re_register/1, + is_feature_advertised/2, subscribe_to_events/1, + is_feature_advertised/3, set_opt/3, auth_SASL/2, + wait_for_master/1, wait_for_slave/1, + make_iq_result/1, start_event_relay/0, + stop_event_relay/1, put_event/2, get_event/1, + bind/1, auth/1, open_session/1, zlib/1, starttls/1]). --define(STREAM_HEADER, - <<"">>). - --define(STREAM_TRAILER, <<"">>). - --define(PUBSUB(Node), <<(?NS_PUBSUB)/binary, "#", Node>>). - --define(recv2(P1, P2), - (fun() -> - case {R1 = recv(), R2 = recv()} of - {P1, P2} -> {R1, R2}; - {P2, P1} -> {R2, R1} - end - end)()). - --define(recv3(P1, P2, P3), - (fun() -> - case R3 = recv() of - P1 -> insert(R3, 1, ?recv2(P2, P3)); - P2 -> insert(R3, 2, ?recv2(P1, P3)); - P3 -> insert(R3, 3, ?recv2(P1, P2)) - end - end)()). - --define(recv4(P1, P2, P3, P4), - (fun() -> - case R4 = recv() of - P1 -> insert(R4, 1, ?recv3(P2, P3, P4)); - P2 -> insert(R4, 2, ?recv3(P1, P3, P4)); - P3 -> insert(R4, 3, ?recv3(P1, P2, P4)); - P4 -> insert(R4, 4, ?recv3(P1, P2, P3)) - end - end)()). - --define(recv5(P1, P2, P3, P4, P5), - (fun() -> - case R5 = recv() of - P1 -> insert(R5, 1, ?recv4(P2, P3, P4, P5)); - P2 -> insert(R5, 2, ?recv4(P1, P3, P4, P5)); - P3 -> insert(R5, 3, ?recv4(P1, P2, P4, P5)); - P4 -> insert(R5, 4, ?recv4(P1, P2, P3, P5)); - P5 -> insert(R5, 5, ?recv4(P1, P2, P3, P4)) - end - end)()). - --define(COMMON_VHOST, <<"localhost">>). --define(MNESIA_VHOST, <<"mnesia.localhost">>). --define(MYSQL_VHOST, <<"mysql.localhost">>). --define(PGSQL_VHOST, <<"pgsql.localhost">>). --define(LDAP_VHOST, <<"ldap.localhost">>). --define(EXTAUTH_VHOST, <<"extauth.localhost">>). +-include("suite.hrl"). suite() -> [{timetrap, {seconds,10}}]. init_per_suite(Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - [_, _|Tail] = lists:reverse(filename:split(DataDir)), - BaseDir = filename:join(lists:reverse(Tail)), - ConfigPath = filename:join([DataDir, "ejabberd.cfg"]), - LogPath = filename:join([PrivDir, "ejabberd.log"]), - SASLPath = filename:join([PrivDir, "sasl.log"]), - MnesiaDir = filename:join([PrivDir, "mnesia"]), - CertFile = filename:join([DataDir, "cert.pem"]), - LDIFFile = filename:join([DataDir, "ejabberd.ldif"]), - ExtAuthScript = filename:join([DataDir, "extauth.py"]), + NewConfig = init_config(Config), + DataDir = proplists:get_value(data_dir, NewConfig), {ok, CWD} = file:get_cwd(), - {ok, _} = file:copy(CertFile, filename:join([CWD, "cert.pem"])), + ExtAuthScript = filename:join([DataDir, "extauth.py"]), + LDIFFile = filename:join([DataDir, "ejabberd.ldif"]), {ok, _} = file:copy(ExtAuthScript, filename:join([CWD, "extauth.py"])), - 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), {ok, _} = ldap_srv:start(LDIFFile), ok = application:start(ejabberd), - [{server_port, 5222}, - {server_host, "localhost"}, - {server, ?COMMON_VHOST}, - {user, <<"test_single">>}, - {certfile, CertFile}, - {base_dir, BaseDir}, - {ldif_file, LDIFFile}, - {resource, <<"resource">>}, - {password, <<"password">>} - |Config]. + NewConfig. end_per_suite(_Config) -> ok. init_per_group(no_db, Config) -> - User = ?config(user, Config), - Server = ?config(server, Config), - Password = ?config(password, Config), - {atomic, ok} = ejabberd_auth:try_register(User, Server, Password), + re_register(Config), Config; init_per_group(mnesia, Config) -> mod_muc:shutdown_rooms(?MNESIA_VHOST), @@ -293,44 +220,6 @@ stop_ejabberd(Config) -> test_connect(Config) -> disconnect(connect(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)). - -init_stream(Config) -> - ok = send_text(Config, io_lib:format(?STREAM_HEADER, - [?config(server, Config)])), - {xmlstreamstart, <<"stream:stream">>, Attrs} = recv(), - <<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs), - <<"1.0">> = xml: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). - -disconnect(Config) -> - Socket = ?config(socket, Config), - ok = ejabberd_socket:send(Socket, ?STREAM_TRAILER), - {xmlstreamend, <<"stream:stream">>} = recv(), - ejabberd_socket:close(Socket), - Config. - test_starttls(Config) -> case ?config(starttls, Config) of true -> @@ -339,15 +228,6 @@ test_starttls(Config) -> {skipped, 'starttls_not_available'} end. -starttls(Config) -> - 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)). - test_zlib(Config) -> case ?config(compression, Config) of [_|_] = Ms -> @@ -361,12 +241,6 @@ test_zlib(Config) -> {skipped, 'compression_not_available'} end. -zlib(Config) -> - send(Config, #compress{methods = [<<"zlib">>]}), - #compressed{} = recv(), - ZlibSocket = ejabberd_socket:compress(?config(socket, Config)), - init_stream(set_opt(socket, ZlibSocket, Config)). - test_register(Config) -> case ?config(register, Config) of true -> @@ -407,40 +281,35 @@ try_unregister(Config) -> #stream_error{reason = conflict} = recv(), Config. +auth_md5(Config) -> + Mechs = ?config(mechs, Config), + case lists:member(<<"DIGEST-MD5">>, Mechs) of + true -> + disconnect(auth_SASL(<<"DIGEST-MD5">>, Config)); + false -> + disconnect(Config), + {skipped, 'DIGEST-MD5_not_available'} + end. + +auth_plain(Config) -> + Mechs = ?config(mechs, Config), + case lists:member(<<"PLAIN">>, Mechs) of + true -> + disconnect(auth_SASL(<<"PLAIN">>, Config)); + false -> + disconnect(Config), + {skipped, 'PLAIN_not_available'} + end. + test_auth(Config) -> disconnect(auth(Config)). -auth(Config) -> - Mechs = ?config(mechs, Config), - HaveMD5 = lists:member(<<"DIGEST-MD5">>, Mechs), - HavePLAIN = lists:member(<<"PLAIN">>, Mechs), - if HavePLAIN -> - auth_SASL(<<"PLAIN">>, Config); - HaveMD5 -> - auth_SASL(<<"DIGEST-MD5">>, Config); - true -> - ct:fail(no_sasl_mechanisms_available) - end. - test_bind(Config) -> disconnect(bind(Config)). -bind(Config) -> - #iq{type = result, sub_els = [#bind{}]} = - send_recv( - Config, - #iq{type = set, - sub_els = [#bind{resource = ?config(resource, Config)}]}), - Config. - test_open_session(Config) -> disconnect(open_session(Config)). -open_session(Config) -> - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, sub_els = [#session{}]}), - Config. - roster_get(Config) -> #iq{type = result, sub_els = [#roster{items = []}]} = send_recv(Config, #iq{type = get, sub_els = [#roster{}]}), @@ -754,26 +623,6 @@ pubsub(Config) -> jid = my_jid(Config)}}]}), disconnect(Config). -auth_md5(Config) -> - Mechs = ?config(mechs, Config), - case lists:member(<<"DIGEST-MD5">>, Mechs) of - true -> - disconnect(auth_SASL(<<"DIGEST-MD5">>, Config)); - false -> - disconnect(Config), - {skipped, 'DIGEST-MD5_not_available'} - end. - -auth_plain(Config) -> - Mechs = ?config(mechs, Config), - case lists:member(<<"PLAIN">>, Mechs) of - true -> - disconnect(auth_SASL(<<"PLAIN">>, Config)); - false -> - disconnect(Config), - {skipped, 'PLAIN_not_available'} - end. - roster_subscribe_master(Config) -> send(Config, #presence{}), #presence{} = recv(), @@ -1046,217 +895,9 @@ offline_slave(Config) -> end, SubEls), disconnect(Config). -auth_SASL(Mech, Config) -> - {Response, SASL} = sasl_new(Mech, - ?config(user, Config), - ?config(server, Config), - ?config(password, Config)), - send(Config, #sasl_auth{mechanism = Mech, text = Response}), - wait_auth_SASL_result(set_opt(sasl, SASL, Config)). - -wait_auth_SASL_result(Config) -> - case recv() of - #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">> = xml:get_attr_s(<<"xmlns">>, Attrs), - <<"1.0">> = xml:get_attr_s(<<"version">>, Attrs), - #stream_features{} = recv(), - Config; - #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)); - #sasl_failure{} -> - ct:fail(sasl_auth_failed) - end. - %%%=================================================================== %%% Aux functions %%%=================================================================== -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. - -recv() -> - 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 - 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. - -send_text(Config, Text) -> - ejabberd_socket:send(?config(socket, Config), Text). - -send(State, Pkt) -> - {NewID, NewPkt} = case Pkt of - #message{id = I} -> - ID = id(I), - {ID, Pkt#message{id = ID}}; - #presence{id = I} -> - ID = id(I), - {ID, Pkt#presence{id = ID}}; - #iq{id = I} -> - ID = id(I), - {ID, Pkt#iq{id = ID}}; - _ -> - {undefined, Pkt} - end, - El = xmpp_codec:encode(NewPkt), - ct:pal("sent: ~p <-~n~s", [El, xmpp_codec:pp(NewPkt)]), - ok = send_text(State, xml:element_to_binary(El)), - NewID. - -send_recv(State, IQ) -> - ID = send(State, IQ), - #iq{id = ID} = recv(). - -sasl_new(<<"PLAIN">>, User, Server, Password) -> - {<>, - fun (_) -> {error, <<"Invalid SASL challenge">>} end}; -sasl_new(<<"DIGEST-MD5">>, User, Server, Password) -> - {<<"">>, - fun (ServerIn) -> - case cyrsasl_digest:parse(ServerIn) of - bad -> {error, <<"Invalid SASL challenge">>}; - KeyVals -> - Nonce = xml:get_attr_s(<<"nonce">>, KeyVals), - CNonce = id(), - DigestURI = <<"xmpp/", Server/binary>>, - Realm = Server, - NC = <<"00000001">>, - QOP = <<"auth">>, - AuthzId = <<"">>, - MyResponse = response(User, Password, Nonce, AuthzId, - Realm, CNonce, DigestURI, NC, QOP, - <<"AUTHENTICATE">>), - ServerResponse = response(User, Password, Nonce, - AuthzId, Realm, CNonce, DigestURI, - NC, QOP, <<"">>), - Resp = <<"username=\"", User/binary, "\",realm=\"", - Realm/binary, "\",nonce=\"", Nonce/binary, - "\",cnonce=\"", CNonce/binary, "\",nc=", NC/binary, - ",qop=", QOP/binary, ",digest-uri=\"", - DigestURI/binary, "\",response=\"", - MyResponse/binary, "\"">>, - {Resp, - fun (ServerIn2) -> - case cyrsasl_digest:parse(ServerIn2) of - bad -> {error, <<"Invalid SASL challenge">>}; - KeyVals2 -> - RspAuth = xml:get_attr_s(<<"rspauth">>, - KeyVals2), - if RspAuth == ServerResponse -> - {<<"">>, - fun (_) -> - {error, - <<"Invalid SASL challenge">>} - end}; - true -> - {error, <<"Invalid SASL challenge">>} - end - end - end} - end - end}. - -hex(S) -> - p1_sha:to_hexlist(S). - -response(User, Passwd, Nonce, AuthzId, Realm, CNonce, - DigestURI, NC, QOP, A2Prefix) -> - A1 = case AuthzId of - <<"">> -> - <<((crypto:md5(<>)))/binary, - ":", Nonce/binary, ":", CNonce/binary>>; - _ -> - <<((crypto:md5(<>)))/binary, - ":", Nonce/binary, ":", CNonce/binary, ":", - AuthzId/binary>> - end, - A2 = case QOP of - <<"auth">> -> - <>; - _ -> - <> - end, - T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary, - ":", NC/binary, ":", CNonce/binary, ":", QOP/binary, - ":", (hex((crypto:md5(A2))))/binary>>, - hex((crypto:md5(T))). - -my_jid(Config) -> - jlib:make_jid(?config(user, Config), - ?config(server, Config), - ?config(resource, Config)). - -server_jid(Config) -> - jlib:make_jid(<<>>, ?config(server, Config), <<>>). - -pubsub_jid(Config) -> - Server = ?config(server, Config), - jlib:make_jid(<<>>, <<"pubsub.", Server/binary>>, <<>>). - -proxy_jid(Config) -> - Server = ?config(server, Config), - jlib:make_jid(<<>>, <<"proxy.", Server/binary>>, <<>>). - -muc_jid(Config) -> - Server = ?config(server, Config), - jlib:make_jid(<<>>, <<"conference.", Server/binary>>, <<>>). - -muc_room_jid(Config) -> - Server = ?config(server, Config), - jlib:make_jid(<<"test">>, <<"conference.", Server/binary>>, <<>>). - -id() -> - id(undefined). - -id(undefined) -> - randoms:get_string(); -id(ID) -> - ID. - -get_features(Config) -> - get_features(Config, server_jid(Config)). - -get_features(Config, To) -> - #iq{type = result, sub_els = [#disco_info{features = Features}]} = - send_recv(Config, #iq{type = get, sub_els = [#disco_info{}], to = To}), - Features. - -is_feature_advertised(Config, Feature) -> - is_feature_advertised(Config, Feature, server_jid(Config)). - -is_feature_advertised(Config, Feature, To) -> - Features = get_features(Config, To), - lists:member(Feature, Features). - bookmark_conference() -> #bookmark_conference{name = <<"Some name">>, autojoin = true, @@ -1265,20 +906,6 @@ bookmark_conference() -> <<"some.conference.org">>, <<>>)}. -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). - -wait_for_slave(Config) -> - put_event(Config, master_ready), - slave_ready = get_event(Config). - -make_iq_result(#iq{from = From} = IQ) -> - IQ#iq{type = result, to = From, from = undefined, sub_els = []}. - socks5_connect(#streamhost{host = Host, port = Port}, {SID, JID1, JID2}) -> Hash = p1_sha:sha([SID, jlib:jid_to_string(JID1), jlib:jid_to_string(JID2)]), @@ -1302,66 +929,6 @@ socks5_send(Sock, Data) -> socks5_recv(Sock, Data) -> {ok, Data} = gen_tcp:recv(Sock, size(Data)). -%%%=================================================================== -%%% Clients puts and gets events via this relay. -%%%=================================================================== -start_event_relay() -> - spawn(fun event_relay/0). - -stop_event_relay(Config) -> - Pid = ?config(event_relay, Config), - exit(Pid, normal). - -event_relay() -> - event_relay([], []). - -event_relay(Events, Subscribers) -> - receive - {subscribe, From} -> - From ! {ok, self()}, - lists:foreach( - fun(Event) -> From ! {event, Event, self()} - end, Events), - event_relay(Events, [From|Subscribers]); - {put, Event, From} -> - From ! {ok, self()}, - lists:foreach( - fun(Pid) when Pid /= From -> - Pid ! {event, Event, self()}; - (_) -> - ok - end, Subscribers), - event_relay([Event|Events], Subscribers) - end. - -subscribe_to_events(Config) -> - Relay = ?config(event_relay, Config), - Relay ! {subscribe, self()}, - receive - {ok, Relay} -> - ok - end. - -put_event(Config, Event) -> - Relay = ?config(event_relay, Config), - Relay ! {put, Event, self()}, - receive - {ok, Relay} -> - ok - end. - -get_event(Config) -> - Relay = ?config(event_relay, Config), - receive - {event, Event, Relay} -> - Event - end. - -insert(Val, N, Tuple) -> - L = tuple_to_list(Tuple), - {H, T} = lists:split(N-1, L), - list_to_tuple(H ++ [Val|T]). - %%%=================================================================== %%% SQL stuff %%%=================================================================== diff --git a/test/suite.erl b/test/suite.erl new file mode 100644 index 000000000..4520e55cf --- /dev/null +++ b/test/suite.erl @@ -0,0 +1,398 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeniy Khramtsov <> +%%% @copyright (C) 2013, Evgeniy Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 27 Jun 2013 by Evgeniy Khramtsov <> +%%%------------------------------------------------------------------- +-module(suite). + +%% API +-compile(export_all). + +-include("suite.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init_config(Config) -> + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + [_, _|Tail] = lists:reverse(filename:split(DataDir)), + BaseDir = filename:join(lists:reverse(Tail)), + ConfigPath = filename:join([DataDir, "ejabberd.cfg"]), + LogPath = filename:join([PrivDir, "ejabberd.log"]), + SASLPath = filename:join([PrivDir, "sasl.log"]), + MnesiaDir = filename:join([PrivDir, "mnesia"]), + CertFile = filename:join([DataDir, "cert.pem"]), + {ok, CWD} = file:get_cwd(), + {ok, _} = file:copy(CertFile, filename:join([CWD, "cert.pem"])), + 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, 5222}, + {server_host, "localhost"}, + {server, ?COMMON_VHOST}, + {user, <<"test_single">>}, + {certfile, CertFile}, + {base_dir, BaseDir}, + {resource, <<"resource">>}, + {password, <<"password">>} + |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)). + +init_stream(Config) -> + ok = send_text(Config, io_lib:format(?STREAM_HEADER, + [?config(server, Config)])), + {xmlstreamstart, <<"stream:stream">>, Attrs} = recv(), + <<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs), + <<"1.0">> = xml: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). + +disconnect(Config) -> + Socket = ?config(socket, Config), + ok = ejabberd_socket:send(Socket, ?STREAM_TRAILER), + {xmlstreamend, <<"stream:stream">>} = recv(), + ejabberd_socket:close(Socket), + Config. + +starttls(Config) -> + 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)). + +zlib(Config) -> + send(Config, #compress{methods = [<<"zlib">>]}), + #compressed{} = recv(), + ZlibSocket = ejabberd_socket:compress(?config(socket, Config)), + init_stream(set_opt(socket, ZlibSocket, Config)). + +auth(Config) -> + Mechs = ?config(mechs, Config), + HaveMD5 = lists:member(<<"DIGEST-MD5">>, Mechs), + HavePLAIN = lists:member(<<"PLAIN">>, Mechs), + if HavePLAIN -> + auth_SASL(<<"PLAIN">>, Config); + HaveMD5 -> + auth_SASL(<<"DIGEST-MD5">>, Config); + true -> + ct:fail(no_sasl_mechanisms_available) + end. + +bind(Config) -> + #iq{type = result, sub_els = [#bind{}]} = + send_recv( + Config, + #iq{type = set, + sub_els = [#bind{resource = ?config(resource, Config)}]}), + Config. + +open_session(Config) -> + #iq{type = result, sub_els = []} = + send_recv(Config, #iq{type = set, sub_els = [#session{}]}), + Config. + +auth_SASL(Mech, Config) -> + {Response, SASL} = sasl_new(Mech, + ?config(user, Config), + ?config(server, Config), + ?config(password, Config)), + send(Config, #sasl_auth{mechanism = Mech, text = Response}), + wait_auth_SASL_result(set_opt(sasl, SASL, Config)). + +wait_auth_SASL_result(Config) -> + case recv() of + #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">> = xml:get_attr_s(<<"xmlns">>, Attrs), + <<"1.0">> = xml:get_attr_s(<<"version">>, Attrs), + #stream_features{} = recv(), + Config; + #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)); + #sasl_failure{} -> + ct:fail(sasl_auth_failed) + end. + +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. + +recv() -> + 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 + 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. + +send_text(Config, Text) -> + ejabberd_socket:send(?config(socket, Config), Text). + +send(State, Pkt) -> + {NewID, NewPkt} = case Pkt of + #message{id = I} -> + ID = id(I), + {ID, Pkt#message{id = ID}}; + #presence{id = I} -> + ID = id(I), + {ID, Pkt#presence{id = ID}}; + #iq{id = I} -> + ID = id(I), + {ID, Pkt#iq{id = ID}}; + _ -> + {undefined, Pkt} + end, + El = xmpp_codec:encode(NewPkt), + ct:pal("sent: ~p <-~n~s", [El, xmpp_codec:pp(NewPkt)]), + ok = send_text(State, xml:element_to_binary(El)), + NewID. + +send_recv(State, IQ) -> + ID = send(State, IQ), + #iq{id = ID} = recv(). + +sasl_new(<<"PLAIN">>, User, Server, Password) -> + {<>, + fun (_) -> {error, <<"Invalid SASL challenge">>} end}; +sasl_new(<<"DIGEST-MD5">>, User, Server, Password) -> + {<<"">>, + fun (ServerIn) -> + case cyrsasl_digest:parse(ServerIn) of + bad -> {error, <<"Invalid SASL challenge">>}; + KeyVals -> + Nonce = xml:get_attr_s(<<"nonce">>, KeyVals), + CNonce = id(), + Realm = proplists:get_value(<<"realm">>, KeyVals, Server), + DigestURI = <<"xmpp/", Realm/binary>>, + NC = <<"00000001">>, + QOP = <<"auth">>, + AuthzId = <<"">>, + MyResponse = response(User, Password, Nonce, AuthzId, + Realm, CNonce, DigestURI, NC, QOP, + <<"AUTHENTICATE">>), + ServerResponse = response(User, Password, Nonce, + AuthzId, Realm, CNonce, DigestURI, + NC, QOP, <<"">>), + Resp = <<"username=\"", User/binary, "\",realm=\"", + Realm/binary, "\",nonce=\"", Nonce/binary, + "\",cnonce=\"", CNonce/binary, "\",nc=", NC/binary, + ",qop=", QOP/binary, ",digest-uri=\"", + DigestURI/binary, "\",response=\"", + MyResponse/binary, "\"">>, + {Resp, + fun (ServerIn2) -> + case cyrsasl_digest:parse(ServerIn2) of + bad -> {error, <<"Invalid SASL challenge">>}; + KeyVals2 -> + RspAuth = xml:get_attr_s(<<"rspauth">>, + KeyVals2), + if RspAuth == ServerResponse -> + {<<"">>, + fun (_) -> + {error, + <<"Invalid SASL challenge">>} + end}; + true -> + {error, <<"Invalid SASL challenge">>} + end + end + end} + end + end}. + +hex(S) -> + p1_sha:to_hexlist(S). + +response(User, Passwd, Nonce, AuthzId, Realm, CNonce, + DigestURI, NC, QOP, A2Prefix) -> + A1 = case AuthzId of + <<"">> -> + <<((crypto:md5(<>)))/binary, + ":", Nonce/binary, ":", CNonce/binary>>; + _ -> + <<((crypto:md5(<>)))/binary, + ":", Nonce/binary, ":", CNonce/binary, ":", + AuthzId/binary>> + end, + A2 = case QOP of + <<"auth">> -> + <>; + _ -> + <> + end, + T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary, + ":", NC/binary, ":", CNonce/binary, ":", QOP/binary, + ":", (hex((crypto:md5(A2))))/binary>>, + hex((crypto:md5(T))). + +my_jid(Config) -> + jlib:make_jid(?config(user, Config), + ?config(server, Config), + ?config(resource, Config)). + +server_jid(Config) -> + jlib:make_jid(<<>>, ?config(server, Config), <<>>). + +pubsub_jid(Config) -> + Server = ?config(server, Config), + jlib:make_jid(<<>>, <<"pubsub.", Server/binary>>, <<>>). + +proxy_jid(Config) -> + Server = ?config(server, Config), + jlib:make_jid(<<>>, <<"proxy.", Server/binary>>, <<>>). + +muc_jid(Config) -> + Server = ?config(server, Config), + jlib:make_jid(<<>>, <<"conference.", Server/binary>>, <<>>). + +muc_room_jid(Config) -> + Server = ?config(server, Config), + jlib:make_jid(<<"test">>, <<"conference.", Server/binary>>, <<>>). + +id() -> + id(undefined). + +id(undefined) -> + randoms:get_string(); +id(ID) -> + ID. + +get_features(Config) -> + get_features(Config, server_jid(Config)). + +get_features(Config, To) -> + #iq{type = result, sub_els = [#disco_info{features = Features}]} = + send_recv(Config, #iq{type = get, sub_els = [#disco_info{}], to = To}), + Features. + +is_feature_advertised(Config, Feature) -> + is_feature_advertised(Config, Feature, server_jid(Config)). + +is_feature_advertised(Config, Feature, To) -> + Features = get_features(Config, To), + lists:member(Feature, Features). + +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). + +wait_for_slave(Config) -> + put_event(Config, master_ready), + slave_ready = get_event(Config). + +make_iq_result(#iq{from = From} = IQ) -> + IQ#iq{type = result, to = From, from = undefined, sub_els = []}. + +%%%=================================================================== +%%% Clients puts and gets events via this relay. +%%%=================================================================== +start_event_relay() -> + spawn(fun event_relay/0). + +stop_event_relay(Config) -> + Pid = ?config(event_relay, Config), + exit(Pid, normal). + +event_relay() -> + event_relay([], []). + +event_relay(Events, Subscribers) -> + receive + {subscribe, From} -> + From ! {ok, self()}, + lists:foreach( + fun(Event) -> From ! {event, Event, self()} + end, Events), + event_relay(Events, [From|Subscribers]); + {put, Event, From} -> + From ! {ok, self()}, + lists:foreach( + fun(Pid) when Pid /= From -> + Pid ! {event, Event, self()}; + (_) -> + ok + end, Subscribers), + event_relay([Event|Events], Subscribers) + end. + +subscribe_to_events(Config) -> + Relay = ?config(event_relay, Config), + Relay ! {subscribe, self()}, + receive + {ok, Relay} -> + ok + end. + +put_event(Config, Event) -> + Relay = ?config(event_relay, Config), + Relay ! {put, Event, self()}, + receive + {ok, Relay} -> + ok + end. + +get_event(Config) -> + Relay = ?config(event_relay, Config), + receive + {event, Event, Relay} -> + Event + end. diff --git a/test/suite.hrl b/test/suite.hrl new file mode 100644 index 000000000..737098315 --- /dev/null +++ b/test/suite.hrl @@ -0,0 +1,66 @@ +-include_lib("common_test/include/ct.hrl"). +-include("xml.hrl"). +-include("ns.hrl"). +-include("ejabberd.hrl"). +-include("mod_proxy65.hrl"). +-include("xmpp_codec.hrl"). + +-define(STREAM_HEADER, + <<"">>). + +-define(STREAM_TRAILER, <<"">>). + +-define(PUBSUB(Node), <<(?NS_PUBSUB)/binary, "#", Node>>). + +-define(recv2(P1, P2), + (fun() -> + case {R1 = recv(), R2 = recv()} of + {P1, P2} -> {R1, R2}; + {P2, P1} -> {R2, R1} + end + end)()). + +-define(recv3(P1, P2, P3), + (fun() -> + case R3 = recv() of + P1 -> insert(R3, 1, ?recv2(P2, P3)); + P2 -> insert(R3, 2, ?recv2(P1, P3)); + P3 -> insert(R3, 3, ?recv2(P1, P2)) + end + end)()). + +-define(recv4(P1, P2, P3, P4), + (fun() -> + case R4 = recv() of + P1 -> insert(R4, 1, ?recv3(P2, P3, P4)); + P2 -> insert(R4, 2, ?recv3(P1, P3, P4)); + P3 -> insert(R4, 3, ?recv3(P1, P2, P4)); + P4 -> insert(R4, 4, ?recv3(P1, P2, P3)) + end + end)()). + +-define(recv5(P1, P2, P3, P4, P5), + (fun() -> + case R5 = recv() of + P1 -> insert(R5, 1, ?recv4(P2, P3, P4, P5)); + P2 -> insert(R5, 2, ?recv4(P1, P3, P4, P5)); + P3 -> insert(R5, 3, ?recv4(P1, P2, P4, P5)); + P4 -> insert(R5, 4, ?recv4(P1, P2, P3, P5)); + P5 -> insert(R5, 5, ?recv4(P1, P2, P3, P4)) + end + end)()). + +-define(COMMON_VHOST, <<"localhost">>). +-define(MNESIA_VHOST, <<"mnesia.localhost">>). +-define(MYSQL_VHOST, <<"mysql.localhost">>). +-define(PGSQL_VHOST, <<"pgsql.localhost">>). +-define(LDAP_VHOST, <<"ldap.localhost">>). +-define(EXTAUTH_VHOST, <<"extauth.localhost">>). + +insert(Val, N, Tuple) -> + L = tuple_to_list(Tuple), + {H, T} = lists:split(N-1, L), + list_to_tuple(H ++ [Val|T]).