1: %%============================================================================== 2: %% Copyright 2014 Erlang Solutions Ltd. 3: %% 4: %% Licensed under the Apache License, Version 2.0 (the "License"); 5: %% you may not use this file except in compliance with the License. 6: %% You may obtain a copy of the License at 7: %% 8: %% http://www.apache.org/licenses/LICENSE-2.0 9: %% 10: %% Unless required by applicable law or agreed to in writing, software 11: %% distributed under the License is distributed on an "AS IS" BASIS, 12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13: %% See the License for the specific language governing permissions and 14: %% limitations under the License. 15: %%============================================================================== 16: -module(connect_SUITE). 17: 18: -compile([export_all, nowarn_export_all]). 19: 20: -include_lib("common_test/include/ct.hrl"). 21: -include_lib("eunit/include/eunit.hrl"). 22: -include_lib("escalus/include/escalus.hrl"). 23: -include_lib("escalus/include/escalus_xmlns.hrl"). 24: -include_lib("exml/include/exml.hrl"). 25: -include_lib("exml/include/exml_stream.hrl"). 26: -include_lib("stdlib/include/ms_transform.hrl"). 27: -define(assert_equal(E, V), ( 28: [ct:fail("ASSERT EQUAL~n\tExpected ~p~n\tValue ~p~n", [(E), (V)]) 29: || (E) =/= (V)])). 30: -define(SECURE_USER, secure_joe). 31: -define(CACERT_FILE, "priv/ssl/cacert.pem"). 32: -define(CERT_FILE, "priv/ssl/fake_server.pem"). 33: -define(DH_FILE, "priv/ssl/fake_dh_server.pem"). 34: 35: -import(distributed_helper, [mim/0, 36: mim2/0, 37: mim3/0, 38: require_rpc_nodes/1, 39: rpc/4]). 40: -import(domain_helper, [domain/0]). 41: -import(config_parser_helper, [default_c2s_tls/1]). 42: 43: %%-------------------------------------------------------------------- 44: %% Suite configuration 45: %%-------------------------------------------------------------------- 46: 47: all() -> 48: [ 49: {group, session_replacement}, 50: {group, security}, 51: {group, incorrect_behaviors}, 52: {group, proxy_protocol}, 53: %% these groups must be last, as they really... complicate configuration 54: {group, fast_tls}, 55: {group, just_tls} 56: ]. 57: 58: groups() -> 59: [ 60: {starttls_disabled, [parallel], [correct_features_are_advertised_for_disabled_starttls, 61: starttls_should_fail_when_disabled]}, 62: {starttls_optional, [parallel], [bad_xml, 63: invalid_host, 64: invalid_stream_namespace, 65: deny_pre_xmpp_1_0_stream, 66: correct_features_are_advertised_for_optional_starttls]}, 67: {starttls_required, [], [{group, starttls_required_parallel}, metrics_test]}, 68: {starttls_required_parallel, [parallel], [correct_features_are_advertised_for_required_starttls, 69: tls_authenticate, 70: bind_server_generated_resource, 71: cannot_connect_with_proxy_header, 72: should_fail_to_authenticate_without_starttls, 73: auth_bind_pipelined_starttls_skipped_error 74: | protocol_test_cases()]}, 75: {tls, [parallel], auth_bind_pipelined_cases() ++ 76: protocol_test_cases() ++ 77: cipher_test_cases()}, 78: {just_tls, tls_groups()}, 79: {fast_tls, tls_groups()}, 80: {session_replacement, [], [same_resource_replaces_session, 81: clean_close_of_replaced_session, 82: replaced_session_cannot_terminate, 83: replaced_session_cannot_terminate_different_nodes]}, 84: {security, [], [return_proper_stream_error_if_service_is_not_hidden, 85: close_connection_if_service_type_is_hidden]}, 86: {incorrect_behaviors, [parallel], [close_connection_if_start_stream_duplicated, 87: close_connection_if_protocol_violation_after_authentication, 88: close_connection_if_protocol_violation_after_binding]}, 89: {proxy_protocol, [parallel], [cannot_connect_without_proxy_header, 90: connect_with_proxy_header]} 91: ]. 92: 93: tls_groups()-> 94: [ 95: {group, starttls_disabled}, 96: {group, starttls_optional}, 97: {group, starttls_required}, 98: {group, tls} 99: ]. 100: 101: auth_bind_pipelined_cases() -> 102: [ 103: auth_bind_pipelined_session, 104: auth_bind_pipelined_auth_failure 105: ]. 106: 107: protocol_test_cases() -> 108: [ 109: should_fail_with_sslv3, 110: should_fail_with_tlsv1, 111: should_fail_with_tlsv1_1, 112: should_pass_with_tlsv1_2 113: ]. 114: 115: cipher_test_cases() -> 116: [ 117: %% Server certificate is signed only with RSA for now, don't try to use ECDSA! 118: clients_can_connect_with_advertised_ciphers, 119: % String cipher 120: 'clients_can_connect_with_ECDHE-RSA-AES256-GCM-SHA384', 121: %% MIM2 accepts ECDHE-RSA-AES256-GCM-SHA384 exclusively with fast_tls on alternative port 122: %% MIM3 accepts #{cipher => aes_256_gcm, key_exchange => ecdhe_rsa, mac => aead, prf => sha384} 123: %% exclusively with just_tls on alternative port 124: 'clients_can_connect_with_ECDHE-RSA-AES256-GCM-SHA384_only' 125: ]. 126: 127: suite() -> 128: require_rpc_nodes([mim, mim2, mim3]) ++ escalus:suite(). 129: 130: %%-------------------------------------------------------------------- 131: %% Init & teardown 132: %%-------------------------------------------------------------------- 133: 134: init_per_suite(Config) -> 135: Config0 = escalus:init_per_suite([{escalus_user_db, {module, escalus_ejabberd, []}} | Config]), 136: C2SPort = ct:get_config({hosts, mim, c2s_port}), 137: [C2SListener] = mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => mongoose_c2s_listener}), 138: Config1 = [{c2s_listener, C2SListener} | Config0], 139: assert_cert_file_exists(), 140: escalus:create_users(Config1, escalus:get_users([?SECURE_USER, alice])). 141: 142: end_per_suite(Config) -> 143: escalus_fresh:clean(), 144: escalus:delete_users(Config, escalus:get_users([?SECURE_USER, alice])), 145: restore_c2s_listener(Config), 146: escalus:end_per_suite(Config). 147: 148: init_per_group(starttls_optional, Config) -> 149: configure_c2s_listener(Config, #{tls => tls_opts(starttls, Config)}), 150: Config; 151: init_per_group(session_replacement, Config) -> 152: configure_c2s_listener(Config, #{tls => tls_opts(starttls, Config)}), 153: logger_ct_backend:start(), 154: Config; 155: init_per_group(starttls_required, Config) -> 156: configure_c2s_listener(Config, #{tls => tls_opts(starttls_required, Config)}), 157: Config; 158: init_per_group(starttls_disabled, Config) -> 159: configure_c2s_listener(Config, #{}, [tls]), 160: Config; 161: init_per_group(tls, Config) -> 162: configure_c2s_listener(Config, #{tls => tls_opts(tls, Config)}), 163: Users = proplists:get_value(escalus_users, Config, []), 164: JoeSpec = lists:keydelete(starttls, 1, proplists:get_value(?SECURE_USER, Users)), 165: JoeSpec2 = {?SECURE_USER, lists:keystore(ssl, 1, JoeSpec, {ssl, true})}, 166: NewUsers = lists:keystore(?SECURE_USER, 1, Users, JoeSpec2), 167: Config2 = lists:keystore(escalus_users, 1, Config, {escalus_users, NewUsers}), 168: [{c2s_port, ct:get_config({hosts, mim, c2s_port})} | Config2]; 169: init_per_group(just_tls, Config)-> 170: [{tls_module, just_tls} | Config]; 171: init_per_group(fast_tls, Config)-> 172: [{tls_module, fast_tls} | Config]; 173: init_per_group(proxy_protocol, Config) -> 174: configure_c2s_listener(Config, #{proxy_protocol => true}), 175: Config; 176: init_per_group(_, Config) -> 177: Config. 178: 179: end_per_group(session_replacement, Config) -> 180: logger_ct_backend:stop(), 181: Config; 182: end_per_group(_, Config) -> 183: Config. 184: 185: init_per_testcase(close_connection_if_service_type_is_hidden = CN, Config) -> 186: Config1 = mongoose_helper:backup_and_set_config_option(Config, hide_service_name, true), 187: escalus:init_per_testcase(CN, Config1); 188: init_per_testcase(replaced_session_cannot_terminate = CN, Config) -> 189: S = escalus_users:get_server(Config, alice), 190: OptKey = {replaced_wait_timeout, S}, 191: Config1 = mongoose_helper:backup_and_set_config_option(Config, OptKey, 1), 192: escalus:init_per_testcase(CN, Config1); 193: init_per_testcase(replaced_session_cannot_terminate_different_nodes = CN, Config) -> 194: S = escalus_users:get_server(Config, alice), 195: OptKey = {replaced_wait_timeout, S}, 196: Config1 = mongoose_helper:backup_and_set_config_option(Config, OptKey, 1), 197: Config2 = distributed_helper:add_node_to_cluster(mim2(), Config1), 198: logger_ct_backend:start(mim2()), 199: escalus:init_per_testcase(CN, Config2); 200: init_per_testcase(CaseName, Config) -> 201: escalus:init_per_testcase(CaseName, Config). 202: 203: end_per_testcase(replaced_session_cannot_terminate_different_nodes = CaseName, Config) -> 204: logger_ct_backend:stop(mim2()), 205: distributed_helper:remove_node_from_cluster(mim2(), Config), 206: mongoose_helper:restore_config(Config), 207: escalus:end_per_testcase(CaseName, Config); 208: end_per_testcase(CaseName, Config) -> 209: mongoose_helper:restore_config(Config), 210: escalus:end_per_testcase(CaseName, Config). 211: 212: %%-------------------------------------------------------------------- 213: %% Tests 214: %%-------------------------------------------------------------------- 215: 216: bad_xml(Config) -> 217: %% given 218: Spec = escalus_users:get_userspec(Config, alice), 219: %% when 220: [Start, Error, End] = connect_with_bad_xml(Spec), 221: %% then 222: %% See RFC 6120 4.9.1.3 (http://xmpp.org/rfcs/rfc6120.html#streams-error-rules-host). 223: %% Stream start from the server is required in this case. 224: escalus:assert(is_stream_start, Start), 225: escalus:assert(is_stream_error, [<<"xml-not-well-formed">>, <<>>], Error), 226: escalus:assert(is_stream_end, End). 227: 228: invalid_host(Config) -> 229: %% given 230: Spec = escalus_users:get_userspec(Config, alice), 231: %% when 232: [Start, Error, End] = connect_to_invalid_host(Spec), 233: %% then 234: %% See RFC 6120 4.9.1.3 (http://xmpp.org/rfcs/rfc6120.html#streams-error-rules-host). 235: %% Stream start from the server is required in this case. 236: escalus:assert(is_stream_start, Start), 237: escalus:assert(is_stream_error, [<<"host-unknown">>, <<>>], Error), 238: escalus:assert(is_stream_end, End). 239: 240: invalid_stream_namespace(Config) -> 241: %% given 242: Spec = escalus_users:get_userspec(Config, alice), 243: %% when 244: [Start, Error, End] = connect_with_invalid_stream_namespace(Spec), 245: %% then 246: escalus:assert(is_stream_start, Start), 247: escalus:assert(is_stream_error, [<<"invalid-namespace">>, <<>>], Error), 248: escalus:assert(is_stream_end, End). 249: 250: deny_pre_xmpp_1_0_stream(Config) -> 251: %% given 252: Spec = escalus_fresh:freshen_spec(Config, alice), 253: Steps = [ 254: %% when 255: {?MODULE, start_stream_pre_xmpp_1_0} 256: ], 257: {ok, Conn, _} = escalus_connection:start(Spec, Steps), 258: StreamError = escalus:wait_for_stanza(Conn), 259: escalus:assert(is_stream_error, [<<"unsupported-version">>, <<>>], StreamError), 260: escalus_connection:stop(Conn). 261: 262: should_fail_with_sslv3(Config) -> 263: should_fail_with(Config, sslv3). 264: 265: should_fail_with_tlsv1(Config) -> 266: should_fail_with(Config, tlsv1). 267: 268: should_fail_with_tlsv1_1(Config) -> 269: should_fail_with(Config, 'tlsv1.1'). 270: 271: should_fail_with(Config, Protocol) -> 272: %% Connection process is spawned with a link so besides the crash itself, 273: %% we will receive an exit signal. We don't want to terminate the test due to this. 274: %% TODO: Investigate if this behaviour is not a ticking bomb which may affect other test cases. 275: process_flag(trap_exit, true), 276: %% GIVEN 277: UserSpec0 = escalus_users:get_userspec(Config, ?SECURE_USER), 278: UserSpec1 = set_secure_connection_protocol(UserSpec0, Protocol), 279: %% WHEN 280: try escalus_connection:start(UserSpec1) of 281: %% THEN 282: _ -> 283: error({client_connected, Protocol}) 284: catch 285: _C:_R -> 286: ok 287: end. 288: 289: should_pass_with_tlsv1_2(Config) -> 290: UserSpec0 = escalus_fresh:create_fresh_user(Config, ?SECURE_USER), 291: UserSpec1 = set_secure_connection_protocol(UserSpec0, 'tlsv1.2'), 292: 293: %% WHEN 294: Result = escalus_connection:start(UserSpec1), 295: 296: %% THEN 297: ?assertMatch({ok, _, _}, Result). 298: 299: should_fail_to_authenticate_without_starttls(Config) -> 300: %% GIVEN 301: UserSpec = escalus_fresh:freshen_spec(Config, ?SECURE_USER), 302: ConnectionSteps = [start_stream, stream_features], 303: {ok, Conn, Features} = escalus_connection:start(UserSpec, ConnectionSteps), 304: UserName = escalus_utils:get_username(Conn), 305: 306: %% WHEN 307: try escalus_session:authenticate(Conn, Features) of 308: %% THEN 309: _ -> 310: error(authentication_without_tls_suceeded) 311: catch 312: throw:{auth_failed, User, AuthReply} -> 313: ?assertEqual(UserName, User), 314: escalus:assert(is_stream_error, [<<"policy-violation">>, 315: <<"Use of STARTTLS required">>], 316: AuthReply) 317: end. 318: 319: clients_can_connect_with_advertised_ciphers(Config) -> 320: ?assert(length(ciphers_working_with_ssl_clients(Config)) > 0). 321: 322: 'clients_can_connect_with_ECDHE-RSA-AES256-GCM-SHA384'(Config) -> 323: ?assert(lists:member("ECDHE-RSA-AES256-GCM-SHA384", 324: ciphers_working_with_ssl_clients(Config))). 325: 326: 'clients_can_connect_with_ECDHE-RSA-AES256-GCM-SHA384_only'(Config) -> 327: Port = case ?config(tls_module, Config) of 328: just_tls -> ct:get_config({hosts, mim3, c2s_tls_port}); 329: fast_tls -> ct:get_config({hosts, mim2, c2s_tls_port}) 330: end, 331: Config1 = [{c2s_port, Port} | Config], 332: CiphersStr = os:cmd("openssl ciphers 'ECDHE-RSA-AES256-GCM-SHA384'"), 333: ct:pal("Available cipher suites for : ~s", [CiphersStr]), 334: ct:pal("Openssl version: ~s", [os:cmd("openssl version")]), 335: ?assertEqual(["ECDHE-RSA-AES256-GCM-SHA384"], 336: ciphers_working_with_ssl_clients(Config1)). 337: 338: correct_features_are_advertised_for_disabled_starttls(Config) -> 339: UserSpec = escalus_fresh:freshen_spec(Config, alice), 340: Steps = [start_stream, 341: stream_features, 342: {?MODULE, verify_features_without_starttls}, 343: authenticate], 344: escalus_connection:start(UserSpec, Steps). 345: 346: correct_features_are_advertised_for_optional_starttls(Config) -> 347: UserSpec = escalus_fresh:freshen_spec(Config, ?SECURE_USER), 348: Steps = [start_stream, 349: stream_features, 350: {?MODULE, verify_features_with_optional_starttls}, 351: maybe_use_ssl, 352: {?MODULE, verify_features_without_starttls}, 353: authenticate], 354: escalus_connection:start(UserSpec ++ [{ssl_opts, [{verify, verify_none}]}], Steps). 355: 356: correct_features_are_advertised_for_required_starttls(Config) -> 357: UserSpec = escalus_fresh:freshen_spec(Config, ?SECURE_USER), 358: Steps = [start_stream, 359: stream_features, 360: {?MODULE, verify_features_with_required_starttls}, 361: maybe_use_ssl, 362: {?MODULE, verify_features_without_starttls}, 363: authenticate], 364: escalus_connection:start(UserSpec ++ [{ssl_opts, [{verify, verify_none}]}], Steps). 365: 366: verify_features_without_starttls(Conn, Features) -> 367: ?assertEqual({starttls, false}, get_feature(starttls, Features)), 368: ?assertMatch({sasl_mechanisms, [_|_]}, get_feature(sasl_mechanisms, Features)), 369: {Conn, Features}. 370: 371: verify_features_with_optional_starttls(Conn, Features) -> 372: ?assertEqual({starttls, true}, get_feature(starttls, Features)), 373: ?assertMatch({sasl_mechanisms, [_|_]}, get_feature(sasl_mechanisms, Features)), 374: {Conn, Features}. 375: 376: verify_features_with_required_starttls(Conn, Features) -> 377: AdvertisedFeatures = lists:filter(fun is_present/1, Features), 378: ?assertEqual([{starttls, true}], AdvertisedFeatures), 379: {Conn, Features}. 380: 381: is_present({_, Value}) -> 382: Value =/= false andalso Value =/= [] andalso Value =/= undefined. 383: 384: get_feature(Feature, FeatureList) -> 385: lists:keyfind(Feature, 1, FeatureList). 386: 387: starttls_should_fail_when_disabled(Config) -> 388: UserSpec = escalus_fresh:freshen_spec(Config, alice), 389: List = [start_stream, stream_features], 390: {ok, Conn, _Features} = 391: escalus_connection:start(UserSpec ++ [{ssl_opts, [{verify, verify_none}]}], List), 392: 393: %% Client tries to start tls anyway, and fails 394: escalus_connection:send(Conn, escalus_stanza:starttls()), 395: Result = escalus_connection:get_stanza(Conn, failure), 396: %% As defined in https://datatracker.ietf.org/doc/html/rfc6120#section-5.4.2.2, cause 2 397: ?assertEqual(<<"failure">>, Result#xmlel.name), 398: escalus:assert(has_ns, [?NS_TLS], Result), 399: escalus_connection:wait_for_close(Conn, timer:seconds(5)). 400: 401: metrics_test(Config) -> 402: MongooseMetrics = [{[global, data, xmpp, received, xml_stanza_size], changed}, 403: {[global, data, xmpp, sent, xml_stanza_size], changed}, 404: {[global, data, xmpp, received, c2s, tls], changed}, 405: {[global, data, xmpp, sent, c2s, tls], changed}, 406: %% TCP traffic before starttls 407: {[global, data, xmpp, received, c2s, tcp], changed}, 408: {[global, data, xmpp, sent, c2s, tcp], changed}], 409: PreStoryData = escalus_mongooseim:pre_story([{mongoose_metrics, MongooseMetrics}]), 410: tls_authenticate(Config), 411: escalus_mongooseim:post_story(PreStoryData). 412: 413: tls_authenticate(Config) -> 414: %% Given 415: UserSpec = escalus_fresh:create_fresh_user(Config, ?SECURE_USER), 416: ConnectionSteps = [start_stream, stream_features, maybe_use_ssl, authenticate], 417: %% when 418: {ok, Conn, _} = escalus_connection:start(UserSpec ++ [{ssl_opts, [{verify, verify_none}]}], ConnectionSteps), 419: % then 420: true = escalus_tcp:is_using_ssl(Conn#client.rcv_pid). 421: 422: auth_bind_pipelined_session(Config) -> 423: UserSpec = [{ssl, true}, {parser_opts, [{start_tag, <<"stream:stream">>}]}, 424: {ssl_opts, [{verify, verify_none}]} 425: | escalus_fresh:create_fresh_user(Config, alice)], 426: 427: Username = proplists:get_value(username, UserSpec), 428: Conn = pipeline_connect(UserSpec), 429: 430: %% Stream start 431: StreamResponse = escalus_connection:get_stanza(Conn, stream_response), 432: ?assertMatch(#xmlstreamstart{}, StreamResponse), 433: escalus_session:stream_features(Conn, []), 434: 435: %% Auth response 436: escalus_auth:wait_for_success(Username, Conn), 437: AuthStreamResponse = escalus_connection:get_stanza(Conn, stream_response), 438: ?assertMatch(#xmlstreamstart{}, AuthStreamResponse), 439: escalus_session:stream_features(Conn, []), 440: 441: %% Bind response 442: BindResponse = escalus_connection:get_stanza(Conn, bind_response), 443: escalus:assert(is_bind_result, BindResponse), 444: 445: %% Session response 446: SessionResponse = escalus_connection:get_stanza(Conn, session_response), 447: escalus:assert(is_iq_result, SessionResponse). 448: 449: auth_bind_pipelined_auth_failure(Config) -> 450: UserSpec = [{password, <<"badpassword">>}, {ssl, true}, 451: {ssl_opts, [{verify, verify_none}]}, 452: {parser_opts, [{start_tag, <<"stream:stream">>}]} 453: | escalus_fresh:freshen_spec(Config, alice)], 454: 455: Conn = pipeline_connect(UserSpec), 456: 457: %% Stream start 458: StreamResponse = escalus_connection:get_stanza(Conn, stream_response), 459: ?assertMatch(#xmlstreamstart{}, StreamResponse), 460: escalus_session:stream_features(Conn, []), 461: 462: %% Auth response 463: AuthResponse = escalus_connection:get_stanza(Conn, auth_response), 464: ?assertMatch(#xmlel{name = <<"failure">>, attrs = [{<<"xmlns">>, ?NS_SASL}]}, AuthResponse). 465: 466: auth_bind_pipelined_starttls_skipped_error(Config) -> 467: UserSpec = [{parser_opts, [{start_tag, <<"stream:stream">>}]} 468: | escalus_fresh:freshen_spec(Config, ?SECURE_USER)], 469: 470: Conn = pipeline_connect(UserSpec), 471: 472: %% Stream start 473: StreamResponse = escalus_connection:get_stanza(Conn, stream_response), 474: ?assertMatch(#xmlstreamstart{}, StreamResponse), 475: escalus_session:stream_features(Conn, []), 476: 477: %% Auth response 478: AuthResponse = escalus_connection:get_stanza(Conn, auth_response), 479: escalus:assert(is_stream_error, [<<"policy-violation">>, <<"Use of STARTTLS required">>], 480: AuthResponse). 481: 482: bind_server_generated_resource(Config) -> 483: UserSpec = [{resource, <<>>}, {ssl_opts, [{verify, verify_none}]} 484: | escalus_fresh:create_fresh_user(Config, ?SECURE_USER)], 485: ConnectionSteps = [start_stream, stream_features, maybe_use_ssl, authenticate, bind], 486: {ok, #client{props = NewSpec}, _} = escalus_connection:start(UserSpec, ConnectionSteps), 487: {resource, Resource} = lists:keyfind(resource, 1, NewSpec), 488: ?assert(is_binary(Resource)), 489: ?assert(byte_size(Resource) > 0). 490: 491: same_resource_replaces_session(Config) -> 492: UserSpec = [{resource, <<"conflict">>} | escalus_users:get_userspec(Config, alice)], 493: {ok, Alice1, _} = escalus_connection:start(UserSpec), 494: 495: {ok, Alice2, _} = escalus_connection:start(UserSpec), 496: 497: ConflictError = escalus:wait_for_stanza(Alice1), 498: escalus:assert(is_stream_error, [<<"conflict">>, <<>>], ConflictError), 499: 500: mongoose_helper:wait_until(fun() -> escalus_connection:is_connected(Alice1) end, false), 501: 502: escalus_connection:stop(Alice2). 503: 504: clean_close_of_replaced_session(Config) -> 505: logger_ct_backend:capture(warning), 506: 507: same_resource_replaces_session(Config), 508: 509: logger_ct_backend:stop_capture(), 510: FilterFun = fun(_, Msg) -> 511: re:run(Msg, "replaced_wait_timeout") /= nomatch 512: end, 513: [] = logger_ct_backend:recv(FilterFun). 514: 515: replaced_session_cannot_terminate(Config) -> 516: % GIVEN a session that is frozen and cannot terminate 517: logger_ct_backend:capture(warning), 518: UserSpec = [{resource, <<"conflict">>} | escalus_users:get_userspec(Config, alice)], 519: {ok, _Alice1, _} = escalus_connection:start(UserSpec), 520: [C2SPid] = ranch_procs(), 521: ok = rpc(mim(), sys, suspend, [C2SPid]), 522: 523: % WHEN a session gets replaced ... 524: {ok, Alice2, _} = escalus_connection:start(UserSpec), 525: 526: % THEN a timeout warning is logged 527: FilterFun = fun(_, Msg) -> 528: re:run(Msg, "replaced_wait_timeout") /= nomatch 529: end, 530: mongoose_helper:wait_until( 531: fun() -> length(logger_ct_backend:recv(FilterFun)) end, 1), 532: 533: rpc(mim(), sys, resume, [C2SPid]), 534: logger_ct_backend:stop_capture(), 535: 536: escalus_connection:stop(Alice2). 537: 538: replaced_session_cannot_terminate_different_nodes(Config) -> 539: % GIVEN a session that is frozen and cannot terminate 540: logger_ct_backend:capture(warning, mim2()), 541: UserSpec = [{resource, <<"conflict">>} | escalus_users:get_userspec(Config, alice)], 542: {ok, _Alice1, _} = escalus_connection:start(UserSpec), 543: [C2SPid] = ranch_procs(), 544: ok = rpc(mim(), sys, suspend, [C2SPid]), 545: 546: % WHEN a session gets replaced on a different node 547: UserSpec2 = [{port, 5232} | UserSpec], 548: {ok, Alice2, _} = escalus_connection:start(UserSpec2), 549: 550: % THEN a timeout warning is logged 551: FilterFun = fun(_, Msg) -> 552: re:run(Msg, "replaced_wait_timeout") /= nomatch 553: end, 554: mongoose_helper:wait_until( 555: fun() -> length(logger_ct_backend:recv(FilterFun)) end, 1), 556: 557: rpc(mim(), sys, resume, [C2SPid]), 558: logger_ct_backend:stop_capture(), 559: 560: escalus_connection:stop(Alice2). 561: 562: return_proper_stream_error_if_service_is_not_hidden(_Config) -> 563: % GIVEN MongooseIM is running default configuration 564: % WHEN we send non-XMPP payload 565: % THEN the server replies with stream error xml-not-well-formed and closes the connection 566: SendMalformedDataStep = fun(Client, Features) -> 567: escalus_connection:send_raw(Client, <<"malformed">>), 568: {Client, Features} 569: end, 570: {ok, Connection, _} = escalus_connection:start([], [SendMalformedDataStep]), 571: escalus_connection:receive_stanza(Connection, #{ assert => is_stream_start }), 572: StreamErrorAssertion = {is_stream_error, [<<"xml-not-well-formed">>, <<>>]}, 573: escalus_connection:receive_stanza(Connection, #{ assert => StreamErrorAssertion }), 574: %% Sometimes escalus needs a moment to report the connection as closed 575: escalus_connection:wait_for_close(Connection, 5000). 576: 577: close_connection_if_service_type_is_hidden(_Config) -> 578: % GIVEN the option to hide service name is enabled 579: % WHEN we send non-XMPP payload 580: % THEN connection is closed without any response from the server 581: FailIfAnyDataReturned = fun(Reply) -> 582: ct:fail({unexpected_data, Reply}) 583: end, 584: Connection = escalus_tcp:connect(#{ on_reply => FailIfAnyDataReturned }), 585: Ref = monitor(process, Connection), 586: escalus_tcp:send(Connection, <<"malformed">>), 587: receive 588: {'DOWN', Ref, _, _, _} -> ok 589: after 590: 5000 -> 591: ct:fail(connection_not_closed) 592: end. 593: 594: close_connection_if_start_stream_duplicated(Config) -> 595: close_connection_if_protocol_violation(Config, [start_stream, stream_features]). 596: 597: close_connection_if_protocol_violation_after_authentication(Config) -> 598: close_connection_if_protocol_violation(Config, [start_stream, stream_features, authenticate]). 599: 600: close_connection_if_protocol_violation_after_binding(Config) -> 601: close_connection_if_protocol_violation(Config, [start_stream, stream_features, authenticate, bind]). 602: 603: close_connection_if_protocol_violation(Config, Steps) -> 604: AliceSpec = escalus_fresh:create_fresh_user(Config, alice), 605: {ok, Alice, _Features} = escalus_connection:start(AliceSpec, Steps), 606: escalus:send(Alice, escalus_stanza:stream_start(domain(), ?NS_JABBER_CLIENT)), 607: escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], 608: escalus_connection:get_stanza(Alice, no_stream_error_stanza_received)), 609: escalus:assert(is_stream_end, 610: escalus_connection:get_stanza(Alice, no_stream_end_stanza_received)), 611: true = escalus_connection:wait_for_close(Alice,timer:seconds(5)). 612: 613: cannot_connect_with_proxy_header(Config) -> 614: %% GIVEN proxy protocol is disabled 615: UserSpec = escalus_users:get_userspec(Config, alice), 616: 617: %% WHEN 618: ConnectionSteps = [{?MODULE, send_proxy_header}, start_stream], 619: {ok, ConnResult, _} = escalus_connection:start(UserSpec, ConnectionSteps), 620: 621: StreamError = escalus:wait_for_stanza(ConnResult), 622: escalus:assert(is_stream_error, [<<"xml-not-well-formed">>, <<>>], StreamError), 623: escalus_connection:stop(ConnResult). 624: 625: cannot_connect_without_proxy_header(Config) -> 626: %% GIVEN proxy protocol is enabled 627: UserSpec = escalus_users:get_userspec(Config, alice), 628: 629: %% WHEN 630: ConnResult = escalus_connection:start(UserSpec, [start_stream]), 631: 632: %% THEN 633: ?assertMatch({error, {connection_step_failed, _, _}}, ConnResult). 634: 635: connect_with_proxy_header(Config) -> 636: %% GIVEN proxy protocol is enabled 637: UserSpec = escalus_users:get_userspec(Config, alice), 638: 639: %% WHEN 640: ConnectionSteps = [{?MODULE, send_proxy_header}, start_stream, stream_features, 641: authenticate, bind, session], 642: {ok, Conn, _Features} = escalus_connection:start(UserSpec, ConnectionSteps), 643: % make sure the session is present 644: escalus:send(Conn, escalus_stanza:presence(<<"available">>)), 645: escalus:assert(is_presence, escalus:wait_for_stanza(Conn)), 646: 647: %% THEN 648: SessionInfo = mongoose_helper:get_session_info(mim(), Conn), 649: #{src_address := IPAddr, src_port := Port} = proxy_info(), 650: ?assertMatch({IPAddr, Port}, maps:get(ip, SessionInfo)), 651: escalus_connection:stop(Conn). 652: 653: %%-------------------------------------------------------------------- 654: %% Internal functions 655: %%-------------------------------------------------------------------- 656: 657: c2s_port(Config) -> 658: case ?config(c2s_port, Config) of 659: undefined -> ct:get_config({hosts, mim, c2s_tls_port}); 660: Value -> Value 661: end. 662: 663: get_node(Port) -> 664: Mim2Port = ct:get_config({hosts, mim2, c2s_tls_port}), 665: Mim3Port = ct:get_config({hosts, mim3, c2s_tls_port}), 666: case Port of 667: Mim2Port -> 668: mim2(); 669: Mim3Port -> 670: mim3(); 671: _ -> 672: mim() 673: end. 674: 675: ciphers_available_in_os() -> 676: CiphersStr = os:cmd("openssl ciphers 'ALL:eNULL'"), 677: [string:strip(C, both, $\n) || C <- string:tokens(CiphersStr, ":")]. 678: 679: ciphers_working_with_ssl_clients(Config) -> 680: Port = c2s_port(Config), 681: Path = rpc(get_node(Port), os, getenv, ["PWD"]), 682: CertPath = Path ++ "/" ++ ?CERT_FILE, 683: lists:filter(fun(Cipher) -> 684: openssl_client_can_use_cipher(Cipher, Port, CertPath) 685: end, ciphers_available_in_os()). 686: 687: openssl_client_can_use_cipher(Cipher, Port, Path) -> 688: PortStr = integer_to_list(Port), 689: Cmd = "echo '' | openssl s_client -connect localhost:" ++ PortStr ++ 690: " -cert \"" ++ Path ++ "\"" 691: " -cipher \"" ++ Cipher ++ "\" -tls1_2 2>&1", 692: Output = os:cmd(Cmd), 693: 0 == string:str(Output, ":error:") andalso 0 == string:str(Output, "errno=0"). 694: 695: restore_c2s_listener(Config) -> 696: C2SListener = ?config(c2s_listener, Config), 697: mongoose_helper:restart_listener(mim(), C2SListener). 698: 699: assert_cert_file_exists() -> 700: ejabberd_node_utils:file_exists(?CERT_FILE) orelse 701: ct:fail("cert file ~s not exists", [?CERT_FILE]). 702: 703: configure_c2s_listener(Config, ExtraC2sOpts) -> 704: configure_c2s_listener(Config, ExtraC2sOpts, []). 705: 706: configure_c2s_listener(Config, ExtraC2SOpts, RemovedC2SKeys) -> 707: C2SListener = ?config(c2s_listener, Config), 708: NewC2SListener = maps:without(RemovedC2SKeys, maps:merge(C2SListener, ExtraC2SOpts)), 709: ct:pal("C2S listener: ~p", [NewC2SListener]), 710: mongoose_helper:restart_listener(mim(), NewC2SListener). 711: 712: tls_opts(Mode, Config) -> 713: ExtraOpts = #{mode => Mode, cacertfile => ?CACERT_FILE, certfile => ?CERT_FILE, dhfile => ?DH_FILE}, 714: Module = proplists:get_value(tls_module, Config, fast_tls), 715: maps:merge(default_c2s_tls(Module), ExtraOpts). 716: 717: set_secure_connection_protocol(UserSpec, Version) -> 718: [{ssl_opts, [{versions, [Version]}, {verify, verify_none}]} | UserSpec]. 719: 720: connect_to_invalid_host(Spec) -> 721: {ok, Conn, _} = escalus_connection:start(Spec, [{?MODULE, connect_to_invalid_host}]), 722: escalus:wait_for_stanzas(Conn, 3). 723: 724: connect_to_invalid_host(Conn, UnusedFeatures) -> 725: escalus:send(Conn, escalus_stanza:stream_start(<<"hopefullynonexistentdomain">>, 726: ?NS_JABBER_CLIENT)), 727: {Conn, UnusedFeatures}. 728: 729: connect_with_bad_xml(Spec) -> 730: {ok, Conn, _} = escalus_connection:start(Spec, [{?MODULE, connect_with_bad_xml}]), 731: escalus:wait_for_stanzas(Conn, 3). 732: 733: connect_with_bad_xml(Conn, UnusedFeatures) -> 734: escalus_connection:send(Conn, #xmlcdata{content = "asdf\n"}), 735: {Conn, UnusedFeatures}. 736: 737: connect_with_invalid_stream_namespace(Spec) -> 738: F = fun (Conn, UnusedFeatures) -> 739: Start = stream_start_invalid_stream_ns(escalus_users:get_server([], Spec)), 740: escalus:send(Conn, Start), 741: {Conn, UnusedFeatures} 742: end, 743: {ok, Conn, _} = escalus_connection:start(Spec, [F]), 744: escalus:wait_for_stanzas(Conn, 3). 745: 746: start_stream_pre_xmpp_1_0(Conn = #client{props = Props}, UnusedFeatures) -> 747: escalus:send(Conn, stream_start_pre_xmpp_1_0(escalus_users:get_server([], Props))), 748: #xmlstreamstart{attrs = StreamAttrs} = StreamStart = escalus:wait_for_stanza(Conn), 749: escalus:assert(is_stream_start, StreamStart), 750: {<<"id">>, StreamID} = lists:keyfind(<<"id">>, 1, StreamAttrs), 751: {Conn#client{props = [{stream_id, StreamID} | Props]}, UnusedFeatures}. 752: 753: stream_start_pre_xmpp_1_0(To) -> 754: stream_start(lists:keystore(version, 1, default_context(To), {version, <<>>})). 755: 756: stream_start(Context) -> 757: %% Be careful! The closing slash here is a hack to enable implementation of from_template/2 758: %% to parse the snippet properly. In standard XMPP <stream:stream> is just opening of an XML 759: %% element, NOT A SELF CLOSING element. 760: T = <<"<stream:stream {{version}} xml:lang='en' xmlns='jabber:client' " 761: " to='{{to}}' " 762: " xmlns:stream='{{stream_ns}}' />">>, 763: %% So we rewrap the parsed contents from #xmlel{} to #xmlstreamstart{} here. 764: #xmlel{name = Name, attrs = Attrs, children = []} = escalus_stanza:from_template(T, Context), 765: #xmlstreamstart{name = Name, attrs = Attrs}. 766: 767: stream_start_invalid_stream_ns(To) -> 768: stream_start(lists:keystore(stream_ns, 1, default_context(To), 769: {stream_ns, <<"obviously-invalid-namespace">>})). 770: 771: default_context(To) -> 772: [{version, <<"version='1.0'">>}, 773: {to, To}, 774: {stream_ns, ?NS_XMPP}]. 775: 776: children_specs_to_pids(Children) -> 777: [Pid || {_, Pid, _, _} <- Children]. 778: 779: ranch_procs() -> 780: Listeners = maps:keys(rpc(mim(), ranch, info, [])), 781: lists:foldl( 782: fun(Listener, Acc) -> rpc(mim(), ranch, procs, [Listener, connections]) ++ Acc end, 783: [], 784: Listeners). 785: 786: pipeline_connect(UserSpec) -> 787: Server = proplists:get_value(server, UserSpec), 788: Username = proplists:get_value(username, UserSpec), 789: Password = proplists:get_value(password, UserSpec), 790: AuthPayload = <<0:8, Username/binary, 0:8, Password/binary>>, 791: 792: Conn = escalus_connection:connect(UserSpec), 793: 794: Stream = escalus_stanza:stream_start(Server, <<"jabber:client">>), 795: Auth = escalus_stanza:auth(<<"PLAIN">>, [#xmlcdata{content = base64:encode(AuthPayload)}]), 796: AuthStream = escalus_stanza:stream_start(Server, <<"jabber:client">>), 797: Bind = escalus_stanza:bind(<<?MODULE_STRING "_resource">>), 798: Session = escalus_stanza:session(), 799: 800: escalus_connection:send(Conn, [Stream, Auth, AuthStream, Bind, Session]), 801: Conn. 802: 803: send_proxy_header(Conn, UnusedFeatures) -> 804: Header = ranch_proxy_header:header(proxy_info()), 805: escalus_connection:send_raw(Conn, iolist_to_binary(Header)), 806: {Conn, UnusedFeatures}. 807: 808: proxy_info() -> 809: #{version => 2, 810: command => proxy, 811: transport_family => ipv4, 812: transport_protocol => stream, 813: src_address => {1, 2, 3, 4}, 814: src_port => 444, 815: dest_address => {192, 168, 0, 1}, 816: dest_port => 443 817: }.