diff --git a/big_tests/default.spec b/big_tests/default.spec index 67881165cfb..142a0b12617 100644 --- a/big_tests/default.spec +++ b/big_tests/default.spec @@ -12,106 +12,108 @@ %% do not remove below SUITE if testing mongoose {suites, "tests", mongoose_sanity_checks_SUITE}. -{suites, "tests", acc_e2e_SUITE}. -{suites, "tests", accounts_SUITE}. -{suites, "tests", adhoc_SUITE}. -{suites, "tests", amp_big_SUITE}. -{suites, "tests", anonymous_SUITE}. -{suites, "tests", bosh_SUITE}. -{suites, "tests", carboncopy_SUITE}. -{suites, "tests", cluster_commands_SUITE}. -{suites, "tests", component_SUITE}. -{suites, "tests", connect_SUITE}. -{suites, "tests", disco_and_caps_SUITE}. -{suites, "tests", extdisco_SUITE}. -{suites, "tests", gdpr_SUITE}. -{suites, "tests", graphql_SUITE}. -{suites, "tests", graphql_account_SUITE}. -{suites, "tests", graphql_domain_SUITE}. -{suites, "tests", graphql_inbox_SUITE}. -{suites, "tests", graphql_last_SUITE}. -{suites, "tests", graphql_muc_SUITE}. -{suites, "tests", graphql_muc_light_SUITE}. -{suites, "tests", graphql_offline_SUITE}. -{suites, "tests", graphql_private_SUITE}. -{suites, "tests", graphql_roster_SUITE}. -{suites, "tests", graphql_session_SUITE}. -{suites, "tests", graphql_stanza_SUITE}. -{suites, "tests", graphql_stats_SUITE}. -{suites, "tests", graphql_gdpr_SUITE}. -{suites, "tests", graphql_token_SUITE}. -{suites, "tests", graphql_mnesia_SUITE}. -{suites, "tests", graphql_vcard_SUITE}. -{suites, "tests", graphql_http_upload_SUITE}. -{suites, "tests", graphql_metric_SUITE}. -{suites, "tests", inbox_SUITE}. -{suites, "tests", inbox_extensions_SUITE}. -{suites, "tests", jingle_SUITE}. -{suites, "tests", last_SUITE}. -{suites, "tests", login_SUITE}. -{suites, "tests", mam_SUITE}. +{suites, "tests", mim_c2s_SUITE}. + +% {suites, "tests", acc_e2e_SUITE}. +% {suites, "tests", accounts_SUITE}. +% {suites, "tests", adhoc_SUITE}. +% {suites, "tests", amp_big_SUITE}. +% {suites, "tests", anonymous_SUITE}. +% {suites, "tests", bosh_SUITE}. +% {suites, "tests", carboncopy_SUITE}. +% {suites, "tests", cluster_commands_SUITE}. +% {suites, "tests", component_SUITE}. +% {suites, "tests", connect_SUITE}. +% {suites, "tests", disco_and_caps_SUITE}. +% {suites, "tests", extdisco_SUITE}. +% {suites, "tests", gdpr_SUITE}. +% {suites, "tests", graphql_SUITE}. +% {suites, "tests", graphql_account_SUITE}. +% {suites, "tests", graphql_domain_SUITE}. +% {suites, "tests", graphql_inbox_SUITE}. +% {suites, "tests", graphql_last_SUITE}. +% {suites, "tests", graphql_muc_SUITE}. +% {suites, "tests", graphql_muc_light_SUITE}. +% {suites, "tests", graphql_offline_SUITE}. +% {suites, "tests", graphql_private_SUITE}. +% {suites, "tests", graphql_roster_SUITE}. +% {suites, "tests", graphql_session_SUITE}. +% {suites, "tests", graphql_stanza_SUITE}. +% {suites, "tests", graphql_stats_SUITE}. +% {suites, "tests", graphql_gdpr_SUITE}. +% {suites, "tests", graphql_token_SUITE}. +% {suites, "tests", graphql_mnesia_SUITE}. +% {suites, "tests", graphql_vcard_SUITE}. +% {suites, "tests", graphql_http_upload_SUITE}. +% {suites, "tests", graphql_metric_SUITE}. +% {suites, "tests", inbox_SUITE}. +% {suites, "tests", inbox_extensions_SUITE}. +% {suites, "tests", jingle_SUITE}. +% {suites, "tests", last_SUITE}. +% {suites, "tests", login_SUITE}. +% {suites, "tests", mam_SUITE}. {suites, "tests", mam_proper_SUITE}. -{suites, "tests", metrics_api_SUITE}. -{suites, "tests", metrics_c2s_SUITE}. -{suites, "tests", metrics_register_SUITE}. -{suites, "tests", metrics_roster_SUITE}. -{suites, "tests", metrics_session_SUITE}. -{suites, "tests", mod_blocking_SUITE}. -{suites, "tests", mod_event_pusher_rabbit_SUITE}. -{suites, "tests", mod_event_pusher_http_SUITE}. -{suites, "tests", mod_event_pusher_sns_SUITE}. -{suites, "tests", mod_global_distrib_SUITE}. -{suites, "tests", mod_http_upload_SUITE}. -{suites, "tests", mod_ping_SUITE}. -{suites, "tests", mod_time_SUITE}. -{suites, "tests", mod_version_SUITE}. -{suites, "tests", mongoose_cassandra_SUITE}. -{suites, "tests", mongoose_elasticsearch_SUITE}. -{suites, "tests", mongooseimctl_SUITE}. -{suites, "tests", muc_SUITE}. -{suites, "tests", muc_http_api_SUITE}. -{suites, "tests", muc_light_SUITE}. -{suites, "tests", muc_light_http_api_SUITE}. -{suites, "tests", muc_light_legacy_SUITE}. -{suites, "tests", oauth_SUITE}. -{suites, "tests", offline_stub_SUITE}. -{suites, "tests", offline_SUITE}. -{suites, "tests", pep_SUITE}. +% {suites, "tests", metrics_api_SUITE}. +% {suites, "tests", metrics_c2s_SUITE}. +% {suites, "tests", metrics_register_SUITE}. +% {suites, "tests", metrics_roster_SUITE}. +% {suites, "tests", metrics_session_SUITE}. +% {suites, "tests", mod_blocking_SUITE}. +% {suites, "tests", mod_event_pusher_rabbit_SUITE}. +% {suites, "tests", mod_event_pusher_http_SUITE}. +% {suites, "tests", mod_event_pusher_sns_SUITE}. +% {suites, "tests", mod_global_distrib_SUITE}. +% {suites, "tests", mod_http_upload_SUITE}. +% {suites, "tests", mod_ping_SUITE}. +% {suites, "tests", mod_time_SUITE}. +% {suites, "tests", mod_version_SUITE}. +% {suites, "tests", mongoose_cassandra_SUITE}. +% {suites, "tests", mongoose_elasticsearch_SUITE}. +% {suites, "tests", mongooseimctl_SUITE}. +% {suites, "tests", muc_SUITE}. +% {suites, "tests", muc_http_api_SUITE}. +% {suites, "tests", muc_light_SUITE}. +% {suites, "tests", muc_light_http_api_SUITE}. +% {suites, "tests", muc_light_legacy_SUITE}. +% {suites, "tests", oauth_SUITE}. +% {suites, "tests", offline_stub_SUITE}. +% {suites, "tests", offline_SUITE}. +% {suites, "tests", pep_SUITE}. {suites, "tests", persistent_cluster_id_SUITE}. -{suites, "tests", presence_SUITE}. -{suites, "tests", privacy_SUITE}. -{suites, "tests", private_SUITE}. -{suites, "tests", pubsub_SUITE}. -{suites, "tests", pubsub_s2s_SUITE}. -{suites, "tests", push_SUITE}. -{suites, "tests", push_http_SUITE}. -{suites, "tests", push_integration_SUITE}. -{suites, "tests", push_pubsub_SUITE}. -{suites, "tests", race_conditions_SUITE}. -{suites, "tests", rdbms_SUITE}. -{suites, "tests", rest_SUITE}. -{suites, "tests", rest_client_SUITE}. -{suites, "tests", s2s_SUITE}. -{suites, "tests", sasl_SUITE}. -{suites, "tests", sasl_external_SUITE}. -{suites, "tests", service_mongoose_system_metrics_SUITE}. -{suites, "tests", shared_roster_SUITE}. -{suites, "tests", sic_SUITE}. -{suites, "tests", smart_markers_SUITE}. -{suites, "tests", sm_SUITE}. -{suites, "tests", users_api_SUITE}. -{suites, "tests", vcard_SUITE}. -{suites, "tests", vcard_simple_SUITE}. -{suites, "tests", websockets_SUITE}. -{suites, "tests", xep_0352_csi_SUITE}. -{suites, "tests", service_domain_db_SUITE}. -{suites, "tests", domain_isolation_SUITE}. -{suites, "tests", domain_removal_SUITE}. -{suites, "tests", mam_send_message_SUITE}. -{suites, "tests", dynamic_domains_SUITE}. -{suites, "tests", auth_methods_for_c2s_SUITE}. -{suites, "tests", local_iq_SUITE}. -{suites, "tests", tcp_listener_SUITE}. +% {suites, "tests", presence_SUITE}. +% {suites, "tests", privacy_SUITE}. +% {suites, "tests", private_SUITE}. +% {suites, "tests", pubsub_SUITE}. +% {suites, "tests", pubsub_s2s_SUITE}. +% {suites, "tests", push_SUITE}. +% {suites, "tests", push_http_SUITE}. +% {suites, "tests", push_integration_SUITE}. +% {suites, "tests", push_pubsub_SUITE}. +% {suites, "tests", race_conditions_SUITE}. +% {suites, "tests", rdbms_SUITE}. +% {suites, "tests", rest_SUITE}. +% {suites, "tests", rest_client_SUITE}. +% {suites, "tests", s2s_SUITE}. +% {suites, "tests", sasl_SUITE}. +% {suites, "tests", sasl_external_SUITE}. +% {suites, "tests", service_mongoose_system_metrics_SUITE}. +% {suites, "tests", shared_roster_SUITE}. +% {suites, "tests", sic_SUITE}. +% {suites, "tests", smart_markers_SUITE}. +% {suites, "tests", sm_SUITE}. +% {suites, "tests", users_api_SUITE}. +% {suites, "tests", vcard_SUITE}. +% {suites, "tests", vcard_simple_SUITE}. +% {suites, "tests", websockets_SUITE}. +% {suites, "tests", xep_0352_csi_SUITE}. +% {suites, "tests", service_domain_db_SUITE}. +% {suites, "tests", domain_isolation_SUITE}. +% {suites, "tests", domain_removal_SUITE}. +% {suites, "tests", mam_send_message_SUITE}. +% {suites, "tests", dynamic_domains_SUITE}. +% {suites, "tests", auth_methods_for_c2s_SUITE}. +% {suites, "tests", local_iq_SUITE}. +% {suites, "tests", tcp_listener_SUITE}. {config, ["test.config"]}. {logdir, "ct_report"}. diff --git a/big_tests/dynamic_domains.spec b/big_tests/dynamic_domains.spec index a8ebfd80f4e..2315e7deebd 100644 --- a/big_tests/dynamic_domains.spec +++ b/big_tests/dynamic_domains.spec @@ -9,154 +9,156 @@ %% http://www.erlang.org/doc/apps/common_test/run_test_chapter.html#test_specifications {include, "tests"}. -{suites, "tests", accounts_SUITE}. +{suites, "tests", mim_c2s_SUITE}. -{suites, "tests", anonymous_SUITE}. +% {suites, "tests", accounts_SUITE}. -{suites, "tests", acc_e2e_SUITE}. +% {suites, "tests", anonymous_SUITE}. -{suites, "tests", adhoc_SUITE}. +% {suites, "tests", acc_e2e_SUITE}. -{suites, "tests", amp_big_SUITE}. +% {suites, "tests", adhoc_SUITE}. -{suites, "tests", bosh_SUITE}. +% {suites, "tests", amp_big_SUITE}. -{suites, "tests", carboncopy_SUITE}. +% {suites, "tests", bosh_SUITE}. -{suites, "tests", cluster_commands_SUITE}. +% {suites, "tests", carboncopy_SUITE}. -{suites, "tests", connect_SUITE}. +% {suites, "tests", cluster_commands_SUITE}. -{suites, "tests", disco_and_caps_SUITE}. +% {suites, "tests", connect_SUITE}. -{suites, "tests", domain_isolation_SUITE}. +% {suites, "tests", disco_and_caps_SUITE}. -{suites, "tests", dynamic_domains_SUITE}. +% {suites, "tests", domain_isolation_SUITE}. -{suites, "tests", extdisco_SUITE}. +% {suites, "tests", dynamic_domains_SUITE}. -{suites, "tests", gdpr_SUITE}. -{skip_groups, "tests", gdpr_SUITE, - [retrieve_personal_data_pubsub, - remove_personal_data_pubsub], - "at the moment mod_pubsub doesn't support dynamic domains"}. +% {suites, "tests", extdisco_SUITE}. -{suites, "tests", graphql_SUITE}. -{suites, "tests", graphql_account_SUITE}. -{suites, "tests", graphql_domain_SUITE}. -{suites, "tests", graphql_inbox_SUITE}. -{suites, "tests", graphql_last_SUITE}. -{suites, "tests", graphql_muc_SUITE}. -{suites, "tests", graphql_muc_light_SUITE}. -{suites, "tests", graphql_private_SUITE}. -{suites, "tests", graphql_roster_SUITE}. -{suites, "tests", graphql_session_SUITE}. -{suites, "tests", graphql_stanza_SUITE}. -{suites, "tests", graphql_vcard_SUITE}. -{suites, "tests", graphql_offline_SUITE}. -{suites, "tests", graphql_stats_SUITE}. -{suites, "tests", graphql_gdpr_SUITE}. -{suites, "tests", graphql_token_SUITE}. -{suites, "tests", graphql_mnesia_SUITE}. -{suites, "tests", graphql_http_upload_SUITE}. -{suites, "tests", graphql_metric_SUITE}. +% {suites, "tests", gdpr_SUITE}. +% {skip_groups, "tests", gdpr_SUITE, +% [retrieve_personal_data_pubsub, +% remove_personal_data_pubsub], +% "at the moment mod_pubsub doesn't support dynamic domains"}. -{suites, "tests", inbox_SUITE}. +% {suites, "tests", graphql_SUITE}. +% {suites, "tests", graphql_account_SUITE}. +% {suites, "tests", graphql_domain_SUITE}. +% {suites, "tests", graphql_inbox_SUITE}. +% {suites, "tests", graphql_last_SUITE}. +% {suites, "tests", graphql_muc_SUITE}. +% {suites, "tests", graphql_muc_light_SUITE}. +% {suites, "tests", graphql_private_SUITE}. +% {suites, "tests", graphql_roster_SUITE}. +% {suites, "tests", graphql_session_SUITE}. +% {suites, "tests", graphql_stanza_SUITE}. +% {suites, "tests", graphql_vcard_SUITE}. +% {suites, "tests", graphql_offline_SUITE}. +% {suites, "tests", graphql_stats_SUITE}. +% {suites, "tests", graphql_gdpr_SUITE}. +% {suites, "tests", graphql_token_SUITE}. +% {suites, "tests", graphql_mnesia_SUITE}. +% {suites, "tests", graphql_http_upload_SUITE}. +% {suites, "tests", graphql_metric_SUITE}. -{suites, "tests", inbox_extensions_SUITE}. +% {suites, "tests", inbox_SUITE}. -{suites, "tests", last_SUITE}. +% {suites, "tests", inbox_extensions_SUITE}. -{suites, "tests", login_SUITE}. +% {suites, "tests", last_SUITE}. -{suites, "tests", mam_SUITE}. +% {suites, "tests", login_SUITE}. -{suites, "tests", mam_proper_SUITE}. +% {suites, "tests", mam_SUITE}. -{suites, "tests", mam_send_message_SUITE}. +% {suites, "tests", mam_proper_SUITE}. -{suites, "tests", metrics_c2s_SUITE}. +% {suites, "tests", mam_send_message_SUITE}. -{suites, "tests", metrics_register_SUITE}. +% {suites, "tests", metrics_c2s_SUITE}. -{suites, "tests", metrics_roster_SUITE}. +% {suites, "tests", metrics_register_SUITE}. -{suites, "tests", metrics_session_SUITE}. +% {suites, "tests", metrics_roster_SUITE}. -{suites, "tests", metrics_api_SUITE}. +% {suites, "tests", metrics_session_SUITE}. -{suites, "tests", mod_blocking_SUITE}. +% {suites, "tests", metrics_api_SUITE}. -{suites, "tests", mod_http_upload_SUITE}. +% {suites, "tests", mod_blocking_SUITE}. -{suites, "tests", mod_ping_SUITE}. +% {suites, "tests", mod_http_upload_SUITE}. -{suites, "tests", mod_time_SUITE}. +% {suites, "tests", mod_ping_SUITE}. -{suites, "tests", mod_version_SUITE}. +% {suites, "tests", mod_time_SUITE}. -{suites, "tests", mongooseimctl_SUITE}. +% {suites, "tests", mod_version_SUITE}. -{suites, "tests", muc_SUITE}. -{skip_groups, "tests", muc_SUITE, - [register_over_s2s], - "at the moment S2S doesn't support dynamic domains " - "(requires mod_register creating CT users)"}. +% {suites, "tests", mongooseimctl_SUITE}. -{suites, "tests", muc_http_api_SUITE}. +% {suites, "tests", muc_SUITE}. +% {skip_groups, "tests", muc_SUITE, +% [register_over_s2s], +% "at the moment S2S doesn't support dynamic domains " +% "(requires mod_register creating CT users)"}. -{suites, "tests", muc_light_SUITE}. +% {suites, "tests", muc_http_api_SUITE}. -{suites, "tests", muc_light_legacy_SUITE}. +% {suites, "tests", muc_light_SUITE}. -{suites, "tests", muc_light_http_api_SUITE}. +% {suites, "tests", muc_light_legacy_SUITE}. -{suites, "tests", oauth_SUITE}. +% {suites, "tests", muc_light_http_api_SUITE}. -{suites, "tests", offline_SUITE}. +% {suites, "tests", oauth_SUITE}. -{suites, "tests", offline_stub_SUITE}. +% {suites, "tests", offline_SUITE}. + +% {suites, "tests", offline_stub_SUITE}. {suites, "tests", persistent_cluster_id_SUITE}. -{suites, "tests", presence_SUITE}. +% {suites, "tests", presence_SUITE}. -{suites, "tests", privacy_SUITE}. +% {suites, "tests", privacy_SUITE}. -{suites, "tests", private_SUITE}. +% {suites, "tests", private_SUITE}. -{suites, "tests", race_conditions_SUITE}. +% {suites, "tests", race_conditions_SUITE}. -{suites, "tests", rdbms_SUITE}. +% {suites, "tests", rdbms_SUITE}. -{suites, "tests", rest_SUITE}. +% {suites, "tests", rest_SUITE}. -{suites, "tests", rest_client_SUITE}. +% {suites, "tests", rest_client_SUITE}. -{suites, "tests", sasl_SUITE}. -{suites, "tests", sasl_external_SUITE}. +% {suites, "tests", sasl_SUITE}. +% {suites, "tests", sasl_external_SUITE}. -{suites, "tests", service_domain_db_SUITE}. +% {suites, "tests", service_domain_db_SUITE}. -{suites, "tests", service_mongoose_system_metrics_SUITE}. -{skip_cases, "tests", service_mongoose_system_metrics_SUITE, - [xmpp_components_are_reported], - "at the moment external components doesn't support dynamic domains"}. +% {suites, "tests", service_mongoose_system_metrics_SUITE}. +% {skip_cases, "tests", service_mongoose_system_metrics_SUITE, +% [xmpp_components_are_reported], +% "at the moment external components doesn't support dynamic domains"}. -{suites, "tests", sic_SUITE}. +% {suites, "tests", sic_SUITE}. -{suites, "tests", smart_markers_SUITE}. -{suites, "tests", sm_SUITE}. -{suites, "tests", users_api_SUITE}. -{suites, "tests", vcard_SUITE}. -{suites, "tests", vcard_simple_SUITE}. -{suites, "tests", websockets_SUITE}. -{suites, "tests", xep_0352_csi_SUITE}. +% {suites, "tests", smart_markers_SUITE}. +% {suites, "tests", sm_SUITE}. +% {suites, "tests", users_api_SUITE}. +% {suites, "tests", vcard_SUITE}. +% {suites, "tests", vcard_simple_SUITE}. +% {suites, "tests", websockets_SUITE}. +% {suites, "tests", xep_0352_csi_SUITE}. -{suites, "tests", domain_removal_SUITE}. -{suites, "tests", auth_methods_for_c2s_SUITE}. -{suites, "tests", local_iq_SUITE}. -{suites, "tests", tcp_listener_SUITE}. +% {suites, "tests", domain_removal_SUITE}. +% {suites, "tests", auth_methods_for_c2s_SUITE}. +% {suites, "tests", local_iq_SUITE}. +% {suites, "tests", tcp_listener_SUITE}. {config, ["dynamic_domains.config", "test.config"]}. diff --git a/big_tests/rebar.config b/big_tests/rebar.config index ad182dca587..b6105df6ee7 100644 --- a/big_tests/rebar.config +++ b/big_tests/rebar.config @@ -16,7 +16,7 @@ {proper, "1.4.0"}, {gun, "2.0.0-rc.2"}, {fusco, "0.1.1"}, - {escalus, "4.2.7"}, + {escalus, "4.2.8"}, {cowboy, "2.9.0"}, {csv, "3.0.3", {pkg, csve}}, {amqp_client, "3.9.5"}, diff --git a/big_tests/rebar.lock b/big_tests/rebar.lock index 66328492030..5e3163f57d8 100644 --- a/big_tests/rebar.lock +++ b/big_tests/rebar.lock @@ -8,12 +8,12 @@ {pkg,<<"credentials_obfuscation">>,<<"2.4.0">>}, 2}, {<<"csv">>,{pkg,<<"csve">>,<<"3.0.3">>},0}, - {<<"escalus">>,{pkg,<<"escalus">>,<<"4.2.7">>},0}, + {<<"escalus">>,{pkg,<<"escalus">>,<<"4.2.8">>},0}, {<<"esip">>,{pkg,<<"esip">>,<<"1.0.43">>},0}, {<<"exml">>,{pkg,<<"hexml">>,<<"3.2.1">>},0}, {<<"fast_pbkdf2">>,{pkg,<<"fast_pbkdf2">>,<<"1.0.3">>},2}, {<<"fast_scram">>,{pkg,<<"fast_scram">>,<<"0.4.4">>},1}, - {<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.13">>},1}, + {<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.15">>},1}, {<<"fusco">>,{pkg,<<"fusco">>,<<"0.1.1">>},0}, {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1}, {<<"gun">>,{pkg,<<"gun">>,<<"2.0.0-rc.2">>},0}, @@ -31,7 +31,7 @@ {<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.27">>},1}, {<<"stun">>,{pkg,<<"stun">>,<<"1.0.44">>},1}, {<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.4">>},1}, - {<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"6.0.0">>},1}]}. + {<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"6.0.1">>},1}]}. [ {pkg_hash,[ {<<"amqp_client">>, <<"BE7B022550F1C0DDFB23A3145FC3D59B5EDB9FDFCC1DD705796C97F77E9EAC98">>}, @@ -41,12 +41,12 @@ {<<"cowlib">>, <<"0B9FF9C346629256C42EBE1EEB769A83C6CB771A6EE5960BD110AB0B9B872063">>}, {<<"credentials_obfuscation">>, <<"9FB57683B84899CA3546B384E59AB5D3054A9F334EBA50D74C82CD0AE82DD6CA">>}, {<<"csv">>, <<"69E7D9B3FDC72016644368762C6A3E6CBFEB85BCCADBF1BD99AB6C827E360E04">>}, - {<<"escalus">>, <<"496D14C505224023C4296E11A8A4C231036517A5E739FACF0C5F87277E48A61A">>}, + {<<"escalus">>, <<"6E65603DC5E9B2839994793E89AF0F9D53081AC58F5A5AD9426C10BF6E673137">>}, {<<"esip">>, <<"1CBDC073073F80B9B50E2759F66CA13A353EB4F874BCF92501BD4CD767E34D46">>}, {<<"exml">>, <<"2B5E288658C92ACD4791E95838422CC70B51FD4AAC48355B4CF3E780404E6A7B">>}, {<<"fast_pbkdf2">>, <<"4F09D6C6C20DBEE1970E0A6AE91432E1B7731F88426C671D083BAC31FFA1FDAD">>}, {<<"fast_scram">>, <<"299A2D430955A62A94CB43B1A727C5D21A5C4BD11AEBA476AE2F3A24CFBE89C3">>}, - {<<"fast_tls">>, <<"828CDC75E1E8FCE8158846D2B971D8B4FE2B2DDCC75B759E88D751079BF78AFD">>}, + {<<"fast_tls">>, <<"398E7BA1076DB139307EBEA839428E2836AB682E4DAC61D95B4705A26AFF06B7">>}, {<<"fusco">>, <<"3DD6A90151DFEF30EA1937CC44E9A59177C0094918388D9BCAA2F2DC5E2AE4AA">>}, {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}, {<<"gun">>, <<"7C489A32DEDCCB77B6E82D1F3C5A7DADFBFA004EC14E322CDB5E579C438632D2">>}, @@ -64,7 +64,7 @@ {<<"stringprep">>, <<"02808C7024BC6285CA6A8A67E7ADDFC16F35DDA55551A582C5181D8EA960E890">>}, {<<"stun">>, <<"30B6B774864B24B05BA901291ABE583BFF19081E7C4EFB3361DF50B781EC9D3B">>}, {<<"uuid">>, <<"77C3E3EE1E1701A2856CE945846D7CEB71931C60633A305D0B0FEAE03B2B3B5C">>}, - {<<"worker_pool">>, <<"F7B442B30121EED6D8C828833533E5C15DB61E4AB2EF343C8B67824267656822">>}]}, + {<<"worker_pool">>, <<"CA262C2DFB3B4AF661B206C82065D86F83922B7227508AA6E0BC34D3E5AE5135">>}]}, {pkg_hash_ext,[ {<<"amqp_client">>, <<"DC7CD4D35B4AFF28620F7CD58A9B8F394DAD93CF896704B1F770554F5FF20FDE">>}, {<<"base16">>, <<"06EA2D48343282E712160BA89F692B471DB8B36ABE8394F3445FF9032251D772">>}, @@ -73,12 +73,12 @@ {<<"cowlib">>, <<"2B3E9DA0B21C4565751A6D4901C20D1B4CC25CBB7FD50D91D2AB6DD287BC86A9">>}, {<<"credentials_obfuscation">>, <<"D28A89830E30698B075DE9A4DBE683A20685C6BED1E3B7DF744A0C06E6FF200A">>}, {<<"csv">>, <<"741D1A55AABADAA3E0FE13051050101A73E90C4570B9F9403A939D9546813521">>}, - {<<"escalus">>, <<"05614337F7CC4383EC73C036CD2C6D3B9A87E86A5D1EE82F160736188F460675">>}, + {<<"escalus">>, <<"E0CF0AE2A68884A2C5458A77C5E3EADA196B14F323B8D52B4590DF71E3C92ED7">>}, {<<"esip">>, <<"B2C758AE52C4588E0399C0B4CE550BFA56551A5A2F828A28389F2614797E4F4B">>}, {<<"exml">>, <<"CC63B8A0CD96C1CA20B5F0C0CCCA75DED99D681385896A10E3BC848423D0AAC9">>}, {<<"fast_pbkdf2">>, <<"2900431E2E6402F23A92754448BBD949DA366BC9C984FDC791DDCFCC41042434">>}, {<<"fast_scram">>, <<"4B30084E3BDB39158076381FC871035BEFD157D5EE614BDA5E19EA482855E5D5">>}, - {<<"fast_tls">>, <<"D1F422AF40C7777FE534496F508EE86515CB929AD10F7D1D56AA94CE899B44A0">>}, + {<<"fast_tls">>, <<"EF516AA226DE9A4605704C18499284CD4FC115A73BD72490341972CE0C2B4D30">>}, {<<"fusco">>, <<"6343551BD1E824F2A6CA85E1158C5B37C320FD449FBFEC7450A73F192AAF9022">>}, {<<"goldrush">>, <<"99CB4128CFFCB3227581E5D4D803D5413FA643F4EB96523F77D9E6937D994CEB">>}, {<<"gun">>, <<"6B9D1EAE146410D727140DBF8B404B9631302ECC2066D1D12F22097AD7D254FC">>}, @@ -96,5 +96,5 @@ {<<"stringprep">>, <<"A5967B1144CA8002A58A03D16DD109FBD0BCDB82616CEAD2F983944314AF6A00">>}, {<<"stun">>, <<"E45BBA816CBEFFF01D820E49E66814F450DF25A7A468A70D68D1E64218D46520">>}, {<<"uuid">>, <<"7A4CCD1C151D9B88B4383FA802BCCF9BCB3754B7F53D7CAA164D51A14A6652E4">>}, - {<<"worker_pool">>, <<"F9D95B85E80C5C27B7AB2E60BF780DA70A5E26DD9E6D30BE45032742FC039CC5">>}]} + {<<"worker_pool">>, <<"772E12CCB26909EA7F804B52E86E733DF66BB8150F683B591B0A762196494C74">>}]} ]. diff --git a/big_tests/tests/carboncopy_SUITE.erl b/big_tests/tests/carboncopy_SUITE.erl index 58cd6671d88..5e0f37c980a 100644 --- a/big_tests/tests/carboncopy_SUITE.erl +++ b/big_tests/tests/carboncopy_SUITE.erl @@ -15,7 +15,7 @@ all() -> [{group, all}]. groups() -> - G = [{all, [parallel], + [{all, [parallel], [discovering_support, enabling_carbons, disabling_carbons, @@ -28,8 +28,7 @@ groups() -> prop_forward_received_chat_messages, prop_forward_sent_chat_messages, prop_normal_routing_to_bare_jid - ]}], - ct_helper:repeat_all_until_all_ok(G). + ]}]. %%%=================================================================== %%% Overall setup/teardown diff --git a/big_tests/tests/mim_c2s_SUITE.erl b/big_tests/tests/mim_c2s_SUITE.erl new file mode 100644 index 00000000000..ae735e1aa59 --- /dev/null +++ b/big_tests/tests/mim_c2s_SUITE.erl @@ -0,0 +1,110 @@ +-module(mim_c2s_SUITE). + +-compile([export_all, nowarn_export_all]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-import(distributed_helper, [mim/0]). + +%%-------------------------------------------------------------------- +%% Suite configuration +%%-------------------------------------------------------------------- + +all() -> + [ + {group, basic} + ]. + +groups() -> + [ + {basic, [parallel], + [ + two_users_can_log_and_chat, + too_big_stanza_rejected + ]} + ]. + +%%-------------------------------------------------------------------- +%% Init & teardown +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + HostType = domain_helper:host_type(), + Steps = [start_stream, stream_features, maybe_use_ssl, authenticate, bind], + EscalusOverrides = [{initial_activity, fun(_) -> ok end}, + {start_ready_clients, fun ?MODULE:escalus_start/2}], + Config1 = save_c2s_listener(Config), + Config2 = dynamic_modules:save_modules(HostType, Config1), + Config3 = escalus_users:update_userspec(Config2, alice, connection_steps, Steps), + Config4 = escalus_users:update_userspec(Config3, bob, connection_steps, Steps), + configure_c2s_listener(Config4, #{max_stanza_size => 1024}), + escalus:init_per_suite([{escalus_overrides, EscalusOverrides} | Config4 ]). + +end_per_suite(Config) -> + restore_c2s_listener(Config), + escalus_fresh:clean(), + dynamic_modules:restore_modules(Config), + escalus:end_per_suite(Config). + +init_per_group(_, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(Name, Config) -> + escalus:init_per_testcase(Name, Config). + +end_per_testcase(Name, Config) -> + mongoose_helper:restore_config(Config), + escalus:end_per_testcase(Name, Config). + +%%-------------------------------------------------------------------- +%% tests +%%-------------------------------------------------------------------- +two_users_can_log_and_chat(Config) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus_client:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi!">>)), + escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Bob)), + escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Hi!">>)), + escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Alice)) + end). + +too_big_stanza_rejected(Config) -> + AliceSpec = escalus_fresh:create_fresh_user(Config, alice), + {ok, Alice, _Features} = escalus_connection:start(AliceSpec), + BigBody = base16:encode(crypto:strong_rand_bytes(1024)), + escalus_client:send(Alice, escalus_stanza:chat_to(Alice, BigBody)), + escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], escalus_client:wait_for_stanza(Alice)), + escalus:assert(is_stream_end, escalus_client:wait_for_stanza(Alice)), + true = escalus_connection:wait_for_close(Alice, timer:seconds(1)). + +%%-------------------------------------------------------------------- +%% helpers +%%-------------------------------------------------------------------- + +save_c2s_listener(Config) -> + C2SPort = ct:get_config({hosts, mim, c2s_port}), + [C2SListener] = mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => mongoose_c2s_listener}), + [{c2s_listener, C2SListener} | Config]. + +restore_c2s_listener(Config) -> + C2SListener = ?config(c2s_listener, Config), + mongoose_helper:restart_listener(mim(), C2SListener). + +configure_c2s_listener(Config, ExtraC2SOpts) -> + C2SListener = ?config(c2s_listener, Config), + NewC2SListener = maps:merge(C2SListener, ExtraC2SOpts), + mongoose_helper:restart_listener(mim(), NewC2SListener). + +escalus_start(Cfg, FlatCDs) -> + {_, RClients} = lists:foldl( + fun({UserSpec, BaseResource}, {N, Acc}) -> + Resource = escalus_overridables:do(Cfg, modify_resource, [BaseResource], + {escalus_utils, identity}), + {ok, Client} = escalus_client:start(Cfg, UserSpec, Resource), + {N+1, [Client|Acc]} + end, {1, []}, FlatCDs), + Clients = lists:reverse(RClients), + [ escalus_assert:has_no_stanzas(Client) || Client <- Clients ], + Clients. diff --git a/include/jlib.hrl b/include/jlib.hrl index 6df76b7fa1f..d5a20c55496 100644 --- a/include/jlib.hrl +++ b/include/jlib.hrl @@ -32,6 +32,8 @@ sub_el :: [exml:element()] | exml:element() }). +-record(xmlstreamerror, {name :: binary()}). + -define(STREAM_TRAILER, <<"">>). -endif. diff --git a/rebar.config b/rebar.config index 2bca5f53518..cc9bea81adf 100644 --- a/rebar.config +++ b/rebar.config @@ -23,6 +23,8 @@ mod_global_distrib_mapping_backend, mod_pubsub_db_backend, mod_shared_roster, + mongoose_c2s, + mongoose_c2s_acc, %% Deprecated functions {crypto, rand_uniform, 2}, {ranch, start_listener, 6}, @@ -78,6 +80,7 @@ %%% HTTP tools {graphql, {git, "https://github.com/esl/graphql-erlang.git", {branch, "master"}}}, + {ranch, "2.1.0"}, {cowboy, "2.9.0"}, {gun, "1.3.3"}, {fusco, "0.1.1"}, diff --git a/rebar.lock b/rebar.lock index 1a31e04263e..e07ff4826c2 100644 --- a/rebar.lock +++ b/rebar.lock @@ -107,7 +107,7 @@ {<<"proper">>,{pkg,<<"proper">>,<<"1.4.0">>},0}, {<<"quickrand">>,{pkg,<<"quickrand">>,<<"2.0.4">>},1}, {<<"rabbit_common">>,{pkg,<<"rabbit_common">>,<<"3.9.11">>},1}, - {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},1}, + {<<"ranch">>,{pkg,<<"ranch">>,<<"2.1.0">>},0}, {<<"re2">>,{pkg,<<"re2">>,<<"1.9.7">>},1}, {<<"recon">>,{pkg,<<"recon">>,<<"2.5.2">>},0}, {<<"redbug">>,{pkg,<<"redbug">>,<<"1.2.1">>},1}, @@ -186,7 +186,7 @@ {<<"proper">>, <<"89A44B8C39D28BB9B4BE8E4D715D534905B325470F2E0EC5E004D12484A79434">>}, {<<"quickrand">>, <<"168CA3A8466A26912B8C3A1D6AA58975E1BB49E5C7AFB4998B80F6B90F910490">>}, {<<"rabbit_common">>, <<"25DF900B1AEC7357C90253CC4528B43C5FF064F27C8C627707B747AE986EBF77">>}, - {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, + {<<"ranch">>, <<"2261F9ED9574DCFCC444106B9F6DA155E6E540B2F82BA3D42B339B93673B72A3">>}, {<<"re2">>, <<"8114654E72EF62F605A8A393F219702F253CB7A02F671503918B76D0614DB046">>}, {<<"recon">>, <<"CBA53FA8DB83AD968C9A652E09C3ED7DDCC4DA434F27C3EAA9CA47FFB2B1FF03">>}, {<<"redbug">>, <<"9153EE50E42C39CE3F6EFA65EE746F4A52896DA66862CFB59E7C0F838B7B8414">>}, @@ -248,7 +248,7 @@ {<<"proper">>, <<"18285842185BD33EFBDA97D134A5CB5A0884384DB36119FEE0E3CFA488568CBB">>}, {<<"quickrand">>, <<"4CB18E9304CF28E054E8DC6E151D1AC7F174E6FE31D5C1A07F71279B92A90800">>}, {<<"rabbit_common">>, <<"1BCAC63760A0BF0E55D7D3C2FF36ED2310E0B560BD110A5A2D602D76D9C08E1A">>}, - {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, + {<<"ranch">>, <<"244EE3FA2A6175270D8E1FC59024FD9DBC76294A321057DE8F803B1479E76916">>}, {<<"re2">>, <<"FF0703CA095B5BEBF57DD12571AF24B3BA404180E9B5E43128790B5D31EBE803">>}, {<<"recon">>, <<"2C7523C8DEE91DFF41F6B3D63CBA2BD49EB6D2FE5BF1EEC0DF7F87EB5E230E1C">>}, {<<"redbug">>, <<"BFC7BCB8743C55DBE0134DBCB89DD6A57606288CC4E2570ECCD701061FBCBD93">>}, diff --git a/src/c2s/mongoose_c2s.erl b/src/c2s/mongoose_c2s.erl new file mode 100644 index 00000000000..e545a8dbddf --- /dev/null +++ b/src/c2s/mongoose_c2s.erl @@ -0,0 +1,923 @@ +-module(mongoose_c2s). + +-behaviour(gen_statem). +-include("mongoose_logger.hrl"). +-include("mongoose.hrl"). +-include("jlib.hrl"). +-include_lib("exml/include/exml_stream.hrl"). +-define(AUTH_RETRIES, 3). +-define(BIND_RETRIES, 5). + +%% gen_statem callbacks +-export([callback_mode/0, init/1, handle_event/4, terminate/3]). + +%% utils +-export([start_link/2]). +-export([get_host_type/1, get_lserver/1, get_sid/1, get_jid/1, + get_mod_state/2, remove_mod_state/2, + get_ip/1, get_socket/1, get_lang/1, get_stream_id/1]). +-export([filter_mechanism/2, c2s_stream_error/2, maybe_retry_state/1, + reroute/2, stop/2, merge_states/2]). + +-ignore_xref([get_ip/1, get_socket/1]). + +-record(c2s_data, { + host_type :: undefined | mongooseim:host_type(), + lserver = ?MYNAME :: jid:lserver(), + lang = ?MYLANG :: ejabberd:lang(), + sid = ejabberd_sm:make_new_sid() :: ejabberd_sm:sid(), + streamid = new_stream_id() :: binary(), + jid :: undefined | jid:jid(), + socket :: ranch_transport:socket(), + parser :: undefined | exml_stream:parser(), + shaper :: undefined | shaper:shaper(), + listener_opts :: mongoose_listener:options(), + state_mod = #{} :: #{module() => term()} + }). +-type c2s_data() :: #c2s_data{} | ejabberd_c2s:state(). +-type maybe_ok() :: ok | {error, atom()}. +-type fsm_res() :: gen_statem:event_handler_result(c2s_state(), c2s_data()). +-type packet() :: {jid:jid(), jid:jid(), exml:element()}. + +-type retries() :: 0..8. +-type stream_state() :: stream_start | authenticated. +-type c2s_state() :: connect + | {wait_for_stream, stream_state()} + | {wait_for_feature_before_auth, cyrsasl:sasl_state(), retries()} + | {wait_for_feature_after_auth, retries()} + | {wait_for_sasl_response, cyrsasl:sasl_state(), retries()} + | session_established. + +-export_type([packet/0, c2s_data/0, c2s_state/0]). + +%%%---------------------------------------------------------------------- +%%% gen_statem +%%%---------------------------------------------------------------------- + +-spec callback_mode() -> gen_statem:callback_mode_result(). +callback_mode() -> + handle_event_function. + +-spec init({ranch:ref(), ranch_tcp, mongoose_listener:options()}) -> + gen_statem:init_result(c2s_state(), c2s_data()). +init({RanchRef, ranch_tcp, LOpts}) -> + StateData = #c2s_data{listener_opts = LOpts}, + ConnectEvent = {next_event, internal, {connect, RanchRef}}, + {ok, connect, StateData, ConnectEvent}. + +-spec handle_event(gen_statem:event_type(), term(), c2s_state(), c2s_data()) -> fsm_res(). +handle_event(internal, {connect, RanchRef}, connect, + StateData = #c2s_data{listener_opts = #{shaper := ShaperName, + max_stanza_size := MaxStanzaSize} = LOpts}) -> + {ok, Parser} = exml_stream:new_parser([{max_child_size, MaxStanzaSize}]), + Shaper = shaper:new(ShaperName), + C2SSocket = mongoose_c2s_socket:new_socket(RanchRef, LOpts), + StateData1 = StateData#c2s_data{socket = C2SSocket, parser = Parser, shaper = Shaper}, + {next_state, {wait_for_stream, stream_start}, StateData1, state_timeout(LOpts)}; + +handle_event(internal, #xmlstreamstart{name = Name, attrs = Attrs}, {wait_for_stream, StreamState}, StateData) -> + StreamStart = #xmlel{name = Name, attrs = Attrs}, + handle_stream_start(StateData, StreamStart, StreamState); +handle_event(internal, _Unexpected, {wait_for_stream, _}, StateData = #c2s_data{lserver = LServer}) -> + case mongoose_config:get_opt(hide_service_name, false) of + true -> + {stop, {shutdown, stream_error}}; + false -> + send_header(StateData, LServer, <<"1.0">>, <<>>), + c2s_stream_error(StateData, mongoose_xmpp_errors:xml_not_well_formed()) + end; +handle_event(internal, #xmlstreamstart{}, _, StateData) -> + c2s_stream_error(StateData, mongoose_xmpp_errors:policy_violation()); + +handle_event(internal, #xmlstreamend{}, _, StateData) -> + send_trailer(StateData), + {stop, {shutdown, stream_end}}; +handle_event(internal, #xmlstreamerror{name = <<"child element too big">> = Err}, _, StateData) -> + c2s_stream_error(StateData, mongoose_xmpp_errors:policy_violation(StateData#c2s_data.lang, Err)); +handle_event(internal, #xmlstreamerror{name = Err}, _, StateData) -> + c2s_stream_error(StateData, mongoose_xmpp_errors:xml_not_well_formed(StateData#c2s_data.lang, Err)); +handle_event(internal, #xmlel{name = <<"starttls">>} = El, {wait_for_feature_before_auth, SaslState, Retries}, StateData) -> + case exml_query:attr(El, <<"xmlns">>) of + ?NS_TLS -> + handle_starttls(StateData, El, SaslState, Retries); + _ -> + c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_namespace()) + end; +handle_event(internal, #xmlel{name = <<"auth">>} = El, {wait_for_feature_before_auth, SaslState, Retries}, StateData) -> + case exml_query:attr(El, <<"xmlns">>) of + ?NS_SASL -> + handle_auth_start(StateData, El, SaslState, Retries); + _ -> + c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_namespace()) + end; +handle_event(internal, #xmlel{name = <<"response">>} = El, {wait_for_sasl_response, SaslState, Retries}, StateData) -> + case exml_query:attr(El, <<"xmlns">>) of + ?NS_SASL -> + handle_auth_continue(StateData, El, SaslState, Retries); + _ -> + c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_namespace()) + end; +handle_event(internal, #xmlel{name = <<"iq">>} = El, {wait_for_feature_after_auth, _} = C2SState, StateData) -> + case jlib:iq_query_info(El) of + #iq{type = set, xmlns = ?NS_BIND} = IQ -> + handle_bind_resource(StateData, C2SState, El, IQ); + _ -> + Err = jlib:make_error_reply(El, mongoose_xmpp_errors:bad_request()), + send_element_from_server_jid(StateData, Err), + maybe_retry_state(StateData, C2SState) + end; +handle_event(internal, #xmlel{} = El, session_established, StateData) -> + case verify_from(El, StateData#c2s_data.jid) of + false -> + c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_from()); + true -> + handle_c2s_packet(StateData, session_established, El) + end; + +%% This is an xml packet in any state other than session_established, +%% which is not one of the default ones defined in the RFC. For example stream management. +handle_event(internal, #xmlel{} = El, C2SState, StateData) -> + handle_foreign_packet(StateData, C2SState, El); + +handle_event(info, Info, FsmState, StateData) -> + handle_info(StateData, FsmState, Info); + +handle_event({timeout, Name}, Payload, C2SState, StateData) -> + handle_timeout(StateData, C2SState, Name, Payload); + +handle_event(state_timeout, state_timeout_termination, _FsmState, StateData) -> + StreamConflict = mongoose_xmpp_errors:connection_timeout(), + send_element_from_server_jid(StateData, StreamConflict), + send_trailer(StateData), + {stop, {shutdown, state_timeout}}; + +handle_event(EventType, EventContent, C2SState, StateData) -> + handle_foreign_event(StateData, C2SState, EventType, EventContent). + +-spec terminate(term(), c2s_state(), c2s_data()) -> term(). +terminate(Reason, C2SState, #c2s_data{host_type = HostType, lserver = LServer} = StateData) -> + ?LOG_DEBUG(#{what => c2s_statem_terminate, reason => Reason, c2s_state => C2SState, c2s_data => StateData}), + Acc0 = mongoose_acc:new(#{host_type => HostType, lserver => LServer, location => ?LOCATION}), + Acc1 = mongoose_acc:set(c2s, terminate, Reason, Acc0), + Acc2 = mongoose_c2s_hooks:user_terminate(HostType, Acc1, hook_arg(StateData)), + close_session(StateData, C2SState, Acc2, Reason), + close_parser(StateData), + close_socket(StateData), + bounce_messages(StateData), + ok. + +%%%---------------------------------------------------------------------- +%%% socket helpers +%%%---------------------------------------------------------------------- + +-spec handle_socket_data(c2s_data(), {_, _, iodata()}) -> fsm_res(). +handle_socket_data(StateData = #c2s_data{socket = Socket}, Payload) -> + case mongoose_c2s_socket:handle_socket_data(Socket, Payload) of + {error, _Reason} -> + {stop, {shutdown, socket_error}, StateData}; + Data -> + handle_socket_packet(StateData, Data) + end. + +-spec handle_socket_packet(c2s_data(), iodata()) -> fsm_res(). +handle_socket_packet(StateData = #c2s_data{parser = Parser, shaper = Shaper}, Packet) -> + ?LOG_DEBUG(#{what => received_xml_on_stream, packet => Packet, c2s_pid => self()}), + case exml_stream:parse(Parser, Packet) of + {error, Reason} -> + NextEvent = {next_event, internal, #xmlstreamerror{name = iolist_to_binary(Reason)}}, + {keep_state, StateData, NextEvent}; + {ok, NewParser, XmlElements} -> + Size = iolist_size(Packet), + {NewShaper, Pause} = shaper:update(Shaper, Size), + mongoose_metrics:update(global, [data, xmpp, received, xml_stanza_size], Size), + NewStateData = StateData#c2s_data{parser = NewParser, shaper = NewShaper}, + MaybePauseTimeout = maybe_pause(NewStateData, Pause), + StreamEvents = [ {next_event, internal, XmlEl} || XmlEl <- XmlElements ], + {keep_state, NewStateData, MaybePauseTimeout ++ StreamEvents} + end. + +-spec maybe_pause(c2s_data(), integer()) -> any(). +maybe_pause(_StateData, Pause) when Pause > 0 -> + [{{timeout, activate_socket}, Pause, activate_socket}]; +maybe_pause(#c2s_data{socket = Socket}, _) -> + mongoose_c2s_socket:activate_socket(Socket), + []. + +-spec close_socket(c2s_data()) -> ok | {error, term()}. +close_socket(#c2s_data{socket = Socket}) -> + mongoose_c2s_socket:close(Socket). + +-spec activate_socket(c2s_data()) -> ok | {error, term()}. +activate_socket(#c2s_data{socket = Socket}) -> + mongoose_c2s_socket:activate_socket(Socket). + +-spec send_text(c2s_data(), iodata()) -> ok | {error, term()}. +send_text(#c2s_data{socket = Socket}, Text) -> + mongoose_c2s_socket:send_text(Socket, Text). + +-spec filter_mechanism(c2s_data(), binary()) -> boolean(). +filter_mechanism(#c2s_data{socket = Socket}, <<"SCRAM-SHA-1-PLUS">>) -> + mongoose_c2s_socket:is_channel_binding_supported(Socket); +filter_mechanism(#c2s_data{socket = Socket}, <<"SCRAM-SHA-", _N:3/binary, "-PLUS">>) -> + mongoose_c2s_socket:is_channel_binding_supported(Socket); +filter_mechanism(#c2s_data{socket = Socket, listener_opts = LOpts}, <<"EXTERNAL">>) -> + mongoose_c2s_socket:has_peer_cert(Socket, LOpts); +filter_mechanism(_, _) -> + true. + +%%%---------------------------------------------------------------------- +%%% error handler helpers +%%%---------------------------------------------------------------------- + +-spec handle_foreign_event(c2s_data(), c2s_state(), gen_statem:event_type(), term()) -> fsm_res(). +handle_foreign_event(StateData = #c2s_data{host_type = HostType, lserver = LServer}, + C2SState, EventType, EventContent) -> + Params = (hook_arg(StateData, C2SState))#{event_type => EventType, event_content => EventContent}, + AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION}, + Acc0 = mongoose_acc:new(AccParams), + case mongoose_c2s_hooks:foreign_event(HostType, Acc0, Params) of + {ok, _Acc1} -> + ?LOG_WARNING(#{what => unknown_statem_event, c2s_state => C2SState, + event_type => EventType, event_content => EventContent}), + keep_state_and_data; + {stop, Acc1} -> + handle_state_after_packet(StateData, C2SState, Acc1) + end. + +-spec handle_stop_request(c2s_data(), c2s_state(), atom()) -> fsm_res(). +handle_stop_request(StateData = #c2s_data{host_type = HostType, lserver = LServer}, C2SState, Reason) -> + Params = (hook_arg(StateData, C2SState))#{extra => Reason}, + AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION}, + Acc0 = mongoose_acc:new(AccParams), + Res = mongoose_c2s_hooks:user_stop_request(HostType, Acc0, Params), + stop_if_unhandled(StateData, C2SState, Res, Reason). + +-spec handle_socket_closed(c2s_data(), c2s_state(), term()) -> fsm_res(). +handle_socket_closed(StateData = #c2s_data{host_type = HostType, lserver = LServer}, C2SState, Reason) -> + Params = (hook_arg(StateData, C2SState))#{extra => Reason}, + AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION}, + Acc0 = mongoose_acc:new(AccParams), + Res = mongoose_c2s_hooks:user_socket_closed(HostType, Acc0, Params), + stop_if_unhandled(StateData, C2SState, Res, socket_closed). + +-spec handle_socket_error(c2s_data(), c2s_state(), term()) -> fsm_res(). +handle_socket_error(StateData = #c2s_data{host_type = HostType, lserver = LServer}, C2SState, Reason) -> + Params = (hook_arg(StateData, C2SState))#{extra => Reason}, + AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION}, + Acc0 = mongoose_acc:new(AccParams), + Res = mongoose_c2s_hooks:user_socket_error(HostType, Acc0, Params), + stop_if_unhandled(StateData, C2SState, Res, socket_error). + +%% These conditions required that one and only one handler declared full control over it, +%% by making the hook stop at that point. If so, the process remains alive, +%% in control of the handler, otherwise, the condition is treated as terminal. +-spec stop_if_unhandled(c2s_data(), c2s_state(), gen_hook:hook_fn_res(mongoose_acc:t()), atom()) -> fsm_res(). +stop_if_unhandled(StateData, C2SState, {stop, Acc}, _) -> + handle_state_after_packet(StateData, C2SState, Acc); +stop_if_unhandled(_, _, {ok, _Acc}, Reason) -> + {stop, {shutdown, Reason}}. + +%%%---------------------------------------------------------------------- +%%% helpers +%%%---------------------------------------------------------------------- + +-spec handle_stream_start(c2s_data(), exml:element(), stream_state()) -> fsm_res(). +handle_stream_start(S0, StreamStart, StreamState) -> + Lang = get_xml_lang(StreamStart), + LServer = jid:nameprep(exml_query:attr(StreamStart, <<"to">>, <<>>)), + case {StreamState, + exml_query:attr(StreamStart, <<"xmlns:stream">>, <<>>), + exml_query:attr(StreamStart, <<"version">>, <<>>), + mongoose_domain_api:get_domain_host_type(LServer)} of + {stream_start, ?NS_STREAM, <<"1.0">>, {ok, HostType}} -> + S = S0#c2s_data{host_type = HostType, lserver = LServer, lang = Lang}, + stream_start_features_before_auth(S); + {authenticated, ?NS_STREAM, <<"1.0">>, {ok, HostType}} -> + S = S0#c2s_data{host_type = HostType, lserver = LServer, lang = Lang}, + stream_start_features_after_auth(S); + {_, ?NS_STREAM, _Pre1_0, {ok, HostType}} -> + %% (http://xmpp.org/rfcs/rfc6120.html#streams-negotiation-features) + S = S0#c2s_data{host_type = HostType, lserver = LServer, lang = Lang}, + stream_start_error(S, mongoose_xmpp_errors:unsupported_version()); + {_, ?NS_STREAM, _, {error, not_found}} -> + stream_start_error(S0, mongoose_xmpp_errors:host_unknown()); + {_, _, _, _} -> + stream_start_error(S0, mongoose_xmpp_errors:invalid_namespace()) + end. + +%% As stated in BCP47, 4.4.1: +%% Protocols or specifications that specify limited buffer sizes for +%% language tags MUST allow for language tags of at least 35 characters. +%% Do not store long language tag to avoid possible DoS/flood attacks +-spec get_xml_lang(exml:element()) -> <<_:8, _:_*8>>. +get_xml_lang(StreamStart) -> + case exml_query:attr(StreamStart, <<"xml:lang">>) of + Lang when is_binary(Lang), 0 < byte_size(Lang), byte_size(Lang) =< 35 -> + Lang; + _ -> + ?MYLANG + end. + +-spec handle_starttls(c2s_data(), exml:element(), cyrsasl:sasl_state(), retries()) -> fsm_res(). +handle_starttls(StateData = #c2s_data{socket = TcpSocket, + parser = Parser, + listener_opts = LOpts}, El, SaslState, Retries) -> + send_xml(StateData, mongoose_c2s_stanzas:tls_proceed()), %% send last negotiation chunk via tcp + case mongoose_c2s_socket:tcp_to_tls(TcpSocket, LOpts) of + {ok, TlsSocket} -> + {ok, NewParser} = exml_stream:reset_parser(Parser), + NewStateData = StateData#c2s_data{socket = TlsSocket, + parser = NewParser, + streamid = new_stream_id()}, + activate_socket(NewStateData), + {next_state, {wait_for_stream, stream_start}, NewStateData, state_timeout(LOpts)}; + {error, already_tls_connection} -> + ErrorStanza = mongoose_xmpp_errors:bad_request(StateData#c2s_data.lang, <<"bad_config">>), + Err = jlib:make_error_reply(El, ErrorStanza), + send_element_from_server_jid(StateData, Err), + maybe_retry_state(StateData, {wait_for_feature_before_auth, SaslState, Retries}); + {error, closed} -> + {stop, {shutdown, tls_closed}}; + {error, timeout} -> + {stop, {shutdown, tls_timeout}}; + {error, {tls_alert, TlsAlert}} -> + {stop, TlsAlert} + end. + +-spec handle_auth_start(c2s_data(), exml:element(), cyrsasl:sasl_state(), retries()) -> fsm_res(). +handle_auth_start(StateData, El, SaslState, Retries) -> + case {mongoose_c2s_socket:is_ssl(StateData#c2s_data.socket), StateData#c2s_data.listener_opts} of + {false, #{tls := #{mode := starttls_required}}} -> + Error = mongoose_xmpp_errors:policy_violation( + StateData#c2s_data.lang, <<"Use of STARTTLS required">>), + c2s_stream_error(StateData, Error); + _ -> + do_handle_auth_start(StateData, El, SaslState, Retries) + end. + +-spec do_handle_auth_start(c2s_data(), exml:element(), cyrsasl:sasl_state(), retries()) -> fsm_res(). +do_handle_auth_start(StateData, El, SaslState, Retries) -> + Mech = exml_query:attr(El, <<"mechanism">>), + ClientIn = base64:mime_decode(exml_query:cdata(El)), + HostType = StateData#c2s_data.host_type, + AuthMech = [M || M <- cyrsasl:listmech(HostType), filter_mechanism(StateData, M)], + SocketData = #{socket => StateData#c2s_data.socket, auth_mech => AuthMech}, + StepResult = cyrsasl:server_start(SaslState, Mech, ClientIn, SocketData), + handle_sasl_step(StateData, StepResult, SaslState, Retries). + +-spec handle_auth_continue(c2s_data(), exml:element(), cyrsasl:sasl_state(), retries()) -> fsm_res(). +handle_auth_continue(StateData, El, SaslState, Retries) -> + ClientIn = base64:mime_decode(exml_query:cdata(El)), + StepResult = cyrsasl:server_step(SaslState, ClientIn), + handle_sasl_step(StateData, StepResult, SaslState, Retries). + +-spec handle_sasl_step(c2s_data(), cyrsasl:sasl_result(), cyrsasl:sasl_state(), retries()) -> fsm_res(). +handle_sasl_step(StateData, {ok, Creds}, _, _) -> + handle_sasl_success(StateData, Creds); +handle_sasl_step(StateData = #c2s_data{listener_opts = LOpts}, {continue, ServerOut, NewSaslState}, _, Retries) -> + Challenge = [#xmlcdata{content = jlib:encode_base64(ServerOut)}], + send_element_from_server_jid(StateData, mongoose_c2s_stanzas:sasl_challenge_stanza(Challenge)), + {next_state, {wait_for_sasl_response, NewSaslState, Retries}, StateData, state_timeout(LOpts)}; +handle_sasl_step(#c2s_data{host_type = HostType, lserver = Server} = StateData, + {error, Error, Username}, SaslState, Retries) -> + ?LOG_INFO(#{what => auth_failed, + text => <<"Failed SASL authentication">>, + user => Username, lserver => Server, c2s_state => StateData}), + mongoose_hooks:auth_failed(HostType, Server, Username), + send_element_from_server_jid(StateData, mongoose_c2s_stanzas:sasl_failure_stanza(Error)), + maybe_retry_state(StateData, {wait_for_feature_before_auth, SaslState, Retries}); +handle_sasl_step(#c2s_data{host_type = HostType, lserver = Server} = StateData, + {error, Error}, SaslState, Retries) -> + mongoose_hooks:auth_failed(HostType, Server, unknown), + send_element_from_server_jid(StateData, mongoose_c2s_stanzas:sasl_failure_stanza(Error)), + maybe_retry_state(StateData, {wait_for_feature_before_auth, SaslState, Retries}). + +-spec handle_sasl_success(c2s_data(), term()) -> fsm_res(). +handle_sasl_success(State = #c2s_data{listener_opts = LOpts}, Creds) -> + ServerOut = mongoose_credentials:get(Creds, sasl_success_response, undefined), + send_element_from_server_jid(State, mongoose_c2s_stanzas:sasl_success_stanza(ServerOut)), + User = mongoose_credentials:get(Creds, username), + NewState = State#c2s_data{streamid = new_stream_id(), + jid = jid:make_bare(User, State#c2s_data.lserver)}, + ?LOG_INFO(#{what => auth_success, text => <<"Accepted SASL authentication">>, + c2s_state => NewState}), + {next_state, {wait_for_stream, authenticated}, NewState, state_timeout(LOpts)}. + +-spec stream_start_features_before_auth(c2s_data()) -> fsm_res(). +stream_start_features_before_auth(#c2s_data{host_type = HostType, lserver = LServer, + lang = Lang, listener_opts = LOpts} = S) -> + send_header(S, LServer, <<"1.0">>, Lang), + CredOpts = mongoose_credentials:make_opts(LOpts), + Creds = mongoose_credentials:new(LServer, HostType, CredOpts), + SASLState = cyrsasl:server_new(<<"jabber">>, LServer, HostType, <<>>, [], Creds), + StreamFeatures = mongoose_c2s_stanzas:stream_features_before_auth(HostType, LServer, LOpts, S), + send_element_from_server_jid(S, StreamFeatures), + {next_state, {wait_for_feature_before_auth, SASLState, ?AUTH_RETRIES}, S, state_timeout(LOpts)}. + +-spec stream_start_features_after_auth(c2s_data()) -> fsm_res(). +stream_start_features_after_auth(#c2s_data{host_type = HostType, lserver = LServer, + lang = Lang, listener_opts = LOpts} = S) -> + send_header(S, LServer, <<"1.0">>, Lang), + StreamFeatures = mongoose_c2s_stanzas:stream_features_after_auth(HostType, LServer), + send_element_from_server_jid(S, StreamFeatures), + {next_state, {wait_for_feature_after_auth, ?BIND_RETRIES}, S, state_timeout(LOpts)}. + +-spec handle_bind_resource(c2s_data(), c2s_state(), exml:element(), jlib:iq()) -> fsm_res(). +handle_bind_resource(StateData, C2SState, El, #iq{sub_el = SubEl} = IQ) -> + case jid:resourceprep(exml_query:path(SubEl, [{element, <<"resource">>}, cdata])) of + error -> + Err = jlib:make_error_reply(El, mongoose_xmpp_errors:bad_request()), + send_element_from_server_jid(StateData, Err), + maybe_retry_state(StateData, C2SState); + Resource -> + NewStateData = replace_resource(StateData, Resource), + Acc = element_to_origin_accum(NewStateData, El), + verify_user_and_open_session(NewStateData, C2SState, Acc, El, IQ) + end. + +-spec verify_user_and_open_session(c2s_data(), c2s_state(), mongoose_acc:t(), exml:element(), jlib:iq()) -> + fsm_res(). +verify_user_and_open_session(#c2s_data{host_type = HostType, jid = Jid} = StateData, C2SState, Acc, El, IQ) -> + case mongoose_c2s_hooks:user_open_session(HostType, Acc, (hook_arg(StateData, C2SState))) of + {ok, Acc1} -> + ?LOG_INFO(#{what => c2s_opened_session, c2s_state => StateData}), + do_open_session(StateData, C2SState, Acc1, IQ); + {stop, Acc1} -> + Acc2 = mongoose_hooks:forbidden_session_hook(HostType, Acc1, Jid), + ?LOG_INFO(#{what => forbidden_session, text => <<"User not allowed to open session">>, + acc => Acc2, c2s_state => StateData}), + Err = jlib:make_error_reply(El, mongoose_xmpp_errors:not_allowed()), + send_element_from_server_jid(StateData, Err), + maybe_retry_state(StateData, C2SState) + end. + +%% Note that RFC 3921 said: +%% > Upon establishing a session, a connected resource is said to be an "active resource". +%% But, RFC 6121 says: +%% > [RFC3921] specified one additional +%% precondition: formal establishment of an instant messaging and +%% presence session. Implementation and deployment experience has +%% shown that this additional step is unnecessary. However, for +%% backward compatibility an implementation MAY still offer that +%% feature. This enables older software to connect while letting +%% newer software save a round trip. +%% Note that RFC 3921 said: +%% > If no priority is provided, a server SHOULD consider the priority to be zero. +%% But, RFC 6121 says: +%% > If no priority is provided, the processing server or client MUST consider the priority to be zero. +-spec do_open_session(c2s_data(), c2s_state(), mongoose_acc:t(), jlib:iq()) -> fsm_res(). +do_open_session(#c2s_data{host_type = HostType, sid = SID, jid = Jid} = StateData, C2SState, Acc, IQ) -> + BindResult = mongoose_c2s_stanzas:successful_resource_binding(IQ, Jid), + MAcc = mongoose_c2s_acc:new(#{c2s_state => session_established, socket_send => [BindResult]}), + Acc1 = mongoose_acc:set_statem_acc(MAcc, Acc), + FsmActions = case ejabberd_sm:open_session(HostType, SID, Jid, 0, #{}) of + [] -> []; + ReplacedPids -> + Timeout = mongoose_config:get_opt({replaced_wait_timeout, HostType}), + [{{timeout, replaced_wait_timeout}, Timeout, ReplacedPids}] + end, + Acc2 = mongoose_c2s_acc:to_acc(Acc1, actions, FsmActions), + handle_state_after_packet(StateData, C2SState, Acc2). + +-spec maybe_retry_state(c2s_data(), c2s_state()) -> fsm_res(). +maybe_retry_state(StateData = #c2s_data{listener_opts = LOpts}, C2SState) -> + case maybe_retry_state(C2SState) of + {stop, Reason} -> + {stop, Reason, StateData}; + NextFsmState -> + {next_state, NextFsmState, StateData, state_timeout(LOpts)} + end. + +-spec handle_info(c2s_data(), c2s_state(), term()) -> fsm_res(). +handle_info(StateData, C2SState, {route, Acc}) -> + handle_stanza_to_client(StateData, C2SState, Acc); +handle_info(StateData, C2SState, {route, _From, _To, Acc}) -> + handle_stanza_to_client(StateData, C2SState, Acc); +handle_info(StateData, _C2SState, {TcpOrSSl, _Socket, _Packet} = SocketData) + when TcpOrSSl =:= tcp; TcpOrSSl =:= ssl -> + handle_socket_data(StateData, SocketData); +handle_info(StateData, C2SState, {Closed, _Socket} = SocketData) + when Closed =:= tcp_closed; Closed =:= ssl_closed -> + handle_socket_closed(StateData, C2SState, SocketData); +handle_info(StateData, C2SState, {Error, _Socket} = SocketData) + when Error =:= tcp_error; Error =:= ssl_error -> + handle_socket_error(StateData, C2SState, SocketData); +handle_info(StateData, _C2SState, replaced) -> + StreamConflict = mongoose_xmpp_errors:stream_conflict(), + send_element_from_server_jid(StateData, StreamConflict), + send_trailer(StateData), + {stop, {shutdown, replaced}}; +handle_info(StateData, _C2SState, {exit, Reason}) -> + StreamConflict = mongoose_xmpp_errors:stream_conflict(), + send_element_from_server_jid(StateData, StreamConflict), + send_trailer(StateData), + {stop, {shutdown, Reason}}; +handle_info(StateData, C2SState, {stop, Reason}) -> + handle_stop_request(StateData, C2SState, Reason); +handle_info(StateData, C2SState, Info) -> + handle_foreign_event(StateData, C2SState, info, Info). + +-spec handle_timeout(c2s_data(), c2s_state(), atom(), term()) -> fsm_res(). +handle_timeout(StateData, _C2SState, activate_socket, activate_socket) -> + activate_socket(StateData), + keep_state_and_data; +handle_timeout(StateData, C2SState, replaced_wait_timeout, ReplacedPids) -> + [ verify_process_alive(StateData, C2SState, Pid) || Pid <- ReplacedPids ], + keep_state_and_data; +handle_timeout(StateData, C2SState, Name, Handler) when is_atom(Name), is_function(Handler, 2) -> + C2sAcc = Handler(Name, StateData), + handle_state_result(StateData, C2SState, undefined, C2sAcc); +handle_timeout(StateData, _C2SState, state_timeout, state_timeout_termination) -> + StreamConflict = mongoose_xmpp_errors:connection_timeout(), + send_element_from_server_jid(StateData, StreamConflict), + send_trailer(StateData), + {stop, {shutdown, state_timeout}}. + +verify_process_alive(StateData, C2SState, Pid) -> + IsAlive = case node(Pid) =:= node() of + true -> erlang:is_process_alive(Pid); + false -> erlang:process_info(Pid) =:= undefined + end, + case IsAlive of + false -> ok; + true -> + ?LOG_WARNING(#{what => c2s_replaced_wait_timeout, + text => <<"Some processes are not responding when handling replace messages">>, + replaced_pid => Pid, state_name => C2SState, c2s_state => StateData}) + end. + +-spec maybe_retry_state(c2s_state()) -> c2s_state() | {stop, term()}. +maybe_retry_state(connect) -> connect; +maybe_retry_state(session_established) -> session_established; +maybe_retry_state({wait_for_stream, StreamState}) -> + {wait_for_stream, StreamState}; +maybe_retry_state({wait_for_feature_after_auth, 0}) -> + {stop, {shutdown, retries}}; +maybe_retry_state({wait_for_feature_before_auth, _, 0}) -> + {stop, {shutdown, retries}}; +maybe_retry_state({wait_for_sasl_response, _, 0}) -> + {stop, {shutdown, retries}}; +maybe_retry_state({wait_for_feature_after_auth, Retries}) -> + {wait_for_feature_after_auth, Retries - 1}; +maybe_retry_state({wait_for_feature_before_auth, SaslState, Retries}) -> + {wait_for_feature_before_auth, SaslState, Retries - 1}; +maybe_retry_state({wait_for_sasl_response, SaslState, Retries}) -> + {wait_for_sasl_response, SaslState, Retries - 1}. + +%% @doc Check 'from' attribute in stanza RFC 6120 Section 8.1.2.1 +-spec verify_from(exml:element(), jid:jid()) -> boolean(). +verify_from(El, StateJid) -> + case exml_query:attr(El, <<"from">>) of + undefined -> true; + SJid -> + jid:are_equal(jid:from_binary(SJid), StateJid) + end. + +-spec handle_foreign_packet(c2s_data(), c2s_state(), exml:element()) -> fsm_res(). +handle_foreign_packet(StateData = #c2s_data{host_type = HostType, lserver = LServer}, C2SState, El) -> + ?LOG_DEBUG(#{what => packet_before_session_established_sent, packet => El, c2s_pid => self()}), + ServerJid = jid:make_noprep(<<>>, LServer, <<>>), + AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION, + element => El, from_jid => ServerJid, to_jid => ServerJid}, + Acc0 = mongoose_acc:new(AccParams), + Params = hook_arg(StateData, C2SState), + {_, Acc1} = mongoose_c2s_hooks:user_send_xmlel(HostType, Acc0, Params), + handle_state_after_packet(StateData, C2SState, Acc1). + +-spec handle_c2s_packet(c2s_data(), c2s_state(), exml:element()) -> fsm_res(). +handle_c2s_packet(StateData = #c2s_data{host_type = HostType}, C2SState, El) -> + Acc0 = element_to_origin_accum(StateData, El), + case mongoose_c2s_hooks:c2s_preprocessing_hook(HostType, Acc0, hook_arg(StateData)) of + {ok, Acc1} -> + do_handle_c2s_packet(StateData, C2SState, Acc1); + {stop, _Acc1} -> + {next_state, session_established, StateData} + end. + +-spec do_handle_c2s_packet(c2s_data(), c2s_state(), mongoose_acc:t()) -> fsm_res(). +do_handle_c2s_packet(StateData = #c2s_data{host_type = HostType}, C2SState, Acc) -> + case mongoose_c2s_hooks:user_send_packet(HostType, Acc, hook_arg(StateData)) of + {ok, Acc1} -> + Acc2 = handle_stanza_from_client(StateData, Acc1, mongoose_acc:stanza_name(Acc1)), + handle_state_after_packet(StateData, C2SState, Acc2); + {stop, Acc1} -> + handle_state_after_packet(StateData, C2SState, Acc1) + end. + +%% @doc Process packets sent by the user (coming from user on c2s XMPP connection) +-spec handle_stanza_from_client(c2s_data(), mongoose_acc:t(), binary()) -> mongoose_acc:t(). +handle_stanza_from_client(StateData = #c2s_data{host_type = HostType}, Acc, <<"message">>) -> + TS0 = mongoose_acc:timestamp(Acc), + Acc1 = mongoose_c2s_hooks:user_send_message(HostType, Acc, hook_arg(StateData)), + Acc2 = maybe_route(Acc1), + TS1 = erlang:system_time(microsecond), + mongoose_metrics:update(HostType, [data, xmpp, sent, message, processing_time], (TS1 - TS0)), + Acc2; +handle_stanza_from_client(StateData = #c2s_data{host_type = HostType}, Acc, <<"iq">>) -> + Acc1 = mongoose_c2s_hooks:user_send_iq(HostType, Acc, hook_arg(StateData)), + maybe_route(Acc1); +handle_stanza_from_client(StateData = #c2s_data{host_type = HostType}, Acc, <<"presence">>) -> + {_, Acc1} = mongoose_c2s_hooks:user_send_presence(HostType, Acc, hook_arg(StateData)), + Acc1; +handle_stanza_from_client(StateData = #c2s_data{host_type = HostType}, Acc, _) -> + {_, Acc1} = mongoose_c2s_hooks:user_send_xmlel(HostType, Acc, hook_arg(StateData)), + Acc1. + +-spec handle_stanza_to_client(c2s_data(), c2s_state(), mongoose_acc:t()) -> fsm_res(). +handle_stanza_to_client(StateData = #c2s_data{host_type = HostType}, C2SState, Acc) -> + {From, To, El} = mongoose_acc:packet(Acc), + FinalEl = jlib:replace_from_to(From, To, El), + ParamsAcc = #{from_jid => From, to_jid => To, element => FinalEl}, + Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc), + case mongoose_c2s_hooks:user_receive_packet(HostType, Acc1, hook_arg(StateData)) of + {ok, Acc2} -> + Res = process_stanza_to_client(StateData, Acc2, mongoose_acc:stanza_name(Acc2)), + Acc3 = maybe_deliver(StateData, Res), + handle_state_after_packet(StateData, C2SState, Acc3); + {stop, _Acc1} -> + keep_state_and_data + end. + +%% @doc Process packets sent to the user (coming to user on c2s XMPP connection) +-spec process_stanza_to_client(c2s_data(), mongoose_acc:t(), binary()) -> + gen_hook:hook_fn_ret(mongoose_acc:t()). +process_stanza_to_client(StateData = #c2s_data{host_type = HostType}, Acc, <<"message">>) -> + mongoose_c2s_hooks:user_receive_message(HostType, Acc, hook_arg(StateData)); +process_stanza_to_client(StateData = #c2s_data{host_type = HostType}, Acc, <<"iq">>) -> + mongoose_c2s_hooks:user_receive_iq(HostType, Acc, hook_arg(StateData)); +process_stanza_to_client(StateData = #c2s_data{host_type = HostType}, Acc, <<"presence">>) -> + mongoose_c2s_hooks:user_receive_presence(HostType, Acc, hook_arg(StateData)); +process_stanza_to_client(StateData = #c2s_data{host_type = HostType}, Acc, _) -> + mongoose_c2s_hooks:user_receive_xmlel(HostType, Acc, hook_arg(StateData)). + +-spec handle_state_after_packet(c2s_data(), c2s_state(), mongoose_acc:t()) -> fsm_res(). +handle_state_after_packet(StateData, C2SState, Acc) -> + handle_state_result(StateData, C2SState, Acc, mongoose_c2s_acc:get_statem_result(Acc)). + +-spec handle_state_result(c2s_data(), + c2s_state(), + undefined | mongoose_acc:t(), + mongoose_c2s_acc:t()) -> fsm_res(). +handle_state_result(StateData, _, _, #{hard_stop := Reason}) when Reason =/= undefined -> + {stop, {shutdown, Reason}, StateData}; +handle_state_result(StateData0, C2SState, MaybeAcc, + #{state_mod := ModuleStates, actions := MaybeActions, + c2s_state := MaybeNewFsmState, c2s_data := MaybeNewFsmData, + socket_send := MaybeSocketSend}) -> + NextFsmState = case MaybeNewFsmState of + undefined -> C2SState; + _ -> MaybeNewFsmState + end, + StateData1 = case MaybeNewFsmData of + undefined -> StateData0; + _ -> MaybeNewFsmData + end, + StateData2 = case map_size(ModuleStates) of + 0 -> StateData1; + _ -> merge_mod_state(StateData1, ModuleStates) + end, + maybe_send_xml(StateData2, MaybeAcc, MaybeSocketSend), + {next_state, NextFsmState, StateData2, MaybeActions}. + +maybe_send_xml(_StateData, _Acc, []) -> + ok; +maybe_send_xml(StateData = #c2s_data{host_type = HostType, lserver = LServer}, undefined, ToSend) -> + Acc = mongoose_acc:new(#{host_type => HostType, lserver => LServer, location => ?LOCATION}), + send_element(StateData, ToSend, Acc); +maybe_send_xml(StateData, Acc, ToSend) -> + send_element(StateData, ToSend, Acc). + +-spec maybe_deliver(c2s_data(), gen_hook:hook_fn_ret(mongoose_acc:t())) -> mongoose_acc:t(). +maybe_deliver(StateData, {ok, Acc}) -> + Element = mongoose_acc:element(Acc), + send_element(StateData, Element, Acc), + Acc; +maybe_deliver(_, {stop, Acc}) -> + Acc. + +-spec maybe_route(gen_hook:hook_fn_ret(mongoose_acc:t())) -> mongoose_acc:t(). +maybe_route({ok, Acc}) -> + {FromJid, ToJid, El} = mongoose_acc:packet(Acc), + ejabberd_router:route(FromJid, ToJid, Acc, El), + Acc; +maybe_route({stop, Acc}) -> + Acc. + +%% @doc This function is executed when c2s receives a stanza from the TCP connection. +-spec element_to_origin_accum(c2s_data(), exml:element()) -> mongoose_acc:t(). +element_to_origin_accum(StateData = #c2s_data{sid = SID, jid = Jid}, El) -> + BaseParams = #{host_type => StateData#c2s_data.host_type, + lserver => StateData#c2s_data.lserver, + location => ?LOCATION, + element => El, + from_jid => Jid}, + Params = case exml_query:attr(El, <<"to">>) of + undefined -> BaseParams#{ to_jid => jid:to_bare(Jid) }; + _ToBin -> BaseParams + end, + Acc = mongoose_acc:new(Params), + mongoose_acc:set_permanent(c2s, [{module, ?MODULE}, {origin_sid, SID}, {origin_jid, Jid}], Acc). + +-spec stream_start_error(c2s_data(), exml:element()) -> fsm_res(). +stream_start_error(StateData, Error) -> + send_header(StateData, ?MYNAME, <<>>, StateData#c2s_data.lang), + c2s_stream_error(StateData, Error). + +-spec send_header(StateData :: c2s_data(), + Server :: jid:lserver(), + Version :: binary(), + Lang :: ejabberd:lang()) -> any(). +send_header(StateData, Server, Version, Lang) -> + Header = mongoose_c2s_stanzas:stream_header(Server, Version, Lang, StateData#c2s_data.streamid), + send_text(StateData, Header). + +send_trailer(StateData) -> + send_text(StateData, ?STREAM_TRAILER). + +-spec c2s_stream_error(c2s_data(), exml:element()) -> fsm_res(). +c2s_stream_error(StateData, Error) -> + ?LOG_DEBUG(#{what => c2s_stream_error, xml_error => Error, c2s_state => StateData}), + send_element_from_server_jid(StateData, Error), + send_text(StateData, ?STREAM_TRAILER), + {stop, {shutdown, stream_error}, StateData}. + +-spec send_element_from_server_jid(c2s_data(), exml:element()) -> any(). +send_element_from_server_jid(StateData, El) -> + Acc = mongoose_acc:new( + #{host_type => StateData#c2s_data.host_type, + lserver => StateData#c2s_data.lserver, + location => ?LOCATION, + from_jid => jid:make_noprep(<<>>, StateData#c2s_data.lserver, <<>>), + to_jid => StateData#c2s_data.jid, + element => El}), + send_element(StateData, El, Acc). + +-spec bounce_messages(c2s_data()) -> ok. +bounce_messages(StateData) -> + receive + {route, Acc} -> + reroute(StateData, Acc), + bounce_messages(StateData); + {route, _From, _To, Acc} -> + reroute(StateData, Acc), + bounce_messages(StateData); + _ -> + bounce_messages(StateData) + after 0 -> ok + end. + +reroute(#c2s_data{sid = Sid}, Acc) -> + {From, To, _El} = mongoose_acc:packet(Acc), + Acc2 = patch_acc_for_reroute(Acc, Sid), + ejabberd_router:route(From, To, Acc2). + +patch_acc_for_reroute(Acc, Sid) -> + case mongoose_acc:stanza_name(Acc) of + <<"message">> -> + Acc; + _ -> %% IQs and presences are allowed to come to the same Sid only + case mongoose_acc:get(c2s, receiver_sid, undefined, Acc) of + undefined -> + mongoose_acc:set_permanent(c2s, receiver_sid, Sid, Acc); + _ -> + Acc + end + end. + +-spec close_parser(c2s_data()) -> ok. +close_parser(#c2s_data{parser = undefined}) -> ok; +close_parser(#c2s_data{parser = Parser}) -> exml_stream:free_parser(Parser). + +-spec close_session(c2s_data(), c2s_state(), mongoose_acc:t(), term()) -> mongoose_acc:t(). +close_session(StateData, session_established, Acc, Reason) -> + Status = close_session_status(Reason), + PresenceUnavailable = mongoose_c2s_stanzas:presence_unavailable(Status), + Acc1 = mongoose_acc:update_stanza(#{from_jid => StateData#c2s_data.jid, + to_jid => jid:to_bare(StateData#c2s_data.jid), + element => PresenceUnavailable}, Acc), + ejabberd_sm:close_session_unset_presence( + Acc1, StateData#c2s_data.sid, StateData#c2s_data.jid, Status, sm_unset_reason(Reason)); +close_session(_, _, Acc, _) -> + Acc. + +close_session_status({shutdown, retries}) -> + <<"Too many attempts">>; +close_session_status({shutdown, replaced}) -> + <<"Replaced by new connection">>; +close_session_status(normal) -> + <<>>; +close_session_status(_) -> + <<"Unknown condition">>. + +sm_unset_reason({shutdown, retries}) -> + retries; +sm_unset_reason({shutdown, replaced}) -> + replaced; +sm_unset_reason(normal) -> + normal; +sm_unset_reason(_) -> + error. + +%% @doc This is the termination point - from here stanza is sent to the user +-spec send_element(c2s_data(), exml:element(), mongoose_acc:t()) -> maybe_ok(). +send_element(StateData = #c2s_data{host_type = <<>>}, El, _) -> + send_xml(StateData, El); +send_element(StateData = #c2s_data{host_type = HostType}, El, Acc) -> + mongoose_hooks:xmpp_send_element(HostType, Acc, El), + send_xml(StateData, El). + +-spec send_xml(c2s_data(), exml_stream:element() | [exml_stream:element()]) -> maybe_ok(). +send_xml(StateData, Xml) -> + send_text(StateData, exml:to_iolist(Xml)). + +state_timeout(#{c2s_state_timeout := Timeout}) -> + {state_timeout, Timeout, state_timeout_termination}. + +-spec replace_resource(c2s_data(), binary()) -> c2s_data(). +replace_resource(StateData, <<>>) -> + replace_resource(StateData, generate_random_resource()); +replace_resource(#c2s_data{jid = OldJid} = StateData, NewResource) -> + StateData#c2s_data{jid = jid:replace_resource(OldJid, NewResource)}. + +-spec new_stream_id() -> binary(). +new_stream_id() -> + mongoose_bin:gen_from_crypto(). + +-spec generate_random_resource() -> jid:lresource(). +generate_random_resource() -> + <<(mongoose_bin:gen_from_timestamp())/binary, "-", (mongoose_bin:gen_from_crypto())/binary>>. + +-spec hook_arg(c2s_data()) -> mongoose_c2s_hooks:hook_params(). +hook_arg(StateData) -> + hook_arg(StateData, session_established). + +-spec hook_arg(c2s_data(), c2s_state()) -> mongoose_c2s_hooks:hook_params(). +hook_arg(StateData, C2SState) -> + #{c2s_data => StateData, c2s_state => C2SState}. + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- + +-spec start_link({ranch:ref(), ranch_tcp, mongoose_listener:options()}, [gen_statem:start_opt()]) -> + gen_statem:start_ret(). +start_link(Params, ProcOpts) -> + gen_statem:start_link(?MODULE, Params, ProcOpts). + +-spec stop(gen_statem:server_ref(), atom()) -> ok. +stop(Pid, Reason) -> + gen_statem:cast(Pid, {stop, Reason}). + +-spec get_host_type(c2s_data()) -> mongooseim:host_type(). +get_host_type(#c2s_data{host_type = HostType}) -> + HostType. + +-spec get_lserver(c2s_data()) -> jid:lserver(). +get_lserver(#c2s_data{lserver = LServer}) -> + LServer. + +-spec get_sid(c2s_data()) -> ejabberd_sm:sid(). +get_sid(#c2s_data{sid = Sid}) -> + Sid. + +-spec get_ip(c2s_data()) -> term(). +get_ip(#c2s_data{socket = Socket}) -> + mongoose_c2s_socket:get_ip(Socket). + +-spec get_socket(c2s_data()) -> term(). +get_socket(#c2s_data{socket = Socket}) -> + Socket. + +-spec get_jid(c2s_data()) -> jid:jid() | undefined. +get_jid(#c2s_data{jid = Jid}) -> + Jid. + +-spec get_lang(c2s_data()) -> ejabberd:lang(). +get_lang(#c2s_data{lang = Lang}) -> + Lang. + +-spec get_stream_id(c2s_data()) -> binary(). +get_stream_id(#c2s_data{streamid = StreamId}) -> + StreamId. + +-spec get_mod_state(c2s_data(), atom()) -> term() | {error, not_found}. +get_mod_state(#c2s_data{state_mod = Handlers}, HandlerName) -> + maps:get(HandlerName, Handlers, {error, not_found}). + +-spec merge_mod_state(c2s_data(), map()) -> c2s_data(). +merge_mod_state(StateData = #c2s_data{state_mod = StateHandlers}, MoreHandlers) -> + StateData#c2s_data{state_mod = maps:merge(StateHandlers, MoreHandlers)}. + +-spec remove_mod_state(c2s_data(), atom()) -> c2s_data(). +remove_mod_state(StateData = #c2s_data{state_mod = Handlers}, HandlerName) -> + StateData#c2s_data{state_mod = maps:remove(HandlerName, Handlers)}. + +-spec merge_states(c2s_data(), c2s_data()) -> c2s_data(). +merge_states(S0 = #c2s_data{}, + S1 = #c2s_data{}) -> + S1#c2s_data{ + host_type = S0#c2s_data.host_type, + lserver = S0#c2s_data.lserver, + jid = S0#c2s_data.jid, + state_mod = S0#c2s_data.state_mod + }. diff --git a/src/c2s/mongoose_c2s_acc.erl b/src/c2s/mongoose_c2s_acc.erl new file mode 100644 index 00000000000..9091863212b --- /dev/null +++ b/src/c2s/mongoose_c2s_acc.erl @@ -0,0 +1,129 @@ +%% @doc Interface for actions that handler modules might request `mongoose_c2s' to act upon. +%% Note that some of the keys take a list of elements, which, in the case of a hook with many +%% handlers, means that at the end of the queue `mongoose_c2s' will get a list of requests +%% by different modules. These will be acted upon in the same order they were inserted in the +%% hook list, according to their priority. +%% The following keys are defined: +%% - `state_mod': key-value pairs of gen_mod module names and their desired state. +%% - `actions': a list of valid `gen_statem:action()' to request the `mongoose_c2s' engine. +%% - `c2s_state': a new state is requested for the state machine. +%% - `c2s_data': a new state data is requested for the state machine. +%% - `stop': an action of type `{info, {stop, Reason}}' is to be triggered. +%% - `hard_stop': no other request is allowed, the state machine is immediatly triggered to stop. +%% - `socket_send': xml elements to send on the socket to the user. +-module(mongoose_c2s_acc). + +-export([new/0, new/1, + get_statem_result/1, + from_mongoose_acc/2, + to_acc/3, to_acc_many/2 + ]). + +-type key() :: state_mod | actions | c2s_state | c2s_data | stop | hard_stop | socket_send. +-type pairs() :: [pair()]. +-type pair() :: {state_mod, {module(), term()}} + | {actions, gen_statem:action()} + | {c2s_state, mongoose_c2s:c2s_state()} + | {c2s_data, mongoose_c2s:c2s_data()} + | {stop, term() | {shutdown, atom()}} + | {hard_stop, term() | {shutdown, atom()}} + | {socket_send, exml:element()}. + +-type t() :: #{ + state_mod := #{module() => term()}, + actions := [gen_statem:action()], + c2s_state := undefined | mongoose_c2s:c2s_state(), + c2s_data := undefined | mongoose_c2s:c2s_data(), + hard_stop := undefined | Reason :: term(), + socket_send := [exml:element()] + }. + +-type params() :: #{ + state_mod => #{module() => term()}, + actions => [gen_statem:action()], + c2s_state => mongoose_c2s:c2s_state(), + c2s_data => mongoose_c2s:c2s_data(), + stop => Reason :: term(), + hard_stop => Reason :: term(), + socket_send => [exml:element()] + }. + +-export_type([t/0]). + +%% -------------------------------------------------------- +%% API +%% -------------------------------------------------------- + +-spec new() -> t(). +new() -> + #{ + state_mod => #{}, + actions => [], + c2s_state => undefined, + c2s_data => undefined, + hard_stop => undefined, + socket_send => [] + }. + +-spec new(params()) -> t(). +new(Params = #{stop := Reason}) -> + WithoutStop = maps:remove(stop, Params), + NewAction = [{next_event, info, {stop, Reason}}], + Fun = fun(Actions) -> [NewAction | Actions] end, + NewParams = maps:update_with(actions, Fun, NewAction, WithoutStop), + new(NewParams); +new(T) -> + maps:merge(new(), T). + +-spec get_statem_result(mongoose_acc:t()) -> mongoose_c2s_acc:t(). +get_statem_result(Acc) -> + C2SAcc = #{actions := Actions, + socket_send := SocketSend} = mongoose_acc:get_statem_acc(Acc), + C2SAcc#{actions := lists:reverse(Actions), + socket_send := lists:reverse(SocketSend)}. + +-spec from_mongoose_acc(mongoose_acc:t(), key()) -> term(). +from_mongoose_acc(Acc, Key) -> + #{Key := Value} = mongoose_acc:get_statem_acc(Acc), + Value. + +-spec to_acc(mongoose_acc:t(), state_mod, {module(), term()}) -> mongoose_acc:t(); + (mongoose_acc:t(), actions, gen_statem:action() | [gen_statem:action()]) -> mongoose_acc:t(); + (mongoose_acc:t(), c2s_state, term()) -> mongoose_acc:t(); + (mongoose_acc:t(), c2s_data, mongoose_c2s:c2s_data()) -> mongoose_acc:t(); + (mongoose_acc:t(), hard_stop, atom()) -> mongoose_acc:t(); + (mongoose_acc:t(), stop, atom() | {shutdown, atom()}) -> mongoose_acc:t(); + (mongoose_acc:t(), socket_send, exml:element()) -> mongoose_acc:t(). +to_acc(Acc, Key, NewValue) -> + C2SAcc = mongoose_acc:get_statem_acc(Acc), + C2SAcc1 = to_c2s_acc(C2SAcc, {Key, NewValue}), + mongoose_acc:set_statem_acc(C2SAcc1, Acc). + +-spec to_acc_many(mongoose_acc:t(), pairs()) -> mongoose_acc:t(). +to_acc_many(Acc, Pairs) -> + C2SAcc = mongoose_acc:get_statem_acc(Acc), + to_acc_many(Acc, C2SAcc, Pairs). + +-spec to_acc_many(mongoose_acc:t(), mongoose_c2s_acc:t(), pairs()) -> mongoose_acc:t(). +to_acc_many(Acc, C2SAcc, []) -> + mongoose_acc:set_statem_acc(C2SAcc, Acc); +to_acc_many(Acc, C2SAcc, [Pair | Pairs]) -> + NewCAcc = to_c2s_acc(C2SAcc, Pair), + to_acc_many(Acc, NewCAcc, Pairs). + +-spec to_c2s_acc(mongoose_c2s_acc:t(), pair()) -> mongoose_c2s_acc:t(). +to_c2s_acc(C2SAcc = #{state_mod := Handlers}, {state_mod, {Name, Handler}}) -> + C2SAcc#{state_mod := Handlers#{Name => Handler}}; +to_c2s_acc(C2SAcc = #{actions := Actions}, {actions, NewActions}) when is_list(NewActions) -> + C2SAcc#{actions := lists:reverse(NewActions) ++ Actions}; +to_c2s_acc(C2SAcc = #{actions := Actions}, {actions, Action}) -> + C2SAcc#{actions := [Action | Actions]}; +to_c2s_acc(C2SAcc = #{socket_send := Stanzas}, {socket_send, NewStanzas}) when is_list(NewStanzas) -> + C2SAcc#{socket_send := lists:reverse(NewStanzas) ++ Stanzas}; +to_c2s_acc(C2SAcc = #{socket_send := Stanzas}, {socket_send, Stanza}) -> + C2SAcc#{socket_send := [Stanza | Stanzas]}; +to_c2s_acc(C2SAcc = #{actions := Actions}, {stop, Reason}) -> + C2SAcc#{actions := [{next_event, info, {stop, Reason}} | Actions]}; +to_c2s_acc(C2SAcc, {Key, NewValue}) -> + #{Key := _OldValue} = C2SAcc, + C2SAcc#{Key := NewValue}. diff --git a/src/c2s/mongoose_c2s_hooks.erl b/src/c2s/mongoose_c2s_hooks.erl new file mode 100644 index 00000000000..3aad17064c5 --- /dev/null +++ b/src/c2s/mongoose_c2s_hooks.erl @@ -0,0 +1,220 @@ +%% @doc This module builds an interface to c2s event handling +-module(mongoose_c2s_hooks). + +-type hook_fn() :: fun((mongoose_acc:t(), mongoose_c2s_hooks:hook_params(), gen_hook:hook_extra()) -> + hook_result()). +-type hook_params() :: #{c2s_data := mongoose_c2s:state(), + c2s_state := mongoose_c2s:c2s_state(), + atom() => _}. +-type hook_result() :: gen_hook:hook_fn_ret(mongoose_acc:t()). +-export_type([hook_fn/0, hook_params/0, hook_result/0]). + +%% XML handlers +-export([c2s_preprocessing_hook/3]). +-export([user_send_packet/3, + user_receive_packet/3, + user_send_message/3, + user_send_iq/3, + user_send_presence/3, + user_send_xmlel/3, + user_receive_message/3, + user_receive_iq/3, + user_receive_presence/3, + user_receive_xmlel/3 + ]). + +%% General event handlers +-export([foreign_event/3, + user_open_session/3, + user_terminate/3, + user_stop_request/3, + user_socket_closed/3, + user_socket_error/3]). + +%%% @doc Event triggered after a user sends _any_ packet to the server. +%%% The purpose is to modify, or reject, packets, before they are further processed. +%%% TODO: this can really be merged into `user_send_packet' with early priority. +-spec c2s_preprocessing_hook(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +c2s_preprocessing_hook(HostType, Acc, #{c2s_data := StateData} = Params) -> + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, [StateData]), + {Tag, Acc1} = gen_hook:run_fold(c2s_preprocessing_hook, HostType, Acc, ParamsWithLegacyArgs), + case mongoose_acc:get(hook, result, undefined, Acc1) of + drop -> {stop, Acc1}; + _ -> {Tag, Acc1} + end. + +%%% @doc Event triggered after a user sends _any_ packet to the server. +%%% Examples of handlers can be metrics, archives, and any other subsystem +%%% that wants to see all stanzas the user delivers. +-spec user_send_packet(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_send_packet(HostType, Acc, Params) -> + {From, To, El} = mongoose_acc:packet(Acc), + Args = [From, To, El], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + gen_hook:run_fold(user_send_packet, HostType, Acc, ParamsWithLegacyArgs). + +%% @doc Triggered when a user receives a packet through any routing mechanism. +%% Examples of handlers can be metrics or carbons. +-spec user_receive_packet(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_receive_packet(HostType, Acc, #{c2s_data := C2SState} = Params) -> + {From, To, El} = mongoose_acc:packet(Acc), + Jid = mongoose_c2s:get_jid(C2SState), + Args = [Jid, From, To, El], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + gen_hook:run_fold(user_receive_packet, HostType, Acc, ParamsWithLegacyArgs). + +%% @doc Triggered when the user sends a stanza of type `message' +-spec user_send_message(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_send_message(HostType, Acc, Params) -> + gen_hook:run_fold(user_send_message, HostType, Acc, Params). + +%% @doc Triggered when the user sends a stanza of type `iq' +-spec user_send_iq(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_send_iq(HostType, Acc, Params) -> + Acc1 = mongoose_iq:update_acc_info(Acc), + gen_hook:run_fold(user_send_iq, HostType, Acc1, Params). + +%% @doc Triggered when the user sends a stanza of type `presence' +-spec user_send_presence(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_send_presence(HostType, Acc, Params) -> + gen_hook:run_fold(user_send_presence, HostType, Acc, Params). + +%% @doc Triggered when the user sends a packet which is not a proper XMPP stanza, i.e., +%% it is not of types `message', `iq', nor `presence'. +-spec user_send_xmlel(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_send_xmlel(HostType, Acc, Params) -> + gen_hook:run_fold(user_send_xmlel, HostType, Acc, Params). + + +%% @doc Triggered when the user received a stanza of type `message' +-spec user_receive_message(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_receive_message(HostType, Acc, Params) -> + gen_hook:run_fold(user_receive_message, HostType, Acc, Params). + +%% @doc Triggered when the user received a stanza of type `iq' +-spec user_receive_iq(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_receive_iq(HostType, Acc, Params) -> + Acc1 = mongoose_iq:update_acc_info(Acc), + gen_hook:run_fold(user_receive_iq, HostType, Acc1, Params). + +%% @doc Triggered when the user received a stanza of type `presence' +-spec user_receive_presence(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_receive_presence(HostType, Acc, Params) -> + gen_hook:run_fold(user_receive_presence, HostType, Acc, Params). + +%% @doc Triggered when the user received a packet which is not a proper XMPP stanza, i.e., +%% it is not of types `message', `iq', nor `presence'. +-spec user_receive_xmlel(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_receive_xmlel(HostType, Acc, Params) -> + gen_hook:run_fold(user_receive_xmlel, HostType, Acc, Params). + +%% @doc Triggered when the c2s statem process receives any event it is not defined to handle. +%% These events should not by default stop the process, and they are expected to +%% be handled by a single event handler, which should then stop the hook fold. +%% If no handler handles the event, it is logged. +-spec foreign_event(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: map(), + Result :: hook_result(). +foreign_event(HostType, Acc, Params) -> + gen_hook:run_fold(foreign_event, HostType, Acc, Params). + +%% @doc Triggered when the user binds a resource and attempts to open a session +%% This is ran _before_ registering the user in the session table. +%% If any handler returns a `stop' tag, the session establishment is rejected +%% and the user may be allowed to retry +-spec user_open_session(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_open_session(HostType, Acc, Params) -> + gen_hook:run_fold(user_open_session, HostType, Acc, Params). + +%% @doc Triggered when the user session is irrevocably terminating. +%% This is ran _before_ removing the user from the session table and closing his socket. +-spec user_terminate(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: mongoose_acc:t(). +user_terminate(HostType, Acc, Params) -> + {_, Res} = gen_hook:run_fold(user_terminate, HostType, Acc, Params), + Res. + +%% These conditions required that one and only one handler declared full control over it, +%% by making the hook stop at that point. If so, the process remains alive, +%% in control of the handler, otherwise, the condition is treated as terminal. +%% See `mongoose_c2s:stop_if_unhandled/3' + +%% @doc Triggered when an external event requests the connection to be closed. +-spec user_stop_request(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_stop_request(HostType, Acc, Params) -> + gen_hook:run_fold(user_stop_request, HostType, Acc, Params). + +%% @doc Triggered when the socket dies. +-spec user_socket_closed(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_socket_closed(HostType, Acc, Params) -> + gen_hook:run_fold(user_socket_closed, HostType, Acc, Params). + +%% @doc Triggered when the socket errors out. +-spec user_socket_error(HostType, Acc, Params) -> Result when + HostType :: mongooseim:host_type(), + Acc :: mongoose_acc:t(), + Params :: hook_params(), + Result :: hook_result(). +user_socket_error(HostType, Acc, Params) -> + gen_hook:run_fold(user_socket_error, HostType, Acc, Params). diff --git a/src/c2s/mongoose_c2s_listener.erl b/src/c2s/mongoose_c2s_listener.erl new file mode 100644 index 00000000000..8b17cddb46f --- /dev/null +++ b/src/c2s/mongoose_c2s_listener.erl @@ -0,0 +1,125 @@ +-module(mongoose_c2s_listener). + +-include("jlib.hrl"). +-include("mongoose.hrl"). +-include("mongoose_ns.hrl"). + +-behaviour(mongoose_listener). +-export([socket_type/0, start_listener/1]). + +-behaviour(ranch_protocol). +-export([start_link/3]). + +-behaviour(supervisor). +-export([start_link/1, init/1]). +-ignore_xref([start_link/1]). + +%% Hook handlers +-export([handle_user_open_session/3]). +%% backwards compatibility, process iq-session +-export([process_iq/5]). + +-type options() :: #{module := module(), + atom() => any()}. + +%% mongoose_listener +-spec socket_type() -> mongoose_listener:socket_type(). +socket_type() -> + xml_stream. + +-spec start_listener(options()) -> ok. +start_listener(Opts) -> + ListenerId = mongoose_listener_config:listener_id(Opts), + ChildSpec = listener_child_spec(ListenerId, Opts), + mongoose_listener_sup:start_child(ChildSpec), + ok. + +%% Hooks and handlers +-spec handle_user_open_session(mongoose_acc:t(), mongoose_c2s_hooks:hook_params(), map()) -> + mongoose_c2s_hooks:hook_result(). +handle_user_open_session(Acc, #{c2s_data := StateData}, #{host_type := HostType, access := Access}) -> + Jid = mongoose_c2s:get_jid(StateData), + LServer = mongoose_c2s:get_lserver(StateData), + case acl:match_rule(HostType, LServer, Access, Jid) of + allow -> + case mongoose_hooks:session_opening_allowed_for_user(HostType, Jid) of + allow -> {ok, Acc}; + _ -> {stop, Acc} + end; + deny -> + {stop, Acc} + end. + +process_iq(Acc, _From, _To, #iq{type = set, sub_el = #xmlel{name = <<"session">>}} = IQ, _) -> + {Acc, IQ#iq{type = result}}. + +%% ranch_protocol +start_link(Ref, Transport, Opts = #{hibernate_after := HibernateAfterTimeout}) -> + mongoose_c2s:start_link({Ref, Transport, Opts}, [{hibernate_after, HibernateAfterTimeout}]). + +%% supervisor +-spec start_link(options()) -> any(). +start_link(Opts) -> + supervisor:start_link(?MODULE, Opts). + +-spec init(options()) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +init(#{module := Module} = Opts) -> + HostTypes = ?ALL_HOST_TYPES, + acc_session_iq_handler(HostTypes), + maybe_add_access_check(HostTypes, Opts), + TransportOpts = prepare_socket_opts(Opts), + ListenerId = mongoose_listener_config:listener_id(Opts), + OptsWithTlsConfig = process_tls_opts(Opts), + Child = ranch:child_spec(ListenerId, ranch_tcp, TransportOpts, Module, OptsWithTlsConfig), + {ok, {#{strategy => one_for_one, intensity => 100, period => 1}, [Child]}}. + +acc_session_iq_handler(HostTypes) -> + [ gen_iq_handler:add_iq_handler_for_domain( + HostType, ?NS_SESSION, ejabberd_sm, fun ?MODULE:process_iq/5, #{}, no_queue) + || HostType <- HostTypes ]. + +maybe_add_access_check(_, #{access := all}) -> + ok; +maybe_add_access_check(HostTypes, #{access := Access}) -> + AclHooks = [ {user_open_session, HostType, fun ?MODULE:handle_user_open_session/3, #{access => Access}, 10} + || HostType <- HostTypes ], + gen_hook:add_handlers(AclHooks). + +process_tls_opts(Opts = #{tls := TlsOpts}) -> + ReadyTlsOpts = just_tls:make_ssl_opts(TlsOpts), + Opts#{tls := TlsOpts#{opts => ReadyTlsOpts}}; +process_tls_opts(Opts) -> + Opts. + +listener_child_spec(ListenerId, Opts) -> + #{id => ListenerId, + start => {?MODULE, start_link, [Opts]}, + restart => permanent, + shutdown => infinity, + type => supervisor, + modules => [?MODULE]}. + +prepare_socket_opts(#{port := Port, + ip_version := IPVersion, + ip_tuple := IPTuple, + backlog := Backlog, + num_acceptors := NumAcceptors, + max_connections := MaxConnections, + reuse_port := ReusePort}) -> + SocketOpts = [{nodelay, true}, + {keepalive, true}, + {ip, IPTuple}, + {port, Port}, + {backlog, Backlog}, + mongoose_listener_config:address_family(IPVersion) + | maybe_reuseport(ReusePort)], + #{max_connections => MaxConnections, + num_acceptors => NumAcceptors, + num_listen_sockets => num_listen_sockets(ReusePort), + socket_opts => SocketOpts}. + +maybe_reuseport(false) -> []; +maybe_reuseport(true) -> [{raw, 1, 15, <<1:32/native>>}]. + +num_listen_sockets(false) -> 1; +num_listen_sockets(true) -> erlang:system_info(schedulers_online). diff --git a/src/c2s/mongoose_c2s_socket.erl b/src/c2s/mongoose_c2s_socket.erl new file mode 100644 index 00000000000..afba2f6e4b4 --- /dev/null +++ b/src/c2s/mongoose_c2s_socket.erl @@ -0,0 +1,174 @@ +-module(mongoose_c2s_socket). + +-include("mongoose_logger.hrl"). + +-export([new_socket/2, + tcp_to_tls/2, + handle_socket_data/2, + activate_socket/1, + close/1, + send_text/2, + has_peer_cert/2, + is_channel_binding_supported/1, + is_ssl/1, + get_ip/1 + ]). + +-record(c2s_socket, { + transport :: transport(), + socket :: ranch_transport:socket(), + ip :: undefined | {inet:ip_address(), inet:port_number()}, + ranch_ref :: ranch:ref() + }). +-type socket() :: #c2s_socket{}. +-type transport() :: ranch_tcp | ranch_ssl | fast_tls. +-export_type([transport/0, socket/0]). + +%%%---------------------------------------------------------------------- +%%% socket helpers +%%%---------------------------------------------------------------------- + +-spec new_socket(ranch:ref(), mongoose_listener:options()) -> socket(). +new_socket(RanchRef, Opts = #{proxy_protocol := true}) -> + {ok, #{src_address := PeerIp, src_port := PeerPort}} = ranch:recv_proxy_header(RanchRef, 1000), + verify_ip_is_not_blacklisted(PeerIp), + {ok, TcpSocket} = ranch:handshake(RanchRef), + Ip = {PeerIp, PeerPort}, + handle_socket_and_ssl_config(RanchRef, Opts, TcpSocket, Ip); +new_socket(RanchRef, Opts = #{proxy_protocol := false}) -> + {ok, TcpSocket} = ranch:handshake(RanchRef), + {ok, {PeerIp, _PeerPort} = Ip} = ranch_tcp:peername(TcpSocket), + verify_ip_is_not_blacklisted(PeerIp), + handle_socket_and_ssl_config(RanchRef, Opts, TcpSocket, Ip). + +handle_socket_and_ssl_config( + RanchRef, #{tls := #{mode := tls, module := TlsMod, opts := TlsOpts}}, TcpSocket, Ip) -> + case tcp_to_tls(TlsMod, TcpSocket, TlsOpts) of + {ok, TlsSocket} -> + C2SSocket = #c2s_socket{transport = TlsMod, socket = TlsSocket, + ip = Ip, ranch_ref = RanchRef}, + activate_socket(C2SSocket), + C2SSocket; + {error, closed} -> + throw({stop, {shutdown, tls_closed}}); + {error, timeout} -> + throw({stop, {shutdown, tls_timeout}}); + {error, {tls_alert, TlsAlert}} -> + throw({stop, TlsAlert}) + end; +handle_socket_and_ssl_config(RanchRef, _Opts, TcpSocket, Ip) -> + C2SSocket = #c2s_socket{transport = ranch_tcp, socket = TcpSocket, + ip = Ip, ranch_ref = RanchRef}, + activate_socket(C2SSocket), + C2SSocket. + +-spec tcp_to_tls(socket(), mongoose_listener:options()) -> {ok, socket()} | {error, term()}. +tcp_to_tls(#c2s_socket{transport = ranch_tcp, socket = TcpSocket} = C2SSocket, + #{tls := #{module := TlsMod, opts := TlsOpts}}) -> + ranch_tcp:setopts(TcpSocket, [{active, false}]), + case tcp_to_tls(TlsMod, TcpSocket, TlsOpts) of + {ok, TlsSocket} -> + C2SSocket1 = C2SSocket#c2s_socket{transport = TlsMod, socket = TlsSocket}, + activate_socket(C2SSocket1), + {ok, C2SSocket1}; + {error, Reason} -> + {error, Reason} + end; +tcp_to_tls(_, _) -> + {error, already_tls_connection}. + +tcp_to_tls(fast_tls, TcpSocket, TlsOpts) -> + case fast_tls:tcp_to_tls(TcpSocket, TlsOpts) of + {ok, TlsSocket} -> + fast_tls:recv_data(TlsSocket, <<>>), + {ok, TlsSocket}; + Other -> Other + end; +tcp_to_tls(just_tls, TcpSocket, TlsOpts) -> + case ranch_ssl:handshake(TcpSocket, TlsOpts, 1000) of + {ok, TlsSocket, _} -> {ok, TlsSocket}; + Other -> Other + end. + +verify_ip_is_not_blacklisted(PeerIp) -> + case mongoose_hooks:check_bl_c2s(PeerIp) of + true -> + ?LOG_INFO(#{what => c2s_blacklisted_ip, ip => PeerIp, + text => <<"Connection attempt from blacklisted IP">>}), + throw({stop, {shutdown, ip_blacklisted}}); + false -> + ok + end. + +-spec handle_socket_data(socket(), {tcp | ssl, term(), iodata()}) -> + iodata() | {error, term()}. +handle_socket_data(#c2s_socket{transport = fast_tls, socket = TlsSocket}, {tcp, _Socket, Data}) -> + mongoose_metrics:update(global, [data, xmpp, received, encrypted_size], iolist_size(Data)), + case fast_tls:recv_data(TlsSocket, Data) of + {ok, DecryptedData} -> + DecryptedData; + {error, Reason} -> + {error, Reason} + end; +handle_socket_data(#c2s_socket{transport = ranch_ssl, socket = Socket}, {ssl, Socket, Data}) -> + mongoose_metrics:update(global, [data, xmpp, received, encrypted_size], iolist_size(Data)), + Data; +handle_socket_data(#c2s_socket{transport = ranch_tcp, socket = Socket}, {tcp, Socket, Data}) -> + Data; +handle_socket_data(_, _) -> + {error, bad_packet}. + +-spec activate_socket(socket()) -> ok | {error, term()}. +activate_socket(#c2s_socket{transport = fast_tls, socket = Socket}) -> + fast_tls:setopts(Socket, [{active, once}]); +activate_socket(#c2s_socket{transport = ranch_ssl, socket = Socket}) -> + ranch_ssl:setopts(Socket, [{active, once}]); +activate_socket(#c2s_socket{transport = ranch_tcp, socket = Socket}) -> + ranch_tcp:setopts(Socket, [{active, once}]). + +-spec close(socket()) -> ok. +close(#c2s_socket{transport = fast_tls, socket = Socket}) when Socket =/= undefined -> + fast_tls:close(Socket); +close(#c2s_socket{transport = ranch_ssl, socket = Socket}) when Socket =/= undefined -> + ranch_ssl:close(Socket); +close(#c2s_socket{transport = ranch_tcp, socket = Socket}) when Socket =/= undefined -> + ranch_tcp:close(Socket); +close(_) -> + ok. + +-spec send_text(socket(), iodata()) -> ok | {error, term()}. +send_text(#c2s_socket{transport = fast_tls, socket = Socket}, Text) -> + fast_tls:send(Socket, Text); +send_text(#c2s_socket{transport = ranch_ssl, socket = Socket}, Text) -> + ranch_ssl:send(Socket, Text); +send_text(#c2s_socket{transport = ranch_tcp, socket = Socket}, Text) -> + ranch_tcp:send(Socket, Text). + +%% 18 is OpenSSL's and fast_tls's error code for self-signed certs +-spec has_peer_cert(socket(), mongoose_listener:options()) -> boolean(). +has_peer_cert(#c2s_socket{transport = fast_tls, socket = Socket}, #{tls := TlsOpts}) -> + case {fast_tls:get_verify_result(Socket), fast_tls:get_peer_certificate(Socket), TlsOpts} of + {0, {ok, _}, _} -> true; + {18, {ok, _}, #{verify_mode := selfsigned_peer}} -> true; + {_, {ok, _}, _} -> false; + {_, error, _} -> false + end; +has_peer_cert(#c2s_socket{transport = ranch_ssl, socket = Socket}, _) -> + case ssl:peercert(Socket) of + {ok, _PeerCert} -> true; + _ -> false + end; +has_peer_cert(#c2s_socket{transport = ranch_tcp}, _) -> + false. + +-spec is_channel_binding_supported(socket()) -> boolean(). +is_channel_binding_supported(#c2s_socket{transport = Transport}) -> + fast_tls =:= Transport. + +-spec is_ssl(socket()) -> term(). +is_ssl(#c2s_socket{transport = Transport}) -> + ranch_tcp =/= Transport. + +-spec get_ip(socket()) -> term(). +get_ip(#c2s_socket{ip = Ip}) -> + Ip. diff --git a/src/c2s/mongoose_c2s_stanzas.erl b/src/c2s/mongoose_c2s_stanzas.erl new file mode 100644 index 00000000000..c6dea8cca69 --- /dev/null +++ b/src/c2s/mongoose_c2s_stanzas.erl @@ -0,0 +1,148 @@ +-module(mongoose_c2s_stanzas). + +-include("jlib.hrl"). + +-export([ + stream_header/4, + stream_features_before_auth/4, + tls_proceed/0, + stream_features_after_auth/2, + sasl_success_stanza/1, + sasl_failure_stanza/1, + sasl_challenge_stanza/1, + successful_resource_binding/2, + presence_unavailable/1 + ]). + +stream_header(Server, Version, Lang, StreamId) -> + VersionStr = case Version of + <<>> -> <<>>; + _ -> <<" version='", (Version)/binary, "'">> + end, + LangStr = case Lang of + <<>> -> <<>>; + _ when is_binary(Lang) -> <<" xml:lang='", (Lang)/binary, "'">> + end, + <<"", + "">>. + +-spec stream_features([exml:element() | exml:cdata()]) -> exml:element(). +stream_features(Features) -> + #xmlel{name = <<"stream:features">>, children = Features}. + +-spec stream_features_before_auth( + mongooseim:host_type(), jid:lserver(), mongoose_listener:options(), mongoose_c2s:c2s_data()) -> + exml:element(). +stream_features_before_auth(HostType, LServer, LOpts, StateData) -> + IsSSL = mongoose_c2s_socket:is_ssl(mongoose_c2s:get_socket(StateData)), + Features = determine_features(HostType, LServer, LOpts, IsSSL, StateData), + stream_features(Features). + +%% From RFC 6120, section 5.3.1: +%% +%% If TLS is mandatory-to-negotiate, the receiving entity SHOULD NOT +%% advertise support for any stream feature except STARTTLS during the +%% initial stage of the stream negotiation process, because further stream +%% features might depend on prior negotiation of TLS given the order of +%% layers in XMPP (e.g., the particular SASL mechanisms offered by the +%% receiving entity will likely depend on whether TLS has been negotiated). +%% +%% http://xmpp.org/rfcs/rfc6120.html#tls-rules-mtn +determine_features(_, _, #{tls := #{mode := starttls_required}}, false, _StateData) -> + [starttls_stanza(required)]; +determine_features(HostType, LServer, #{tls := #{mode := tls}}, _, StateData) -> + mongoose_hooks:c2s_stream_features(HostType, LServer) ++ maybe_sasl_mechanisms(HostType, StateData); +determine_features(HostType, LServer, _, _, StateData) -> + [starttls_stanza(optional) + | mongoose_hooks:c2s_stream_features(HostType, LServer) ++ maybe_sasl_mechanisms(HostType, StateData)]. + +maybe_sasl_mechanisms(HostType, StateData) -> + case cyrsasl:listmech(HostType) of + [] -> []; + Mechanisms -> + [#xmlel{name = <<"mechanisms">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = [ mechanism(M) + || M <- Mechanisms, mongoose_c2s:filter_mechanism(StateData, M) ]}] + end. + +-spec mechanism(binary()) -> exml:element(). +mechanism(M) -> + #xmlel{name = <<"mechanism">>, children = [#xmlcdata{content = M}]}. + +-spec starttls_stanza(required | optional) -> exml:element(). +starttls_stanza(TLSRequired) when TLSRequired =:= required; TLSRequired =:= optional -> + #xmlel{name = <<"starttls">>, + attrs = [{<<"xmlns">>, ?NS_TLS}], + children = [ #xmlel{name = <<"required">>} || TLSRequired =:= required ]}. + +-spec tls_proceed() -> exml:element(). +tls_proceed() -> + #xmlel{name = <<"proceed">>, + attrs = [{<<"xmlns">>, ?NS_TLS}]}. + +-spec stream_features_after_auth(mongooseim:host_type(), jid:lserver()) -> exml:element(). +stream_features_after_auth(HostType, LServer) -> + Features = [#xmlel{name = <<"bind">>, + attrs = [{<<"xmlns">>, ?NS_BIND}]} + | hook_enabled_features(HostType, LServer)], + stream_features(Features). + +hook_enabled_features(HostType, LServer) -> + mongoose_hooks:roster_get_versioning_feature(HostType) + ++ mongoose_hooks:c2s_stream_features(HostType, LServer). + +-spec sasl_success_stanza(binary()) -> exml:element(). +sasl_success_stanza(ServerOut) -> + C = case ServerOut of + undefined -> []; + _ -> [#xmlcdata{content = jlib:encode_base64(ServerOut)}] + end, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = C}. + +-spec sasl_failure_stanza(binary() | {binary(), iodata() | undefined}) -> exml:element(). +sasl_failure_stanza(Error) when is_binary(Error) -> + sasl_failure_stanza({Error, undefined}); +sasl_failure_stanza({Error, Text}) -> + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = [#xmlel{name = Error} | maybe_text_tag(Text)]}. + +maybe_text_tag(undefined) -> []; +maybe_text_tag(Text) -> + [#xmlel{name = <<"text">>, + children = [#xmlcdata{content = Text}]}]. + +-spec sasl_challenge_stanza([exml:element() | exml:cdata()]) -> exml:element(). +sasl_challenge_stanza(Challenge) -> + #xmlel{name = <<"challenge">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = Challenge}. + +-spec successful_resource_binding(jlib:iq(), jid:jid()) -> exml:element(). +successful_resource_binding(IQ, Jid) -> + JIDEl = #xmlel{name = <<"jid">>, + children = [#xmlcdata{content = jid:to_binary(Jid)}]}, + Res = IQ#iq{type = result, + sub_el = [#xmlel{name = <<"bind">>, + attrs = [{<<"xmlns">>, ?NS_BIND}], + children = [JIDEl]}]}, + jlib:iq_to_xml(Res). + +-spec presence_unavailable(binary()) -> exml:element(). +presence_unavailable(<<>>) -> + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}]}; +presence_unavailable(Status) -> + StatusEl = #xmlel{name = <<"status">>, + children = [#xmlcdata{content = Status}]}, + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = [StatusEl]}. diff --git a/src/config/mongoose_config_spec.erl b/src/config/mongoose_config_spec.erl index abdef5cfa6c..d9187fd31ee 100644 --- a/src/config/mongoose_config_spec.erl +++ b/src/config/mongoose_config_spec.erl @@ -26,7 +26,8 @@ process_acl_condition/1, process_s2s_host_policy/1, process_s2s_address/1, - process_domain_cert/1]). + process_domain_cert/1, + process_infinity_as_zero/1]). -include("mongoose_config_spec.hrl"). @@ -282,17 +283,18 @@ xmpp_listener_common() -> #section{items = #{<<"backlog">> => #option{type = integer, validate = non_negative}, <<"proxy_protocol">> => #option{type = boolean}, - <<"hibernate_after">> => #option{type = integer, + <<"hibernate_after">> => #option{type = int_or_infinity, validate = non_negative}, <<"max_stanza_size">> => #option{type = int_or_infinity, - validate = positive}, + validate = positive, + process = fun ?MODULE:process_infinity_as_zero/1}, <<"num_acceptors">> => #option{type = integer, - validate = positive} + validate = positive} }, - defaults = #{<<"backlog">> => 100, + defaults = #{<<"backlog">> => 1024, <<"proxy_protocol">> => false, <<"hibernate_after">> => 0, - <<"max_stanza_size">> => infinity, + <<"max_stanza_size">> => 0, <<"num_acceptors">> => 100} }. @@ -303,15 +305,22 @@ xmpp_listener_extra(c2s) -> validate = non_empty}, <<"zlib">> => #option{type = integer, validate = positive}, - <<"max_fsm_queue">> => #option{type = integer, - validate = positive}, + <<"max_connections">> => #option{type = int_or_infinity, + validate = positive}, + <<"c2s_state_timeout">> => #option{type = int_or_infinity, + validate = non_negative, + wrap = global_config}, + <<"reuse_port">> => #option{type = boolean}, <<"allowed_auth_methods">> => #list{items = #option{type = atom, validate = {module, ejabberd_auth}}, validate = unique}, <<"tls">> => c2s_tls()}, defaults = #{<<"access">> => all, - <<"shaper">> => none} + <<"shaper">> => none, + <<"max_connections">> => infinity, + <<"c2s_state_timeout">> => 5000, + <<"reuse_port">> => false} }; xmpp_listener_extra(s2s) -> TLSSection = tls([server], [fast_tls]), @@ -1025,7 +1034,7 @@ process_listener([item, Type | _], Opts) -> mongoose_listener_config:ensure_ip_options(Opts#{module => listener_module(Type)}). listener_module(<<"http">>) -> ejabberd_cowboy; -listener_module(<<"c2s">>) -> ejabberd_c2s; +listener_module(<<"c2s">>) -> mongoose_c2s_listener; listener_module(<<"s2s">>) -> ejabberd_s2s_in; listener_module(<<"service">>) -> ejabberd_service. @@ -1104,3 +1113,6 @@ process_s2s_address(M) -> process_domain_cert(#{domain := Domain, certfile := Certfile}) -> {Domain, Certfile}. + +process_infinity_as_zero(infinity) -> 0; +process_infinity_as_zero(Num) -> Num. diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index a27087d8c3d..ab4101b3e24 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -199,7 +199,7 @@ route(From, To, Acc, El) -> Acc end. --spec make_new_sid() -> ejabberd_sm:sid(). +-spec make_new_sid() -> sid(). make_new_sid() -> {erlang:system_time(microsecond), self()}. @@ -329,8 +329,7 @@ disconnect_removed_user(Acc, User, Server) -> -spec get_user_resources(JID :: jid:jid()) -> [binary()]. -get_user_resources(JID) -> - #jid{luser = LUser, lserver = LServer} = JID, +get_user_resources(#jid{luser = LUser, lserver = LServer}) -> Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), [element(3, S#session.usr) || S <- clean_session_list(Ss)]. @@ -730,10 +729,11 @@ do_route_no_resource(<<"presence">>, From, To, Acc, El) -> true -> PResources = get_user_present_resources(To), lists:foldl(fun({_, R}, A) -> - do_route(A, From, jid:replace_resource(To, R), El) - end, - Acc, - PResources); + NewTo = jid:replace_resource(To, R), + NewAccParams = #{element => El, from_jid => From, to_jid => NewTo}, + A0 = mongoose_acc:update_stanza(NewAccParams, A), + do_route(A0, From, NewTo, El) + end, Acc, PResources); false -> Acc end; diff --git a/src/gen_hook.erl b/src/gen_hook.erl index 5125f3e2340..a0943514575 100644 --- a/src/gen_hook.erl +++ b/src/gen_hook.erl @@ -26,35 +26,42 @@ -include("mongoose.hrl"). -type hook_name() :: atom(). --type hook_tag() :: mongoose:host_type() | global. +-type hook_tag() :: mongooseim:host_type() | global. %% while Accumulator is not limited to any type, it's recommended to use maps. -type hook_acc() :: any(). -type hook_params() :: map(). -type hook_extra() :: map(). +-type extra() :: #{hook_name := hook_name(), + hook_tag := hook_tag(), + host_type => mongooseim:host_type(), + _ => _}. --type hook_fn_ret_value() :: {ok | stop, NewAccumulator :: hook_acc()}. +-type hook_fn_ret() :: hook_fn_ret(hook_acc()). +-type hook_fn_ret(Acc) :: {ok | stop, Acc}. -type hook_fn() :: %% see run_fold/4 documentation fun((Accumulator :: hook_acc(), ExecutionParameters :: hook_params(), - ExtraParameters :: hook_extra()) -> hook_fn_ret_value()). + ExtraParameters :: extra()) -> hook_fn_ret()). -type key() :: {HookName :: atom(), Tag :: any()}. --type hook_tuple() :: {HookName :: hook_name(), - Tag :: hook_tag(), - Function :: hook_fn(), - Extra :: hook_extra(), - Priority :: pos_integer()}. +-type hook_tuple() :: hook_tuple(hook_fn()). +-type hook_tuple(HookFn) :: {HookName :: hook_name(), + Tag :: hook_tag(), + Function :: HookFn, + Extra :: hook_extra(), + Priority :: pos_integer()}. --type hook_list() :: [hook_tuple()]. +-type hook_list() :: hook_list(hook_fn()). +-type hook_list(HookFn) :: [hook_tuple(HookFn)]. --export_type([hook_fn/0, hook_list/0]). +-export_type([hook_fn/0, hook_list/0, hook_list/1, hook_fn_ret/0, hook_fn_ret/1, extra/0]). -record(hook_handler, {prio :: pos_integer(), hook_fn :: hook_fn(), - extra :: map()}). + extra :: extra()}). -define(TABLE, ?MODULE). %%%---------------------------------------------------------------------- @@ -118,7 +125,7 @@ delete_handler({HookName, Tag, _, _, _} = HookTuple) -> -spec run_fold(HookName :: hook_name(), Tag :: hook_tag(), Acc :: hook_acc(), - Params :: hook_params()) -> hook_fn_ret_value(). + Params :: hook_params()) -> hook_fn_ret(). run_fold(HookName, Tag, Acc, Params) -> Key = hook_key(HookName, Tag), case ets:lookup(?TABLE, Key) of @@ -196,7 +203,7 @@ code_change(_OldVsn, State, _Extra) -> %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- --spec run_hook([#hook_handler{}], hook_acc(), hook_params(), key()) -> hook_fn_ret_value(). +-spec run_hook([#hook_handler{}], hook_acc(), hook_params(), key()) -> hook_fn_ret(). run_hook([], Acc, _Params, _Key) -> {ok, Acc}; run_hook([Handler | Ls], Acc, Params, Key) -> @@ -211,7 +218,7 @@ run_hook([Handler | Ls], Acc, Params, Key) -> end. -spec apply_hook_function(#hook_handler{}, hook_acc(), hook_params()) -> - hook_fn_ret_value() | {'EXIT', Reason :: any()}. + hook_fn_ret() | {'EXIT', Reason :: any()}. apply_hook_function(#hook_handler{hook_fn = HookFn, extra = Extra}, Acc, Params) -> safely:apply(HookFn, [Acc, Params, Extra]). diff --git a/src/global_distrib/mod_global_distrib_receiver.erl b/src/global_distrib/mod_global_distrib_receiver.erl index 3a2d84b8b36..a56d62ed5c6 100644 --- a/src/global_distrib/mod_global_distrib_receiver.erl +++ b/src/global_distrib/mod_global_distrib_receiver.erl @@ -26,7 +26,7 @@ -include("jlib.hrl"). -include("global_distrib_metrics.hrl"). --export([start_link/4]). +-export([start_link/3]). -export([start/2, stop/1, deps/2]). -export([init/1, handle_info/2, handle_cast/2, handle_call/3, code_change/3, terminate/2]). @@ -48,10 +48,10 @@ %% API %%-------------------------------------------------------------------- --spec start_link(Ref :: reference(), Socket :: gen_tcp:socket(), Transport :: ranch_tcp, +-spec start_link(Ref :: reference(), Transport :: ranch_tcp, Opts :: [term()]) -> {ok, pid()}. -start_link(Ref, Socket, ranch_tcp, Opts) -> - Pid = proc_lib:spawn_link(?MODULE, init, [{Ref, Socket, Opts}]), +start_link(Ref, ranch_tcp, Opts) -> + Pid = proc_lib:spawn_link(?MODULE, init, [{Ref, ranch_tcp, Opts}]), {ok, Pid}. %%-------------------------------------------------------------------- @@ -82,9 +82,9 @@ deps(_HostType, Opts) -> %% ranch_protocol API %%-------------------------------------------------------------------- -init({Ref, RawSocket, _Opts}) -> +init({Ref, ranch_tcp, _Opts}) -> process_flag(trap_exit, true), - ok = ranch:accept_ack(Ref), + {ok, RawSocket} = ranch:handshake(Ref), ConnOpts = opt(connections), {ok, Socket} = mod_global_distrib_transport:wrap(RawSocket, ConnOpts), ok = mod_global_distrib_transport:setopts(Socket, [{active, once}]), @@ -204,7 +204,7 @@ start_listeners() -> RetriesLeft :: non_neg_integer()) -> any(). start_listener({Addr, Port} = Ref, RetriesLeft) -> ?LOG_INFO(#{what => gd_start_listener, address => Addr, port => Port}), - case ranch:start_listener(Ref, 10, ranch_tcp, [{ip, Addr}, {port, Port}], ?MODULE, []) of + case ranch:start_listener(Ref, ranch_tcp, #{num_acceptors => 10, ip => Addr, port => Port}, ?MODULE, []) of {ok, _} -> ok; {error, eaddrinuse} when RetriesLeft > 0 -> ?LOG_ERROR(#{what => gd_start_listener_failed, address => Addr, port => Port, diff --git a/src/metrics/mongoose_metrics_definitions.hrl b/src/metrics/mongoose_metrics_definitions.hrl index 3da2a140759..3ff92088951 100644 --- a/src/metrics/mongoose_metrics_definitions.hrl +++ b/src/metrics/mongoose_metrics_definitions.hrl @@ -71,7 +71,9 @@ [data, xmpp, received, xml_stanza_size], [data, xmpp, sent, encrypted_size], [data, xmpp, sent, compressed_size], - [data, xmpp, sent, xml_stanza_size]]). + [data, xmpp, sent, xml_stanza_size], + [data, xmpp, sent, message, processing_time] + ]). -define(DATA_FUN_METRICS, [{[data, dist], diff --git a/src/mongoose_acc.erl b/src/mongoose_acc.erl index 1f91212dd32..c1c6a43cf08 100644 --- a/src/mongoose_acc.erl +++ b/src/mongoose_acc.erl @@ -34,6 +34,8 @@ ]). % Stanza update -export([update_stanza/2]). +% C2S accumulator +-export([get_statem_acc/1, set_statem_acc/2]). % Access to namespaced fields -export([ set/3, @@ -85,6 +87,7 @@ lserver := jid:lserver(), host_type := binary() | undefined, non_strippable := [ns_key()], + statem_acc := mongoose_c2s_acc:t(), ns_key() => Value :: any() }. @@ -96,7 +99,8 @@ element => exml:element() | undefined, host_type => binary() | undefined, % optional from_jid => jid:jid() | undefined, % optional - to_jid => jid:jid() | undefined % optional + to_jid => jid:jid() | undefined, % optional + statem_acc => mongoose_c2s_acc:t() % optional }. -type strip_params() :: #{ @@ -131,12 +135,13 @@ new(#{ location := Location, lserver := LServer } = Params) -> #{ mongoose_acc => true, ref => make_ref(), - timestamp => os:system_time(microsecond), + timestamp => erlang:system_time(microsecond), origin_pid => self(), origin_location => Location, stanza => Stanza, lserver => LServer, host_type => HostType, + statem_acc => get_mongoose_c2s_acc(Params), %% The non_strippable elements must be unique. %% This used to be represented with the sets module, but as the number of elements inserted %% was too small, sets were themselves an overhead, and also annoying when printing @@ -177,7 +182,7 @@ to_jid(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID } }) -> to_jid(#{ mongoose_acc := true }) -> undefined. --spec packet(Acc :: t()) -> ejabberd_c2s:packet() | undefined. +-spec packet(Acc :: t()) -> mongoose_c2s:packet() | undefined. packet(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID, from_jid := FromJID, element := El } }) -> @@ -207,6 +212,14 @@ stanza_ref(#{ mongoose_acc := true }) -> update_stanza(NewStanzaParams, #{ mongoose_acc := true } = Acc) -> Acc#{ stanza := stanza_from_params(NewStanzaParams) }. +-spec get_statem_acc(Acc :: t()) -> mongoose_c2s_acc:t(). +get_statem_acc(#{ mongoose_acc := true, statem_acc := StatemAcc }) -> + StatemAcc. + +-spec set_statem_acc(NewStatemAcc :: mongoose_c2s_acc:t(), Acc :: t()) -> t(). +set_statem_acc(NewStatemAcc, Acc = #{ mongoose_acc := true }) -> + Acc#{statem_acc := NewStatemAcc}. + %% Values set with this function are discarded during 'strip' operation... -spec set(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t(). set(NS, K, V, #{ mongoose_acc := true } = Acc) -> @@ -291,7 +304,8 @@ delete(NS, Acc) -> -spec strip(Acc :: t()) -> t(). strip(#{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) -> - maps:with(NonStrippable ++ default_non_strippable(), Acc). + Stripped = maps:with(NonStrippable ++ default_non_strippable(), Acc), + Stripped#{statem_acc := mongoose_c2s_acc:new()}. -spec strip(ParamsToOverwrite :: strip_params(), Acc :: t()) -> t(). strip(#{ lserver := NewLServer } = Params, Acc) -> @@ -308,6 +322,12 @@ get_host_type(#{host_type := HostType}) -> get_host_type(_) -> undefined. +-spec get_mongoose_c2s_acc(new_acc_params() | strip_params()) -> mongoose_c2s_acc:t() | undefined. +get_mongoose_c2s_acc(#{statem_acc := C2SAcc}) -> + C2SAcc; +get_mongoose_c2s_acc(_) -> + mongoose_c2s_acc:new(). + -spec stanza_from_params(Params :: stanza_params() | strip_params()) -> stanza_metadata(). stanza_from_params(#{ element := El } = Params) -> @@ -341,6 +361,7 @@ default_non_strippable() -> stanza, lserver, host_type, + statem_acc, non_strippable ]. diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index c60646178f9..08f8a2aa2e5 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -570,7 +570,7 @@ c2s_filter_packet(State, Feature, To, Packet) -> -spec c2s_preprocessing_hook(HostType, Acc, State) -> Result when HostType :: mongooseim:host_type(), Acc :: mongoose_acc:t(), - State :: ejabberd_c2s:state(), + State :: mongoose_c2s:c2s_data(), Result :: mongoose_acc:t(). c2s_preprocessing_hook(HostType, Acc, State) -> run_hook_for_host_type(c2s_preprocessing_hook, HostType, Acc, [State]). diff --git a/src/sasl/cyrsasl.erl b/src/sasl/cyrsasl.erl index bf80574e6cb..52f11f71658 100644 --- a/src/sasl/cyrsasl.erl +++ b/src/sasl/cyrsasl.erl @@ -51,8 +51,11 @@ -type error() :: {error, binary() | {binary(), binary()}} | {error, binary() | {binary(), binary()}, jid:user()}. --export_type([mechanism/0, - error/0]). +-type sasl_result() :: {ok, mongoose_credentials:t()} + | {continue, binary(), sasl_state()} + | error(). + +-export_type([mechanism/0, error/0, sasl_result/0]). -callback mechanism() -> mechanism(). @@ -100,9 +103,7 @@ server_new(Service, ServerFQDN, HostType, UserRealm, _SecFlags, Creds) -> Mech :: mechanism(), ClientIn :: binary(), SocketData :: map(), - Result :: {ok, mongoose_credentials:t()} - | {'continue', _, sasl_state()} - | error(). + Result :: sasl_result(). server_start(#sasl_state{myname = Host, host_type = HostType} = State, Mech, ClientIn, SocketData) -> case [M || M <- get_modules(HostType), M:mechanism() =:= Mech, @@ -122,9 +123,7 @@ is_module_supported(HostType, Module) -> mongoose_fips:supports_sasl_module(Module) andalso ejabberd_auth:supports_sasl_module(HostType, Module). -spec server_step(State :: sasl_state(), ClientIn :: binary()) -> Result when - Result :: {ok, mongoose_credentials:t()} - | {'continue', _, sasl_state()} - | error(). + Result :: sasl_result(). server_step(State, ClientIn) -> Module = State#sasl_state.mech_mod, MechState = State#sasl_state.mech_state, diff --git a/test/common/config_parser_helper.erl b/test/common/config_parser_helper.erl index f579e8f21c5..067fc765155 100644 --- a/test/common/config_parser_helper.erl +++ b/test/common/config_parser_helper.erl @@ -1056,10 +1056,10 @@ default_room_opts() -> subject_author => <<>>}. common_xmpp_listener_config() -> - (common_listener_config())#{backlog => 100, + (common_listener_config())#{backlog => 1024, proxy_protocol => false, hibernate_after => 0, - max_stanza_size => infinity, + max_stanza_size => 0, num_acceptors => 100}. common_listener_config() -> @@ -1109,7 +1109,10 @@ default_config([listen, http, protocol]) -> default_config([listen, http, tls]) -> #{verify_mode => peer}; default_config([listen, c2s]) -> - (common_xmpp_listener_config())#{module => ejabberd_c2s, + (common_xmpp_listener_config())#{module => mongoose_c2s_listener, + max_connections => infinity, + c2s_state_timeout => 5000, + reuse_port => false, access => all, shaper => none}; default_config([listen, c2s, tls]) -> diff --git a/test/config_parser_SUITE.erl b/test/config_parser_SUITE.erl index 4893786a73b..6c9d15fb58c 100644 --- a/test/config_parser_SUITE.erl +++ b/test/config_parser_SUITE.erl @@ -477,13 +477,15 @@ listen_c2s(_Config) -> ?cfg(P ++ [access], rule1, T(#{<<"access">> => <<"rule1">>})), ?cfg(P ++ [shaper], c2s_shaper, T(#{<<"shaper">> => <<"c2s_shaper">>})), ?cfg(P ++ [zlib], 1024, T(#{<<"zlib">> => 1024})), - ?cfg(P ++ [max_fsm_queue], 1000, T(#{<<"max_fsm_queue">> => 1000})), + ?cfg(P ++ [reuse_port], true, T(#{<<"reuse_port">> => true})), + ?cfg(P ++ [max_connections], 1000, T(#{<<"max_connections">> => 1000})), ?cfg(P ++ [allowed_auth_methods], [rdbms, http], T(#{<<"allowed_auth_methods">> => [<<"rdbms">>, <<"http">>]})), ?err(T(#{<<"access">> => <<>>})), ?err(T(#{<<"shaper">> => <<>>})), ?err(T(#{<<"zlib">> => 0})), - ?err(T(#{<<"max_fsm_queue">> => 0})), + ?err(T(#{<<"reuse_port">> => 0})), + ?err(T(#{<<"max_connections">> => 0})), ?err(T(#{<<"allowed_auth_methods">> => [<<"bad_method">>]})), ?err(T(#{<<"allowed_auth_methods">> => [<<"rdbms">>, <<"rdbms">>]})). @@ -680,6 +682,7 @@ test_listen_xmpp(P, T) -> ?cfg(P ++ [proxy_protocol], true, T(#{<<"proxy_protocol">> => true})), ?cfg(P ++ [hibernate_after], 10, T(#{<<"hibernate_after">> => 10})), ?cfg(P ++ [max_stanza_size], 10000, T(#{<<"max_stanza_size">> => 10000})), + ?cfg(P ++ [max_stanza_size], 0, T(#{<<"max_stanza_size">> => <<"infinity">>})), ?cfg(P ++ [num_acceptors], 100, T(#{<<"num_acceptors">> => 100})), ?err(T(#{<<"backlog">> => -10})), ?err(T(#{<<"proxy_protocol">> => <<"awesome">>})), diff --git a/test/http_helper.erl b/test/http_helper.erl index 673910f9da5..f527784e0e5 100644 --- a/test/http_helper.erl +++ b/test/http_helper.erl @@ -22,8 +22,8 @@ start(Port, Path, HandleFun) -> application:ensure_all_started(cowboy), Dispatch = cowboy_router:compile([{'_', [{Path, http_helper, HandleFun}]}]), {ok, _} = cowboy:start_clear(http_helper_listener, - [{port, Port}, {num_acceptors, 200}], - #{env => #{dispatch => Dispatch}}). + #{socket_opts => [{port, Port}], num_acceptors => 200}, + #{env => #{dispatch => Dispatch}}). stop() -> cowboy:stop_listener(http_helper_listener). diff --git a/test/mim_ct_rest.erl b/test/mim_ct_rest.erl index 96c5b6a03d9..78002ccce46 100644 --- a/test/mim_ct_rest.erl +++ b/test/mim_ct_rest.erl @@ -118,7 +118,7 @@ init([BasicAuth]) -> ]), {ok, _} = cowboy:start_clear(tests_listener, - [{port, 12000}, {num_acceptors, 5}], + #{socket_opts => [{port, 12000}], num_acceptors => 5}, #{env => #{dispatch => DispatchEJD}}), {ok, #state{ basic_auth = list_to_binary(BasicAuth) }}.