1: %%%=================================================================== 2: %%% @copyright (C) 2012, Erlang Solutions Ltd. 3: %%% @doc Suite for testing s2s connection 4: %%% @end 5: %%%=================================================================== 6: 7: -module(s2s_SUITE). 8: -compile([export_all, nowarn_export_all]). 9: 10: -include_lib("escalus/include/escalus.hrl"). 11: -include_lib("exml/include/exml.hrl"). 12: -include_lib("exml/include/exml_stream.hrl"). 13: 14: %% Module aliases 15: -define(dh, distributed_helper). 16: 17: %%%=================================================================== 18: %%% Suite configuration 19: %%%=================================================================== 20: 21: all() -> 22: [ 23: {group, both_plain}, 24: {group, both_tls_optional}, %% default MongooseIM config 25: {group, both_tls_required}, 26: 27: {group, node1_tls_optional_node2_tls_required}, 28: {group, node1_tls_required_node2_tls_optional}, 29: 30: {group, node1_tls_required_trusted_node2_tls_optional}, 31: {group, node1_tls_optional_node2_tls_required_trusted_with_cachain}, 32: 33: {group, node1_tls_false_node2_tls_optional}, 34: {group, node1_tls_optional_node2_tls_false}, 35: 36: {group, node1_tls_false_node2_tls_required}, 37: {group, node1_tls_required_node2_tls_false} 38: 39: ]. 40: 41: groups() -> 42: [{both_plain, [sequence], all_tests()}, 43: {both_tls_optional, [], essentials()}, 44: {both_tls_required, [], essentials()}, 45: 46: {node1_tls_optional_node2_tls_required, [], essentials()}, 47: {node1_tls_required_node2_tls_optional, [], essentials()}, 48: 49: %% Node1 closes connection from nodes with invalid certs 50: {node1_tls_required_trusted_node2_tls_optional, [], negative()}, 51: 52: %% Node1 accepts connection provided the cert can be verified 53: {node1_tls_optional_node2_tls_required_trusted_with_cachain, [parallel], 54: essentials() ++ connection_cases()}, 55: 56: {node1_tls_false_node2_tls_optional, [], essentials()}, 57: {node1_tls_optional_node2_tls_false, [], essentials()}, 58: 59: {node1_tls_false_node2_tls_required, [], negative()}, 60: {node1_tls_required_node2_tls_false, [], negative()}]. 61: 62: essentials() -> 63: [simple_message]. 64: 65: all_tests() -> 66: essentials() ++ [connections_info, nonexistent_user, unknown_domain, malformed_jid]. 67: 68: negative() -> 69: [timeout_waiting_for_message]. 70: 71: connection_cases() -> 72: [successful_external_auth_with_valid_cert, 73: start_stream_fails_for_wrong_namespace, 74: start_stream_fails_for_wrong_version, 75: start_stream_fails_without_version, 76: start_stream_fails_without_host, 77: start_stream_fails_for_unknown_host, 78: starttls_fails_for_unknown_host, 79: only_messages_from_authenticated_domain_users_are_accepted, 80: auth_with_valid_cert_fails_when_requested_name_is_not_in_the_cert, 81: auth_with_valid_cert_fails_for_other_mechanism_than_external]. 82: 83: suite() -> 84: s2s_helper:suite(escalus:suite()). 85: 86: users() -> 87: [alice2, alice, bob]. 88: 89: 90: 91: %%%=================================================================== 92: %%% Init & teardown 93: %%%=================================================================== 94: 95: init_per_suite(Config) -> 96: Config1 = s2s_helper:init_s2s(escalus:init_per_suite(Config)), 97: escalus:create_users(Config1, escalus:get_users(users())). 98: 99: end_per_suite(Config) -> 100: escalus_fresh:clean(), 101: s2s_helper:end_s2s(Config), 102: escalus:delete_users(Config, escalus:get_users(users())), 103: escalus:end_per_suite(Config). 104: 105: init_per_group(GroupName, Config) -> 106: s2s_helper:configure_s2s(GroupName, Config). 107: 108: end_per_group(_GroupName, _Config) -> 109: ok. 110: 111: init_per_testcase(CaseName, Config) -> 112: escalus:init_per_testcase(CaseName, Config). 113: 114: end_per_testcase(CaseName, Config) -> 115: escalus:end_per_testcase(CaseName, Config). 116: 117: %%%=================================================================== 118: %%% Server-to-server communication test 119: %%%=================================================================== 120: 121: simple_message(Config) -> 122: escalus:fresh_story(Config, [{alice2, 1}, {alice, 1}], fun(Alice2, Alice1) -> 123: 124: %% User on the main server sends a message to a user on a federated server 125: escalus:send(Alice1, escalus_stanza:chat_to(Alice2, <<"Hi, foreign Alice!">>)), 126: 127: %% User on the federated server receives the message 128: Stanza = escalus:wait_for_stanza(Alice2, 10000), 129: escalus:assert(is_chat_message, [<<"Hi, foreign Alice!">>], Stanza), 130: 131: %% User on the federated server sends a message to the main server 132: escalus:send(Alice2, escalus_stanza:chat_to(Alice1, <<"Nice to meet you!">>)), 133: 134: %% User on the main server receives the message 135: Stanza2 = escalus:wait_for_stanza(Alice1, 10000), 136: escalus:assert(is_chat_message, [<<"Nice to meet you!">>], Stanza2) 137: 138: end). 139: 140: timeout_waiting_for_message(Config) -> 141: try 142: simple_message(Config), 143: ct:fail("got message but shouldn't") 144: catch 145: error:timeout_when_waiting_for_stanza -> 146: ok 147: end. 148: 149: connections_info(Config) -> 150: simple_message(Config), 151: FedDomain = ct:get_config({hosts, fed, domain}), 152: S2SIn = ?dh:rpc(?dh:mim(), ejabberd_s2s, get_info_s2s_connections, [in]), 153: ct:pal("S2sIn: ~p", [S2SIn]), 154: true = lists:any(fun(PropList) -> [FedDomain] =:= proplists:get_value(domains, PropList) end, 155: S2SIn), 156: S2SOut = ?dh:rpc(?dh:mim(), ejabberd_s2s, get_info_s2s_connections, [out]), 157: ct:pal("S2sOut: ~p", [S2SOut]), 158: true = lists:any(fun(PropList) -> FedDomain =:= proplists:get_value(server, PropList) end, 159: S2SOut), 160: 161: ok. 162: 163: 164: nonexistent_user(Config) -> 165: escalus:fresh_story(Config, [{alice, 1}, {alice2, 1}], fun(Alice1, Alice2) -> 166: 167: %% Alice@localhost1 sends message to Xyz@localhost2 168: RemoteServer = escalus_client:server(Alice2), 169: Fake = <<"xyz@", RemoteServer/binary>>, 170: escalus:send(Alice1, escalus_stanza:chat_to(Fake, 171: <<"Hello, nonexistent!">>)), 172: 173: %% Alice@localhost1 receives stanza error: service-unavailable 174: Stanza = escalus:wait_for_stanza(Alice1), 175: escalus:assert(is_error, [<<"cancel">>, <<"service-unavailable">>], Stanza) 176: 177: end). 178: 179: unknown_domain(Config) -> 180: escalus:fresh_story(Config, [{alice, 1}], fun(Alice1) -> 181: 182: %% Alice@localhost1 sends message to Xyz@localhost3 183: escalus:send(Alice1, escalus_stanza:chat_to( 184: <<"xyz@somebogushost">>, 185: <<"Hello, unreachable!">>)), 186: 187: %% Alice@localhost1 receives stanza error: remote-server-not-found 188: Stanza = escalus:wait_for_stanza(Alice1, 10000), 189: escalus:assert(is_error, [<<"cancel">>, <<"remote-server-not-found">>], Stanza) 190: 191: end). 192: 193: malformed_jid(Config) -> 194: escalus:fresh_story(Config, [{alice, 1}], fun(Alice1) -> 195: 196: %% Alice@localhost1 sends message to Xyz@localhost3 197: escalus:send(Alice1, escalus_stanza:chat_to( 198: <<"not a jid">>, 199: <<"Hello, unreachable!">>)), 200: 201: %% Alice@localhost1 receives stanza error: remote-server-not-found 202: Stanza = escalus:wait_for_stanza(Alice1, 10000), 203: escalus:assert(is_error, [<<"cancel">>, <<"remote-server-not-found">>], Stanza) 204: 205: end). 206: 207: nonascii_addr(Config) -> 208: escalus:fresh_story(Config, [{alice, 1}, {bob2, 1}], fun(Alice, Bob) -> 209: 210: %% Bob@localhost2 sends message to Alice@localhost1 211: escalus:send(Bob, escalus_stanza:chat_to(Alice, <<"Cześć Alice!">>)), 212: 213: %% Alice@localhost1 receives message from Bob@localhost2 214: Stanza = escalus:wait_for_stanza(Alice, 10000), 215: escalus:assert(is_chat_message, [<<"Cześć Alice!">>], Stanza), 216: 217: %% Alice@localhost1 sends message to Bob@localhost2 218: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"Miło Cię poznać">>)), 219: 220: %% Bob@localhost2 receives message from Alice@localhost1 221: Stanza2 = escalus:wait_for_stanza(Bob, 10000), 222: escalus:assert(is_chat_message, [<<"Miło Cię poznać">>], Stanza2) 223: 224: end). 225: 226: successful_external_auth_with_valid_cert(Config) -> 227: ConnectionArgs = connection_args("localhost.bis", <<"localhost">>, Config), 228: {ok, Client, _Features} = escalus_connection:start(ConnectionArgs, 229: [fun s2s_start_stream/2, 230: fun s2s_starttls/2, 231: fun s2s_external_auth/2]), 232: escalus_connection:stop(Client). 233: 234: start_stream_fails_for_wrong_namespace(Config) -> 235: start_stream_fails(Config, <<"invalid-namespace">>, 236: [fun s2s_start_stream_with_wrong_namespace/2]). 237: 238: start_stream_fails_for_wrong_version(Config) -> 239: %% TLS authentication requires version 1.0 240: start_stream_fails(Config, <<"invalid-xml">>, 241: [fun s2s_start_stream_with_wrong_version/2]). 242: 243: start_stream_fails_without_version(Config) -> 244: %% TLS authentication requires version 1.0 245: start_stream_fails(Config, <<"invalid-xml">>, 246: [fun s2s_start_stream_without_version/2]). 247: 248: start_stream_fails_without_host(Config) -> 249: start_stream_fails(Config, <<"improper-addressing">>, 250: [fun s2s_start_stream_without_host/2]). 251: 252: start_stream_fails_for_unknown_host(Config) -> 253: start_stream_fails(Config, <<"host-unknown">>, 254: [fun s2s_start_stream_to_wrong_host/2]). 255: 256: starttls_fails_for_unknown_host(Config) -> 257: start_stream_fails(Config, <<"host-unknown">>, 258: [fun s2s_start_stream/2, 259: fun s2s_starttls_to_wrong_host/2]). 260: 261: start_stream_fails(Config, ErrorType, ConnectionSteps) -> 262: ConnectionArgs = connection_args("localhost.bis", <<"localhost">>, Config), 263: {ok, Client, _} = escalus_connection:start(ConnectionArgs, ConnectionSteps), 264: [Start, Error, End] = escalus:wait_for_stanzas(Client, 3), 265: escalus:assert(is_stream_start, Start), 266: escalus:assert(is_stream_error, [ErrorType, <<>>], Error), 267: escalus:assert(is_stream_end, End). 268: 269: only_messages_from_authenticated_domain_users_are_accepted(Config) -> 270: ConnectionArgs = connection_args("localhost.bis", <<"localhost">>, Config), 271: {ok, Client, _Features} = escalus_connection:start(ConnectionArgs, 272: [fun s2s_start_stream/2, 273: fun s2s_starttls/2, 274: fun s2s_external_auth/2]), 275: escalus:fresh_story(Config, [{alice2, 1}], fun(Alice) -> 276: 277: UserInWrongDomain = <<"a_user@this_is_not_my.domain.com">>, 278: ChatToAliceFromUserInWrongDomain = escalus_stanza:chat(UserInWrongDomain, 279: Alice, <<"Miło Cię poznać">>), 280: %% Client is a s2s connection established and authenticated for domain "localhost" 281: %% Now we try to send a message from other domain than "localhost" 282: %% over the established s2s connection 283: escalus:send(Client, ChatToAliceFromUserInWrongDomain), 284: 285: %% Alice@fed1 does not receives message from a_user@this_is_not_my.domain.com 286: timer:sleep(timer:seconds(5)), 287: escalus_assert:has_no_stanzas(Alice) 288: 289: end), 290: 291: escalus_connection:stop(Client). 292: 293: auth_with_valid_cert_fails_when_requested_name_is_not_in_the_cert(Config) -> 294: ConnectionArgs = connection_args("not_in_cert_domain", <<"not_in_cert_domain">>, Config), 295: {ok, Client, _Features} = escalus_connection:start(ConnectionArgs, 296: [fun s2s_start_stream/2, 297: fun s2s_starttls/2]), 298: 299: try 300: escalus_auth:auth_sasl_external(Client, Client#client.props), 301: ct:fail("Authenitcated but MUST NOT") 302: catch throw:{auth_failed, _, _} -> 303: escalus_connection:wait_for_close(Client, timer:seconds(5)) 304: end. 305: 306: auth_with_valid_cert_fails_for_other_mechanism_than_external(Config) -> 307: ConnectionArgs = connection_args("localhost", <<"localhost">>, Config), 308: {ok, Client, _Features} = escalus_connection:start(ConnectionArgs, 309: [fun s2s_start_stream/2, 310: fun s2s_starttls/2 311: ]), 312: 313: Stanza = escalus_stanza:auth(<<"ANONYMOUS">>), 314: ok = escalus_connection:send(Client, Stanza), 315: #xmlel{name = <<"failure">>} = escalus_connection:get_stanza(Client, wait_for_auth_reply), 316: 317: escalus_connection:wait_for_close(Client, timer:seconds(5)). 318: 319: connection_args(FromServer, RequestedName, Config) -> 320: {KeyFile, CertFile} = get_main_key_and_cert_files(Config), 321: [{host, "localhost"}, 322: {to_server, "fed1"}, 323: {from_server, FromServer}, 324: {requested_name, RequestedName}, 325: {starttls, required}, 326: {port, ct:get_config({hosts, fed, incoming_s2s_port})}, 327: {ssl_opts, [{certfile, CertFile}, {keyfile, KeyFile}]}]. 328: 329: s2s_start_stream_with_wrong_namespace(Conn = #client{props = Props}, Features) -> 330: Start = s2s_stream_start_stanza(Props, fun(Attrs) -> Attrs#{<<"xmlns">> => <<"42">>} end), 331: ok = escalus_connection:send(Conn, Start), 332: {Conn, Features}. 333: 334: s2s_start_stream_with_wrong_version(Conn = #client{props = Props}, Features) -> 335: Start = s2s_stream_start_stanza(Props, fun(Attrs) -> Attrs#{<<"version">> => <<"42">>} end), 336: ok = escalus_connection:send(Conn, Start), 337: {Conn, Features}. 338: 339: s2s_start_stream_without_version(Conn = #client{props = Props}, Features) -> 340: Start = s2s_stream_start_stanza(Props, fun(Attrs) -> maps:remove(<<"version">>, Attrs) end), 341: ok = escalus_connection:send(Conn, Start), 342: {Conn, Features}. 343: 344: s2s_start_stream_without_host(Conn = #client{props = Props}, Features) -> 345: Start = s2s_stream_start_stanza(Props, fun(Attrs) -> maps:remove(<<"to">>, Attrs) end), 346: ok = escalus_connection:send(Conn, Start), 347: {Conn, Features}. 348: 349: s2s_start_stream_to_wrong_host(Conn = #client{props = Props}, Features) -> 350: Start = s2s_stream_start_stanza(Props, fun(Attrs) -> Attrs#{<<"to">> => <<"42">>} end), 351: ok = escalus_connection:send(Conn, Start), 352: {Conn, Features}. 353: 354: s2s_start_stream(Conn = #client{props = Props}, []) -> 355: StreamStartRep = s2s_start_stream_and_wait_for_response(Conn), 356: 357: #xmlstreamstart{attrs = Attrs} = StreamStartRep, 358: Id = proplists:get_value(<<"id">>, Attrs), 359: 360: escalus_session:stream_features(Conn#client{props = [{sid, Id} | Props]}, []). 361: 362: s2s_start_stream_and_wait_for_response(Conn = #client{props = Props}) -> 363: StreamStart = s2s_stream_start_stanza(Props, fun(Attrs) -> Attrs end), 364: ok = escalus_connection:send(Conn, StreamStart), 365: escalus_connection:get_stanza(Conn, wait_for_stream). 366: 367: s2s_stream_start_stanza(Props, F) -> 368: Attrs = (stream_start_attrs())#{<<"to">> => proplists:get_value(to_server, Props), 369: <<"from">> => proplists:get_value(from_server, Props)}, 370: #xmlstreamstart{name = <<"stream:stream">>, attrs = maps:to_list(F(Attrs))}. 371: 372: stream_start_attrs() -> 373: #{<<"xmlns">> => <<"jabber:server">>, 374: <<"xmlns:stream">> => <<"http://etherx.jabber.org/streams">>, 375: <<"version">> => <<"1.0">>}. 376: 377: s2s_starttls(Client, Features, StartStreamF) -> 378: case proplists:get_value(starttls, Features) of 379: false -> 380: ct:fail("The server does not offer STARTTLS"); 381: _ -> 382: ok 383: end, 384: 385: escalus_connection:send(Client, escalus_stanza:starttls()), 386: escalus_connection:get_stanza(Client, proceed), 387: escalus_connection:upgrade_to_tls(Client), 388: StartStreamF(Client, []). 389: 390: s2s_starttls(Client, Features) -> 391: s2s_starttls(Client, Features, fun s2s_start_stream/2). 392: 393: s2s_starttls_to_wrong_host(Client, Features) -> 394: s2s_starttls(Client, Features, fun s2s_start_stream_to_wrong_host/2). 395: 396: s2s_external_auth(Client = #client{props = Props}, Features) -> 397: case proplists:get_value(sasl_mechanisms, Features) of 398: [<<"EXTERNAL">>] -> 399: ok; 400: SASL -> 401: ct:fail("Server does not provide EXTERNAL auth: ~p", [SASL]) 402: end, 403: escalus_auth:auth_sasl_external(Client, Props), 404: s2s_start_stream(Client, []). 405: 406: get_main_key_and_cert_files(Config) -> 407: CertFile = get_main_file_path(Config, "cert.pem"), 408: KeyFile = get_main_file_path(Config, "key.pem"), 409: {KeyFile, CertFile}. 410: 411: get_main_file_path(Config, File) -> 412: filename:join([path_helper:repo_dir(Config), 413: "tools", "ssl", "mongooseim", File]).