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(CERT_FILE, "priv/ssl/fake_server.pem"). 32: -define(DH_FILE, "priv/ssl/fake_dh_server.pem"). 33: 34: -import(distributed_helper, [mim/0, 35: require_rpc_nodes/1, 36: rpc/4]). 37: 38: -import(domain_helper, [domain/0]). 39: 40: %%-------------------------------------------------------------------- 41: %% Suite configuration 42: %%-------------------------------------------------------------------- 43: 44: all() -> 45: [ 46: {group, session_replacement}, 47: {group, security}, 48: {group, incorrect_behaviors}, 49: {group, proxy_protocol}, 50: %% these groups must be last, as they really... complicate configuration 51: {group, fast_tls}, 52: {group, just_tls} 53: ]. 54: 55: groups() -> 56: G = [ {c2s_noproc, [], [reset_stream_noproc, 57: starttls_noproc, 58: compress_noproc, 59: bad_xml, 60: invalid_host, 61: invalid_stream_namespace, 62: deny_pre_xmpp_1_0_stream]}, 63: {starttls, [], [should_fail_to_authenticate_without_starttls, 64: should_not_send_other_features_with_starttls_required, 65: auth_bind_pipelined_starttls_skipped_error | protocol_test_cases()]}, 66: {tls, [parallel], auth_bind_pipelined_cases() ++ 67: protocol_test_cases() ++ 68: cipher_test_cases()}, 69: {feature_order, [parallel], [stream_features_test, 70: tls_authenticate, 71: tls_compression_fail, 72: tls_compression_authenticate_fail, 73: tls_authenticate_compression, 74: auth_compression_bind_session, 75: auth_bind_compression_session, 76: bind_server_generated_resource, 77: cannot_connect_with_proxy_header]}, 78: {just_tls, tls_groups()}, 79: {fast_tls, tls_groups()}, 80: {session_replacement, [], [ 81: same_resource_replaces_session, 82: clean_close_of_replaced_session, 83: replaced_session_cannot_terminate 84: ]}, 85: {security, [], [ 86: return_proper_stream_error_if_service_is_not_hidden, 87: close_connection_if_service_type_is_hidden 88: ]}, 89: {incorrect_behaviors, [parallel], [close_connection_if_start_stream_duplicated, 90: close_connection_if_protocol_violation_after_authentication, 91: close_connection_if_protocol_violation_after_binding]}, 92: {proxy_protocol, [parallel], [cannot_connect_without_proxy_header, 93: connect_with_proxy_header]} 94: ], 95: ct_helper:repeat_all_until_all_ok(G). 96: 97: tls_groups()-> 98: [{group, starttls}, 99: {group, c2s_noproc}, 100: {group, feature_order}, 101: {group, tls}]. 102: 103: auth_bind_pipelined_cases() -> 104: [ 105: auth_bind_pipelined_session, 106: auth_bind_pipelined_auth_failure 107: ]. 108: 109: protocol_test_cases() -> 110: [ 111: should_fail_with_sslv3, 112: should_fail_with_tlsv1, 113: should_fail_with_tlsv1_1, 114: should_pass_with_tlsv1_2 115: ]. 116: 117: cipher_test_cases() -> 118: [ 119: %% Server certificate is signed only with RSA for now, don't try to use ECDSA! 120: clients_can_connect_with_advertised_ciphers, 121: % String cipher 122: 'clients_can_connect_with_ECDHE-RSA-AES256-GCM-SHA384', 123: %% MIM2 accepts ECDHE-RSA-AES256-GCM-SHA384 exclusively with fast_tls on alternative port 124: %% MIM3 accepts #{cipher => aes_256_gcm, key_exchange => ecdhe_rsa, mac => aead, prf => sha384} 125: %% exclusively with just_tls on alternative port 126: 'clients_can_connect_with_ECDHE-RSA-AES256-GCM-SHA384_only' 127: ]. 128: 129: suite() -> 130: require_rpc_nodes([mim, mim2, mim3]) ++ escalus:suite(). 131: 132: %%-------------------------------------------------------------------- 133: %% Init & teardown 134: %%-------------------------------------------------------------------- 135: 136: init_per_suite(Config) -> 137: Config0 = escalus:init_per_suite([{escalus_user_db, {module, escalus_ejabberd, []}} | Config]), 138: C2SPort = ct:get_config({hosts, mim, c2s_port}), 139: [C2SListener] = mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => ejabberd_c2s}), 140: Config1 = [{c2s_listener, C2SListener} | Config0], 141: assert_cert_file_exists(), 142: escalus:create_users(Config1, escalus:get_users([?SECURE_USER, alice])). 143: 144: end_per_suite(Config) -> 145: escalus_fresh:clean(), 146: escalus:delete_users(Config, escalus:get_users([?SECURE_USER, alice])), 147: restore_c2s_listener(Config), 148: escalus:end_per_suite(Config). 149: 150: init_per_group(c2s_noproc, Config) -> 151: configure_c2s_listener(Config, #{tls => [starttls | common_tls_opts(Config)]}), 152: Config; 153: init_per_group(session_replacement, Config) -> 154: configure_c2s_listener(Config, #{tls => [starttls | common_tls_opts(Config)]}), 155: logger_ct_backend:start(), 156: Config; 157: init_per_group(starttls, Config) -> 158: configure_c2s_listener(Config, #{tls => [starttls_required | common_tls_opts(Config)]}), 159: Config; 160: init_per_group(tls, Config) -> 161: configure_c2s_listener(Config, #{tls => [tls | common_tls_opts(Config)]}), 162: Users = proplists:get_value(escalus_users, Config, []), 163: JoeSpec = lists:keydelete(starttls, 1, proplists:get_value(?SECURE_USER, Users)), 164: JoeSpec2 = {?SECURE_USER, lists:keystore(ssl, 1, JoeSpec, {ssl, true})}, 165: NewUsers = lists:keystore(?SECURE_USER, 1, Users, JoeSpec2), 166: Config2 = lists:keystore(escalus_users, 1, Config, {escalus_users, NewUsers}), 167: [{c2s_port, ct:get_config({hosts, mim, c2s_port})} | Config2]; 168: init_per_group(feature_order, Config) -> 169: configure_c2s_listener(Config, #{zlib => 10000, 170: tls => [starttls_required | common_tls_opts(Config)]}), 171: Config; 172: init_per_group(just_tls,Config)-> 173: [{tls_module, just_tls} | Config]; 174: init_per_group(fast_tls,Config)-> 175: [{tls_module, fast_tls} | Config]; 176: init_per_group(proxy_protocol, Config) -> 177: configure_c2s_listener(Config, #{proxy_protocol => true}), 178: Config; 179: init_per_group(_, Config) -> 180: Config. 181: 182: end_per_group(session_replacement, Config) -> 183: logger_ct_backend:stop(), 184: Config; 185: end_per_group(_, Config) -> 186: Config. 187: 188: init_per_testcase(close_connection_if_service_type_is_hidden = CN, Config) -> 189: Config1 = mongoose_helper:backup_and_set_config_option(Config, hide_service_name, true), 190: escalus:init_per_testcase(CN, Config1); 191: init_per_testcase(replaced_session_cannot_terminate = CN, Config) -> 192: S = escalus_users:get_server(Config, alice), 193: OptKey = {replaced_wait_timeout, S}, 194: Config1 = mongoose_helper:backup_and_set_config_option(Config, OptKey, 1), 195: escalus:init_per_testcase(CN, Config1); 196: init_per_testcase(CaseName, Config) -> 197: escalus:init_per_testcase(CaseName, Config). 198: 199: end_per_testcase(CaseName, Config) -> 200: mongoose_helper:restore_config(Config), 201: escalus:end_per_testcase(CaseName, Config). 202: 203: %%-------------------------------------------------------------------- 204: %% Tests 205: %%-------------------------------------------------------------------- 206: 207: bad_xml(Config) -> 208: %% given 209: Spec = escalus_users:get_userspec(Config, alice), 210: %% when 211: [Start, Error, End] = connect_with_bad_xml(Spec), 212: %% then 213: %% See RFC 6120 4.9.1.3 (http://xmpp.org/rfcs/rfc6120.html#streams-error-rules-host). 214: %% Stream start from the server is required in this case. 215: escalus:assert(is_stream_start, Start), 216: escalus:assert(is_stream_error, [<<"xml-not-well-formed">>, <<>>], Error), 217: escalus:assert(is_stream_end, End). 218: 219: invalid_host(Config) -> 220: %% given 221: Spec = escalus_users:get_userspec(Config, alice), 222: %% when 223: [Start, Error, End] = connect_to_invalid_host(Spec), 224: %% then 225: %% See RFC 6120 4.9.1.3 (http://xmpp.org/rfcs/rfc6120.html#streams-error-rules-host). 226: %% Stream start from the server is required in this case. 227: escalus:assert(is_stream_start, Start), 228: escalus:assert(is_stream_error, [<<"host-unknown">>, <<>>], Error), 229: escalus:assert(is_stream_end, End). 230: 231: invalid_stream_namespace(Config) -> 232: %% given 233: Spec = escalus_users:get_userspec(Config, alice), 234: %% when 235: [Start, Error, End] = connect_with_invalid_stream_namespace(Spec), 236: %% then 237: escalus:assert(is_stream_start, Start), 238: escalus:assert(is_stream_error, [<<"invalid-namespace">>, <<>>], Error), 239: escalus:assert(is_stream_end, End). 240: 241: deny_pre_xmpp_1_0_stream(Config) -> 242: %% given 243: Spec = escalus_fresh:freshen_spec(Config, alice), 244: Steps = [ 245: %% when 246: {?MODULE, start_stream_pre_xmpp_1_0} 247: ], 248: {ok, Conn, _} = escalus_connection:start(Spec, Steps), 249: StreamError = escalus:wait_for_stanza(Conn), 250: escalus:assert(is_stream_error, [<<"unsupported-version">>, <<>>], StreamError), 251: escalus_connection:stop(Conn). 252: 253: should_fail_with_sslv3(Config) -> 254: should_fail_with(Config, sslv3). 255: 256: should_fail_with_tlsv1(Config) -> 257: should_fail_with(Config, tlsv1). 258: 259: should_fail_with_tlsv1_1(Config) -> 260: should_fail_with(Config, 'tlsv1.1'). 261: 262: should_fail_with(Config, Protocol) -> 263: %% Connection process is spawned with a link so besides the crash itself, 264: %% we will receive an exit signal. We don't want to terminate the test due to this. 265: %% TODO: Investigate if this behaviour is not a ticking bomb which may affect other test cases. 266: process_flag(trap_exit, true), 267: %% GIVEN 268: UserSpec0 = escalus_users:get_userspec(Config, ?SECURE_USER), 269: UserSpec1 = set_secure_connection_protocol(UserSpec0, Protocol), 270: %% WHEN 271: try escalus_connection:start(UserSpec1) of 272: %% THEN 273: _ -> 274: error({client_connected, Protocol}) 275: catch 276: _C:_R -> 277: ok 278: end. 279: 280: should_pass_with_tlsv1_2(Config) -> 281: UserSpec0 = escalus_fresh:create_fresh_user(Config, ?SECURE_USER), 282: UserSpec1 = set_secure_connection_protocol(UserSpec0, 'tlsv1.2'), 283: 284: %% WHEN 285: Result = escalus_connection:start(UserSpec1), 286: 287: %% THEN 288: ?assertMatch({ok, _, _}, Result). 289: 290: should_fail_to_authenticate_without_starttls(Config) -> 291: %% GIVEN 292: UserSpec = escalus_users:get_userspec(Config, ?SECURE_USER), 293: {Conn, Features} = start_stream_with_compression(UserSpec), 294: 295: %% WHEN 296: try escalus_session:authenticate(Conn, Features) of 297: %% THEN 298: _ -> 299: error(authentication_without_tls_suceeded) 300: catch 301: throw:{auth_failed, User, AuthReply} -> 302: ?assertEqual(atom_to_binary(?SECURE_USER, utf8), User), 303: escalus:assert(is_stream_error, [<<"policy-violation">>, 304: <<"Use of STARTTLS required">>], 305: AuthReply) 306: end. 307: 308: should_not_send_other_features_with_starttls_required(Config) -> 309: UserSpec = escalus_users:get_userspec(Config, ?SECURE_USER), 310: {ok, Conn, _} = escalus_connection:start(UserSpec, [start_stream]), 311: Features = case escalus_connection:get_stanza(Conn, wait_for_features) of 312: #xmlel{name = <<"stream:features">>, children = Children} -> Children; 313: #xmlel{name = <<"features">>, children = Children} -> Children 314: end, 315: ?assertMatch([#xmlel{name = <<"starttls">>, 316: children = [#xmlel{name = <<"required">>}]}], 317: Features). 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: reset_stream_noproc(Config) -> 339: UserSpec = escalus_users:get_userspec(Config, alice), 340: Steps = [start_stream, stream_features], 341: {ok, Conn, _Features} = escalus_connection:start(UserSpec, Steps), 342: 343: [C2sPid] = children_specs_to_pids(rpc(mim(), supervisor, which_children, [ejabberd_c2s_sup])), 344: [RcvPid] = children_specs_to_pids(rpc(mim(), supervisor, which_children, [ejabberd_receiver_sup])), 345: MonRef = erlang:monitor(process, C2sPid), 346: ok = rpc(mim(), sys, suspend, [C2sPid]), 347: %% Add auth element into message queue of the c2s process 348: %% There is no reply because the process is suspended 349: ?assertThrow({timeout, auth_reply}, escalus_session:authenticate(Conn)), 350: %% Sim client disconnection 351: ok = rpc(mim(), ejabberd_receiver, close, [RcvPid]), 352: %% ...c2s process receives close and DOWN messages... 353: %% Resume 354: ok = rpc(mim(), sys, resume, [C2sPid]), 355: receive 356: {'DOWN', MonRef, process, C2sPid, normal} -> 357: ok; 358: {'DOWN', MonRef, process, C2sPid, Reason} -> 359: ct:fail("ejabberd_c2s exited with reason ~p", [Reason]) 360: after 5000 -> 361: ct:fail("c2s_monitor_timeout", []) 362: end, 363: ok. 364: 365: starttls_noproc(Config) -> 366: UserSpec = escalus_users:get_userspec(Config, alice), 367: Steps = [start_stream, stream_features], 368: {ok, Conn, _Features} = escalus_connection:start(UserSpec, Steps), 369: 370: [C2sPid] = children_specs_to_pids(rpc(mim(), supervisor, which_children, [ejabberd_c2s_sup])), 371: [RcvPid] = children_specs_to_pids(rpc(mim(), supervisor, which_children, [ejabberd_receiver_sup])), 372: MonRef = erlang:monitor(process, C2sPid), 373: ok = rpc(mim(), sys, suspend, [C2sPid]), 374: %% Add starttls element into message queue of the c2s process 375: %% There is no reply because the process is suspended 376: ?assertThrow({timeout, proceed}, escalus_session:starttls(Conn)), 377: %% Sim client disconnection 378: ok = rpc(mim(), ejabberd_receiver, close, [RcvPid]), 379: %% ...c2s process receives close and DOWN messages... 380: %% Resume 381: ok = rpc(mim(), sys, resume, [C2sPid]), 382: receive 383: {'DOWN', MonRef, process, C2sPid, normal} -> 384: ok; 385: {'DOWN', MonRef, process, C2sPid, Reason} -> 386: ct:fail("ejabberd_c2s exited with reason ~p", [Reason]) 387: after 5000 -> 388: ct:fail("c2s_monitor_timeout", []) 389: end, 390: ok. 391: 392: compress_noproc(Config) -> 393: UserSpec = escalus_users:get_userspec(Config, alice), 394: Steps = [start_stream, stream_features], 395: {ok, Conn = #client{props = Props}, _Features} = escalus_connection:start(UserSpec, Steps), 396: 397: [C2sPid] = children_specs_to_pids(rpc(mim(), supervisor, which_children, [ejabberd_c2s_sup])), 398: [RcvPid] = children_specs_to_pids(rpc(mim(), supervisor, which_children, [ejabberd_receiver_sup])), 399: MonRef = erlang:monitor(process, C2sPid), 400: ok = rpc(mim(), sys, suspend, [C2sPid]), 401: %% Add compress element into message queue of the c2s process 402: %% There is no reply because the process is suspended 403: ?assertThrow({timeout, compressed}, 404: escalus_session:compress(Conn#client{props = [{compression, <<"zlib">>}|Props]})), 405: %% Sim client disconnection 406: ok = rpc(mim(), ejabberd_receiver, close, [RcvPid]), 407: %% ...c2s process receives close and DOWN messages... 408: %% Resume 409: ok = rpc(mim(), sys, resume, [C2sPid]), 410: receive 411: {'DOWN', MonRef, process, C2sPid, normal} -> 412: ok; 413: {'DOWN', MonRef, process, C2sPid, Reason} -> 414: ct:fail("ejabberd_c2s exited with reason ~p", [Reason]) 415: after 5000 -> 416: ct:fail("c2s_monitor_timeout", []) 417: end, 418: ok. 419: 420: %% Tests features advertisement 421: stream_features_test(Config) -> 422: UserSpec = escalus_fresh:freshen_spec(Config, ?SECURE_USER), 423: List = [start_stream, stream_features, {?MODULE, verify_features}], 424: escalus_connection:start(UserSpec, List), 425: ok. 426: 427: verify_features(Conn, Features) -> 428: %% should not advertise compression before tls 429: ?assert_equal(false, has_feature(compression, Features)), 430: %% start tls. Starttls should be then removed from list and compression should be added 431: Conn1 = escalus_session:starttls(Conn), 432: {Conn2, Features2} = escalus_session:stream_features(Conn1, []), 433: ?assert_equal(false, has_feature(starttls, Features2)), 434: ?assert(false =/= has_feature(compression, Features2)), 435: %% start compression. Compression should be then removed from list 436: {Conn3, _Features3} = escalus_session:authenticate(Conn2, Features2), 437: Conn4 = escalus_session:compress(Conn3), 438: {Conn5, Features5} = escalus_session:stream_features(Conn4, []), 439: ?assert_equal(false, has_feature(compression, Features5)), 440: ?assert_equal(false, has_feature(starttls, Features5)), 441: {Conn5, Features5}. 442: 443: has_feature(Feature, FeatureList) -> 444: {_, Value} = lists:keyfind(Feature, 1, FeatureList), 445: Value. 446: 447: %% should fail 448: tls_compression_authenticate_fail(Config) -> 449: %% Given 450: UserSpec = escalus_fresh:freshen_spec(Config, ?SECURE_USER), 451: ConnetctionSteps = [start_stream, stream_features, maybe_use_ssl, maybe_use_compression, authenticate], 452: %% when and then 453: try escalus_connection:start(UserSpec, ConnetctionSteps) of 454: _ -> 455: error(compression_without_auth_suceeded) 456: catch 457: error:{assertion_failed, assert, is_compressed, Stanza, _} -> 458: case Stanza of 459: #xmlel{name = <<"failure">>} -> 460: ok; 461: _ -> 462: error(unknown_compression_response) 463: end 464: end. 465: 466: tls_authenticate_compression(Config) -> 467: %% Given 468: UserSpec = escalus_fresh:create_fresh_user(Config, ?SECURE_USER), 469: ConnectionSteps = [start_stream, stream_features, maybe_use_ssl, authenticate, maybe_use_compression], 470: %% when 471: {ok, Conn, _} = escalus_connection:start(UserSpec, ConnectionSteps), 472: % then 473: true = escalus_tcp:is_using_compression(Conn#client.rcv_pid), 474: true = escalus_tcp:is_using_ssl(Conn#client.rcv_pid). 475: 476: tls_authenticate(Config) -> 477: %% Given 478: UserSpec = escalus_fresh:create_fresh_user(Config, ?SECURE_USER), 479: ConnetctionSteps = [start_stream, stream_features, maybe_use_ssl, authenticate], 480: %% when 481: {ok, Conn, _} = escalus_connection:start(UserSpec, ConnetctionSteps), 482: % then 483: true = escalus_tcp:is_using_ssl(Conn#client.rcv_pid). 484: 485: %% should fail 486: tls_compression_fail(Config) -> 487: %% Given 488: UserSpec = escalus_fresh:freshen_spec(Config, ?SECURE_USER), 489: ConnetctionSteps = [start_stream, stream_features, maybe_use_ssl, maybe_use_compression], 490: %% then and when 491: try escalus_connection:start(UserSpec, ConnetctionSteps) of 492: _ -> 493: error(compression_without_auth_suceeded) 494: catch 495: error:{assertion_failed, assert, is_compressed, Stanza, _} -> 496: case Stanza of 497: #xmlel{name = <<"failure">>} -> 498: ok; 499: _ -> 500: error(unknown_compression_response) 501: end 502: end. 503: 504: auth_compression_bind_session(Config) -> 505: %% Given 506: UserSpec = escalus_fresh:create_fresh_user(Config, ?SECURE_USER), 507: ConnetctionSteps = [start_stream, stream_features, maybe_use_ssl, 508: authenticate, maybe_use_compression, bind, session], 509: %% when 510: {ok, Conn, _} = escalus_connection:start(UserSpec, ConnetctionSteps), 511: % then 512: true = escalus_tcp:is_using_compression(Conn#client.rcv_pid). 513: 514: auth_bind_compression_session(Config) -> 515: %% Given 516: UserSpec = escalus_fresh:create_fresh_user(Config, ?SECURE_USER), 517: ConnetctionSteps = [start_stream, stream_features, maybe_use_ssl, 518: authenticate, bind, maybe_use_compression, session], 519: %% when 520: {ok, Conn, _} = escalus_connection:start(UserSpec, ConnetctionSteps), 521: % then 522: true = escalus_tcp:is_using_compression(Conn#client.rcv_pid). 523: 524: auth_bind_pipelined_session(Config) -> 525: UserSpec = [{ssl, true}, {parser_opts, [{start_tag, <<"stream:stream">>}]} 526: | escalus_fresh:create_fresh_user(Config, alice)], 527: 528: Username = proplists:get_value(username, UserSpec), 529: Conn = pipeline_connect(UserSpec), 530: 531: %% Stream start 532: StreamResponse = escalus_connection:get_stanza(Conn, stream_response), 533: ?assertMatch(#xmlstreamstart{}, StreamResponse), 534: escalus_session:stream_features(Conn, []), 535: 536: %% Auth response 537: escalus_auth:wait_for_success(Username, Conn), 538: AuthStreamResponse = escalus_connection:get_stanza(Conn, stream_response), 539: ?assertMatch(#xmlstreamstart{}, AuthStreamResponse), 540: escalus_session:stream_features(Conn, []), 541: 542: %% Bind response 543: BindResponse = escalus_connection:get_stanza(Conn, bind_response), 544: escalus:assert(is_bind_result, BindResponse), 545: 546: %% Session response 547: SessionResponse = escalus_connection:get_stanza(Conn, session_response), 548: escalus:assert(is_iq_result, SessionResponse). 549: 550: auth_bind_pipelined_auth_failure(Config) -> 551: UserSpec = [{password, <<"badpassword">>}, {ssl, true}, 552: {parser_opts, [{start_tag, <<"stream:stream">>}]} 553: | escalus_fresh:freshen_spec(Config, alice)], 554: 555: Conn = pipeline_connect(UserSpec), 556: 557: %% Stream start 558: StreamResponse = escalus_connection:get_stanza(Conn, stream_response), 559: ?assertMatch(#xmlstreamstart{}, StreamResponse), 560: escalus_session:stream_features(Conn, []), 561: 562: %% Auth response 563: AuthResponse = escalus_connection:get_stanza(Conn, auth_response), 564: ?assertMatch(#xmlel{name = <<"failure">>, attrs = [{<<"xmlns">>, ?NS_SASL}]}, AuthResponse). 565: 566: auth_bind_pipelined_starttls_skipped_error(Config) -> 567: UserSpec = [{parser_opts, [{start_tag, <<"stream:stream">>}]} 568: | escalus_fresh:freshen_spec(Config, ?SECURE_USER)], 569: 570: Conn = pipeline_connect(UserSpec), 571: 572: %% Stream start 573: StreamResponse = escalus_connection:get_stanza(Conn, stream_response), 574: ?assertMatch(#xmlstreamstart{}, StreamResponse), 575: escalus_session:stream_features(Conn, []), 576: 577: %% Auth response 578: AuthResponse = escalus_connection:get_stanza(Conn, auth_response), 579: escalus:assert(is_stream_error, [<<"policy-violation">>, <<"Use of STARTTLS required">>], 580: AuthResponse). 581: 582: bind_server_generated_resource(Config) -> 583: UserSpec = [{resource, <<>>} | escalus_fresh:create_fresh_user(Config, ?SECURE_USER)], 584: ConnectionSteps = [start_stream, stream_features, maybe_use_ssl, authenticate, bind], 585: {ok, #client{props = NewSpec}, _} = escalus_connection:start(UserSpec, ConnectionSteps), 586: {resource, Resource} = lists:keyfind(resource, 1, NewSpec), 587: ?assert(is_binary(Resource)), 588: ?assert(byte_size(Resource) > 0). 589: 590: same_resource_replaces_session(Config) -> 591: UserSpec = [{resource, <<"conflict">>} | escalus_users:get_userspec(Config, alice)], 592: {ok, Alice1, _} = escalus_connection:start(UserSpec), 593: 594: {ok, Alice2, _} = escalus_connection:start(UserSpec), 595: 596: ConflictError = escalus:wait_for_stanza(Alice1), 597: escalus:assert(is_stream_error, [<<"conflict">>, <<>>], ConflictError), 598: 599: mongoose_helper:wait_until(fun() -> escalus_connection:is_connected(Alice1) end, false), 600: 601: escalus_connection:stop(Alice2). 602: 603: clean_close_of_replaced_session(Config) -> 604: logger_ct_backend:capture(warning), 605: 606: same_resource_replaces_session(Config), 607: 608: logger_ct_backend:stop_capture(), 609: FilterFun = fun(_, Msg) -> 610: re:run(Msg, "replaced_wait_timeout") /= nomatch 611: end, 612: [] = logger_ct_backend:recv(FilterFun). 613: 614: replaced_session_cannot_terminate(Config) -> 615: % GIVEN a session that is frozen and cannot terminate 616: logger_ct_backend:capture(warning), 617: UserSpec = [{resource, <<"conflict">>} | escalus_users:get_userspec(Config, alice)], 618: {ok, _Alice1, _} = escalus_connection:start(UserSpec), 619: [C2SPid] = children_specs_to_pids(rpc(mim(), supervisor, which_children, [ejabberd_c2s_sup])), 620: ok = rpc(mim(), sys, suspend, [C2SPid]), 621: 622: % WHEN a session gets replaced ... 623: {ok, Alice2, _} = escalus_connection:start(UserSpec), 624: 625: % THEN a timeout warning is logged 626: FilterFun = fun(_, Msg) -> 627: re:run(Msg, "replaced_wait_timeout") /= nomatch 628: end, 629: mongoose_helper:wait_until( 630: fun() -> length(logger_ct_backend:recv(FilterFun)) end, 1), 631: 632: rpc(mim(), sys, resume, [C2SPid]), 633: logger_ct_backend:stop_capture(), 634: 635: escalus_connection:stop(Alice2). 636: 637: return_proper_stream_error_if_service_is_not_hidden(_Config) -> 638: % GIVEN MongooseIM is running default configuration 639: % WHEN we send non-XMPP payload 640: % THEN the server replies with stream error xml-not-well-formed and closes the connection 641: SendMalformedDataStep = fun(Client, Features) -> 642: escalus_connection:send_raw(Client, <<"malformed">>), 643: {Client, Features} 644: end, 645: {ok, Connection, _} = escalus_connection:start([], [SendMalformedDataStep]), 646: escalus_connection:receive_stanza(Connection, #{ assert => is_stream_start }), 647: StreamErrorAssertion = {is_stream_error, [<<"xml-not-well-formed">>, <<>>]}, 648: escalus_connection:receive_stanza(Connection, #{ assert => StreamErrorAssertion }), 649: %% Sometimes escalus needs a moment to report the connection as closed 650: escalus_connection:wait_for_close(Connection, 5000). 651: 652: close_connection_if_service_type_is_hidden(_Config) -> 653: % GIVEN the option to hide service name is enabled 654: % WHEN we send non-XMPP payload 655: % THEN connection is closed without any response from the server 656: FailIfAnyDataReturned = fun(Reply) -> 657: ct:fail({unexpected_data, Reply}) 658: end, 659: Connection = escalus_tcp:connect(#{ on_reply => FailIfAnyDataReturned }), 660: Ref = monitor(process, Connection), 661: escalus_tcp:send(Connection, <<"malformed">>), 662: receive 663: {'DOWN', Ref, _, _, _} -> ok 664: after 665: 5000 -> 666: ct:fail(connection_not_closed) 667: end. 668: 669: close_connection_if_start_stream_duplicated(Config) -> 670: close_connection_if_protocol_violation(Config, [start_stream, stream_features]). 671: 672: close_connection_if_protocol_violation_after_authentication(Config) -> 673: close_connection_if_protocol_violation(Config, [start_stream, stream_features, authenticate]). 674: 675: close_connection_if_protocol_violation_after_binding(Config) -> 676: close_connection_if_protocol_violation(Config, [start_stream, stream_features, authenticate, bind]). 677: 678: close_connection_if_protocol_violation(Config, Steps) -> 679: AliceSpec = escalus_fresh:create_fresh_user(Config, alice), 680: {ok, Alice, _Features} = escalus_connection:start(AliceSpec, Steps), 681: escalus:send(Alice, escalus_stanza:stream_start(domain(), ?NS_JABBER_CLIENT)), 682: escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], 683: escalus_connection:get_stanza(Alice, no_stream_error_stanza_received)), 684: escalus:assert(is_stream_end, 685: escalus_connection:get_stanza(Alice, no_stream_end_stanza_received)), 686: true = escalus_connection:wait_for_close(Alice,timer:seconds(5)). 687: 688: cannot_connect_with_proxy_header(Config) -> 689: %% GIVEN proxy protocol is disabled 690: UserSpec = escalus_users:get_userspec(Config, alice), 691: 692: %% WHEN 693: ConnectionSteps = [{?MODULE, send_proxy_header}, start_stream], 694: ConnResult = escalus_connection:start(UserSpec, ConnectionSteps), 695: 696: %% THEN 697: ?assertMatch({error, {connection_step_failed, _, _}}, ConnResult). 698: 699: cannot_connect_without_proxy_header(Config) -> 700: %% GIVEN proxy protocol is enabled 701: UserSpec = escalus_users:get_userspec(Config, alice), 702: 703: %% WHEN 704: ConnResult = escalus_connection:start(UserSpec, [start_stream]), 705: 706: %% THEN 707: ?assertMatch({error, {connection_step_failed, _, _}}, ConnResult). 708: 709: connect_with_proxy_header(Config) -> 710: %% GIVEN proxy protocol is enabled 711: UserSpec = escalus_users:get_userspec(Config, alice), 712: 713: %% WHEN 714: ConnectionSteps = [{?MODULE, send_proxy_header}, start_stream, stream_features, 715: authenticate, bind, session], 716: {ok, Conn, _Features} = escalus_connection:start(UserSpec, ConnectionSteps), 717: % make sure the session is present 718: escalus:send(Conn, escalus_stanza:presence(<<"available">>)), 719: escalus:assert(is_presence, escalus:wait_for_stanza(Conn)), 720: 721: %% THEN 722: SessionInfo = mongoose_helper:get_session_info(mim(), Conn), 723: #{src_address := IPAddr, src_port := Port} = proxy_info(), 724: ?assertMatch({IPAddr, Port}, maps:get(ip, SessionInfo)), 725: escalus_connection:stop(Conn). 726: 727: %%-------------------------------------------------------------------- 728: %% Internal functions 729: %%-------------------------------------------------------------------- 730: 731: c2s_port(Config) -> 732: case ?config(c2s_port, Config) of 733: undefined -> ct:get_config({hosts, mim, c2s_tls_port}); 734: Value -> Value 735: end. 736: 737: ciphers_available_in_os() -> 738: CiphersStr = os:cmd("openssl ciphers 'ALL:eNULL'"), 739: [string:strip(C, both, $\n) || C <- string:tokens(CiphersStr, ":")]. 740: 741: ciphers_working_with_ssl_clients(Config) -> 742: Port = c2s_port(Config), 743: lists:filter(fun(Cipher) -> 744: openssl_client_can_use_cipher(Cipher, Port) 745: end, ciphers_available_in_os()). 746: 747: openssl_client_can_use_cipher(Cipher, Port) -> 748: PortStr = integer_to_list(Port), 749: Cmd = "echo '' | openssl s_client -connect localhost:" ++ PortStr ++ 750: " -cipher \"" ++ Cipher ++ "\" -tls1_2 2>&1", 751: Output = os:cmd(Cmd), 752: 0 == string:str(Output, ":error:") andalso 0 == string:str(Output, "errno=0"). 753: 754: restore_c2s_listener(Config) -> 755: C2SListener = ?config(c2s_listener, Config), 756: mongoose_helper:restart_listener(mim(), C2SListener). 757: 758: assert_cert_file_exists() -> 759: ejabberd_node_utils:file_exists(?CERT_FILE) orelse 760: ct:fail("cert file ~s not exists", [?CERT_FILE]). 761: 762: configure_c2s_listener(Config, ExtraC2SOpts) -> 763: C2SListener = ?config(c2s_listener, Config), 764: NewC2SListener = maps:merge(C2SListener, ExtraC2SOpts), 765: mongoose_helper:restart_listener(mim(), NewC2SListener). 766: 767: common_tls_opts(Config) -> 768: TLSModule = ?config(tls_module, Config), 769: [{tls_module, TLSModule}, 770: {certfile, ?CERT_FILE}, 771: {dhfile, ?DH_FILE}]. 772: 773: set_secure_connection_protocol(UserSpec, Version) -> 774: [{ssl_opts, [{versions, [Version]}]} | UserSpec]. 775: 776: start_stream_with_compression(UserSpec) -> 777: ConnectionSteps = [start_stream, stream_features, maybe_use_compression], 778: {ok, Conn, Features} = escalus_connection:start(UserSpec, ConnectionSteps), 779: {Conn, Features}. 780: 781: connect_to_invalid_host(Spec) -> 782: {ok, Conn, _} = escalus_connection:start(Spec, [{?MODULE, connect_to_invalid_host}]), 783: escalus:wait_for_stanzas(Conn, 3). 784: 785: connect_to_invalid_host(Conn, UnusedFeatures) -> 786: escalus:send(Conn, escalus_stanza:stream_start(<<"hopefullynonexistentdomain">>, 787: ?NS_JABBER_CLIENT)), 788: {Conn, UnusedFeatures}. 789: 790: connect_with_bad_xml(Spec) -> 791: {ok, Conn, _} = escalus_connection:start(Spec, [{?MODULE, connect_with_bad_xml}]), 792: escalus:wait_for_stanzas(Conn, 3). 793: 794: connect_with_bad_xml(Conn, UnusedFeatures) -> 795: escalus_connection:send(Conn, #xmlcdata{content = "asdf\n"}), 796: {Conn, UnusedFeatures}. 797: 798: connect_with_invalid_stream_namespace(Spec) -> 799: F = fun (Conn, UnusedFeatures) -> 800: Start = stream_start_invalid_stream_ns(escalus_users:get_server([], Spec)), 801: escalus:send(Conn, Start), 802: {Conn, UnusedFeatures} 803: end, 804: {ok, Conn, _} = escalus_connection:start(Spec, [F]), 805: escalus:wait_for_stanzas(Conn, 3). 806: 807: start_stream_pre_xmpp_1_0(Conn = #client{props = Props}, UnusedFeatures) -> 808: escalus:send(Conn, stream_start_pre_xmpp_1_0(escalus_users:get_server([], Props))), 809: #xmlstreamstart{attrs = StreamAttrs} = StreamStart = escalus:wait_for_stanza(Conn), 810: escalus:assert(is_stream_start, StreamStart), 811: {<<"id">>, StreamID} = lists:keyfind(<<"id">>, 1, StreamAttrs), 812: {Conn#client{props = [{stream_id, StreamID} | Props]}, UnusedFeatures}. 813: 814: stream_start_pre_xmpp_1_0(To) -> 815: stream_start(lists:keystore(version, 1, default_context(To), {version, <<>>})). 816: 817: stream_start(Context) -> 818: %% Be careful! The closing slash here is a hack to enable implementation of from_template/2 819: %% to parse the snippet properly. In standard XMPP <stream:stream> is just opening of an XML 820: %% element, NOT A SELF CLOSING element. 821: T = <<"<stream:stream {{version}} xml:lang='en' xmlns='jabber:client' " 822: " to='{{to}}' " 823: " xmlns:stream='{{stream_ns}}' />">>, 824: %% So we rewrap the parsed contents from #xmlel{} to #xmlstreamstart{} here. 825: #xmlel{name = Name, attrs = Attrs, children = []} = escalus_stanza:from_template(T, Context), 826: #xmlstreamstart{name = Name, attrs = Attrs}. 827: 828: stream_start_invalid_stream_ns(To) -> 829: stream_start(lists:keystore(stream_ns, 1, default_context(To), 830: {stream_ns, <<"obviously-invalid-namespace">>})). 831: 832: default_context(To) -> 833: [{version, <<"version='1.0'">>}, 834: {to, To}, 835: {stream_ns, ?NS_XMPP}]. 836: 837: children_specs_to_pids(Children) -> 838: [Pid || {_, Pid, _, _} <- Children]. 839: 840: pipeline_connect(UserSpec) -> 841: Server = proplists:get_value(server, UserSpec), 842: Username = proplists:get_value(username, UserSpec), 843: Password = proplists:get_value(password, UserSpec), 844: AuthPayload = <<0:8, Username/binary, 0:8, Password/binary>>, 845: 846: Conn = escalus_connection:connect(UserSpec), 847: 848: Stream = escalus_stanza:stream_start(Server, <<"jabber:client">>), 849: Auth = escalus_stanza:auth(<<"PLAIN">>, [#xmlcdata{content = base64:encode(AuthPayload)}]), 850: AuthStream = escalus_stanza:stream_start(Server, <<"jabber:client">>), 851: Bind = escalus_stanza:bind(<<?MODULE_STRING "_resource">>), 852: Session = escalus_stanza:session(), 853: 854: escalus_connection:send(Conn, [Stream, Auth, AuthStream, Bind, Session]), 855: Conn. 856: 857: send_proxy_header(Conn, UnusedFeatures) -> 858: Header = ranch_proxy_header:header(proxy_info()), 859: escalus_connection:send_raw(Conn, iolist_to_binary(Header)), 860: {Conn, UnusedFeatures}. 861: 862: proxy_info() -> 863: #{version => 2, 864: command => proxy, 865: transport_family => ipv4, 866: transport_protocol => stream, 867: src_address => {1, 2, 3, 4}, 868: src_port => 444, 869: dest_address => {192, 168, 0, 1}, 870: dest_port => 443 871: }.