diff options
Diffstat (limited to 'lib/ssh')
-rw-r--r-- | lib/ssh/doc/src/ssh.xml | 16 | ||||
-rw-r--r-- | lib/ssh/src/ssh.hrl | 6 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.erl | 164 | ||||
-rw-r--r-- | lib/ssh/src/ssh_bits.erl | 8 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 31 | ||||
-rw-r--r-- | lib/ssh/src/ssh_message.erl | 34 | ||||
-rw-r--r-- | lib/ssh/test/Makefile | 1 | ||||
-rw-r--r-- | lib/ssh/test/ssh_basic_SUITE.erl | 70 | ||||
-rw-r--r-- | lib/ssh/test/ssh_upgrade_SUITE.erl | 206 | ||||
-rw-r--r-- | lib/ssh/test/ssh_upgrade_SUITE_data/id_dsa | 13 | ||||
-rw-r--r-- | lib/ssh/test/ssh_upgrade_SUITE_data/id_rsa | 15 | ||||
-rw-r--r-- | lib/ssh/test/ssh_upgrade_SUITE_data/known_hosts | 1 | ||||
-rw-r--r-- | lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key | 13 | ||||
-rw-r--r-- | lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key.pub | 11 | ||||
-rw-r--r-- | lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key | 16 | ||||
-rw-r--r-- | lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key.pub | 5 |
16 files changed, 495 insertions, 115 deletions
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index b39ca0852c..c1235715cc 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -368,7 +368,7 @@ kex is implicit but public_key is set explicitly.</p> an own CLI channel. If set to <c>no_cli</c>, the CLI channels are disabled and only subsystem channels are allowed.</p> </item> - <tag><c><![CDATA[{user_dir, String}]]></c></tag> + <tag><c><![CDATA[{user_dir, string()}]]></c></tag> <item> <p>Sets the user directory. That is, the directory containing <c>ssh</c> configuration files for the user, such as @@ -385,6 +385,7 @@ kex is implicit but public_key is set explicitly.</p> <c><![CDATA[/etc/ssh]]></c>. For security reasons, this directory is normally accessible only to the root user.</p> </item> + <tag><c><![CDATA[{auth_methods, string()}]]></c></tag> <item> <p>Comma-separated string that determines which @@ -392,6 +393,19 @@ kex is implicit but public_key is set explicitly.</p> in what order they are tried. Defaults to <c><![CDATA["publickey,keyboard-interactive,password"]]></c></p> </item> + + <tag><c><![CDATA[{auth_method_kb_interactive_data, PromptTexts}]]> + <br/>where: + <br/>PromptTexts = kb_int_tuple() | fun(Peer::{IP::tuple(),Port::integer()}, User::string(), Service::string()) -> kb_int_tuple() + <br/>kb_int_tuple() = {Name::string(), Instruction::string(), Prompt::string(), Echo::boolean()}</c> + </tag> + <item> + <p>Sets the text strings that the daemon sends to the client for presentation to the user when using <c>keyboar-interactive</c> authentication. If the fun/3 is used, it is called when the actual authentication occurs and may therefore return dynamic data like time, remote ip etc.</p> + <p>The parameter <c>Echo</c> guides the client about need to hide the password.</p> + <p>The default value is: + <c>{auth_method_kb_interactive_data, {"SSH server", "Enter password for \""++User++"\"", "password: ", false}></c></p> + </item> + <tag><c><![CDATA[{user_passwords, [{string() = User, string() = Password}]}]]></c></tag> <item> diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 94154c8a96..a02c87505d 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -128,8 +128,10 @@ user, service, userauth_quiet_mode, % boolean() - userauth_supported_methods , % - userauth_methods, + userauth_supported_methods, % string() eg "keyboard-interactive,password" + userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"] + kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive" + kb_data, userauth_preference, available_host_keys, authenticated = false diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index df9a97c8f8..020fb06530 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -169,7 +169,8 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "password", data = <<?FALSE, ?UINT32(Sz), BinPwd:Sz/binary>>}, _, - #ssh{opts = Opts} = Ssh) -> + #ssh{opts = Opts, + userauth_supported_methods = Methods} = Ssh) -> Password = unicode:characters_to_list(BinPwd), case check_password(User, Password, Opts) of true -> @@ -178,7 +179,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, false -> {not_authorized, {User, {error,"Bad user or password"}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = "", + authentications = Methods, partial_success = false}, Ssh)} end; @@ -191,7 +192,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, %% ?UINT32(Sz2), NewBinPwd:Sz2/binary >> }, _, - Ssh) -> + #ssh{userauth_supported_methods = Methods} = Ssh) -> %% Password change without us having sent SSH_MSG_USERAUTH_PASSWD_CHANGEREQ (because we never do) %% RFC 4252 says: %% SSH_MSG_USERAUTH_FAILURE without partial success - The password @@ -200,7 +201,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, {not_authorized, {User, {error,"Password change not supported"}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = "", + authentications = Methods, partial_success = false}, Ssh)}; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -216,7 +217,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "publickey", data = Data}, - SessionId, #ssh{opts = Opts} = Ssh) -> + SessionId, + #ssh{opts = Opts, + userauth_supported_methods = Methods} = Ssh) -> <<?BYTE(HaveSig), ?UINT32(ALen), BAlg:ALen/binary, ?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data, Alg = binary_to_list(BAlg), @@ -231,7 +234,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, false -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications="publickey,password", + authentications = Methods, partial_success = false}, Ssh)} end; ?FALSE -> @@ -245,49 +248,60 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "keyboard-interactive", data = _}, - _, #ssh{opts = Opts} = Ssh) -> - %% RFC4256 - %% The data field contains: - %% - language tag (deprecated). If =/=[] SHOULD use it however. We skip - %% it for simplicity. - %% - submethods. "... the user can give a hint of which actual methods - %% he wants to use. ...". It's a "MAY use" so we skip - %% it. It also needs an understanding between the client - %% and the server. - %% - %% "The server MUST reply with an SSH_MSG_USERAUTH_SUCCESS, - %% SSH_MSG_USERAUTH_FAILURE, or SSH_MSG_USERAUTH_INFO_REQUEST message." - Default = {"SSH server", - "Enter password for \""++User++"\"", - "pwd: ", - false}, - - {Name, Instruction, Prompt, Echo} = - case proplists:get_value(auth_method_kb_interactive_data, Opts) of - undefined -> - Default; - {_,_,_,_}=V -> - V; - F when is_function(F) -> - {_,PeerName} = Ssh#ssh.peer, - F(PeerName, User, "ssh-connection") - end, - EchoEnc = case Echo of - true -> <<?TRUE>>; - false -> <<?FALSE>> - end, - Msg = #ssh_msg_userauth_info_request{name = unicode:characters_to_list(Name), - instruction = unicode:characters_to_list(Instruction), - language_tag = "", - num_prompts = 1, - data = <<?STRING(unicode:characters_to_binary(Prompt)), - EchoEnc/binary - >> - }, - {not_authorized, {User, undefined}, - ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, - opts = [{max_kb_tries,3},{kb_userauth_info_msg,Msg}|Opts] - })}; + _, #ssh{opts = Opts, + kb_tries_left = KbTriesLeft, + userauth_supported_methods = Methods} = Ssh) -> + case KbTriesLeft of + N when N<1 -> + {not_authorized, {User, {authmethod, "keyboard-interactive"}}, + ssh_transport:ssh_packet( + #ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh)}; + + _ -> + %% RFC4256 + %% The data field contains: + %% - language tag (deprecated). If =/=[] SHOULD use it however. We skip + %% it for simplicity. + %% - submethods. "... the user can give a hint of which actual methods + %% he wants to use. ...". It's a "MAY use" so we skip + %% it. It also needs an understanding between the client + %% and the server. + %% + %% "The server MUST reply with an SSH_MSG_USERAUTH_SUCCESS, + %% SSH_MSG_USERAUTH_FAILURE, or SSH_MSG_USERAUTH_INFO_REQUEST message." + Default = {"SSH server", + "Enter password for \""++User++"\"", + "password: ", + false}, + + {Name, Instruction, Prompt, Echo} = + case proplists:get_value(auth_method_kb_interactive_data, Opts) of + undefined -> + Default; + {_,_,_,_}=V -> + V; + F when is_function(F) -> + {_,PeerName} = Ssh#ssh.peer, + F(PeerName, User, "ssh-connection") + end, + EchoEnc = case Echo of + true -> <<?TRUE>>; + false -> <<?FALSE>> + end, + Msg = #ssh_msg_userauth_info_request{name = unicode:characters_to_list(Name), + instruction = unicode:characters_to_list(Instruction), + language_tag = "", + num_prompts = 1, + data = <<?STRING(unicode:characters_to_binary(Prompt)), + EchoEnc/binary + >> + }, + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, + kb_data = Msg + })} + end; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", @@ -314,33 +328,37 @@ handle_userauth_info_request( handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, data = <<?UINT32(Sz), Password:Sz/binary>>}, - #ssh{opts = Opts0, - user = User} = Ssh) -> - NumTriesLeft = proplists:get_value(max_kb_tries, Opts0, 0) - 1, - Opts = lists:keydelete(max_kb_tries,1,Opts0), + #ssh{opts = Opts, + kb_tries_left = KbTriesLeft0, + kb_data = InfoMsg, + user = User, + userauth_supported_methods = Methods} = Ssh) -> + KbTriesLeft = KbTriesLeft0 - 1, case check_password(User, unicode:characters_to_list(Password), Opts) of true -> {authorized, User, ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)}; - false when NumTriesLeft > 0 -> + false when KbTriesLeft > 0 -> UserAuthInfoMsg = - (proplists:get_value(kb_userauth_info_msg,Opts)) - #ssh_msg_userauth_info_request{name = "", - instruction = - lists:concat( - ["Bad user or password, try again. ", - integer_to_list(NumTriesLeft), - " tries left."])}, + InfoMsg#ssh_msg_userauth_info_request{ + name = "", + instruction = + lists:concat( + ["Bad user or password, try again. ", + integer_to_list(KbTriesLeft), + " tries left."]) + }, {not_authorized, {User, undefined}, ssh_transport:ssh_packet(UserAuthInfoMsg, - Ssh#ssh{opts = [{max_kb_tries,NumTriesLeft}|Opts]})}; + Ssh#ssh{kb_tries_left = KbTriesLeft})}; false -> {not_authorized, {User, {error,"Bad user or password"}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = "", + authentications = Methods, partial_success = false}, - Ssh#ssh{opts = lists:keydelete(kb_userauth_info_msg,1,Opts)} + Ssh#ssh{kb_data = undefined, + kb_tries_left = 0} )} end; @@ -483,22 +501,16 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> end. decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, - ?UINT32(Len1), BinE:Len1/binary, - ?UINT32(Len2), BinN:Len2/binary>> + ?UINT32(Len1), E:Len1/big-signed-integer-unit:8, + ?UINT32(Len2), N:Len2/big-signed-integer-unit:8>> ,"ssh-rsa") -> - E = ssh_bits:erlint(Len1, BinE), - N = ssh_bits:erlint(Len2, BinN), {ok, #'RSAPublicKey'{publicExponent = E, modulus = N}}; decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, - ?UINT32(Len1), BinP:Len1/binary, - ?UINT32(Len2), BinQ:Len2/binary, - ?UINT32(Len3), BinG:Len3/binary, - ?UINT32(Len4), BinY:Len4/binary>> + ?UINT32(Len1), P:Len1/big-signed-integer-unit:8, + ?UINT32(Len2), Q:Len2/big-signed-integer-unit:8, + ?UINT32(Len3), G:Len3/big-signed-integer-unit:8, + ?UINT32(Len4), Y:Len4/big-signed-integer-unit:8>> , "ssh-dss") -> - P = ssh_bits:erlint(Len1, BinP), - Q = ssh_bits:erlint(Len2, BinQ), - G = ssh_bits:erlint(Len3, BinG), - Y = ssh_bits:erlint(Len4, BinY), {ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}}; decode_public_key_v2(_, _) -> diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl index 8aaff93b9f..d5f8df6fe4 100644 --- a/lib/ssh/src/ssh_bits.erl +++ b/lib/ssh/src/ssh_bits.erl @@ -26,7 +26,7 @@ -include("ssh.hrl"). -export([encode/2]). --export([mpint/1, erlint/2, string/1, name_list/1]). +-export([mpint/1, string/1, name_list/1]). -export([random/1]). -define(name_list(X), @@ -145,11 +145,7 @@ enc(Xs, ['...'| []], _Offset) -> enc([], [],_) -> []. -erlint(Len, BinInt) -> - Sz = Len*8, - <<Int:Sz/big-signed-integer>> = BinInt, - Int. - + %% %% Create a binary with constant bytes %% diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e303f02922..e6e5749e07 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -483,17 +483,22 @@ userauth(#ssh_msg_userauth_request{service = "ssh-connection", service = "ssh-connection", peer = {_, Address}} = Ssh0, opts = Opts, starter = Pid} = State) -> - case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of - {authorized, User, {Reply, Ssh}} -> - send_msg(Reply, State), - Pid ! ssh_connected, - connected_fun(User, Address, Method, Opts), - {next_state, connected, - next_packet(State#state{auth_user = User, ssh_params = Ssh})}; - {not_authorized, {User, Reason}, {Reply, Ssh}} -> - retry_fun(User, Address, Reason, Opts), - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + case lists:member(Method, Ssh0#ssh.userauth_methods) of + true -> + case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of + {authorized, User, {Reply, Ssh}} -> + send_msg(Reply, State), + Pid ! ssh_connected, + connected_fun(User, Address, Method, Opts), + {next_state, connected, + next_packet(State#state{auth_user = User, ssh_params = Ssh})}; + {not_authorized, {User, Reason}, {Reply, Ssh}} -> + retry_fun(User, Address, Reason, Opts), + send_msg(Reply, State), + {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + end; + false -> + userauth(Msg#ssh_msg_userauth_request{method="none"}, State) end; userauth(#ssh_msg_userauth_info_request{} = Msg, @@ -1171,9 +1176,9 @@ init_ssh(client = Role, Vsn, Version, Options, Socket) -> }; init_ssh(server = Role, Vsn, Version, Options, Socket) -> - AuthMethods = proplists:get_value(auth_methods, Options, ?SUPPORTED_AUTH_METHODS), + AuthMethodsAsList = string:tokens(AuthMethods, ","), {ok, PeerAddr} = inet:peername(Socket), KeyCb = proplists:get_value(key_cb, Options, ssh_file), @@ -1184,6 +1189,8 @@ init_ssh(server = Role, Vsn, Version, Options, Socket) -> io_cb = proplists:get_value(io_cb, Options, ssh_io), opts = Options, userauth_supported_methods = AuthMethods, + userauth_methods = AuthMethodsAsList, + kb_tries_left = 3, peer = {undefined, PeerAddr}, available_host_keys = supported_host_keys(Role, KeyCb, Options) }. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 66e7717095..483c6cb4aa 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -421,8 +421,8 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) -> decode(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) -> decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10); -decode(<<?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/binary>>) -> - #ssh_msg_kexdh_init{e = erlint(Len, E) +decode(<<?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/big-signed-integer-unit:8>>) -> + #ssh_msg_kexdh_init{e = E }; decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST), ?UINT32(Min), ?UINT32(N), ?UINT32(Max)>>) -> #ssh_msg_kex_dh_gex_request{ @@ -442,11 +442,11 @@ decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), g = Generator }; decode(<<?BYTE(?SSH_MSG_KEXDH_REPLY), ?UINT32(Len0), Key:Len0/binary, - ?UINT32(Len1), F:Len1/binary, + ?UINT32(Len1), F:Len1/big-signed-integer-unit:8, ?UINT32(Len2), Hashsign:Len2/binary>>) -> #ssh_msg_kexdh_reply{ public_host_key = decode_host_key(Key), - f = erlint(Len1, F), + f = F, h_sig = decode_sign(Hashsign) }; @@ -514,10 +514,7 @@ decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) -> Names = string:tokens(unicode:characters_to_list(Data), ","), decode_kex_init(Rest, [Names | Acc], N -1). -erlint(MPIntSize, MPIntValue) -> - Bits = MPIntSize * 8, - <<Integer:Bits/integer>> = MPIntValue, - Integer. + decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) -> Signature. @@ -525,18 +522,19 @@ decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) -> decode_host_key(<<?UINT32(Len), Alg:Len/binary, Rest/binary>>) -> decode_host_key(Alg, Rest). -decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/binary, - ?UINT32(Len1), N:Len1/binary>>) -> - #'RSAPublicKey'{publicExponent = erlint(Len0, E), - modulus = erlint(Len1, N)}; +decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/big-signed-integer-unit:8, + ?UINT32(Len1), N:Len1/big-signed-integer-unit:8>>) -> + #'RSAPublicKey'{publicExponent = E, + modulus = N}; decode_host_key(<<"ssh-dss">>, - <<?UINT32(Len0), P:Len0/binary, - ?UINT32(Len1), Q:Len1/binary, - ?UINT32(Len2), G:Len2/binary, - ?UINT32(Len3), Y:Len3/binary>>) -> - {erlint(Len3, Y), #'Dss-Parms'{p = erlint(Len0, P), q = erlint(Len1, Q), - g = erlint(Len2, G)}}. + <<?UINT32(Len0), P:Len0/big-signed-integer-unit:8, + ?UINT32(Len1), Q:Len1/big-signed-integer-unit:8, + ?UINT32(Len2), G:Len2/big-signed-integer-unit:8, + ?UINT32(Len3), Y:Len3/big-signed-integer-unit:8>>) -> + {Y, #'Dss-Parms'{p = P, + q = Q, + g = G}}. encode_host_key(#'RSAPublicKey'{modulus = N, publicExponent = E}) -> ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]); diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile index 843b1d906d..50efc33f98 100644 --- a/lib/ssh/test/Makefile +++ b/lib/ssh/test/Makefile @@ -38,6 +38,7 @@ MODULES= \ ssh_sftp_SUITE \ ssh_sftpd_SUITE \ ssh_sftpd_erlclient_SUITE \ + ssh_upgrade_SUITE \ ssh_connection_SUITE \ ssh_echo_server \ ssh_peername_sockname_server \ diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index b3a837f40f..873e9a42b1 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -46,6 +46,8 @@ all() -> {group, dsa_pass_key}, {group, rsa_pass_key}, {group, internal_error}, + connectfun_disconnectfun_server, + connectfun_disconnectfun_client, {group, renegotiate}, daemon_already_started, server_password_option, @@ -766,6 +768,74 @@ ssh_msg_debug_fun_option_client(Config) -> end. %%-------------------------------------------------------------------- +connectfun_disconnectfun_server(Config) -> + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = ?config(data_dir, Config), + + Parent = self(), + Ref = make_ref(), + ConnFun = fun(_,_,_) -> Parent ! {connect,Ref} end, + DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end, + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}, + {failfun, fun ssh_test_lib:failfun/2}, + {disconnectfun, DiscFun}, + {connectfun, ConnFun}]), + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_dir, UserDir}, + {user_interaction, false}]), + receive + {connect,Ref} -> + ssh:close(ConnectionRef), + receive + {disconnect,Ref,R} -> + ct:log("Disconnect result: ~p",[R]), + ssh:stop_daemon(Pid) + after 2000 -> + {fail, "No disconnectfun action"} + end + after 2000 -> + {fail, "No connectfun action"} + end. + +%%-------------------------------------------------------------------- +connectfun_disconnectfun_client(Config) -> + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = ?config(data_dir, Config), + + Parent = self(), + Ref = make_ref(), + DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end, + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}, + {failfun, fun ssh_test_lib:failfun/2}]), + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_dir, UserDir}, + {disconnectfun, DiscFun}, + {user_interaction, false}]), + ssh:stop_daemon(Pid), + receive + {disconnect,Ref,R} -> + ct:log("Disconnect result: ~p",[R]) + after 2000 -> + {fail, "No disconnectfun action"} + end. + +%%-------------------------------------------------------------------- ssh_msg_debug_fun_option_server() -> [{doc, "validate client that uses the 'ssh_msg_debug_fun' option"}]. ssh_msg_debug_fun_option_server(Config) -> diff --git a/lib/ssh/test/ssh_upgrade_SUITE.erl b/lib/ssh/test/ssh_upgrade_SUITE.erl new file mode 100644 index 0000000000..861c7ab3dd --- /dev/null +++ b/lib/ssh/test/ssh_upgrade_SUITE.erl @@ -0,0 +1,206 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014-2015. 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/.2 +%% +%% 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_upgrade_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-record(state, { + config, + root_dir, + server, + client, + connection, + soft + }). + + +%%%================================================================ +%%% +%%% CommonTest callbacks +%%% +all() -> + [ + minor_upgrade, + major_upgrade + ]. + +init_per_suite(Config0) -> + catch crypto:stop(), + try {crypto:start(), erlang:system_info({wordsize, internal}) == + erlang:system_info({wordsize, external})} of + {ok, true} -> + case ct_release_test:init(Config0) of + {skip, Reason} -> + {skip, Reason}; + Config -> + ssh:start(), + Config + end; + {ok, false} -> + {skip, "Test server will not handle halfwordemulator correctly. Skip as halfwordemulator is deprecated"} + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(Config) -> + ct_release_test:cleanup(Config), + ssh:stop(), + crypto:stop(), + UserDir = ?config(priv_dir, Config), + ssh_test_lib:clean_rsa(UserDir). + +init_per_testcase(_TestCase, Config) -> + Config. +end_per_testcase(_TestCase, Config) -> + Config. + +%%%================================================================ +%%% +%%% Test cases +%%% +major_upgrade(Config) when is_list(Config) -> + ct_release_test:upgrade(ssh, major,{?MODULE, #state{config = Config}}, Config). + +minor_upgrade(Config) when is_list(Config) -> + ct_release_test:upgrade(ssh, minor,{?MODULE, #state{config = Config}}, Config). + +%%%================================================================ +%%% +%%% ct_release_test callbacks +%%% + +%%%---------------------------------------------------------------- +%%% Initialyze system before upgrade test starts. +%%% Called by ct_release_test:upgrade/4 +upgrade_init(CTData, State) -> + {ok, AppUp={_, _, Up, _Down}} = ct_release_test:get_appup(CTData, ssh), + ct:pal("AppUp: ~p", [AppUp]), + ct:pal("Up: ~p", [Up]), + case Soft = is_soft(Up) of + %% It is symmetrical, if upgrade is soft so is downgrade + true -> + setup_server_client(State#state{soft = Soft}); + false -> + State#state{soft = Soft} + end. + +%%%---------------------------------------------------------------- +%%% Check that upgrade was successful +%%% Called by ct_release_test:upgrade/4 +upgrade_upgraded(_, #state{soft=false} = State) -> + test_hard(State, "upgrade"); + +upgrade_upgraded(_, State) -> + test_soft(State, "upgrade1"). + +%%%---------------------------------------------------------------- +%%% Check that downgrade was successful. +%%% Called by ct_release_test:upgrade/4 +upgrade_downgraded(_, #state{soft=false} = State) -> + test_hard(State, "downgrade"); + +upgrade_downgraded(_, #state{soft=true} = State) -> + test_soft(State, "downgrade1"). + +%%%================================================================ +%%% +%%% Private functions +%%% + +is_soft([{restart_application, ssh}]) -> + false; +is_soft(_) -> + true. + + +test_hard(State0, FileName) -> + ct:pal("test_hard State0=~p, FileName=~p",[State0, FileName]), + State = setup_server_client(State0), + test_connection(FileName, random_contents(), State). + +test_soft(State0, FileName) -> + ct:pal("test_soft State0=~p, FileName=~p",[State0, FileName]), + State = test_connection(FileName, random_contents(), State0), + setup_server_client( close(State) ). + + +setup_server_client(#state{config=Config} = State) -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + + FtpRootDir = filename:join(PrivDir, "ftp_root"), + catch file:make_dir(FtpRootDir), + + SFTP = ssh_sftpd:subsystem_spec([{root,FtpRootDir},{cwd,FtpRootDir}]), + + {Server,Host,Port} = ssh_test_lib:daemon([{system_dir,DataDir}, + {user_passwords,[{"hej","hopp"}]}, + {subsystems,[SFTP]}]), + + {ok, ChannelPid, Connection} = + ssh_sftp:start_channel(Host, Port, [{user_interaction,false}, + {silently_accept_hosts,true}, + {user_dir,DataDir}, + {user,"hej"}, + {password,"hopp"}]), + State#state{server = Server, + client = ChannelPid, + connection = Connection}. + + +test_connection(FileName, FileContents, + #state{client = ChannelPid, + root_dir = FtpRootDir} = State) -> + ct:pal("test_connection Writing with ssh_sftp:write_file",[]), + case ssh_sftp:write_file(ChannelPid, FileName, FileContents) of + ok -> + case ssh_sftp:read_file(ChannelPid, FileName) of + {ok,FileContents} -> + State; + {ok,Unexpected} -> + ct:fail("Expected ~p but got ~p from sftp:read_file(~p,..) in RootDir ~p", + [FileContents,Unexpected,FileName,FtpRootDir] + ); + Other -> + ct:fail("ssh_sftp:read_file(~p,~p) -> ~p~n" + "ssh_sftp:list_dir(~p,\".\") -> ~p", + [ChannelPid,FileName,Other, + ChannelPid, catch ssh_sftp:list_dir(ChannelPid, ".")]) + end; + + Other -> + ct:fail("ssh_sftp:write_file(~p,~p,~p) -> ~p",[ChannelPid,FileName,FileContents,Other]) + end. + + +close(#state{server = Server, + connection = Connection} = State) -> + ssh:close(Connection), + ssh:stop_daemon(Server), + State#state{server = undefined, + client = undefined, + connection = undefined}. + + +random_contents() -> list_to_binary( random_chars(3) ). + +random_chars(N) -> [crypto:rand_uniform($a,$z) || _<-lists:duplicate(N,x)]. diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/id_dsa b/lib/ssh/test/ssh_upgrade_SUITE_data/id_dsa new file mode 100644 index 0000000000..d306f8b26e --- /dev/null +++ b/lib/ssh/test/ssh_upgrade_SUITE_data/id_dsa @@ -0,0 +1,13 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ +APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod +/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP +kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW +JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD +OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt ++9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e +uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX +Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE +ZU8w8Q+H7z0j+a+70x2iAw== +-----END DSA PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/id_rsa b/lib/ssh/test/ssh_upgrade_SUITE_data/id_rsa new file mode 100644 index 0000000000..9d7e0dd5fb --- /dev/null +++ b/lib/ssh/test/ssh_upgrade_SUITE_data/id_rsa @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU +DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl +zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB +AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V +TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3 +CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK +SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p +z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd +WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39 +sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3 +xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ +dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x +ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak= +-----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/known_hosts b/lib/ssh/test/ssh_upgrade_SUITE_data/known_hosts new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/lib/ssh/test/ssh_upgrade_SUITE_data/known_hosts @@ -0,0 +1 @@ + diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key new file mode 100644 index 0000000000..51ab6fbd88 --- /dev/null +++ b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key @@ -0,0 +1,13 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK +wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q +diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA +l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X +skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF +Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP +ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah +/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U +ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W +Lv62jKcdskxNyz2NQoBx +-----END DSA PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..4dbb1305b0 --- /dev/null +++ b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key.pub @@ -0,0 +1,11 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j +YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2 +KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU +aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI +fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT +MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh +DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48 +wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2 +/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg== +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key new file mode 100644 index 0000000000..79968bdd7d --- /dev/null +++ b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key @@ -0,0 +1,16 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337 +zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB +6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB +AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW +NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++ +udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW +WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt +n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5 +sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY ++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt +64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB +m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT +tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR +-----END RSA PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key.pub new file mode 100644 index 0000000000..75d2025c71 --- /dev/null +++ b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key.pub @@ -0,0 +1,5 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8 +semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW +RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q== +---- END SSH2 PUBLIC KEY ---- |