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