%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% -module(ssh_protocol_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/inet.hrl"). -include_lib("ssh/src/ssh.hrl"). % ?UINT32, ?BYTE, #ssh{} ... -include_lib("ssh/src/ssh_transport.hrl"). -include_lib("ssh/src/ssh_auth.hrl"). -include("ssh_test_lib.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). -define(NEWLINE, <<"\r\n">>). -define(REKEY_DATA_TMO, 65000). %%-define(DEFAULT_KEX, 'diffie-hellman-group1-sha1'). -define(DEFAULT_KEX, 'diffie-hellman-group14-sha256'). -define(CIPHERS, ['aes256-ctr','aes192-ctr','aes128-ctr','aes128-cbc','3des-cbc']). -define(DEFAULT_CIPHERS, [{client2server,?CIPHERS}, {server2client,?CIPHERS}]). -define(v(Key, Config), proplists:get_value(Key, Config)). -define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{seconds,40}}]. all() -> [{group,tool_tests}, client_info_line, {group,kex}, {group,service_requests}, {group,authentication}, {group,packet_size_error}, {group,field_size_error}, {group,ext_info} ]. groups() -> [{tool_tests, [], [lib_works_as_client, lib_works_as_server, lib_match, lib_no_match ]}, {packet_size_error, [], [packet_length_too_large, packet_length_too_short]}, {field_size_error, [], [service_name_length_too_large, service_name_length_too_short]}, {kex, [], [no_common_alg_server_disconnects, no_common_alg_client_disconnects, gex_client_init_option_groups, gex_server_gex_limit, gex_client_init_option_groups_moduli_file, gex_client_init_option_groups_file, gex_client_old_request_exact, gex_client_old_request_noexact ]}, {service_requests, [], [bad_service_name, bad_long_service_name, bad_very_long_service_name, empty_service_name, bad_service_name_then_correct ]}, {authentication, [], [client_handles_keyboard_interactive_0_pwds ]}, {ext_info, [], [no_ext_info_s1, no_ext_info_s2, ext_info_s, ext_info_c ]} ]. init_per_suite(Config) -> ?CHECK_CRYPTO(start_std_daemon( setup_dirs( start_apps(Config)))). end_per_suite(Config) -> stop_apps(Config). init_per_testcase(no_common_alg_server_disconnects, Config) -> start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']}, {cipher,?DEFAULT_CIPHERS} ]}]); init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ; TC == gex_client_init_option_groups_moduli_file ; TC == gex_client_init_option_groups_file ; TC == gex_server_gex_limit ; TC == gex_client_old_request_exact ; TC == gex_client_old_request_noexact -> Opts = case TC of gex_client_init_option_groups -> [{dh_gex_groups, [{1023, 5, 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A770E2EC9F }]}]; gex_client_init_option_groups_file -> DataDir = proplists:get_value(data_dir, Config), F = filename:join(DataDir, "dh_group_test"), [{dh_gex_groups, {file,F}}]; gex_client_init_option_groups_moduli_file -> DataDir = proplists:get_value(data_dir, Config), F = filename:join(DataDir, "dh_group_test.moduli"), [{dh_gex_groups, {ssh_moduli_file,F}}]; _ when TC == gex_server_gex_limit ; TC == gex_client_old_request_exact ; TC == gex_client_old_request_noexact -> [{dh_gex_groups, [{1023, 2, 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A771225323}, {1535, 5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827}, {3071, 2, 16#DFAA35D35531E0F524F0099877A482D2AC8D589F374394A262A8E81A8A4FB2F65FADBAB395E05D147B29D486DFAA41F41597A256DA82A8B6F76401AED53D0253F956CEC610D417E42E3B287F7938FC24D8821B40BFA218A956EB7401BED6C96C68C7FD64F8170A8A76B953DD2F05420118F6B144D8FE48060A2BCB85056B478EDEF96DBC70427053ECD2958C074169E9550DD877779A3CF17C5AC850598C7586BEEA9DCFE9DD2A5FB62DF5F33EA7BC00CDA31B9D2DD721F979EA85B6E63F0C4E30BDDCD3A335522F9004C4ED50B15DC537F55324DD4FA119FB3F101467C6D7E1699DE4B3E3C478A8679B8EB3FA5C9B826B44530FD3BE9AD3063B240B0C853EBDDBD68DD940332D98F148D5D9E1DC977D60A0D23D0CA1198637FEAE4E7FAAC173AF2B84313A666CFB4EE6972811921D0AD867CE57F3BBC8D6CB057E3B66757BB46C9F72662624D44E14528327E3A7100E81A12C43C4E236118318CD90C8AA185BBB0C764826DAEAEE8DD245C5B451B4944E6122CC522D1C335C2EEF9429825A2B} ]}, {dh_gex_limits, {1023,2000}} ]; _ -> [] end, start_std_daemon(Config, [{preferred_algorithms,[{cipher,?DEFAULT_CIPHERS} ]} | Opts]); init_per_testcase(_TestCase, Config) -> check_std_daemon_works(Config, ?LINE). end_per_testcase(no_common_alg_server_disconnects, Config) -> stop_std_daemon(Config); end_per_testcase(TC, Config) when TC == gex_client_init_option_groups ; TC == gex_client_init_option_groups_moduli_file ; TC == gex_client_init_option_groups_file ; TC == gex_server_gex_limit ; TC == gex_client_old_request_exact ; TC == gex_client_old_request_noexact -> stop_std_daemon(Config); end_per_testcase(_TestCase, Config) -> check_std_daemon_works(Config, ?LINE). %%%-------------------------------------------------------------------- %%% Test Cases -------------------------------------------------------- %%%-------------------------------------------------------------------- %%%-------------------------------------------------------------------- %%% Connect to an erlang server and check that the testlib acts as a client. lib_works_as_client(Config) -> %% Connect and negotiate keys {ok,InitialState} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_seqnums, print_messages]}] ), {ok,AfterKexState} = connect_and_kex(Config, InitialState), %% Do the authentcation {User,Pwd} = server_user_password(Config), {ok,EndState} = ssh_trpt_test_lib:exec( [{send, #ssh_msg_service_request{name = "ssh-userauth"}}, {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg}, {send, #ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "password", data = <<?BOOLEAN(?FALSE), ?STRING(unicode:characters_to_binary(Pwd))>> }}, {match, #ssh_msg_userauth_success{_='_'}, receive_msg} ], AfterKexState), %% Disconnect {ok,_} = ssh_trpt_test_lib:exec( [{send, #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, description = "End of the fun", language = "" }}, close_socket ], EndState). %%-------------------------------------------------------------------- %%% Connect an erlang client and check that the testlib can act as a server. lib_works_as_server(Config) -> {User,_Pwd} = server_user_password(Config), %% Create a listening socket as server socket: {ok,InitialState} = ssh_trpt_test_lib:exec(listen), HostPort = ssh_trpt_test_lib:server_host_port(InitialState), %% Start a process handling one connection on the server side: spawn_link( fun() -> {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_messages]}, {accept, [{system_dir, system_dir(Config)}, {user_dir, user_dir(Config)}]}, receive_hello, {send, hello}, {send, ssh_msg_kexinit}, {match, #ssh_msg_kexinit{_='_'}, receive_msg}, {match, #ssh_msg_kexdh_init{_='_'}, receive_msg}, {send, ssh_msg_kexdh_reply}, {send, #ssh_msg_newkeys{}}, {match, #ssh_msg_newkeys{_='_'}, receive_msg}, {match, #ssh_msg_service_request{name="ssh-userauth"}, receive_msg}, {send, #ssh_msg_service_accept{name="ssh-userauth"}}, {match, #ssh_msg_userauth_request{service="ssh-connection", method="none", user=User, _='_'}, receive_msg}, {send, #ssh_msg_userauth_failure{authentications = "password", partial_success = false}}, {match, #ssh_msg_userauth_request{service="ssh-connection", method="password", user=User, _='_'}, receive_msg}, {send, #ssh_msg_userauth_success{}}, close_socket, print_state ], InitialState) end), %% and finally connect to it with a regular Erlang SSH client: {ok,_} = std_connect(HostPort, Config, [{preferred_algorithms,[{kex,[?DEFAULT_KEX]}, {cipher,?DEFAULT_CIPHERS} ]} ] ). %%-------------------------------------------------------------------- %%% Matching lib_match(_Config) -> {ok,_} = ssh_trpt_test_lib:exec([{set_options, [print_ops]}, {match, abc, abc}, {match, '$a', {cde,fgh}}, {match, {cde,fgh}, '$a'}, {match, '_', {cde,fgh}}, {match, [a,'$a',b], [a,{cde,fgh},b]}, {match, [a,'$a'|'$b'], [a,{cde,fgh},b,c]}, {match, '$b', [b,c]} ]). %%-------------------------------------------------------------------- %%% Not matching lib_no_match(_Config) -> case ssh_trpt_test_lib:exec([{set_options, [print_ops]}, {match, '$x', b}, {match, a, '$x'}]) of {ok,_} -> {fail,"Unexpected match"}; {error, {_Op,{expected,a,b},_State}} -> ok end. %%-------------------------------------------------------------------- %%% Algo negotiation fail. This should result in a ssh_msg_disconnect %%% being sent from the server. no_common_alg_server_disconnects(Config) -> {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, {print_messages,detail}]}, {connect, server_host(Config),server_port(Config), [{silently_accept_hosts, true}, {user_dir, user_dir(Config)}, {user_interaction, false}, {preferred_algorithms,[{public_key,['ssh-dss']}, {cipher,?DEFAULT_CIPHERS} ]} ]}, receive_hello, {send, hello}, {match, #ssh_msg_kexinit{_='_'}, receive_msg}, {send, ssh_msg_kexinit}, % with server unsupported 'ssh-dss' ! {match, disconnect(), receive_msg} ] ). %%-------------------------------------------------------------------- %%% Algo negotiation fail. This should result in a ssh_msg_disconnect %%% being sent from the client. no_common_alg_client_disconnects(Config) -> %% Create a listening socket as server socket: {ok,InitialState} = ssh_trpt_test_lib:exec(listen), HostPort = ssh_trpt_test_lib:server_host_port(InitialState), Parent = self(), %% Start a process handling one connection on the server side: Pid = spawn_link( fun() -> Parent ! {result,self(), ssh_trpt_test_lib:exec( [{set_options, [print_ops, {print_messages,detail}]}, {accept, [{system_dir, system_dir(Config)}, {user_dir, user_dir(Config)}]}, receive_hello, {send, hello}, {match, #ssh_msg_kexinit{_='_'}, receive_msg}, {send, #ssh_msg_kexinit{ % with unsupported "SOME-UNSUPPORTED" cookie = <<80,158,95,51,174,35,73,130,246,141,200,49,180,190,82,234>>, kex_algorithms = [atom_to_list(?DEFAULT_KEX)], server_host_key_algorithms = ["SOME-UNSUPPORTED"], % SIC! encryption_algorithms_client_to_server = ["aes128-ctr"], encryption_algorithms_server_to_client = ["aes128-ctr"], mac_algorithms_client_to_server = ["hmac-sha2-256"], mac_algorithms_server_to_client = ["hmac-sha2-256"], compression_algorithms_client_to_server = ["none"], compression_algorithms_server_to_client = ["none"], languages_client_to_server = [], languages_server_to_client = [], first_kex_packet_follows = false, reserved = 0 }}, {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg} ], InitialState) } end), %% and finally connect to it with a regular Erlang SSH client %% which of course does not support SOME-UNSUPPORTED as pub key algo: Result = std_connect(HostPort, Config, [{preferred_algorithms,[{public_key,['ssh-dss']}, {cipher,?DEFAULT_CIPHERS} ]}]), ct:log("Result of connect is ~p",[Result]), receive {result,Pid,{ok,_}} -> ok; {result,Pid,{error,{Op,ExecResult,S}}} -> ct:log("ERROR!~nOp = ~p~nExecResult = ~p~nState =~n~s", [Op,ExecResult,ssh_trpt_test_lib:format_msg(S)]), {fail, ExecResult}; X -> ct:log("¤¤¤¤¤"), ct:fail(X) after 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%%-------------------------------------------------------------------- gex_client_init_option_groups(Config) -> do_gex_client_init(Config, {512, 2048, 4000}, {5,16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A770E2EC9F} ). gex_client_init_option_groups_file(Config) -> do_gex_client_init(Config, {2000, 2048, 4000}, {5, 16#DFAA35D35531E0F524F0099877A482D2AC8D589F374394A262A8E81A8A4FB2F65FADBAB395E05D147B29D486DFAA41F41597A256DA82A8B6F76401AED53D0253F956CEC610D417E42E3B287F7938FC24D8821B40BFA218A956EB7401BED6C96C68C7FD64F8170A8A76B953DD2F05420118F6B144D8FE48060A2BCB85056B478EDEF96DBC70427053ECD2958C074169E9550DD877779A3CF17C5AC850598C7586BEEA9DCFE9DD2A5FB62DF5F33EA7BC00CDA31B9D2DD721F979EA85B6E63F0C4E30BDDCD3A335522F9004C4ED50B15DC537F55324DD4FA119FB3F101467C6D7E1699DE4B3E3C478A8679B8EB3FA5C9B826B44530FD3BE9AD3063B240B0C853EBDDBD68DD940332D98F148D5D9E1DC977D60A0D23D0CA1198637FEAE4E7FAAC173AF2B84313A666CFB4EE6972811921D0AD867CE57F3BBC8D6CB057E3B66757BB46C9F72662624D44E14528327E3A7100E81A12C43C4E236118318CD90C8AA185BBB0C764826DAEAEE8DD245C5B451B4944E6122CC522D1C335C2EEF9424273F1F} ). gex_client_init_option_groups_moduli_file(Config) -> do_gex_client_init(Config, {2000, 2048, 4000}, {5, 16#DD2047CBDBB6F8E919BC63DE885B34D0FD6E3DB2887D8B46FE249886ACED6B46DFCD5553168185FD376122171CD8927E60120FA8D01F01D03E58281FEA9A1ABE97631C828E41815F34FDCDF787419FE13A3137649AA93D2584230DF5F24B5C00C88B7D7DE4367693428C730376F218A53E853B0851BAB7C53C15DA7839CBE1285DB63F6FA45C1BB59FE1C5BB918F0F8459D7EF60ACFF5C0FA0F3FCAD1C5F4CE4416D4F4B36B05CDCEBE4FB879E95847EFBC6449CD190248843BC7EDB145FBFC4EDBB1A3C959298F08F3BA2CFBE231BBE204BE6F906209D28BD4820AB3E7BE96C26AE8A809ADD8D1A5A0B008E9570FA4C4697E116B8119892C604293683A9635F} ). gex_server_gex_limit(Config) -> do_gex_client_init(Config, {1000, 3000, 4000}, %% {7,91}). {5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827} ). do_gex_client_init(Config, {Min,N,Max}, {G,P}) -> {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_seqnums, print_messages]}, {connect, server_host(Config),server_port(Config), [{silently_accept_hosts, true}, {user_dir, user_dir(Config)}, {user_interaction, false}, {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha1']}, {cipher,?DEFAULT_CIPHERS} ]} ]}, receive_hello, {send, hello}, {send, ssh_msg_kexinit}, {match, #ssh_msg_kexinit{_='_'}, receive_msg}, {send, #ssh_msg_kex_dh_gex_request{min = Min, n = N, max = Max}}, {match, #ssh_msg_kex_dh_gex_group{p=P, g=G, _='_'}, receive_msg} ] ). %%%-------------------------------------------------------------------- gex_client_old_request_exact(Config) -> do_gex_client_init_old(Config, 1023, {2, 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A771225323} ). gex_client_old_request_noexact(Config) -> do_gex_client_init_old(Config, 1400, {5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827} ). do_gex_client_init_old(Config, N, {G,P}) -> {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_seqnums, print_messages]}, {connect, server_host(Config),server_port(Config), [{silently_accept_hosts, true}, {user_dir, user_dir(Config)}, {user_interaction, false}, {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha1']}, {cipher,?DEFAULT_CIPHERS} ]} ]}, receive_hello, {send, hello}, {send, ssh_msg_kexinit}, {match, #ssh_msg_kexinit{_='_'}, receive_msg}, {send, #ssh_msg_kex_dh_gex_request_old{n = N}}, {match, #ssh_msg_kex_dh_gex_group{p=P, g=G, _='_'}, receive_msg} ] ). %%%-------------------------------------------------------------------- bad_service_name(Config) -> bad_service_name(Config, "kfglkjf"). bad_long_service_name(Config) -> bad_service_name(Config, lists:duplicate(?SSH_MAX_PACKET_SIZE div 2, $a)). bad_very_long_service_name(Config) -> bad_service_name(Config, lists:duplicate(?SSH_MAX_PACKET_SIZE+5, $a)). empty_service_name(Config) -> bad_service_name(Config, ""). bad_service_name_then_correct(Config) -> {ok,InitialState} = connect_and_kex(Config), {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_seqnums, print_messages]}, {send, #ssh_msg_service_request{name = "kdjglkfdjgkldfjglkdfjglkfdjglkj"}}, {send, #ssh_msg_service_request{name = "ssh-connection"}}, {match, disconnect(), receive_msg} ], InitialState). bad_service_name(Config, Name) -> {ok,InitialState} = connect_and_kex(Config), {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_seqnums, print_messages]}, {send, #ssh_msg_service_request{name = Name}}, {match, disconnect(), receive_msg} ], InitialState). %%%-------------------------------------------------------------------- packet_length_too_large(Config) -> bad_packet_length(Config, +4). packet_length_too_short(Config) -> bad_packet_length(Config, -4). bad_packet_length(Config, LengthExcess) -> PacketFun = fun(Msg, Ssh) -> BinMsg = ssh_message:encode(Msg), ssh_transport:pack(BinMsg, Ssh, LengthExcess) end, {ok,InitialState} = connect_and_kex(Config), {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_seqnums, print_messages]}, {send, {special, #ssh_msg_service_request{name="ssh-userauth"}, PacketFun}}, %% Prohibit remote decoder starvation: {send, #ssh_msg_service_request{name="ssh-userauth"}}, {match, disconnect(), receive_msg} ], InitialState). %%%-------------------------------------------------------------------- service_name_length_too_large(Config) -> bad_service_name_length(Config, +4). service_name_length_too_short(Config) -> bad_service_name_length(Config, -4). bad_service_name_length(Config, LengthExcess) -> PacketFun = fun(#ssh_msg_service_request{name=Service}, Ssh) -> BinName = list_to_binary(Service), BinMsg = <<?BYTE(?SSH_MSG_SERVICE_REQUEST), %% A bad string encoding of Service: ?UINT32(size(BinName)+LengthExcess), BinName/binary >>, ssh_transport:pack(BinMsg, Ssh) end, {ok,InitialState} = connect_and_kex(Config), {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_seqnums, print_messages]}, {send, {special, #ssh_msg_service_request{name="ssh-userauth"}, PacketFun} }, %% Prohibit remote decoder starvation: {send, #ssh_msg_service_request{name="ssh-userauth"}}, {match, disconnect(), receive_msg} ], InitialState). %%%-------------------------------------------------------------------- %%% This is due to a fault report (OTP-13255) with OpenSSH-6.6.1 client_handles_keyboard_interactive_0_pwds(Config) -> {User,_Pwd} = server_user_password(Config), %% Create a listening socket as server socket: {ok,InitialState} = ssh_trpt_test_lib:exec(listen), HostPort = ssh_trpt_test_lib:server_host_port(InitialState), %% Start a process handling one connection on the server side: spawn_link( fun() -> {ok,_} = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_messages]}, {accept, [{system_dir, system_dir(Config)}, {user_dir, user_dir(Config)}]}, receive_hello, {send, hello}, {send, ssh_msg_kexinit}, {match, #ssh_msg_kexinit{_='_'}, receive_msg}, {match, #ssh_msg_kexdh_init{_='_'}, receive_msg}, {send, ssh_msg_kexdh_reply}, {send, #ssh_msg_newkeys{}}, {match, #ssh_msg_newkeys{_='_'}, receive_msg}, {match, #ssh_msg_service_request{name="ssh-userauth"}, receive_msg}, {send, #ssh_msg_service_accept{name="ssh-userauth"}}, {match, #ssh_msg_userauth_request{service="ssh-connection", method="none", user=User, _='_'}, receive_msg}, {send, #ssh_msg_userauth_failure{authentications = "keyboard-interactive", partial_success = false}}, {match, #ssh_msg_userauth_request{service="ssh-connection", method="keyboard-interactive", user=User, _='_'}, receive_msg}, {send, #ssh_msg_userauth_info_request{name = "", instruction = "", language_tag = "", num_prompts = 1, data = <<0,0,0,10,80,97,115,115,119,111,114,100,58,32,0>> }}, {match, #ssh_msg_userauth_info_response{num_responses = 1, _='_'}, receive_msg}, %% the next is strange, but openssh 6.6.1 does this and this is what this testcase is about {send, #ssh_msg_userauth_info_request{name = "", instruction = "", language_tag = "", num_prompts = 0, data = <<>> }}, {match, #ssh_msg_userauth_info_response{num_responses = 0, data = <<>>, _='_'}, receive_msg}, %% Here we know that the tested fault is fixed {send, #ssh_msg_userauth_success{}}, close_socket, print_state ], InitialState) end), %% and finally connect to it with a regular Erlang SSH client: {ok,_} = std_connect(HostPort, Config, [{preferred_algorithms,[{kex,[?DEFAULT_KEX]}, {cipher,?DEFAULT_CIPHERS} ]}] ). %%%-------------------------------------------------------------------- client_info_line(_Config) -> %% A client must not send an info-line. If it does, the server should handle %% handle this gracefully {ok,Pid} = ssh_eqc_event_handler:add_report_handler(), {_, _, Port} = ssh_test_lib:daemon([]), %% Fake client: {ok,S} = gen_tcp:connect("localhost",Port,[]), gen_tcp:send(S,"An illegal info-string\r\n"), gen_tcp:close(S), %% wait for server to react: timer:sleep(1000), %% check if a badmatch was received: {ok, Reports} = ssh_eqc_event_handler:get_reports(Pid), case lists:any(fun({error_report,_,{_,supervisor_report,L}}) when is_list(L) -> lists:member({reason,{badmatch,{error,closed}}}, L); (_) -> false end, Reports) of true -> ct:fail("Bad error report on info_line from client"); false -> ok end. %%%-------------------------------------------------------------------- %%% The server does not send the extension because %%% the client does not tell the server to send it no_ext_info_s1(Config) -> %% Start the dameon Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,true}, {system_dir, system_dir(Config)}]), {ok,AfterKexState} = connect_and_kex([{server,Server}|Config]), {ok,_} = ssh_trpt_test_lib:exec( [{send, #ssh_msg_service_request{name = "ssh-userauth"}}, {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg} ], AfterKexState), ssh:stop_daemon(Pid). %%%-------------------------------------------------------------------- %%% The server does not send the extension because %%% the server is not configured to send it no_ext_info_s2(Config) -> %% Start the dameon Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,false}, {system_dir, system_dir(Config)}]), {ok,AfterKexState} = connect_and_kex([{extra_options,[{recv_ext_info,true}]}, {server,Server} | Config]), {ok,_} = ssh_trpt_test_lib:exec( [{send, #ssh_msg_service_request{name = "ssh-userauth"}}, {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg} ], AfterKexState), ssh:stop_daemon(Pid). %%%-------------------------------------------------------------------- %%% The server sends the extension ext_info_s(Config) -> %% Start the dameon Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,true}, {system_dir, system_dir(Config)}]), {ok,AfterKexState} = connect_and_kex([{extra_options,[{recv_ext_info,true}]}, {server,Server} | Config]), {ok,_} = ssh_trpt_test_lib:exec( [{match, #ssh_msg_ext_info{_='_'}, receive_msg} ], AfterKexState), ssh:stop_daemon(Pid). %%%-------------------------------------------------------------------- %%% The client sends the extension ext_info_c(Config) -> {User,_Pwd} = server_user_password(Config), %% Create a listening socket as server socket: {ok,InitialState} = ssh_trpt_test_lib:exec(listen), HostPort = ssh_trpt_test_lib:server_host_port(InitialState), Parent = self(), %% Start a process handling one connection on the server side: Pid = spawn_link( fun() -> Result = ssh_trpt_test_lib:exec( [{set_options, [print_ops, print_messages]}, {accept, [{system_dir, system_dir(Config)}, {user_dir, user_dir(Config)}, {recv_ext_info, true} ]}, receive_hello, {send, hello}, {send, ssh_msg_kexinit}, {match, #ssh_msg_kexinit{_='_'}, receive_msg}, {match, #ssh_msg_kexdh_init{_='_'}, receive_msg}, {send, ssh_msg_kexdh_reply}, {send, #ssh_msg_newkeys{}}, {match, #ssh_msg_newkeys{_='_'}, receive_msg}, {match, #ssh_msg_ext_info{_='_'}, receive_msg}, close_socket, print_state ], InitialState), Parent ! {result,self(),Result} end), %% connect to it with a regular Erlang SSH client %% (expect error due to the close_socket in daemon): {error,_} = std_connect(HostPort, Config, [{preferred_algorithms,[{kex,[?DEFAULT_KEX]}, {cipher,?DEFAULT_CIPHERS} ]}, {tstflg, [{ext_info_client,true}]}, {send_ext_info, true} ] ), %% Check that the daemon got expected result: receive {result, Pid, {ok,_}} -> ok; {result, Pid, Error} -> ct:fail("Error: ~p",[Error]) end. %%%================================================================ %%%==== Internal functions ======================================== %%%================================================================ %%%---- init_suite and end_suite --------------------------------------- start_apps(Config) -> catch ssh:stop(), ok = ssh:start(), Config. stop_apps(_Config) -> ssh:stop(). setup_dirs(Config) -> DataDir = proplists:get_value(data_dir, Config), PrivDir = proplists:get_value(priv_dir, Config), ssh_test_lib:setup_dsa(DataDir, PrivDir), ssh_test_lib:setup_rsa(DataDir, PrivDir), Config. system_dir(Config) -> filename:join(proplists:get_value(priv_dir, Config), system). user_dir(Config) -> proplists:get_value(priv_dir, Config). %%%---------------------------------------------------------------- start_std_daemon(Config) -> start_std_daemon(Config, []). start_std_daemon(Config, ExtraOpts) -> PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), UserPasswords = [{"user1","pwd1"}], Options = [%%{preferred_algorithms,[{public_key,['ssh-rsa']}]}, %% For some test cases {system_dir, system_dir(Config)}, {user_dir, UserDir}, {user_passwords, UserPasswords}, {failfun, fun ssh_test_lib:failfun/2} | ExtraOpts], Ref = {Server, Host, Port} = ssh_test_lib:daemon(Options), ct:log("Std server ~p started at ~p:~p~nOptions=~p",[Server, Host, Port, Options]), [{server,Ref}, {user_passwords, UserPasswords} | Config]. stop_std_daemon(Config) -> ssh:stop_daemon(server_pid(Config)), ct:log("Std server ~p at ~p:~p stopped", [server_pid(Config), server_host(Config), server_port(Config)]), lists:keydelete(server, 1, Config). check_std_daemon_works(Config, Line) -> case std_connect(Config) of {ok,C} -> ct:log("Server ~p:~p ~p is ok at line ~p", [server_host(Config), server_port(Config), server_pid(Config), Line]), ok = ssh:close(C), Config; Error = {error,_} -> ct:fail("Standard server ~p:~p ~p is ill at line ~p: ~p", [server_host(Config), server_port(Config), server_pid(Config), Line, Error]) end. server_pid(Config) -> element(1,?v(server,Config)). server_host(Config) -> element(2,?v(server,Config)). server_port(Config) -> element(3,?v(server,Config)). server_user_password(Config) -> server_user_password(1, Config). server_user_password(N, Config) -> lists:nth(N, ?v(user_passwords,Config)). std_connect(Config) -> std_connect({server_host(Config), server_port(Config)}, Config). std_connect({Host,Port}, Config) -> std_connect({Host,Port}, Config, []). std_connect({Host,Port}, Config, Opts) -> std_connect(Host, Port, Config, Opts). std_connect(Host, Port, Config, Opts) -> {User,Pwd} = server_user_password(Config), ssh:connect(Host, Port, %% Prefere User's Opts to the default opts [O || O = {Tag,_} <- [{user,User},{password,Pwd}, {silently_accept_hosts, true}, {user_dir, user_dir(Config)}, {user_interaction, false}], not lists:keymember(Tag, 1, Opts) ] ++ Opts, 30000). %%%---------------------------------------------------------------- connect_and_kex(Config) -> connect_and_kex(Config, ssh_trpt_test_lib:exec([]) ). connect_and_kex(Config, InitialState) -> ssh_trpt_test_lib:exec( [{connect, server_host(Config),server_port(Config), [{preferred_algorithms,[{kex,[?DEFAULT_KEX]}, {cipher,?DEFAULT_CIPHERS} ]}, {silently_accept_hosts, true}, {recv_ext_info, false}, {user_dir, user_dir(Config)}, {user_interaction, false} | proplists:get_value(extra_options,Config,[]) ]}, receive_hello, {send, hello}, {send, ssh_msg_kexinit}, {match, #ssh_msg_kexinit{_='_'}, receive_msg}, {send, ssh_msg_kexdh_init}, {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg}, {send, #ssh_msg_newkeys{}}, {match, #ssh_msg_newkeys{_='_'}, receive_msg} ], InitialState). %%%---------------------------------------------------------------- %%% For matching peer disconnection disconnect() -> disconnect('_'). disconnect(Code) -> {'or',[#ssh_msg_disconnect{code = Code, _='_'}, tcp_closed, {tcp_error,econnaborted} ]}.