From ceda0737666760e5fa93bed0a9ab160778d29358 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 21 Sep 2016 10:45:11 +0300 Subject: [PATCH] Add tests for external component --- src/ejabberd_c2s.erl | 2 +- src/ejabberd_service.erl | 29 +++++----- test/ejabberd_SUITE.erl | 82 ++++++++++++++++++++++---- test/ejabberd_SUITE_data/ejabberd.yml | 5 ++ test/suite.erl | 83 +++++++++++++++++++++------ 5 files changed, 154 insertions(+), 47 deletions(-) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index e98a9eb8f..7dc9960e6 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -494,7 +494,7 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> catch _:{xmpp_codec, Why} -> Txt = xmpp:format_error(Why), send_header(StateData, ?MYNAME, <<"1.0">>, ?MYLANG), - send_element(StateData, xmpp:serr_not_well_formed(Txt, ?MYLANG)), + send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)), {stop, normal, StateData} end; wait_for_stream(timeout, StateData) -> diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 002d74949..f4338593d 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -125,7 +125,12 @@ init([{SockMod, Socket}, Opts]) -> wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of - #stream_start{xmlns = ?NS_COMPONENT, to = To} when is_record(To, jid) -> + #stream_start{xmlns = NS_COMPONENT, stream_xmlns = NS_STREAM} + when NS_COMPONENT /= ?NS_COMPONENT; NS_STREAM /= ?NS_STREAM -> + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData}; + #stream_start{to = To} when is_record(To, jid) -> Host = To#jid.lserver, send_header(StateData, Host), HostOpts = case dict:is_key(Host, StateData#state.host_opts) of @@ -141,13 +146,9 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> end, {next_state, wait_for_handshake, StateData#state{host = Host, host_opts = HostOpts}}; - #stream_start{xmlns = ?NS_COMPONENT} -> - send_header(StateData, ?MYNAME), - send_element(StateData, xmpp:serr_improper_addressing()), - {stop, normal, StateData}; #stream_start{} -> send_header(StateData, ?MYNAME), - send_element(StateData, xmpp:serr_invalid_namespace()), + send_element(StateData, xmpp:serr_improper_addressing()), {stop, normal, StateData} catch _:{xmpp_codec, Why} -> Txt = xmpp:format_error(Why), @@ -203,7 +204,8 @@ stream_established(El, StateData) when ?is_stanza(El) -> To = xmpp:get_to(El), Lang = xmpp:get_lang(El), if From == undefined orelse To == undefined -> - send_error(StateData, El, xmpp:err_jid_malformed()); + Txt = <<"Missing 'from' or 'to' attribute">>, + send_error(StateData, El, xmpp:err_jid_malformed(Txt, Lang)); true -> FromJID = case StateData#state.check_from of false -> @@ -214,19 +216,16 @@ stream_established(El, StateData) when ?is_stanza(El) -> From; _ -> %% The default is the standard behaviour in XEP-0114 - case From of - #jid{lserver = Server} -> - case dict:is_key(Server, StateData#state.host_opts) of - true -> From; - false -> error - end; - _ -> error + Server = From#jid.lserver, + case dict:is_key(Server, StateData#state.host_opts) of + true -> From; + false -> error end end, if FromJID /= error -> ejabberd_router:route(FromJID, To, El); true -> - Txt = <<"Incorrect value of 'from' or 'to' attribute">>, + Txt = <<"Improper domain part of 'from' attribute">>, send_error(StateData, El, xmpp:err_not_allowed(Txt, Lang)) end end, diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 0af81dd33..dbcd9a905 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -125,6 +125,15 @@ do_init_per_group(riak, Config) -> Err -> {skip, {riak_not_available, Err}} end; +do_init_per_group(component, Config) -> + Server = ?config(server, Config), + Port = ?config(component_port, Config), + set_opt(xmlns, ?NS_COMPONENT, + set_opt(server, <<"component.", Server/binary>>, + set_opt(type, component, + set_opt(server_port, Port, + set_opt(stream_version, <<"">>, + set_opt(lang, <<"">>, Config)))))); do_init_per_group(_GroupName, Config) -> Pid = start_event_relay(), set_opt(event_relay, Pid, Config). @@ -147,6 +156,8 @@ end_per_group(extauth, _Config) -> ok; end_per_group(riak, _Config) -> ok; +end_per_group(component, _Config) -> + ok; end_per_group(_GroupName, Config) -> stop_event_relay(Config), ok. @@ -252,7 +263,7 @@ no_db_tests() -> [{generic, [parallel], [test_connect_bad_xml, test_connect_unknown_ns, - test_connect_bad_ns_client, + test_connect_bad_xmlns, test_connect_bad_ns_stream, test_connect_bad_lang, test_connect_bad_to, @@ -421,10 +432,27 @@ extauth_tests() -> test_auth_fail, test_unregister]}]. +component_tests() -> + [{component_tests, [sequence], + [test_connect_bad_xml, + test_connect_unknown_ns, + test_connect_bad_xmlns, + test_connect_bad_ns_stream, + test_connect_missing_to, + test_connect, + test_auth, + test_auth_fail, + component_missing_address, + component_invalid_from, + component_send, + bad_nonza, + codec_failure]}]. + groups() -> [{ldap, [sequence], ldap_tests()}, {extauth, [sequence], extauth_tests()}, {no_db, [sequence], no_db_tests()}, + {component, [sequence], component_tests()}, {mnesia, [sequence], db_tests(mnesia)}, {redis, [sequence], db_tests(redis)}, {mysql, [sequence], db_tests(mysql)}, @@ -433,7 +461,8 @@ groups() -> {riak, [sequence], db_tests(riak)}]. all() -> - [%%{group, ldap}, + [{group, component}, + %%{group, ldap}, {group, no_db}, {group, mnesia}, %%{group, redis}, @@ -451,19 +480,19 @@ stop_ejabberd(Config) -> Config. test_connect_bad_xml(Config) -> - Config0 = init_stream(set_opt(ns_client, <<"'">>, Config)), + Config0 = init_stream(set_opt(xmlns, <<"'">>, Config)), ?recv1(#stream_error{reason = 'not-well-formed'}), ?recv1({xmlstreamend, <<"stream:stream">>}), close_socket(Config0). test_connect_unknown_ns(Config) -> - Config0 = init_stream(set_opt(ns_client, <<"wrong">>, Config)), - ?recv1(#stream_error{reason = 'not-well-formed'}), + Config0 = init_stream(set_opt(xmlns, <<"wrong">>, Config)), + ?recv1(#stream_error{reason = 'invalid-xml'}), ?recv1({xmlstreamend, <<"stream:stream">>}), close_socket(Config0). -test_connect_bad_ns_client(Config) -> - Config0 = init_stream(set_opt(ns_client, ?NS_SERVER, Config)), +test_connect_bad_xmlns(Config) -> + Config0 = init_stream(set_opt(xmlns, ?NS_SERVER, Config)), ?recv1(#stream_error{reason = 'invalid-namespace'}), ?recv1({xmlstreamend, <<"stream:stream">>}), close_socket(Config0). @@ -495,6 +524,12 @@ test_connect_missing_to(Config) -> test_connect(Config) -> disconnect(connect(Config)). +test_component_connect(Config) -> + disconnect(component_connect(Config)). + +component_connect(Config) -> + init_stream(Config). + test_starttls(Config) -> case ?config(starttls, Config) of true -> @@ -578,6 +613,25 @@ invalid_from(Config) -> ?recv1({xmlstreamend, <<"stream:stream">>}), close_socket(Config). +component_missing_address(Config) -> + Server = server_jid(Config), + #iq{type = error} = send_recv(Config, #iq{type = get, from = Server}), + #iq{type = error} = send_recv(Config, #iq{type = get, to = Server}), + disconnect(Config). + +component_invalid_from(Config) -> + From = jid:make(randoms:get_string()), + To = jid:make(randoms:get_string()), + #iq{type = error} = + send_recv(Config, #iq{type = get, from = From, to = To}), + disconnect(Config). + +component_send(Config) -> + JID = my_jid(Config), + send(Config, #message{from = JID, to = JID}), + #message{from = JID, to = JID} = recv(), + disconnect(Config). + auth_md5(Config) -> Mechs = ?config(mechs, Config), case lists:member(<<"DIGEST-MD5">>, Mechs) of @@ -621,7 +675,8 @@ test_auth(Config) -> disconnect(auth(Config)). test_auth_fail(Config0) -> - Config = set_opt(user, <<"wrong">>, Config0), + Config = set_opt(user, <<"wrong">>, + set_opt(password, <<"wrong">>, Config0)), disconnect(auth(Config, _ShouldFail = true)). test_bind(Config) -> @@ -659,7 +714,9 @@ roster_ver(Config) -> disconnect(Config). codec_failure(Config) -> - #iq{type = error} = send_recv(Config, #iq{type = wrong}), + JID = my_jid(Config), + #iq{type = error} = + send_recv(Config, #iq{type = wrong, from = JID, to = JID}), disconnect(Config). unsupported_query(Config) -> @@ -829,13 +886,14 @@ private(Config) -> <<>>)}, Storage = #bookmark_storage{conference = [Conference]}, StorageXMLOut = xmpp_codec:encode(Storage), + WrongEl = #xmlel{name = <<"wrong">>}, #iq{type = error} = - send_recv(Config, #iq{type = get, sub_els = [#private{}], - to = server_jid(Config)}), + send_recv(Config, #iq{type = get, + sub_els = [#private{xml_els = [WrongEl]}]}), #iq{type = result, sub_els = []} = send_recv( Config, #iq{type = set, - sub_els = [#private{xml_els = [StorageXMLOut]}]}), + sub_els = [#private{xml_els = [WrongEl, StorageXMLOut]}]}), #iq{type = result, sub_els = [#private{xml_els = [StorageXMLIn]}]} = send_recv( diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index b2a0cd5ac..99af18bbf 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -428,6 +428,11 @@ listen: port: @@web_port@@ module: ejabberd_http captcha: true + - + port: @@component_port@@ + module: ejabberd_service + password: >- + @@password@@ loglevel: @@loglevel@@ max_fsm_queue: 1000 modules: diff --git a/test/suite.erl b/test/suite.erl index 93141d846..f78b7bd1a 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -30,11 +30,14 @@ init_config(Config) -> {ok, CWD} = file:get_cwd(), {ok, _} = file:copy(CertFile, filename:join([CWD, "cert.pem"])), {ok, CfgContentTpl} = file:read_file(ConfigPathTpl), + Password = <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>, CfgContent = process_config_tpl(CfgContentTpl, [ {c2s_port, 5222}, {loglevel, 5}, {s2s_port, 5269}, + {component_port, 5270}, {web_port, 5280}, + {password, Password}, {mysql_server, <<"localhost">>}, {mysql_port, 3306}, {mysql_db, <<"ejabberd_test">>}, @@ -58,13 +61,15 @@ init_config(Config) -> 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)}, {server, ?COMMON_VHOST}, {user, <<"test_single!#$%^*()`~+-;_=[]{}|\\">>}, {master_nick, <<"master_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {slave_nick, <<"slave_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {room_subject, <<"hello, world!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {certfile, CertFile}, - {ns_client, ?NS_CLIENT}, + {type, client}, + {xmlns, ?NS_CLIENT}, {ns_stream, ?NS_STREAM}, {stream_version, <<"1.0">>}, {stream_id, <<"">>}, @@ -74,7 +79,7 @@ init_config(Config) -> {resource, <<"resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {master_resource, <<"master_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {slave_resource, <<"slave_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, - {password, <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, + {password, Password}, {backends, get_config_backends()} |Config]. @@ -126,20 +131,29 @@ process_config_tpl(Content, [{Name, DefaultValue} | Rest]) -> stream_header(Config) -> NSStream = ?config(ns_stream, Config), - NSClient = ?config(ns_client, Config), - Lang = ?config(lang, Config), + XMLNS = ?config(xmlns, Config), + Lang = case ?config(lang, Config) of + <<"">> -> <<"">>; + L -> iolist_to_binary(["xml:lang='", L, "'"]) + end, To = case ?config(server, Config) of <<"">> -> <<"">>; Server -> <<"to='", Server/binary, "'">> end, - Version = ?config(stream_version, Config), + Version = case ?config(stream_version, Config) of + <<"">> -> <<"">>; + V -> <<"version='", V/binary, "'">> + end, io_lib:format("", - [NSStream, NSClient, To, Version, Lang]). + "xmlns:stream='~s' xmlns='~s' ~s ~s ~s>", + [NSStream, XMLNS, To, Version, Lang]). connect(Config) -> - process_stream_features(init_stream(Config)). + NewConfig = init_stream(Config), + case ?config(type, NewConfig) of + client -> process_stream_features(NewConfig); + component -> NewConfig + end. init_stream(Config) -> Version = ?config(stream_version, Config), @@ -149,8 +163,11 @@ init_stream(Config) -> [binary, {packet, 0}, {active, false}]), NewConfig = set_opt(socket, Sock, Config), ok = send_text(NewConfig, stream_header(NewConfig)), - #stream_start{id = ID, xmlns = ?NS_CLIENT, - version = Version} = recv(), + XMLNS = case ?config(type, Config) of + client -> ?NS_CLIENT; + component -> ?NS_COMPONENT + end, + #stream_start{id = ID, xmlns = XMLNS, version = Version} = recv(), set_opt(stream_id, ID, NewConfig). process_stream_features(Config) -> @@ -174,7 +191,11 @@ process_stream_features(Config) -> disconnect(Config) -> Socket = ?config(socket, Config), - ok = ejabberd_socket:send(Socket, ?STREAM_TRAILER), + try + ok = send_text(Config, ?STREAM_TRAILER) + catch exit:normal -> + ok + end, {xmlstreamend, <<"stream:stream">>} = recv(), ejabberd_socket:close(Socket), Config. @@ -203,6 +224,7 @@ auth(Config) -> auth(Config, false). auth(Config, ShouldFail) -> + Type = ?config(type, Config), Mechs = ?config(mechs, Config), HaveMD5 = lists:member(<<"DIGEST-MD5">>, Mechs), HavePLAIN = lists:member(<<"PLAIN">>, Mechs), @@ -210,16 +232,23 @@ auth(Config, ShouldFail) -> auth_SASL(<<"PLAIN">>, Config, ShouldFail); HaveMD5 -> auth_SASL(<<"DIGEST-MD5">>, Config, ShouldFail); - true -> - auth_legacy(Config, false, ShouldFail) + Type == client -> + auth_legacy(Config, false, ShouldFail); + Type == component -> + auth_component(Config, ShouldFail) end. bind(Config) -> - #iq{type = result, sub_els = [#bind{}]} = - send_recv( - Config, - #iq{type = set, - sub_els = [#bind{resource = ?config(resource, Config)}]}), + case ?config(type, Config) of + client -> + #iq{type = result, sub_els = [#bind{}]} = + send_recv( + Config, + #iq{type = set, + sub_els = [#bind{resource = ?config(resource, Config)}]}); + component -> + ok + end, Config. open_session(Config) -> @@ -279,6 +308,22 @@ auth_legacy(Config, IsDigest, ShouldFail) -> end end. +auth_component(Config, ShouldFail) -> + StreamID = ?config(stream_id, Config), + Password = ?config(password, Config), + Digest = p1_sha:sha(<>), + send(Config, #handshake{data = Digest}), + case recv() of + #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) -> auth_SASL(Mech, Config, false). -- 2.40.0