From 5e698cb679546dae32c64fabd4e5a65cb5886297 Mon Sep 17 00:00:00 2001 From: Svilen Ivanov Date: Mon, 19 Sep 2016 15:59:21 +0300 Subject: Fix SSH custom REPL exit status When user defined SSH shell REPL process exits with reason normal SSH channel callback module should report successful exit status to the SSH client. This provides simple way for SSH clients to check for successful completion of executed commands. --- lib/ssh/src/ssh_cli.erl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 74cd2e081a..8af0ecc5f9 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -208,8 +208,15 @@ handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty, write_chars(ConnectionHandler, ChannelId, Chars), {ok, State#state{buf = NewBuf}}; -handle_msg({'EXIT', Group, _Reason}, #state{group = Group, - channel = ChannelId} = State) -> +handle_msg({'EXIT', Group, Reason}, #state{group = Group, + cm = ConnectionHandler, + channel = ChannelId} = State) -> + Status = case Reason of + normal -> 0; + _ -> -1 + end, + ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), + ssh_connection:send_eof(ConnectionHandler, ChannelId), {stop, ChannelId, State}; handle_msg(_, State) -> -- cgit v1.2.3 From abf7b8c8397acaa9bee0ccf284b1af4e130c16af Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 5 Oct 2016 15:15:35 +0200 Subject: ssh: Handle gen_server:call/3 exits properly Handle all possible exit values that should be interpreted as {error, closed}. Failing to do so could lead to unexpected crashes for users of the ssh application. --- lib/ssh/src/ssh_channel.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index a8e6ebde16..426e2f5125 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -93,11 +93,16 @@ call(ChannelPid, Msg, TimeOute) -> catch exit:{noproc, _} -> {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{shutdown, _} -> + {error, closed}; + exit:{{shutdown, _}, _} -> + {error, closed}; exit:{timeout, _} -> {error, timeout} end. - cast(ChannelPid, Msg) -> gen_server:cast(ChannelPid, Msg). -- cgit v1.2.3 From eadc9b7a1a0349422a6b9ad1d52229562fc22375 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 5 Oct 2016 16:02:21 +0200 Subject: ssh: Prepare release --- lib/ssh/src/ssh.appup.src | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index e38cecf226..4cda8fee95 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -20,9 +20,13 @@ {"%VSN%", [ + {<<"4.3.2">>, [{load_module, ssh_channel, soft_purge, soft_purge, []} + ]}, {<<".*">>, [{restart_application, ssh}]} ], [ + {<<"4.3.2">>, [{load_module, ssh_channel, soft_purge, soft_purge, []} + ]}, {<<".*">>, [{restart_application, ssh}]} ] }. -- cgit v1.2.3 From 046539298604ef214c0986524f731695d0c47262 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 10 Oct 2016 12:06:46 +0200 Subject: Revert "ssh: Add non-blocking send" since it introduces Error Reports This reverts commit 28baf1314b556bb592c24181f6967e1f324f44a7. --- lib/ssh/src/ssh_connection_handler.erl | 92 +++++++++++++--------------------- 1 file changed, 34 insertions(+), 58 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index facf6b561a..abfba4baf1 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -339,7 +339,6 @@ renegotiate_data(ConnectionHandler) -> ssh_params :: #ssh{} | undefined, socket :: inet:socket(), - sender :: pid() | undefined, decrypted_data_buffer = <<>> :: binary(), encrypted_data_buffer = <<>> :: binary(), undecrypted_packet_length :: undefined | non_neg_integer(), @@ -368,10 +367,9 @@ init_connection_handler(Role, Socket, Opts) -> {Protocol, Callback, CloseTag} = proplists:get_value(transport, Opts, ?DefaultTransport), S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), - sender = spawn_link(fun() -> nonblocking_sender(Socket, Callback) end), - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag } of S -> @@ -527,7 +525,7 @@ handle_event(_, _Event, {init_error,Error}, _) -> %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)), - send_bytes(VsnMsg, D), + ok = send_bytes(VsnMsg, D), case inet:getopts(Socket=D#data.socket, [recbuf]) of {ok, [{recbuf,Size}]} -> %% Set the socket to the hello text line handling mode: @@ -552,7 +550,7 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) -> server -> %% But the client may NOT send them to the server. Openssh answers with cleartext, %% and so do we - send_bytes("Protocol mismatch.", D), + ok = send_bytes("Protocol mismatch.", D), {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}} end; @@ -567,7 +565,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D) -> {active, once}, {recbuf, D#data.inet_initial_recbuf_size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), - send_bytes(SshPacket, D), + ok = send_bytes(SshPacket, D), {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; not_supported -> @@ -585,7 +583,7 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload), Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of {ok, NextKexMsg, Ssh2} when Role==client -> - send_bytes(NextKexMsg, D), + ok = send_bytes(NextKexMsg, D), Ssh2; {ok, Ssh2} when Role==server -> Ssh2 @@ -598,43 +596,43 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, %%%---- diffie-hellman handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), - send_bytes(KexdhReply, D), + ok = send_bytes(KexdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- diffie-hellman group exchange handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - send_bytes(GexGroup, D), + ok = send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - send_bytes(GexGroup, D), + ok = send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params), - send_bytes(KexGexInit, D), + ok = send_bytes(KexGexInit, D), {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- elliptic curve diffie-hellman handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), - send_bytes(KexEcdhReply, D), + ok = send_bytes(KexEcdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; @@ -642,9 +640,9 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), - send_bytes(KexGexReply, D), + ok = send_bytes(KexGexReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; @@ -652,7 +650,7 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}}; @@ -664,7 +662,7 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> Ssh = case Role of client -> {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1), - send_bytes(MsgReq, D), + ok = send_bytes(MsgReq, D), Ssh2; server -> Ssh1 @@ -682,7 +680,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s "ssh-userauth" -> Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params, {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - send_bytes(Reply, D), + ok = send_bytes(Reply, D), {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; _ -> @@ -694,7 +692,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), - send_bytes(Msg, State), + ok = send_bytes(Msg, State), {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; @@ -711,7 +709,7 @@ handle_event(_, %% Probably the very first userauth_request but we deny unauthorized login {not_authorized, _, {Reply,Ssh}} = ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), - send_bytes(Reply, D), + ok = send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}}; {"ssh-connection", "ssh-connection", Method} -> @@ -721,7 +719,7 @@ handle_event(_, %% Yepp! we support this method case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of {authorized, User, {Reply, Ssh}} -> - send_bytes(Reply, D), + ok = send_bytes(Reply, D), D#data.starter ! ssh_connected, connected_fun(User, Method, D), {next_state, {connected,server}, @@ -729,11 +727,11 @@ handle_event(_, ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Reason, D), - send_bytes(Reply, D), + ok = send_bytes(Reply, D), {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D), - send_bytes(Reply, D), + ok = send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}} end; false -> @@ -1447,15 +1445,18 @@ start_the_connection_child(UserPid, Role, Socket, Options) -> %% Stopping -type finalize_termination_result() :: ok . -finalize_termination(_StateName, D) -> - case D#data.connection_state of +finalize_termination(_StateName, #data{transport_cb = Transport, + connection_state = Connection, + socket = Socket}) -> + case Connection of #connection{system_supervisor = SysSup, sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) -> ssh_system_sup:stop_subsystem(SysSup, SubSysSup); _ -> do_nothing end, - close_transport(D). + (catch Transport:close(Socket)), + ok. %%-------------------------------------------------------------------- %% "Invert" the Role @@ -1510,33 +1511,8 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> send_bytes(Bytes, State), State#data{ssh_params=Ssh}. -send_bytes(Bytes, #data{sender = Sender}) -> - Sender ! {send,Bytes}, - ok. - -close_transport(D) -> - D#data.sender ! close, - ok. - - -nonblocking_sender(Socket, Callback) -> - receive - {send, Bytes} -> - case Callback:send(Socket, Bytes) of - ok -> - nonblocking_sender(Socket, Callback); - E = {error,_} -> - exit({shutdown,E}) - end; - - close -> - case Callback:close(Socket) of - ok -> - ok; - E = {error,_} -> - exit({shutdown,E}) - end - end. +send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> + Transport:send(Socket, Bytes). handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), -- cgit v1.2.3 From f6c9b5caaa1ba6c22248ff22cc678b30d89cdd36 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 13 Oct 2016 10:33:26 +0200 Subject: ssh: Removed matching of 'ok' after send which could cause error reports --- lib/ssh/src/ssh_connection_handler.erl | 50 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 24 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index abfba4baf1..ced049f0d0 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -525,7 +525,7 @@ handle_event(_, _Event, {init_error,Error}, _) -> %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)), - ok = send_bytes(VsnMsg, D), + send_bytes(VsnMsg, D), case inet:getopts(Socket=D#data.socket, [recbuf]) of {ok, [{recbuf,Size}]} -> %% Set the socket to the hello text line handling mode: @@ -545,12 +545,13 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) -> case Role of client -> %% The server may send info lines to the client before the version_exchange + %% RFC4253/4.2 inet:setopts(D#data.socket, [{active, once}]), keep_state_and_data; server -> %% But the client may NOT send them to the server. Openssh answers with cleartext, %% and so do we - ok = send_bytes("Protocol mismatch.", D), + send_bytes("Protocol mismatch.", D), {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}} end; @@ -565,7 +566,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D) -> {active, once}, {recbuf, D#data.inet_initial_recbuf_size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), - ok = send_bytes(SshPacket, D), + send_bytes(SshPacket, D), {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; not_supported -> @@ -583,7 +584,7 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload), Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of {ok, NextKexMsg, Ssh2} when Role==client -> - ok = send_bytes(NextKexMsg, D), + send_bytes(NextKexMsg, D), Ssh2; {ok, Ssh2} when Role==server -> Ssh2 @@ -596,43 +597,43 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, %%%---- diffie-hellman handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), - ok = send_bytes(KexdhReply, D), + send_bytes(KexdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- diffie-hellman group exchange handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - ok = send_bytes(GexGroup, D), + send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - ok = send_bytes(GexGroup, D), + send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params), - ok = send_bytes(KexGexInit, D), + send_bytes(KexGexInit, D), {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- elliptic curve diffie-hellman handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), - ok = send_bytes(KexEcdhReply, D), + send_bytes(KexEcdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; @@ -640,9 +641,9 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), - ok = send_bytes(KexGexReply, D), + send_bytes(KexGexReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; @@ -650,7 +651,7 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}}; @@ -662,7 +663,7 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> Ssh = case Role of client -> {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1), - ok = send_bytes(MsgReq, D), + send_bytes(MsgReq, D), Ssh2; server -> Ssh1 @@ -680,7 +681,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s "ssh-userauth" -> Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params, {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; _ -> @@ -692,7 +693,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), - ok = send_bytes(Msg, State), + send_bytes(Msg, State), {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; @@ -709,7 +710,7 @@ handle_event(_, %% Probably the very first userauth_request but we deny unauthorized login {not_authorized, _, {Reply,Ssh}} = ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}}; {"ssh-connection", "ssh-connection", Method} -> @@ -719,7 +720,7 @@ handle_event(_, %% Yepp! we support this method case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of {authorized, User, {Reply, Ssh}} -> - ok = send_bytes(Reply, D), + send_bytes(Reply, D), D#data.starter ! ssh_connected, connected_fun(User, Method, D), {next_state, {connected,server}, @@ -727,11 +728,11 @@ handle_event(_, ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Reason, D), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}} end; false -> @@ -1512,7 +1513,8 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> State#data{ssh_params=Ssh}. send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> - Transport:send(Socket, Bytes). + _ = Transport:send(Socket, Bytes), + ok. handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), -- cgit v1.2.3 From e875ff334a6d6f8db547868e5d57e71c80ff1859 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 17 Oct 2016 12:03:21 +0200 Subject: ssh: fix renegotiation problem --- lib/ssh/src/ssh_connection_handler.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ced049f0d0..dd414894d4 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -671,8 +671,9 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> {next_state, {service_request,Role}, D#data{ssh_params=Ssh}}; %% Subsequent key exchange rounds (renegotiation): -handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, D) -> - {next_state, {connected,Role}, D}; +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) -> + {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), + {next_state, {connected,Role}, D#data{ssh_params=Ssh}}; %%% ######## {service_request, client|server} -- cgit v1.2.3 From 39cb552ba0a5aaa36cb07526b3b895677ba1f3dc Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 26 Oct 2016 12:31:27 +0200 Subject: ssh: Add a 'catch' in ssh_channel.erl to prevent double close errors --- lib/ssh/src/ssh_channel.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index 426e2f5125..85b31f3669 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -261,7 +261,7 @@ handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager, adjust_window(Msg), {noreply, State#state{channel_state = ChannelState}, Timeout}; {stop, ChannelId, ChannelState} -> - ssh_connection:close(ConnectionManager, ChannelId), + catch ssh_connection:close(ConnectionManager, ChannelId), {stop, normal, State#state{close_sent = true, channel_state = ChannelState}} end; -- cgit v1.2.3 From 22a29422236f28adc24090dace03c0fd29311c9c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 27 Oct 2016 15:14:38 +0200 Subject: ssh: more info about shrinked binaries in ssh_dbg --- lib/ssh/src/ssh_dbg.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index bd6bc0335b..ce5596e0f9 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -113,7 +113,12 @@ setup_tracer(Write, MangleArg) -> ok. %%%---------------------------------------------------------------- -shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN',size(B),element(1,split_binary(B,20)),'***'}; +shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN', + size(B), + element(1,split_binary(B,20)), + '...', + element(2,split_binary(B,size(B)-20)) + }; shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L); shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T))); shrink_bin(X) -> X. -- cgit v1.2.3 From 067d8310093620ce1139e8d8a030bce6dd22d886 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 1 Nov 2016 15:01:01 +0100 Subject: ssh: exported ssh_dbg:shrink_bin and ssh_dbg:wr_record/3 for debugging purposes --- lib/ssh/src/Makefile | 2 +- lib/ssh/src/ssh_dbg.erl | 3 +++ lib/ssh/src/ssh_dbg.hrl | 27 +++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 lib/ssh/src/ssh_dbg.hrl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 69d5a47f83..7ab6f22424 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -96,7 +96,7 @@ APP_TARGET= $(EBIN)/$(APP_FILE) APPUP_SRC= $(APPUP_FILE).src APPUP_TARGET= $(EBIN)/$(APPUP_FILE) -INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl +INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl ssh_dbg.hrl # ---------------------------------------------------- # FLAGS diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index ce5596e0f9..dff2bae9f2 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -28,6 +28,9 @@ stop/0 ]). +-export([shrink_bin/1, + wr_record/3]). + -include("ssh.hrl"). -include("ssh_transport.hrl"). -include("ssh_connect.hrl"). diff --git a/lib/ssh/src/ssh_dbg.hrl b/lib/ssh/src/ssh_dbg.hrl new file mode 100644 index 0000000000..e94664737b --- /dev/null +++ b/lib/ssh/src/ssh_dbg.hrl @@ -0,0 +1,27 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(SSH_DBG_HRL). +-define(SSH_DBG_HRL, 1). + +-define(formatrec(RecName,R), + ssh_dbg:wr_record(R, record_info(fields,RecName), [])). + +-endif. % SSH_DBG_HRL defined -- cgit v1.2.3 From d1c8c59ec8bcec6758d7a00bed1d23e0907fb3a5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 9 Nov 2016 12:51:57 +0100 Subject: ssh: Change order on next_event actions in ssh_connection_handler --- lib/ssh/src/ssh_connection_handler.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index dd414894d4..e7c52f345e 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1194,12 +1194,12 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D)) of Msg = #ssh_msg_kexinit{} -> - {keep_state, D, [{next_event, internal, {Msg,DecryptedBytes}}, - {next_event, internal, prepare_next_packet} + {keep_state, D, [{next_event, internal, prepare_next_packet}, + {next_event, internal, {Msg,DecryptedBytes}} ]}; Msg -> - {keep_state, D, [{next_event, internal, Msg}, - {next_event, internal, prepare_next_packet} + {keep_state, D, [{next_event, internal, prepare_next_packet}, + {next_event, internal, Msg} ]} catch _C:_E -> -- cgit v1.2.3 From d13af8b8c6a7b04ec541585d1b8945103fffb988 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 11 Nov 2016 12:56:59 +0100 Subject: ssh: Adjust inet buffers if too small --- lib/ssh/src/ssh_connection_handler.erl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e7c52f345e..7451c9e6d0 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -928,6 +928,7 @@ handle_event(internal, Msg=#ssh_msg_channel_request{}, StateName, D) - handle_connection_msg(Msg, StateName, D); handle_event(internal, Msg=#ssh_msg_channel_success{}, StateName, D) -> + update_inet_buffers(D#data.socket), handle_connection_msg(Msg, StateName, D); handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, D) -> @@ -1007,6 +1008,7 @@ handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = RemoteId} -> Msg = ssh_connection:channel_success_msg(RemoteId), + update_inet_buffers(D#data.socket), {keep_state, send_msg(Msg,D)}; undefined -> @@ -1738,6 +1740,11 @@ send_replies(Repls, State) -> Repls). get_repl({connection_reply,Msg}, {CallRepls,S}) -> + if is_record(Msg, ssh_msg_channel_success) -> + update_inet_buffers(S#data.socket); + true -> + ok + end, {CallRepls, send_msg(Msg,S)}; get_repl({channel_data,undefined,_Data}, Acc) -> Acc; @@ -1926,3 +1933,13 @@ handshake(Pid, Ref, Timeout) -> {error, timeout} end. +update_inet_buffers(Socket) -> + {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]), + MinVal = 655360, + case + [{Tag,MinVal} || {Tag,Val} <- BufSzs0, + Val < MinVal] + of + [] -> ok; + NewOpts -> inet:setopts(Socket, NewOpts) + end. -- cgit v1.2.3 From 8215ea28fa2f699499b64d6f2c712e068b199390 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 11 Nov 2016 16:59:08 +0100 Subject: ssh: Add fun and fingerprint to option 'silently_accept_host' --- lib/ssh/src/ssh.erl | 9 +++++++++ lib/ssh/src/ssh_transport.erl | 16 ++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 1d7be3547b..31e343e81b 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -617,6 +617,15 @@ handle_ssh_option({user_dir_fun, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) -> Opt; +handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_function(Value,2) -> + Opt; +handle_ssh_option({silently_accept_hosts, {DigestAlg,Value}} = Opt) when is_function(Value,2) -> + case lists:member(DigestAlg, [md5, sha, sha224, sha256, sha384, sha512]) of + true -> + Opt; + false -> + throw({error, {eoptions, Opt}}) + end; handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 15b80de30a..21ba34506a 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -734,12 +734,16 @@ public_algo({#'ECPoint'{},{namedCurve,OID}}) -> list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). -accepted_host(Ssh, PeerName, Opts) -> +accepted_host(Ssh, PeerName, Public, Opts) -> case proplists:get_value(silently_accept_hosts, Opts, false) of + F when is_function(F,2) -> + true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public))); + {DigestAlg,F} when is_function(F,2) -> + true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))); true -> - yes; + true; false -> - yes_no(Ssh, "New host " ++ PeerName ++ " accept") + yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") end. known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh, @@ -749,10 +753,10 @@ known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh, true -> ok; false -> - case accepted_host(Ssh, PeerName, Opts) of - yes -> + case accepted_host(Ssh, PeerName, Public, Opts) of + true -> Mod:add_host_key(PeerName, Public, Opts); - no -> + false -> {error, rejected} end end. -- cgit v1.2.3 From 3a519b7b74ae07f4d66989313a0c065c96bcad8c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 21 Nov 2016 17:52:06 +0100 Subject: ssh: fix error when large client packet size and small on server --- lib/ssh/src/ssh_connection.erl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index d0f2d54c06..1153095135 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -287,6 +287,9 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, ssh_channel:cache_update(Cache, Channel#channel{ remote_id = RemoteId, + recv_packet_size = max(32768, % rfc4254/5.2 + min(PacketSz, Channel#channel.recv_packet_size) + ), send_window_size = WindowSz, send_packet_size = PacketSz}), {Reply, Connection} = reply_msg(Channel, Connection0, {open, ChannelId}), -- cgit v1.2.3 From 3eddb0f762de248d3230b38bc9d478bfbc8e7331 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Wed, 7 Dec 2016 13:15:31 +0100 Subject: Update copyright-year --- lib/ssh/src/ssh.appup.src | 2 +- lib/ssh/src/ssh_acceptor.erl | 2 +- lib/ssh/src/ssh_connection.erl | 2 +- lib/ssh/src/ssh_info.erl | 2 +- lib/ssh/src/ssh_sftpd.erl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 4cda8fee95..2540720c41 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -1,7 +1,7 @@ %% -*- erlang -*- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2015. All Rights Reserved. +%% Copyright Ericsson AB 2004-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 9f3e60bd62..13c9d9af4a 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 1153095135..c7a2c92670 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl index 0c24c09887..d464def6fa 100644 --- a/lib/ssh/src/ssh_info.erl +++ b/lib/ssh/src/ssh_info.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index dca018f20f..b739955836 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2015. All Rights Reserved. +%% Copyright Ericsson AB 2005-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From a8ea98ef814022dc02a1917105a0572007952e52 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 28 Nov 2016 14:50:08 +0100 Subject: ssh: [test] Put tstflg values in a proplist --- lib/ssh/src/ssh_auth.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index ac35b70209..9b54ecb2dd 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -406,7 +406,11 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, kb_tries_left = KbTriesLeft, user = User, userauth_supported_methods = Methods} = Ssh) -> - SendOneEmpty = proplists:get_value(tstflg, Opts) == one_empty, + SendOneEmpty = + (proplists:get_value(tstflg,Opts) == one_empty) + orelse + proplists:get_value(one_empty, proplists:get_value(tstflg,Opts,[]), false), + case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of {true,Ssh1} when SendOneEmpty==true -> Msg = #ssh_msg_userauth_info_request{name = "", -- cgit v1.2.3 From 5901661b62a006a6c55d77503a7198c7c56dabe7 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 13 Dec 2016 10:35:31 +0100 Subject: ssh: Optimize handling of #ssh.shared_secret It is not necessary to mpint-encode it every time it is used (in MAC:s), it sufficies to do it once after key exchange --- lib/ssh/src/ssh_transport.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 21ba34506a..53e9ef485b 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -367,7 +367,7 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, h_sig = H_SIG }, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}, - shared_secret = K, + shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh1, H)}}; @@ -393,7 +393,7 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, + {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H)}}; Error -> @@ -532,7 +532,7 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey, f = Public, h_sig = H_SIG}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, + {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H) }}; @@ -568,7 +568,7 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, + {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H)}}; _Error -> @@ -618,7 +618,7 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, h_sig = H_SIG}, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{MyPublic,MyPrivate},Curve}, - shared_secret = K, + shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh1, H)}} catch @@ -644,7 +644,7 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, + {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H)}}; Error -> @@ -1577,7 +1577,7 @@ hash(SSH, Char, Bits) -> hash(_SSH, _Char, 0, _HASH) -> <<>>; hash(SSH, Char, N, HASH) -> - K = ssh_bits:mpint(SSH#ssh.shared_secret), +K = SSH#ssh.shared_secret, % K = ssh_bits:mpint(SSH#ssh.shared_secret), H = SSH#ssh.exchanged_hash, SessionID = SSH#ssh.session_id, K1 = HASH([K, H, Char, SessionID]), -- cgit v1.2.3 From 2b36238bc2a2444b97a3d01fa35ab1ceecfe1c4d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 13 Dec 2016 10:38:27 +0100 Subject: ssh: Optimize ssh_bits:name_list It is better (=faster) to use built-in functions and library functions. --- lib/ssh/src/ssh_bits.erl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl index 8bedaaf0c5..cc2e7e2be5 100644 --- a/lib/ssh/src/ssh_bits.erl +++ b/lib/ssh/src/ssh_bits.erl @@ -30,13 +30,7 @@ -export([random/1]). %%%---------------------------------------------------------------- -name_list([Name]) -> to_bin(Name); -name_list([Name|Ns]) -> <<(to_bin(Name))/binary, ",", (name_list(Ns))/binary>>; -name_list([]) -> <<>>. - -to_bin(A) when is_atom(A) -> list_to_binary(atom_to_list(A)); -to_bin(S) when is_list(S) -> list_to_binary(S); -to_bin(B) when is_binary(B) -> B. +name_list(NamesList) -> list_to_binary(lists:join($,, NamesList)). %%%---------------------------------------------------------------- %%% Multi Precision Integer encoding -- cgit v1.2.3 From 767234a17378db0985ca49415ae5b9d3423ed754 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 13 Dec 2016 10:40:31 +0100 Subject: ssh: Optimze ssh_bits:mpint/1 By using binary constructors we push the hard work down into the emulator --- lib/ssh/src/ssh_bits.erl | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl index cc2e7e2be5..3ce7758447 100644 --- a/lib/ssh/src/ssh_bits.erl +++ b/lib/ssh/src/ssh_bits.erl @@ -36,27 +36,25 @@ name_list(NamesList) -> list_to_binary(lists:join($,, NamesList)). %%% Multi Precision Integer encoding mpint(-1) -> <<0,0,0,1,16#ff>>; mpint(0) -> <<0,0,0,0>>; -mpint(X) when X < 0 -> mpint_neg(X,0,[]); -mpint(X) -> mpint_pos(X,0,[]). - -mpint_neg(-1,I,Ds=[MSB|_]) -> - if MSB band 16#80 =/= 16#80 -> - <>; - true -> - <> +mpint(I) when I>0 -> + <> = binary:encode_unsigned(I), + case B1 band 16#80 of + 16#80 -> + <<(size(V)+2):32/unsigned-big-integer, 0,B1,V/binary >>; + _ -> + <<(size(V)+1):32/unsigned-big-integer, B1,V/binary >> end; -mpint_neg(X,I,Ds) -> - mpint_neg(X bsr 8,I+1,[(X band 255)|Ds]). - -mpint_pos(0,I,Ds=[MSB|_]) -> - if MSB band 16#80 == 16#80 -> - <>; - true -> - <> - end; -mpint_pos(X,I,Ds) -> - mpint_pos(X bsr 8,I+1,[(X band 255)|Ds]). - +mpint(N) when N<0 -> + Sxn = 8*size(binary:encode_unsigned(-N)), + Sxn1 = Sxn+8, + <> = <<1, 0:Sxn>>, + <> = binary:encode_unsigned(W+N), + case B1 band 16#80 of + 16#80 -> + <<(size(V)+1):32/unsigned-big-integer, B1,V/binary >>; + _ -> + <<(size(V)+2):32/unsigned-big-integer, 255,B1,V/binary >> + end. %%%---------------------------------------------------------------- %% random/1 -- cgit v1.2.3 From 6d393493ded1462dd5469cb4bfc36db97134f5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Fri, 16 Dec 2016 18:48:42 +0100 Subject: ssh: Remove whitespace errors in ssh_sftp.erl --- lib/ssh/src/ssh_sftp.erl | 128 +++++++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 64 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index afc2fb88ff..a648247ef9 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -37,7 +37,7 @@ -export([open/3, open_tar/3, opendir/2, close/2, readdir/2, pread/4, read/3, open/4, open_tar/4, opendir/3, close/3, readdir/3, pread/5, read/4, apread/4, aread/3, pwrite/4, write/3, apwrite/4, awrite/3, - pwrite/5, write/4, + pwrite/5, write/4, position/3, real_path/2, read_file_info/2, get_file_info/2, position/4, real_path/3, read_file_info/3, get_file_info/3, write_file_info/3, read_link_info/2, read_link/2, make_symlink/3, @@ -52,7 +52,7 @@ %% TODO: Should be placed elsewhere ssh_sftpd should not call functions in ssh_sftp! -export([info_to_attr/1, attr_to_info/1]). --record(state, +-record(state, { xf, rep_buf = <<>>, @@ -64,7 +64,7 @@ -record(fileinf, { - handle, + handle, offset, size, mode @@ -81,7 +81,7 @@ enc_text_buf = <<>>, % Encrypted text plain_text_buf = <<>> % Decrypted text }). - + -define(FILEOP_TIMEOUT, infinity). -define(NEXT_REQID(S), @@ -98,7 +98,7 @@ start_channel(Cm) when is_pid(Cm) -> start_channel(Socket) when is_port(Socket) -> start_channel(Socket, []); start_channel(Host) when is_list(Host) -> - start_channel(Host, []). + start_channel(Host, []). start_channel(Socket, Options) when is_port(Socket) -> Timeout = @@ -110,7 +110,7 @@ start_channel(Socket, Options) when is_port(Socket) -> TO end, case ssh:connect(Socket, Options, Timeout) of - {ok,Cm} -> + {ok,Cm} -> case start_channel(Cm, Options) of {ok, Pid} -> {ok, Pid, Cm}; @@ -124,13 +124,13 @@ start_channel(Cm, Opts) when is_pid(Cm) -> Timeout = proplists:get_value(timeout, Opts, infinity), {_, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []), case ssh_xfer:attach(Cm, [], ChanOpts) of - {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, + {ok, ChannelId, Cm} -> + case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId, SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> - {ok, Pid}; + {ok, Pid}; TimeOut -> TimeOut end; @@ -150,7 +150,7 @@ start_channel(Host, Port, Opts) -> Timeout = proplists:get_value(timeout, SftpOpts, infinity), case ssh_xfer:connect(Host, Port, SshOpts, ChanOpts, Timeout) of {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, + case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId, SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of @@ -165,7 +165,7 @@ start_channel(Host, Port, Opts) -> {error, ignore} end; Error -> - Error + Error end. stop_channel(Pid) -> @@ -174,12 +174,12 @@ stop_channel(Pid) -> OldValue = process_flag(trap_exit, true), link(Pid), exit(Pid, ssh_sftp_stop_channel), - receive + receive {'EXIT', Pid, normal} -> ok after 5000 -> exit(Pid, kill), - receive + receive {'EXIT', Pid, killed} -> ok end @@ -209,9 +209,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> erl_tar:init(Pid, write, fun(write, {_,Data}) -> write_to_remote_tar(Pid, Handle, to_bin(Data), FileOpTimeout); - (position, {_,Pos}) -> + (position, {_,Pos}) -> position(Pid, Handle, Pos, FileOpTimeout); - (close, _) -> + (close, _) -> close(Pid, Handle, FileOpTimeout) end); {true,false,[{crypto,{CryptoInitFun,CryptoEncryptFun,CryptoEndFun}}]} -> @@ -245,9 +245,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> erl_tar:init(Pid, read, fun(read2, {_,Len}) -> read_repeat(Pid, Handle, Len, FileOpTimeout); - (position, {_,Pos}) -> + (position, {_,Pos}) -> position(Pid, Handle, Pos, FileOpTimeout); - (close, _) -> + (close, _) -> close(Pid, Handle, FileOpTimeout) end); {false,true,[{crypto,{CryptoInitFun,CryptoDecryptFun}}]} -> @@ -258,9 +258,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> erl_tar:init(Pid, read, fun(read2, {_,Len}) -> read_buf(Pid, SftpHandle, BufHandle, Len, FileOpTimeout); - (position, {_,Pos}) -> + (position, {_,Pos}) -> position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout); - (close, _) -> + (close, _) -> call(Pid, {erase_bufinf,BufHandle}, FileOpTimeout), close(Pid, SftpHandle, FileOpTimeout) end); @@ -292,7 +292,7 @@ pread(Pid, Handle, Offset, Len, FileOpTimeout) -> read(Pid, Handle, Len) -> read(Pid, Handle, Len, ?FILEOP_TIMEOUT). read(Pid, Handle, Len, FileOpTimeout) -> - call(Pid, {read,false,Handle, Len}, FileOpTimeout). + call(Pid, {read,false,Handle, Len}, FileOpTimeout). %% TODO this ought to be a cast! Is so in all practial meaning %% even if it is obscure! @@ -301,7 +301,7 @@ apread(Pid, Handle, Offset, Len) -> %% TODO this ought to be a cast! aread(Pid, Handle, Len) -> - call(Pid, {read,true,Handle, Len}, infinity). + call(Pid, {read,true,Handle, Len}, infinity). pwrite(Pid, Handle, Offset, Data) -> pwrite(Pid, Handle, Offset, Data, ?FILEOP_TIMEOUT). @@ -367,7 +367,7 @@ make_symlink(Pid, Name, Target) -> make_symlink(Pid, Name, Target, ?FILEOP_TIMEOUT). make_symlink(Pid, Name, Target, FileOpTimeout) -> call(Pid, {make_symlink,false, Name, Target}, FileOpTimeout). - + rename(Pid, FromFile, ToFile) -> rename(Pid, FromFile, ToFile, ?FILEOP_TIMEOUT). rename(Pid, FromFile, ToFile, FileOpTimeout) -> @@ -411,8 +411,8 @@ list_dir(Pid, Name, FileOpTimeout) -> close(Pid, Handle, FileOpTimeout), case Res of {ok, List} -> - NList = lists:foldl(fun({Nm, _Info},Acc) -> - [Nm|Acc] end, + NList = lists:foldl(fun({Nm, _Info},Acc) -> + [Nm|Acc] end, [], List), {ok,NList}; Error -> Error @@ -482,7 +482,7 @@ write_file_loop(Pid, Handle, Pos, Bin, Remain, PacketSz, FileOpTimeout) -> <<_:Pos/binary, Data:PacketSz/binary, _/binary>> = Bin, case write(Pid, Handle, Data, FileOpTimeout) of ok -> - write_file_loop(Pid, Handle, + write_file_loop(Pid, Handle, Pos+PacketSz, Bin, Remain-PacketSz, PacketSz, FileOpTimeout); Error -> @@ -510,7 +510,7 @@ init([Cm, ChannelId, Options]) -> Xf = #ssh_xfer{cm = Cm, channel = ChannelId}, {ok, #state{xf = Xf, - req_id = 0, + req_id = 0, rep_buf = <<>>, inf = new_inf(), opts = Options}}; @@ -519,7 +519,7 @@ init([Cm, ChannelId, Options]) -> Error -> {stop, {shutdown, Error}} end. - + %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages @@ -541,7 +541,7 @@ handle_call({{timeout, Timeout}, wait_for_version_negotiation}, From, handle_call({_, wait_for_version_negotiation}, _, State) -> {reply, ok, State}; - + handle_call({{timeout, infinity}, Msg}, From, State) -> do_handle_call(Msg, From, State); handle_call({{timeout, Timeout}, Msg}, From, #state{req_id = Id} = State) -> @@ -636,7 +636,7 @@ do_handle_call({pread,Async,Handle,At,Length}, From, State) -> binary -> {{ok,Data}, State2}; text -> {{ok,binary_to_list(Data)}, State2} end; - (Rep, State2) -> + (Rep, State2) -> {Rep, State2} end); Error -> @@ -777,7 +777,7 @@ do_handle_call(recv_window, _From, State) -> do_handle_call(stop, _From, State) -> {stop, shutdown, ok, State}; -do_handle_call(Call, _From, State) -> +do_handle_call(Call, _From, State) -> {reply, {error, bad_call, Call, State}, State}. %%-------------------------------------------------------------------- @@ -785,13 +785,13 @@ do_handle_call(Call, _From, State) -> %% %% Description: Handles channel messages %%-------------------------------------------------------------------- -handle_ssh_msg({ssh_cm, _ConnectionManager, - {data, _ChannelId, 0, Data}}, #state{rep_buf = Data0} = +handle_ssh_msg({ssh_cm, _ConnectionManager, + {data, _ChannelId, 0, Data}}, #state{rep_buf = Data0} = State0) -> State = handle_reply(State0, <>), {ok, State}; -handle_ssh_msg({ssh_cm, _ConnectionManager, +handle_ssh_msg({ssh_cm, _ConnectionManager, {data, _ChannelId, 1, Data}}, State) -> error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]), {ok, State}; @@ -803,7 +803,7 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) -> %% Ignore signals according to RFC 4254 section 6.9. {ok, State}; -handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State0) -> State = reply_all(State0, {error, Error}), {stop, ChannelId, State}; @@ -823,7 +823,7 @@ handle_msg({ssh_channel_up, _, _}, #state{opts = Options, xf = Xf} = State) -> {ok, State}; %% Version negotiation timed out -handle_msg({timeout, undefined, From}, +handle_msg({timeout, undefined, From}, #state{xf = #ssh_xfer{channel = ChannelId}} = State) -> ssh_channel:reply(From, {error, timeout}), {stop, ChannelId, State}; @@ -839,12 +839,12 @@ handle_msg({timeout, Id, From}, #state{req_list = ReqList0} = State) -> end; %% Connection manager goes down -handle_msg({'DOWN', _Ref, _Type, _Process, _}, +handle_msg({'DOWN', _Ref, _Type, _Process, _}, #state{xf = #ssh_xfer{channel = ChannelId}} = State) -> {stop, ChannelId, State}; - + %% Stopped by user -handle_msg({'EXIT', _, ssh_sftp_stop_channel}, +handle_msg({'EXIT', _, ssh_sftp_stop_channel}, #state{xf = #ssh_xfer{channel = ChannelId}} = State) -> {stop, ChannelId, State}; @@ -883,10 +883,10 @@ call(Pid, Msg, TimeOut) -> handle_reply(State, <>) -> do_handle_reply(State, Reply, Rest); -handle_reply(State, Data) -> +handle_reply(State, Data) -> State#state{rep_buf = Data}. -do_handle_reply(#state{xf = Xf} = State, +do_handle_reply(#state{xf = Xf} = State, <>, Rest) -> Ext = ssh_xfer:decode_ext(BinExt), case Xf#ssh_xfer.vsn of @@ -899,7 +899,7 @@ do_handle_reply(#state{xf = Xf} = State, ok end, ssh_channel:reply(From, ok) - end, + end, State#state{xf = Xf#ssh_xfer{vsn = Version, ext = Ext}, rep_buf = Rest}; do_handle_reply(State0, Data, Rest) -> @@ -919,9 +919,9 @@ handle_req_reply(State0, {_, ReqID, _} = XfReply) -> List = lists:keydelete(ReqID, 1, State0#state.req_list), State1 = State0#state { req_list = List }, case catch Fun(xreply(XfReply),State1) of - {'EXIT', _} -> + {'EXIT', _} -> State1; - State -> + State -> State end end. @@ -998,15 +998,15 @@ reply_all(State, Reply) -> make_reply(ReqID, true, From, State) -> {reply, {async, ReqID}, update_request_info(ReqID, State, - fun(Reply,State1) -> + fun(Reply,State1) -> async_reply(ReqID,Reply,From,State1) end)}; make_reply(ReqID, false, From, State) -> {noreply, update_request_info(ReqID, State, - fun(Reply,State1) -> - sync_reply(Reply, From, State1) + fun(Reply,State1) -> + sync_reply(Reply, From, State1) end)}. make_reply_post(ReqID, true, From, State, PostFun) -> @@ -1074,13 +1074,13 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) -> unix_to_datetime(undefined) -> undefined; unix_to_datetime(UTCSecs) -> - UTCDateTime = + UTCDateTime = calendar:gregorian_seconds_to_datetime(UTCSecs + 62167219200), erlang:universaltime_to_localtime(UTCDateTime). datetime_to_unix(undefined) -> undefined; -datetime_to_unix(LocalDateTime) -> +datetime_to_unix(LocalDateTime) -> UTCDateTime = erlang:localtime_to_universaltime(LocalDateTime), calendar:datetime_to_gregorian_seconds(UTCDateTime) - 62167219200. @@ -1229,7 +1229,7 @@ lseek_pos({cur, Offset}, CurOffset, _CurSize) true -> {ok, NewOffset} end; -lseek_pos({eof, Offset}, _CurOffset, CurSize) +lseek_pos({eof, Offset}, _CurOffset, CurSize) when is_integer(Offset) andalso -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset andalso Offset < ?SSH_FILEXFER_LARGEFILESIZE -> NewOffset = CurSize + Offset, @@ -1239,7 +1239,7 @@ lseek_pos({eof, Offset}, _CurOffset, CurSize) {ok, NewOffset} end; lseek_pos(_, _, _) -> - {error, einval}. + {error, einval}. %%%================================================================ %%% @@ -1277,13 +1277,13 @@ position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout) -> case Pos of {cur,0} when Mode==write -> {ok,Size+size(Buf0)}; - + {cur,0} when Mode==read -> {ok,Size}; - + _ when Mode==read, is_integer(Pos) -> Skip = Pos-Size, - if + if Skip < 0 -> {error, cannot_rewind}; Skip == 0 -> @@ -1318,7 +1318,7 @@ read_buf(Pid, SftpHandle, BufHandle, WantedLen, FileOpTimeout) -> eof end. -do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout, +do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout, B=#bufinf{plain_text_buf=PlainBuf0, size = Size}) when size(PlainBuf0) >= WantedLen -> @@ -1327,7 +1327,7 @@ do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout, {ok,ResultBin,B#bufinf{plain_text_buf=PlainBuf, size = Size + WantedLen}}; -do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, +do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B0=#bufinf{plain_text_buf = PlainBuf0, enc_text_buf = EncBuf0, chunksize = undefined @@ -1335,12 +1335,12 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, when size(EncBuf0) > 0 -> %% We have (at least) one decodable byte waiting for decodeing. {ok,DecodedBin,B} = apply_crypto(EncBuf0, B0), - do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B#bufinf{plain_text_buf = <>, enc_text_buf = <<>> }); - -do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + +do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B0=#bufinf{plain_text_buf = PlainBuf0, enc_text_buf = EncBuf0, chunksize = ChunkSize0 @@ -1349,11 +1349,11 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, %% We have (at least) one chunk of decodable bytes waiting for decodeing. <> = EncBuf0, {ok,DecodedBin,B} = apply_crypto(ToDecode, B0), - do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B#bufinf{plain_text_buf = <>, enc_text_buf = EncBuf }); - + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B=#bufinf{enc_text_buf = EncBuf0}) -> %% We must read more bytes and append to the buffer of encoded bytes. case read(Pid, SftpHandle, Packet, FileOpTimeout) of @@ -1370,7 +1370,7 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B=#bufinf{enc write_buf(Pid, SftpHandle, BufHandle, PlainBin, FileOpTimeout) -> {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout), {ok,B0=#bufinf{plain_text_buf=PTB}} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout), - case do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + case do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, B0#bufinf{plain_text_buf = <>}) of {ok, B} -> call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout), @@ -1379,7 +1379,7 @@ write_buf(Pid, SftpHandle, BufHandle, PlainBin, FileOpTimeout) -> {error,Error} end. -do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, +do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, B=#bufinf{enc_text_buf = EncBuf0, size = Size}) when size(EncBuf0) >= Packet -> @@ -1421,9 +1421,9 @@ do_the_write_buf(_Pid, _SftpHandle, _Packet, _FileOpTimeout, B) -> apply_crypto(In, B=#bufinf{crypto_state = CState0, crypto_fun = F}) -> case F(In,CState0) of - {ok,EncodedBin,CState} -> + {ok,EncodedBin,CState} -> {ok, EncodedBin, B#bufinf{crypto_state=CState}}; - {ok,EncodedBin,CState,ChunkSize} -> + {ok,EncodedBin,CState,ChunkSize} -> {ok, EncodedBin, B#bufinf{crypto_state=CState, chunksize=ChunkSize}} end. -- cgit v1.2.3 From 4161f80e0197ec5447f9a48ef3a0c9c6cfcfa5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Fri, 16 Dec 2016 18:55:40 +0100 Subject: ssh: Use maps instead of dict in ssh_sftp --- lib/ssh/src/ssh_sftp.erl | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index a648247ef9..b937f0412d 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -555,13 +555,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. do_handle_call({get_bufinf,BufHandle}, _From, S=#state{inf=I0}) -> - {reply, dict:find(BufHandle,I0), S}; + {reply, maps:find(BufHandle,I0), S}; do_handle_call({put_bufinf,BufHandle,B}, _From, S=#state{inf=I0}) -> - {reply, ok, S#state{inf=dict:store(BufHandle,B,I0)}}; + {reply, ok, S#state{inf=maps:put(BufHandle,B,I0)}}; do_handle_call({erase_bufinf,BufHandle}, _From, S=#state{inf=I0}) -> - {reply, ok, S#state{inf=dict:erase(BufHandle,I0)}}; + {reply, ok, S#state{inf=maps:remove(BufHandle,I0)}}; do_handle_call({open, Async,FileName,Mode}, From, #state{xf = XF} = State) -> {Access,Flags,Attrs} = open_mode(XF#ssh_xfer.vsn, Mode), @@ -1128,11 +1128,11 @@ open_mode3(Modes) -> end, {[], Fl, A}. -%% accessors for inf dict -new_inf() -> dict:new(). +%% accessors for inf map +new_inf() -> #{}. add_new_handle(Handle, FileMode, Inf) -> - dict:store(Handle, #fileinf{offset=0, size=0, mode=FileMode}, Inf). + maps:put(Handle, #fileinf{offset=0, size=0, mode=FileMode}, Inf). update_size(Handle, NewSize, State) -> OldSize = get_size(Handle, State), @@ -1152,27 +1152,24 @@ update_offset(Handle, NewOffset, State0) -> %% access size and offset for handle put_size(Handle, Size, State) -> Inf0 = State#state.inf, - case dict:find(Handle, Inf0) of + case maps:find(Handle, Inf0) of {ok, FI} -> - State#state{inf=dict:store(Handle, FI#fileinf{size=Size}, Inf0)}; + State#state{inf=maps:put(Handle, FI#fileinf{size=Size}, Inf0)}; _ -> - State#state{inf=dict:store(Handle, #fileinf{size=Size,offset=0}, - Inf0)} + State#state{inf=maps:put(Handle, #fileinf{size=Size,offset=0}, Inf0)} end. put_offset(Handle, Offset, State) -> Inf0 = State#state.inf, - case dict:find(Handle, Inf0) of + case maps:find(Handle, Inf0) of {ok, FI} -> - State#state{inf=dict:store(Handle, FI#fileinf{offset=Offset}, - Inf0)}; + State#state{inf=maps:put(Handle, FI#fileinf{offset=Offset}, Inf0)}; _ -> - State#state{inf=dict:store(Handle, #fileinf{size=Offset, - offset=Offset}, Inf0)} + State#state{inf=maps:put(Handle, #fileinf{size=Offset, offset=Offset}, Inf0)} end. get_size(Handle, State) -> - case dict:find(Handle, State#state.inf) of + case maps:find(Handle, State#state.inf) of {ok, FI} -> FI#fileinf.size; _ -> @@ -1180,11 +1177,11 @@ get_size(Handle, State) -> end. %% get_offset(Handle, State) -> -%% {ok, FI} = dict:find(Handle, State#state.inf), +%% {ok, FI} = maps:find(Handle, State#state.inf), %% FI#fileinf.offset. get_mode(Handle, State) -> - case dict:find(Handle, State#state.inf) of + case maps:find(Handle, State#state.inf) of {ok, FI} -> FI#fileinf.mode; _ -> @@ -1192,14 +1189,14 @@ get_mode(Handle, State) -> end. erase_handle(Handle, State) -> - FI = dict:erase(Handle, State#state.inf), + FI = maps:remove(Handle, State#state.inf), State#state{inf = FI}. %% %% Caluclate a integer offset %% lseek_position(Handle, Pos, State) -> - case dict:find(Handle, State#state.inf) of + case maps:find(Handle, State#state.inf) of {ok, #fileinf{offset=O, size=S}} -> lseek_pos(Pos, O, S); _ -> -- cgit v1.2.3 From 3c5c1d70ec006e5b0b87dad0bf97a09e62e1e7c6 Mon Sep 17 00:00:00 2001 From: Philip Cristiano Date: Wed, 11 Jan 2017 06:13:49 -0500 Subject: ssh: Correct ssh_sftpd_file_api dialzyer spec The `State` seems to have been included twice in 91acfc. --- lib/ssh/src/ssh_sftpd_file_api.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl index 78f452df67..e444e52ac0 100644 --- a/lib/ssh/src/ssh_sftpd_file_api.erl +++ b/lib/ssh/src/ssh_sftpd_file_api.erl @@ -36,7 +36,7 @@ -callback list_dir(file:name(), State::term()) -> {{ok, Filenames::term()}, State::term()} | {{error, Reason::term()}, State::term()}. -callback make_dir(Dir::term(), State::term()) -> - {{ok, State::term()},State::term()} | {{error, Reason::term()}, State::term()}. + {ok, State::term()} | {{error, Reason::term()}, State::term()}. -callback make_symlink(Path2::term(), Path::term(), State::term()) -> {ok, State::term()} | {{error, Reason::term()}, State::term()}. -callback open(Path::term(), Flags::term(), State::term()) -> -- cgit v1.2.3 From 9ff231ba932dded5d712bb34fffe1f396d975a2c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Jan 2017 16:08:01 +0100 Subject: ssh: Reduce info leakage on decrypt errors Use same message when there are packet errors like too long length, MAC, decrypt or decode errors. This is regarded as good practise to prevent some attacks --- lib/ssh/src/ssh_connection_handler.erl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 7451c9e6d0..8718e92fa2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1206,7 +1206,7 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, catch _C:_E -> disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Encountered unexpected input"}, + description = "Bad packet"}, StateName, D) end; @@ -1221,13 +1221,12 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, {bad_mac, Ssh1} -> disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad mac"}, + description = "Bad packet"}, StateName, D0#data{ssh_params=Ssh1}); - {error, {exceeds_max_size,PacketLen}} -> + {error, {exceeds_max_size,_PacketLen}} -> disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet length " - ++ integer_to_list(PacketLen)}, + description = "Bad packet"}, StateName, D0) catch _C:_E -> -- cgit v1.2.3 From 69feb8bed6118e9a955d71c8d55faa6bc5dec1b1 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 20 Jan 2017 14:57:00 +0100 Subject: ssh: ssh_dbg now reports HELLO msgs and timestamps --- lib/ssh/src/ssh_dbg.erl | 66 ++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 25 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index dff2bae9f2..0345bbdea7 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -50,50 +50,61 @@ messages(Write, MangleArg) when is_function(Write,2), is_function(MangleArg,1) -> catch dbg:start(), setup_tracer(Write, MangleArg), - dbg:p(new,c), + dbg:p(new,[c,timestamp]), dbg_ssh_messages(). dbg_ssh_messages() -> dbg:tp(ssh_message,encode,1, x), dbg:tp(ssh_message,decode,1, x), - dbg:tpl(ssh_transport,select_algorithm,3, x). - + dbg:tpl(ssh_transport,select_algorithm,3, x), + dbg:tp(ssh_transport,hello_version_msg,1, x), + dbg:tp(ssh_transport,handle_hello_version,1, x). + %%%---------------------------------------------------------------- stop() -> dbg:stop(). %%%================================================================ -msg_formater({trace,Pid,call,{ssh_message,encode,[Msg]}}, D) -> - fmt("~nSEND ~p ~s~n", [Pid,wr_record(shrink_bin(Msg))], D); -msg_formater({trace,_Pid,return_from,{ssh_message,encode,1},_Res}, D) -> +msg_formater({trace_ts,Pid,call,{ssh_message,encode,[Msg]},TS}, D) -> + fmt("~n~s SEND ~p ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); +msg_formater({trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) -> D; -msg_formater({trace,_Pid,call,{ssh_message,decode,_}}, D) -> +msg_formater({trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> D; -msg_formater({trace,Pid,return_from,{ssh_message,decode,1},Msg}, D) -> - fmt("~n~p RECV ~s~n", [Pid,wr_record(shrink_bin(Msg))], D); +msg_formater({trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> + fmt("~n~s ~p RECV ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); -msg_formater({trace,_Pid,call,{ssh_transport,select_algorithm,_}}, D) -> +msg_formater({trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) -> + D; +msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg},TS}, D) -> + fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D); + +msg_formater({trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> D; -msg_formater({trace,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg}}, D) -> - fmt("~n~p ALGORITHMS~n~s~n", [Pid, wr_record(Alg)], D); +msg_formater({trace_ts,Pid,return_from,{ssh_transport,hello_version_msg,1},Hello,TS}, D) -> + fmt("~n~s ~p TCP SEND HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); +msg_formater({trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS}, D) -> + fmt("~n~s ~p RECV HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); +msg_formater({trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> + D; -msg_formater({trace,Pid,send,{tcp,Sock,Bytes},Pid}, D) -> - fmt("~n~p TCP SEND on ~p~n ~p~n", [Pid,Sock, shrink_bin(Bytes)], D); +msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> + fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D); -msg_formater({trace,Pid,send,{tcp,Sock,Bytes},Dest}, D) -> - fmt("~n~p TCP SEND from ~p TO ~p~n ~p~n", [Pid,Sock,Dest, shrink_bin(Bytes)], D); +msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Dest,TS}, D) -> + fmt("~n~s ~p TCP SEND from ~p TO ~p~n ~p~n", [ts(TS),Pid,Sock,Dest, shrink_bin(Bytes)], D); -msg_formater({trace,Pid,send,ErlangMsg,Dest}, D) -> - fmt("~n~p ERL MSG SEND TO ~p~n ~p~n", [Pid,Dest, shrink_bin(ErlangMsg)], D); +msg_formater({trace_ts,Pid,send,ErlangMsg,Dest,TS}, D) -> + fmt("~n~s ~p ERL MSG SEND TO ~p~n ~p~n", [ts(TS),Pid,Dest, shrink_bin(ErlangMsg)], D); -msg_formater({trace,Pid,'receive',{tcp,Sock,Bytes}}, D) -> - fmt("~n~p TCP RECEIVE on ~p~n ~p~n", [Pid,Sock,shrink_bin(Bytes)], D); +msg_formater({trace_ts,Pid,'receive',{tcp,Sock,Bytes},TS}, D) -> + fmt("~n~s ~p TCP RECEIVE on ~p~n ~p~n", [ts(TS),Pid,Sock,shrink_bin(Bytes)], D); -msg_formater({trace,Pid,'receive',ErlangMsg}, D) -> - fmt("~n~p ERL MSG RECEIVE~n ~p~n", [Pid,shrink_bin(ErlangMsg)], D); +msg_formater({trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> + fmt("~n~s ~p ERL MSG RECEIVE~n ~p~n", [ts(TS),Pid,shrink_bin(ErlangMsg)], D); msg_formater(M, D) -> @@ -106,6 +117,11 @@ msg_formater(M, D) -> fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) -> D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}. +ts({_,_,Usec}=Now) -> + {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now), + io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); +ts(_) -> + "-". %%%---------------------------------------------------------------- setup_tracer(Write, MangleArg) -> Handler = fun(Arg, D) -> @@ -116,11 +132,11 @@ setup_tracer(Write, MangleArg) -> ok. %%%---------------------------------------------------------------- -shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN', +shrink_bin(B) when is_binary(B), size(B)>256 -> {'*** SHRINKED BIN', size(B), - element(1,split_binary(B,20)), + element(1,split_binary(B,64)), '...', - element(2,split_binary(B,size(B)-20)) + element(2,split_binary(B,size(B)-64)) }; shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L); shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T))); -- cgit v1.2.3 From e0b2554dcfae4a8a20adbb3ebf226f7ebe4f89ab Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 25 Jan 2017 16:36:38 +0100 Subject: ssh: correct host key signature calculation --- lib/ssh/src/ssh_transport.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 21ba34506a..5e8efa2af7 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -432,7 +432,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits} + keyex_info = {Min0, Max0, NBits} }}; {error,_} -> ssh_connection_handler:disconnect( -- cgit v1.2.3 From 80a162cdf59f6a3826fba0cc0d3b861451a6b102 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 26 Jan 2017 22:48:13 +0100 Subject: ssh: optimize kex dh_gex using new crypto functionality --- lib/ssh/src/ssh_connection_handler.erl | 6 +- lib/ssh/src/ssh_transport.erl | 123 ++++++++++++++++++++++++++------- 2 files changed, 102 insertions(+), 27 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8718e92fa2..4496c657c3 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -609,13 +609,15 @@ handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> %%%---- diffie-hellman group exchange handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> - {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), + {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), send_bytes(GexGroup, D), + Ssh = ssh_transport:parallell_gen_key(Ssh1), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> - {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), + {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), send_bytes(GexGroup, D), + Ssh = ssh_transport:parallell_gen_key(Ssh1), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 5e8efa2af7..a7cc4cd52c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -44,6 +44,7 @@ handle_kexdh_reply/2, handle_kex_ecdh_init/2, handle_kex_ecdh_reply/2, + parallell_gen_key/1, extract_public_key/1, ssh_packet/2, pack/2, sha/1, sign/3, verify/4]). @@ -296,9 +297,6 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, end. -%% TODO: diffie-hellman-group14-sha1 should also be supported. -%% Maybe check more things ... - verify_algorithm(#alg{kex = undefined}) -> false; verify_algorithm(#alg{hkey = undefined}) -> false; verify_algorithm(#alg{send_mac = undefined}) -> false; @@ -316,17 +314,29 @@ verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex) key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; Kex == 'diffie-hellman-group14-sha1' -> {G, P} = dh_group(Kex), - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Ssh0#ssh.algorithms), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_init{e = Public}, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}; key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ; Kex == 'diffie-hellman-group-exchange-sha256' -> - {Min,NBits,Max} = + {Min,NBits0,Max} = proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN, ?DEFAULT_DH_GROUP_NBITS, ?DEFAULT_DH_GROUP_MAX}), + DhBits = dh_bits(Ssh0#ssh.algorithms), + NBits1 = + %% NIST Special Publication 800-57 Part 1 Revision 4: Recommendation for Key Management + if + DhBits =< 112 -> 2048; + DhBits =< 128 -> 3072; + DhBits =< 192 -> 7680; + true -> 8192 + end, + NBits = min(max(max(NBits0,NBits1),Min), Max), + {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min, n = NBits, @@ -350,12 +360,13 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% diffie-hellman-group14-sha1 %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, - Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) -> + Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) -> %% server {G, P} = dh_group(Kex), if 1= - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Algs), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), K = compute_key(dh, E, Private, [P,G]), MyPrivHostKey = get_host_key(Ssh0), MyPubHostKey = extract_public_key(MyPrivHostKey), @@ -426,12 +437,11 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)) of - {ok, {_Sz, {G,P}}} -> - {Public, Private} = generate_key(dh, [P,G]), + {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, - Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, + Ssh#ssh{keyex_key = {x, {G, P}}, keyex_info = {Min0, Max0, NBits} }}; {error,_} -> @@ -461,12 +471,11 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)) of - {ok, {_Sz, {G,P}}} -> - {Public, Private} = generate_key(dh, [P,G]), + {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, - Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, + Ssh#ssh{keyex_key = {x, {G, P}}, keyex_info = {-1, -1, NBits} % flag for kex_h hash calc }}; {error,_} -> @@ -507,7 +516,8 @@ adjust_gex_min_max(Min0, Max0, Opts) -> handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) -> %% client - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Ssh0#ssh.algorithms), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0), % Pub = G^Priv mod P (def) @@ -1117,6 +1127,51 @@ verify(PlainText, Hash, Sig, Key) -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Unit: bytes + +-record(cipher_data, { + key_bytes, + iv_bytes, + block_bytes + }). + +%%% Start of a more parameterized crypto handling. +cipher('AEAD_AES_128_GCM') -> + #cipher_data{key_bytes = 16, + iv_bytes = 12, + block_bytes = 16}; + +cipher('AEAD_AES_256_GCM') -> + #cipher_data{key_bytes = 32, + iv_bytes = 12, + block_bytes = 16}; + +cipher('3des-cbc') -> + #cipher_data{key_bytes = 24, + iv_bytes = 8, + block_bytes = 8}; + +cipher('aes128-cbc') -> + #cipher_data{key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes128-ctr') -> + #cipher_data{key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes192-ctr') -> + #cipher_data{key_bytes = 24, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes256-ctr') -> + #cipher_data{key_bytes = 32, + iv_bytes = 16, + block_bytes = 16}. + + encrypt_init(#ssh{encrypt = none} = Ssh) -> {ok, Ssh}; encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> @@ -1497,11 +1552,11 @@ send_mac_init(SSH) -> common -> case SSH#ssh.role of client -> - KeySize = mac_key_size(SSH#ssh.send_mac), + KeySize = 8*mac_key_bytes(SSH#ssh.send_mac), Key = hash(SSH, "E", KeySize), {ok, SSH#ssh { send_mac_key = Key }}; server -> - KeySize = mac_key_size(SSH#ssh.send_mac), + KeySize = 8*mac_key_bytes(SSH#ssh.send_mac), Key = hash(SSH, "F", KeySize), {ok, SSH#ssh { send_mac_key = Key }} end; @@ -1520,10 +1575,10 @@ recv_mac_init(SSH) -> common -> case SSH#ssh.role of client -> - Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)), + Key = hash(SSH, "F", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }}; server -> - Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)), + Key = hash(SSH, "E", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }} end; aead -> @@ -1640,13 +1695,15 @@ sha(?'secp384r1') -> sha(secp384r1); sha(?'secp521r1') -> sha(secp521r1). -mac_key_size('hmac-sha1') -> 20*8; -mac_key_size('hmac-sha1-96') -> 20*8; -mac_key_size('hmac-md5') -> 16*8; -mac_key_size('hmac-md5-96') -> 16*8; -mac_key_size('hmac-sha2-256')-> 32*8; -mac_key_size('hmac-sha2-512')-> 512; -mac_key_size(none) -> 0. +mac_key_bytes('hmac-sha1') -> 20; +mac_key_bytes('hmac-sha1-96') -> 20; +mac_key_bytes('hmac-md5') -> 16; +mac_key_bytes('hmac-md5-96') -> 16; +mac_key_bytes('hmac-sha2-256')-> 32; +mac_key_bytes('hmac-sha2-512')-> 64; +mac_key_bytes('AEAD_AES_128_GCM') -> 0; +mac_key_bytes('AEAD_AES_256_GCM') -> 0; +mac_key_bytes(none) -> 0. mac_digest_size('hmac-sha1') -> 20; mac_digest_size('hmac-sha1-96') -> 12; @@ -1671,6 +1728,13 @@ dh_group('diffie-hellman-group1-sha1') -> ?dh_group1; dh_group('diffie-hellman-group14-sha1') -> ?dh_group14. %%%---------------------------------------------------------------- +parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}}, + algorithms = Algs}) -> + Sz = dh_bits(Algs), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), + Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}. + + generate_key(Algorithm, Args) -> {Public,Private} = crypto:generate_key(Algorithm, Args), {crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}. @@ -1681,6 +1745,15 @@ compute_key(Algorithm, OthersPublic, MyPrivate, Args) -> crypto:bytes_to_integer(Shared). +dh_bits(#alg{encrypt = Encrypt, + send_mac = SendMac}) -> + C = cipher(Encrypt), + 8 * lists:max([C#cipher_data.key_bytes, + C#cipher_data.block_bytes, + C#cipher_data.iv_bytes, + mac_key_bytes(SendMac) + ]). + ecdh_curve('ecdh-sha2-nistp256') -> secp256r1; ecdh_curve('ecdh-sha2-nistp384') -> secp384r1; ecdh_curve('ecdh-sha2-nistp521') -> secp521r1. -- cgit v1.2.3 From 61501c3d0fa0744b107576070eaf3062ae23ac82 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Jan 2017 19:49:57 +0100 Subject: ssh: reordered default algorithms list --- lib/ssh/src/ssh_transport.erl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 4012ae3914..73e5952972 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -95,19 +95,20 @@ supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()]. supported_algorithms(kex) -> select_crypto_supported( [ - {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {ec_curve,secp384r1}, {hashs,sha384}]}, - {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, + {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, + {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, + {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} ]); supported_algorithms(public_key) -> select_crypto_supported( - [{'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + [ {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, + {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} ]); @@ -115,14 +116,15 @@ supported_algorithms(public_key) -> supported_algorithms(cipher) -> same( select_crypto_supported( - [{'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, - {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, - {'aes128-ctr', [{ciphers,{aes_ctr,128}}]}, - {'aes128-cbc', [{ciphers,aes_cbc128}]}, + [ + {'aes256-gcm@openssh.com', [{ciphers,{aes_gcm,256}}]}, + {'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, + {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, {'aes128-gcm@openssh.com', [{ciphers,{aes_gcm,128}}]}, - {'aes256-gcm@openssh.com', [{ciphers,{aes_gcm,256}}]}, - {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]}, + {'aes128-ctr', [{ciphers,{aes_ctr,128}}]}, {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]}, + {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]}, + {'aes128-cbc', [{ciphers,aes_cbc128}]}, {'3des-cbc', [{ciphers,des3_cbc}]} ] )); -- cgit v1.2.3 From ef2aa76fbd0867a2901148edfedbcc8f1bf51809 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Jan 2017 20:34:27 +0100 Subject: ssh: added stronger diffie-hellman groups diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group14-sha256 --- lib/ssh/src/ssh_transport.erl | 32 +++++++++++++++++++++++++++----- lib/ssh/src/ssh_transport.hrl | 13 ++++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 73e5952972..693691f835 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -99,9 +99,12 @@ supported_algorithms(kex) -> {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, - {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, + {'diffie-hellman-group16-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 + {'diffie-hellman-group18-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 + {'diffie-hellman-group14-sha256', [{public_keys,dh}, {hashs,sha256}]}, % In OpenSSH 7.3.p1 {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} + {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, + {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} % Gone in OpenSSH 7.3.p1 ]); supported_algorithms(public_key) -> select_crypto_supported( @@ -110,7 +113,7 @@ supported_algorithms(public_key) -> {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, - {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} + {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1 ]); supported_algorithms(cipher) -> @@ -314,7 +317,11 @@ verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex) %%% Key exchange initialization %%% key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; - Kex == 'diffie-hellman-group14-sha1' -> + Kex == 'diffie-hellman-group14-sha1' ; + Kex == 'diffie-hellman-group14-sha256' ; + Kex == 'diffie-hellman-group16-sha512' ; + Kex == 'diffie-hellman-group18-sha512' + -> {G, P} = dh_group(Kex), Sz = dh_bits(Ssh0#ssh.algorithms), {Public, Private} = generate_key(dh, [P,G,2*Sz]), @@ -360,6 +367,9 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% %%% diffie-hellman-group1-sha1 %%% diffie-hellman-group14-sha1 +%%% diffie-hellman-group14-sha256 +%%% diffie-hellman-group16-sha512 +%%% diffie-hellman-group18-sha512 %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) -> @@ -1614,6 +1624,12 @@ hash(SSH, Char, Bits) -> fun(Data) -> crypto:hash(sha, Data) end; 'diffie-hellman-group14-sha1' -> fun(Data) -> crypto:hash(sha, Data) end; + 'diffie-hellman-group14-sha256' -> + fun(Data) -> crypto:hash(sha256, Data) end; + 'diffie-hellman-group16-sha512' -> + fun(Data) -> crypto:hash(sha512, Data) end; + 'diffie-hellman-group18-sha512' -> + fun(Data) -> crypto:hash(sha512, Data) end; 'diffie-hellman-group-exchange-sha1' -> fun(Data) -> crypto:hash(sha, Data) end; @@ -1690,6 +1706,9 @@ sha(secp384r1) -> sha384; sha(secp521r1) -> sha512; sha('diffie-hellman-group1-sha1') -> sha; sha('diffie-hellman-group14-sha1') -> sha; +sha('diffie-hellman-group14-sha256') -> sha256; +sha('diffie-hellman-group16-sha512') -> sha512; +sha('diffie-hellman-group18-sha512') -> sha512; sha('diffie-hellman-group-exchange-sha1') -> sha; sha('diffie-hellman-group-exchange-sha256') -> sha256; sha(?'secp256r1') -> sha(secp256r1); @@ -1727,7 +1746,10 @@ peer_name({Host, _}) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% dh_group('diffie-hellman-group1-sha1') -> ?dh_group1; -dh_group('diffie-hellman-group14-sha1') -> ?dh_group14. +dh_group('diffie-hellman-group14-sha1') -> ?dh_group14; +dh_group('diffie-hellman-group14-sha256') -> ?dh_group14; +dh_group('diffie-hellman-group16-sha512') -> ?dh_group16; +dh_group('diffie-hellman-group18-sha512') -> ?dh_group18. %%%---------------------------------------------------------------- parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}}, diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index f91cb1dd63..19b3f5c437 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -112,7 +112,7 @@ %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% diffie-hellman-group1-sha1 | diffie-hellman-group14-sha1 +%% diffie-hellman-group*-sha* -define(SSH_MSG_KEXDH_INIT, 30). -define(SSH_MSG_KEXDH_REPLY, 31). @@ -238,4 +238,15 @@ -define(dh_group14, {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF}). +%%% rfc 3526, ch5 +%%% Size 4096-bit +-define(dh_group16, + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF}). + +%%% rfc 3526, ch7 +%%% Size 8192-bit +-define(dh_group18, + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF}). + + -endif. % -ifdef(ssh_transport). -- cgit v1.2.3 From d08006aaec92873c8cca6b7aeb57dcd2786fa330 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Jan 2017 20:44:31 +0100 Subject: ssh: removed 'diffie-hellman-group1-sha1' from default list Reason: very insecure --- lib/ssh/src/ssh_transport.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 693691f835..d172005a85 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -79,6 +79,10 @@ default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()]. algo_classes() -> [kex, public_key, cipher, mac, compression]. +default_algorithms(kex) -> + supported_algorithms(kex, [ + 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 + ]); default_algorithms(cipher) -> supported_algorithms(cipher, same(['AEAD_AES_128_GCM', @@ -104,7 +108,7 @@ supported_algorithms(kex) -> {'diffie-hellman-group14-sha256', [{public_keys,dh}, {hashs,sha256}]}, % In OpenSSH 7.3.p1 {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} % Gone in OpenSSH 7.3.p1 + {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} ]); supported_algorithms(public_key) -> select_crypto_supported( -- cgit v1.2.3 From 6847d9223420fb86cdf72f0e608a5f41a2673053 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 19 Jan 2017 17:19:37 +0100 Subject: ssh: removed 'ssh-dss' from default list Reason: insecure --- lib/ssh/src/ssh_transport.erl | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index d172005a85..7a01f9926c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -84,6 +84,11 @@ default_algorithms(kex) -> 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 ]); +default_algorithms(public_key) -> + supported_algorithms(public_key, [ + 'ssh-dss' % Gone in OpenSSH 7.3.p1 + ]); + default_algorithms(cipher) -> supported_algorithms(cipher, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); -- cgit v1.2.3 From d89206ccb3df4fc4fff4549f561085611febb22a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 19 Jan 2017 10:50:01 +0100 Subject: ssh: better error msg at kex failure --- lib/ssh/src/ssh_transport.erl | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7a01f9926c..b43bcff363 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -289,11 +289,12 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, true -> key_exchange_first_msg(Algoritms#alg.kex, Ssh0#ssh{algorithms = Algoritms}); - _ -> + {false,Alg} -> %% TODO: Correct code? ssh_connection_handler:disconnect( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed" + description = "Selection of key exchange algorithm failed: " + ++ Alg }) end; @@ -303,23 +304,28 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, case verify_algorithm(Algoritms) of true -> {ok, Ssh#ssh{algorithms = Algoritms}}; - _ -> + {false,Alg} -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed" + description = "Selection of key exchange algorithm failed: " + ++ Alg }) end. -verify_algorithm(#alg{kex = undefined}) -> false; -verify_algorithm(#alg{hkey = undefined}) -> false; -verify_algorithm(#alg{send_mac = undefined}) -> false; -verify_algorithm(#alg{recv_mac = undefined}) -> false; -verify_algorithm(#alg{encrypt = undefined}) -> false; -verify_algorithm(#alg{decrypt = undefined}) -> false; -verify_algorithm(#alg{compress = undefined}) -> false; -verify_algorithm(#alg{decompress = undefined}) -> false; -verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex)). +verify_algorithm(#alg{kex = undefined}) -> {false, "kex"}; +verify_algorithm(#alg{hkey = undefined}) -> {false, "hkey"}; +verify_algorithm(#alg{send_mac = undefined}) -> {false, "send_mac"}; +verify_algorithm(#alg{recv_mac = undefined}) -> {false, "recv_mac"}; +verify_algorithm(#alg{encrypt = undefined}) -> {false, "encrypt"}; +verify_algorithm(#alg{decrypt = undefined}) -> {false, "decrypt"}; +verify_algorithm(#alg{compress = undefined}) -> {false, "compress"}; +verify_algorithm(#alg{decompress = undefined}) -> {false, "decompress"}; +verify_algorithm(#alg{kex = Kex}) -> + case lists:member(Kex, supported_algorithms(kex)) of + true -> true; + false -> {false, "kex"} + end. %%%---------------------------------------------------------------- %%% -- cgit v1.2.3 From 497fc8de10bfee9eb693d393c270d8e06dbd15be Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 30 Jan 2017 13:12:06 +0100 Subject: ssh,crypto: prepare for release --- lib/ssh/src/ssh.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 4a76fd9cd3..cb0f087cfb 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -40,6 +40,6 @@ {env, []}, {mod, {ssh_app, []}}, {runtime_dependencies, ["stdlib-2.3","public_key-0.22","kernel-3.0", - "erts-6.0","crypto-3.3"]}]}. + "erts-6.0","crypto-3.6.3.1"]}]}. -- cgit v1.2.3 From 62f9bd09023da0b318e57b6454bd4b346816a27b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 26 Jan 2017 22:48:13 +0100 Subject: ssh: optimize kex dh_gex using new crypto functionality Conflicts: lib/ssh/src/ssh_connection_handler.erl lib/ssh/src/ssh_transport.erl --- lib/ssh/src/ssh_connection_handler.erl | 6 +- lib/ssh/src/ssh_transport.erl | 125 ++++++++++++++++++++++++++------- 2 files changed, 103 insertions(+), 28 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index b73f8b23d2..8c73bb8946 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -429,14 +429,16 @@ key_exchange(#ssh_msg_kexdh_reply{} = Msg, key_exchange(#ssh_msg_kex_dh_gex_request{} = Msg, #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), + {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), send_msg(GexGroup, State), + Ssh = ssh_transport:parallell_gen_key(Ssh1), {next_state, key_exchange_dh_gex_init, next_packet(State#state{ssh_params = Ssh})}; key_exchange(#ssh_msg_kex_dh_gex_request_old{} = Msg, #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), + {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), send_msg(GexGroup, State), + Ssh = ssh_transport:parallell_gen_key(Ssh1), {next_state, key_exchange_dh_gex_init, next_packet(State#state{ssh_params = Ssh})}; key_exchange(#ssh_msg_kex_dh_gex_group{} = Msg, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 18037b8461..5391df723c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -44,6 +44,7 @@ handle_kexdh_reply/2, handle_kex_ecdh_init/2, handle_kex_ecdh_reply/2, + parallell_gen_key/1, extract_public_key/1, ssh_packet/2, pack/2, sign/3, verify/4]). @@ -287,9 +288,6 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, end. -%% TODO: diffie-hellman-group14-sha1 should also be supported. -%% Maybe check more things ... - verify_algorithm(#alg{kex = undefined}) -> false; verify_algorithm(#alg{hkey = undefined}) -> false; verify_algorithm(#alg{send_mac = undefined}) -> false; @@ -307,17 +305,29 @@ verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex) key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; Kex == 'diffie-hellman-group14-sha1' -> {G, P} = dh_group(Kex), - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Ssh0#ssh.algorithms), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_init{e = Public}, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}; key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ; Kex == 'diffie-hellman-group-exchange-sha256' -> - {Min,NBits,Max} = + {Min,NBits0,Max} = proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN, ?DEFAULT_DH_GROUP_NBITS, ?DEFAULT_DH_GROUP_MAX}), + DhBits = dh_bits(Ssh0#ssh.algorithms), + NBits1 = + %% NIST Special Publication 800-57 Part 1 Revision 4: Recommendation for Key Management + if + DhBits =< 112 -> 2048; + DhBits =< 128 -> 3072; + DhBits =< 192 -> 7680; + true -> 8192 + end, + NBits = min(max(max(NBits0,NBits1),Min), Max), + {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min, n = NBits, @@ -341,12 +351,13 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% diffie-hellman-group14-sha1 %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, - Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) -> + Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) -> %% server {G, P} = dh_group(Kex), if 1= - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Algs), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), K = compute_key(dh, E, Private, [P,G]), MyPrivHostKey = get_host_key(Ssh0), MyPubHostKey = extract_public_key(MyPrivHostKey), @@ -418,13 +429,12 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)) of - {ok, {_Sz, {G,P}}} -> - {Public, Private} = generate_key(dh, [P,G]), + {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, - Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits} + Ssh#ssh{keyex_key = {x, {G, P}}, + keyex_info = {Min0, Max0, NBits} }}; {error,_} -> throw(#ssh_msg_disconnect{ @@ -452,12 +462,11 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)) of - {ok, {_Sz, {G,P}}} -> - {Public, Private} = generate_key(dh, [P,G]), + {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, - Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, + Ssh#ssh{keyex_key = {x, {G, P}}, keyex_info = {-1, -1, NBits} % flag for kex_h hash calc }}; {error,_} -> @@ -497,7 +506,8 @@ adjust_gex_min_max(Min0, Max0, Opts) -> handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) -> %% client - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Ssh0#ssh.algorithms), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0), % Pub = G^Priv mod P (def) @@ -1108,6 +1118,51 @@ verify(PlainText, Hash, Sig, Key) -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Unit: bytes + +-record(cipher_data, { + key_bytes, + iv_bytes, + block_bytes + }). + +%%% Start of a more parameterized crypto handling. +cipher('AEAD_AES_128_GCM') -> + #cipher_data{key_bytes = 16, + iv_bytes = 12, + block_bytes = 16}; + +cipher('AEAD_AES_256_GCM') -> + #cipher_data{key_bytes = 32, + iv_bytes = 12, + block_bytes = 16}; + +cipher('3des-cbc') -> + #cipher_data{key_bytes = 24, + iv_bytes = 8, + block_bytes = 8}; + +cipher('aes128-cbc') -> + #cipher_data{key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes128-ctr') -> + #cipher_data{key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes192-ctr') -> + #cipher_data{key_bytes = 24, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes256-ctr') -> + #cipher_data{key_bytes = 32, + iv_bytes = 16, + block_bytes = 16}. + + encrypt_init(#ssh{encrypt = none} = Ssh) -> {ok, Ssh}; encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> @@ -1488,11 +1543,11 @@ send_mac_init(SSH) -> common -> case SSH#ssh.role of client -> - KeySize = mac_key_size(SSH#ssh.send_mac), + KeySize = 8*mac_key_bytes(SSH#ssh.send_mac), Key = hash(SSH, "E", KeySize), {ok, SSH#ssh { send_mac_key = Key }}; server -> - KeySize = mac_key_size(SSH#ssh.send_mac), + KeySize = 8*mac_key_bytes(SSH#ssh.send_mac), Key = hash(SSH, "F", KeySize), {ok, SSH#ssh { send_mac_key = Key }} end; @@ -1511,10 +1566,10 @@ recv_mac_init(SSH) -> common -> case SSH#ssh.role of client -> - Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)), + Key = hash(SSH, "F", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }}; server -> - Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)), + Key = hash(SSH, "E", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }} end; aead -> @@ -1638,13 +1693,15 @@ sha(?'secp384r1') -> sha(secp384r1); sha(?'secp521r1') -> sha(secp521r1). -mac_key_size('hmac-sha1') -> 20*8; -mac_key_size('hmac-sha1-96') -> 20*8; -mac_key_size('hmac-md5') -> 16*8; -mac_key_size('hmac-md5-96') -> 16*8; -mac_key_size('hmac-sha2-256')-> 32*8; -mac_key_size('hmac-sha2-512')-> 512; -mac_key_size(none) -> 0. +mac_key_bytes('hmac-sha1') -> 20; +mac_key_bytes('hmac-sha1-96') -> 20; +mac_key_bytes('hmac-md5') -> 16; +mac_key_bytes('hmac-md5-96') -> 16; +mac_key_bytes('hmac-sha2-256')-> 32; +mac_key_bytes('hmac-sha2-512')-> 64; +mac_key_bytes('AEAD_AES_128_GCM') -> 0; +mac_key_bytes('AEAD_AES_256_GCM') -> 0; +mac_key_bytes(none) -> 0. mac_digest_size('hmac-sha1') -> 20; mac_digest_size('hmac-sha1-96') -> 12; @@ -1669,6 +1726,13 @@ dh_group('diffie-hellman-group1-sha1') -> ?dh_group1; dh_group('diffie-hellman-group14-sha1') -> ?dh_group14. %%%---------------------------------------------------------------- +parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}}, + algorithms = Algs}) -> + Sz = dh_bits(Algs), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), + Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}. + + generate_key(Algorithm, Args) -> {Public,Private} = crypto:generate_key(Algorithm, Args), {crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}. @@ -1679,6 +1743,15 @@ compute_key(Algorithm, OthersPublic, MyPrivate, Args) -> crypto:bytes_to_integer(Shared). +dh_bits(#alg{encrypt = Encrypt, + send_mac = SendMac}) -> + C = cipher(Encrypt), + 8 * lists:max([C#cipher_data.key_bytes, + C#cipher_data.block_bytes, + C#cipher_data.iv_bytes, + mac_key_bytes(SendMac) + ]). + ecdh_curve('ecdh-sha2-nistp256') -> secp256r1; ecdh_curve('ecdh-sha2-nistp384') -> secp384r1; ecdh_curve('ecdh-sha2-nistp521') -> secp521r1. -- cgit v1.2.3 From c0b7998760959b02293013cc9e00599303212458 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 19 Jan 2017 20:59:19 +0100 Subject: ssh: clearer hash calculation --- lib/ssh/src/ssh_transport.erl | 56 ++++++++++++------------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index b43bcff363..85ee88ce5f 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1632,52 +1632,23 @@ mac('hmac-sha2-512', Key, SeqNum, Data) -> crypto:hmac(sha512, Key, [<>, Data]). %% return N hash bytes (HASH) -hash(SSH, Char, Bits) -> - HASH = - case SSH#ssh.kex of - 'diffie-hellman-group1-sha1' -> - fun(Data) -> crypto:hash(sha, Data) end; - 'diffie-hellman-group14-sha1' -> - fun(Data) -> crypto:hash(sha, Data) end; - 'diffie-hellman-group14-sha256' -> - fun(Data) -> crypto:hash(sha256, Data) end; - 'diffie-hellman-group16-sha512' -> - fun(Data) -> crypto:hash(sha512, Data) end; - 'diffie-hellman-group18-sha512' -> - fun(Data) -> crypto:hash(sha512, Data) end; - - 'diffie-hellman-group-exchange-sha1' -> - fun(Data) -> crypto:hash(sha, Data) end; - 'diffie-hellman-group-exchange-sha256' -> - fun(Data) -> crypto:hash(sha256, Data) end; - - 'ecdh-sha2-nistp256' -> - fun(Data) -> crypto:hash(sha256,Data) end; - 'ecdh-sha2-nistp384' -> - fun(Data) -> crypto:hash(sha384,Data) end; - 'ecdh-sha2-nistp521' -> - fun(Data) -> crypto:hash(sha512,Data) end; - _ -> - exit({bad_algorithm,SSH#ssh.kex}) - end, - hash(SSH, Char, Bits, HASH). - -hash(_SSH, _Char, 0, _HASH) -> +hash(_SSH, _Char, 0) -> <<>>; -hash(SSH, Char, N, HASH) -> -K = SSH#ssh.shared_secret, % K = ssh_bits:mpint(SSH#ssh.shared_secret), +hash(SSH, Char, N) -> + HashAlg = sha(SSH#ssh.kex), + K = SSH#ssh.shared_secret, H = SSH#ssh.exchanged_hash, - SessionID = SSH#ssh.session_id, - K1 = HASH([K, H, Char, SessionID]), + K1 = crypto:hash(HashAlg, [K, H, Char, SSH#ssh.session_id]), Sz = N div 8, - <> = hash(K, H, K1, N-128, HASH), + <> = hash(K, H, K1, N-128, HashAlg), Key. -hash(_K, _H, Ki, N, _HASH) when N =< 0 -> +hash(_K, _H, Ki, N, _HashAlg) when N =< 0 -> Ki; -hash(K, H, Ki, N, HASH) -> - Kj = HASH([K, H, Ki]), - hash(K, H, <>, N-128, HASH). +hash(K, H, Ki, N, HashAlg) -> + Kj = crypto:hash(HashAlg, [K, H, Ki]), + hash(K, H, <>, N-128, HashAlg). + kex_h(SSH, Key, E, F, K) -> KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), @@ -1728,7 +1699,10 @@ sha('diffie-hellman-group-exchange-sha1') -> sha; sha('diffie-hellman-group-exchange-sha256') -> sha256; sha(?'secp256r1') -> sha(secp256r1); sha(?'secp384r1') -> sha(secp384r1); -sha(?'secp521r1') -> sha(secp521r1). +sha(?'secp521r1') -> sha(secp521r1); +sha('ecdh-sha2-nistp256') -> sha(secp256r1); +sha('ecdh-sha2-nistp384') -> sha(secp384r1); +sha('ecdh-sha2-nistp521') -> sha(secp521r1). mac_key_bytes('hmac-sha1') -> 20; -- cgit v1.2.3 From 05473252a740ae40894fbd2e5ee4349db6db087c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 19 Jan 2017 21:51:59 +0100 Subject: ssh: minor code unfolding --- lib/ssh/src/ssh_transport.erl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 85ee88ce5f..02209d5dfd 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -783,9 +783,8 @@ accepted_host(Ssh, PeerName, Public, Opts) -> yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") end. -known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh, +known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = {PeerName,_}} = Ssh, Public, Alg) -> - PeerName = peer_name(Peer), case Mod:is_host_key(Public, PeerName, Alg, Opts) of true -> ok; @@ -1631,6 +1630,8 @@ mac('hmac-sha2-256', Key, SeqNum, Data) -> mac('hmac-sha2-512', Key, SeqNum, Data) -> crypto:hmac(sha512, Key, [<>, Data]). + +%%%---------------------------------------------------------------- %% return N hash bytes (HASH) hash(_SSH, _Char, 0) -> <<>>; @@ -1649,7 +1650,7 @@ hash(K, H, Ki, N, HashAlg) -> Kj = crypto:hash(HashAlg, [K, H, Ki]), hash(K, H, <>, N-128, HashAlg). - +%%%---------------------------------------------------------------- kex_h(SSH, Key, E, F, K) -> KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), L = < 16; mac_digest_size('AEAD_AES_256_GCM') -> 16; mac_digest_size(none) -> 0. -peer_name({Host, _}) -> - Host. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% Diffie-Hellman utils -- cgit v1.2.3 From 99a6fe8c485af3024731bbb6a5af9afac7a0045f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 20 Jan 2017 15:52:57 +0100 Subject: ssh: Enable usage of supported but not default host key algorithms --- lib/ssh/src/ssh_connection_handler.erl | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4496c657c3..dcf509ca09 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1481,31 +1481,36 @@ renegotiation(_) -> false. %%-------------------------------------------------------------------- supported_host_keys(client, _, Options) -> try - case proplists:get_value(public_key, - proplists:get_value(preferred_algorithms,Options,[]) - ) of - undefined -> - ssh_transport:default_algorithms(public_key); - L -> - L -- (L--ssh_transport:default_algorithms(public_key)) - end + find_sup_hkeys(Options) of [] -> - {stop, {shutdown, "No public key algs"}}; + error({shutdown, "No public key algs"}); Algs -> [atom_to_list(A) || A<-Algs] catch exit:Reason -> - {stop, {shutdown, Reason}} + error({shutdown, Reason}) end; supported_host_keys(server, KeyCb, Options) -> - [atom_to_list(A) || A <- proplists:get_value(public_key, - proplists:get_value(preferred_algorithms,Options,[]), - ssh_transport:default_algorithms(public_key) - ), + [atom_to_list(A) || A <- find_sup_hkeys(Options), available_host_key(KeyCb, A, Options) ]. + +find_sup_hkeys(Options) -> + case proplists:get_value(public_key, + proplists:get_value(preferred_algorithms,Options,[]) + ) + of + undefined -> + ssh_transport:default_algorithms(public_key); + L -> + NonSupported = L--ssh_transport:supported_algorithms(public_key), + L -- NonSupported + end. + + + %% Alg :: atom() available_host_key(KeyCb, Alg, Opts) -> element(1, catch KeyCb:host_key(Alg, Opts)) == ok. -- cgit v1.2.3 From da0f783dce990e6c3953a7852a8c90a1933b21b2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 6 Feb 2017 12:20:37 +0100 Subject: Revert "ssh: removed 'ssh-dss' from default list" This reverts commit 6847d9223420fb86cdf72f0e608a5f41a2673053. The removal of ssh-dss seems to give a too high risk of failing customer systems. Needs to be properly deprecated. --- lib/ssh/src/ssh_transport.erl | 5 ----- 1 file changed, 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 02209d5dfd..5d178a202d 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -84,11 +84,6 @@ default_algorithms(kex) -> 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 ]); -default_algorithms(public_key) -> - supported_algorithms(public_key, [ - 'ssh-dss' % Gone in OpenSSH 7.3.p1 - ]); - default_algorithms(cipher) -> supported_algorithms(cipher, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); -- cgit v1.2.3 From 859ac82433da2dcd11685b8c8beb972336cf70cf Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Wed, 8 Feb 2017 15:06:43 +0200 Subject: Consider root_dir and cwd in ssh_sftpd, if both are provided The SFTPD server should use root_dir and cwd when resolving file paths, if both are provided. The root directory should be used for resolving absolute file names, and cwd should be used for resolving relative paths. --- lib/ssh/src/ssh_sftpd.erl | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index b739955836..bc30b7fb7d 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -742,6 +742,10 @@ resolve_symlinks_2([], State, _LinkCnt, AccPath) -> {{ok, AccPath}, State}. +%% The File argument is always in a user visible file system, i.e. +%% is under Root and is relative to CWD or Root, if starts with "/". +%% The result of the function is always an absolute path in a +%% "backend" file system. relate_file_name(File, State) -> relate_file_name(File, State, _Canonicalize=true). @@ -749,19 +753,20 @@ relate_file_name(File, State, Canonicalize) when is_binary(File) -> relate_file_name(unicode:characters_to_list(File), State, Canonicalize); relate_file_name(File, #state{cwd = CWD, root = ""}, Canonicalize) -> relate_filename_to_path(File, CWD, Canonicalize); -relate_file_name(File, #state{root = Root}, Canonicalize) -> - case is_within_root(Root, File) of - true -> - File; - false -> - RelFile = make_relative_filename(File), - NewFile = relate_filename_to_path(RelFile, Root, Canonicalize), - case is_within_root(Root, NewFile) of - true -> - NewFile; - false -> - Root - end +relate_file_name(File, #state{cwd = CWD, root = Root}, Canonicalize) -> + CWD1 = case is_within_root(Root, CWD) of + true -> CWD; + false -> Root + end, + AbsFile = case make_relative_filename(File) of + File -> + relate_filename_to_path(File, CWD1, Canonicalize); + RelFile -> + relate_filename_to_path(RelFile, Root, Canonicalize) + end, + case is_within_root(Root, AbsFile) of + true -> AbsFile; + false -> Root end. is_within_root(Root, File) -> -- cgit v1.2.3 From a34576111652d2d7972147160f93cfbbc9f13251 Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Tue, 7 Feb 2017 11:50:40 +0200 Subject: Fix relative path handling in sftpd Relative path handling fixed to allow opening a file by a path relative to the current working directory. --- lib/ssh/src/ssh_sftpd.erl | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index b739955836..a6f4af7879 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -664,7 +664,7 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 -> do_open(ReqId, State, Path, Flags). do_open(ReqId, State0, Path, Flags) -> - #state{file_handler = FileMod, file_state = FS0, root = Root, xf = #ssh_xfer{vsn = Vsn}} = State0, + #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0, XF = State0#state.xf, F = [binary | Flags], {IsDir, _FS1} = FileMod:is_dir(Path, FS0), @@ -676,12 +676,7 @@ do_open(ReqId, State0, Path, Flags) -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, ?SSH_FX_FAILURE, "File is a directory"); false -> - AbsPath = case Root of - "" -> - Path; - _ -> - relate_file_name(Path, State0) - end, + AbsPath = relate_file_name(Path, State0), {Res, FS1} = FileMod:open(AbsPath, F, FS0), State1 = State0#state{file_state = FS1}, case Res of -- cgit v1.2.3 From 002e507bab9209aeb5487ee3a1dbe52a73f80f84 Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Sun, 12 Feb 2017 15:00:36 +0200 Subject: Check for directory with correct path When opening file in the ssh_sftpd, directory check should be performed on the server's file tree. --- lib/ssh/src/ssh_sftpd.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index a6f4af7879..7ebe5ed4ef 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -667,7 +667,8 @@ do_open(ReqId, State0, Path, Flags) -> #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0, XF = State0#state.xf, F = [binary | Flags], - {IsDir, _FS1} = FileMod:is_dir(Path, FS0), + AbsPath = relate_file_name(Path, State0), + {IsDir, _FS1} = FileMod:is_dir(AbsPath, FS0), case IsDir of true when Vsn > 5 -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, @@ -676,7 +677,6 @@ do_open(ReqId, State0, Path, Flags) -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, ?SSH_FX_FAILURE, "File is a directory"); false -> - AbsPath = relate_file_name(Path, State0), {Res, FS1} = FileMod:open(AbsPath, F, FS0), State1 = State0#state{file_state = FS1}, case Res of -- cgit v1.2.3 From 4541b1f6c136bd2225ec6a6392454b2e5dddd6e9 Mon Sep 17 00:00:00 2001 From: Andrew Dryga Date: Tue, 14 Feb 2017 11:28:34 +0200 Subject: Fixed typos in lib/ssh --- lib/ssh/src/ssh_sftp.erl | 6 +++--- lib/ssh/src/ssh_transport.erl | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index b937f0412d..8d994cdb43 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -294,7 +294,7 @@ read(Pid, Handle, Len) -> read(Pid, Handle, Len, FileOpTimeout) -> call(Pid, {read,false,Handle, Len}, FileOpTimeout). -%% TODO this ought to be a cast! Is so in all practial meaning +%% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! apread(Pid, Handle, Offset, Len) -> call(Pid, {pread,true,Handle, Offset, Len}, infinity). @@ -313,12 +313,12 @@ write(Pid, Handle, Data) -> write(Pid, Handle, Data, FileOpTimeout) -> call(Pid, {write,false,Handle,Data}, FileOpTimeout). -%% TODO this ought to be a cast! Is so in all practial meaning +%% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! apwrite(Pid, Handle, Offset, Data) -> call(Pid, {pwrite,true,Handle,Offset,Data}, infinity). -%% TODO this ought to be a cast! Is so in all practial meaning +%% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! awrite(Pid, Handle, Data) -> call(Pid, {write,true,Handle,Data}, infinity). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 5d178a202d..a17ad560d1 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -481,7 +481,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, %% This message was in the draft-00 of rfc4419 %% (https://tools.ietf.org/html/draft-ietf-secsh-dh-group-exchange-00) %% In later drafts and the rfc is "is used for backward compatibility". - %% Unfortunatly the rfc does not specify how to treat the parameter n + %% Unfortunately the rfc does not specify how to treat the parameter n %% if there is no group of that modulus length :( %% The draft-00 however specifies that n is the "... number of bits %% the subgroup should have at least". -- cgit v1.2.3 From 9f23065062eb724e58f39a65e416e5b0e1e9d95d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 10 Feb 2017 14:37:41 +0100 Subject: ssh: allow a list of fingerprint algos in silently_accept_hosts option --- lib/ssh/src/ssh.erl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 31e343e81b..f408086c0f 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -620,11 +620,22 @@ handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) - handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_function(Value,2) -> Opt; handle_ssh_option({silently_accept_hosts, {DigestAlg,Value}} = Opt) when is_function(Value,2) -> - case lists:member(DigestAlg, [md5, sha, sha224, sha256, sha384, sha512]) of - true -> - Opt; - false -> - throw({error, {eoptions, Opt}}) + Algs = if is_atom(DigestAlg) -> [DigestAlg]; + is_list(DigestAlg) -> DigestAlg; + true -> throw({error, {eoptions, Opt}}) + end, + case [A || A <- Algs, + not lists:member(A, [md5, sha, sha224, sha256, sha384, sha512])] of + [_|_] = UnSup1 -> + throw({error, {{eoptions, Opt}, {not_fingerprint_algos,UnSup1}}}); + [] -> + CryptoHashAlgs = proplists:get_value(hashs, crypto:supports(), []), + case [A || A <- Algs, + not lists:member(A, CryptoHashAlgs)] of + [_|_] = UnSup2 -> + throw({error, {{eoptions, Opt}, {unsupported_algo,UnSup2}}}); + [] -> Opt + end end; handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; -- cgit v1.2.3 From 1ca3a090fb9027aa140fea06f57aa22f8790940a Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Wed, 15 Feb 2017 08:24:32 +0200 Subject: Return correct state in the case of failure --- lib/ssh/src/ssh_sftpd.erl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 7ebe5ed4ef..13c68e4c95 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -665,23 +665,24 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 -> do_open(ReqId, State0, Path, Flags) -> #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0, - XF = State0#state.xf, - F = [binary | Flags], AbsPath = relate_file_name(Path, State0), {IsDir, _FS1} = FileMod:is_dir(AbsPath, FS0), case IsDir of true when Vsn > 5 -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, - ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"); + ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"), + State0; true -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, - ?SSH_FX_FAILURE, "File is a directory"); + ?SSH_FX_FAILURE, "File is a directory"), + State0; false -> - {Res, FS1} = FileMod:open(AbsPath, F, FS0), + OpenFlags = [binary | Flags], + {Res, FS1} = FileMod:open(AbsPath, OpenFlags, FS0), State1 = State0#state{file_state = FS1}, case Res of {ok, IoDevice} -> - add_handle(State1, XF, ReqId, file, {Path,IoDevice}); + add_handle(State1, State0#state.xf, ReqId, file, {Path,IoDevice}); {error, Error} -> ssh_xfer:xf_send_status(State1#state.xf, ReqId, ssh_xfer:encode_erlang_status(Error)), -- cgit v1.2.3 From d21031900160a70408f0ee6f1b2f8bd01f1cbde7 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 15:12:37 +0100 Subject: ssh: Add error case for bad socket --- lib/ssh/src/ssh.erl | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 31e343e81b..8d8e20730d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -317,6 +317,7 @@ start_daemon(Socket, Options) -> do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions) catch throw:bad_fd -> {error,bad_fd}; + throw:bad_socket -> {error,bad_socket}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end; {error,SockError} -> @@ -333,6 +334,7 @@ start_daemon(Host, Port, Options, Inet) -> do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions]) catch throw:bad_fd -> {error,bad_fd}; + throw:bad_socket -> {error,bad_socket}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end end. -- cgit v1.2.3 From cd88d70ffb0f325fa84c8548b3dca1f7865ee86d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 15:13:54 +0100 Subject: ssh: More exact test for is_tcp_socket --- lib/ssh/src/ssh.erl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 8d8e20730d..657cf4c62d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -280,9 +280,11 @@ valid_socket_to_use(Socket, Options) -> {error, {unsupported,L4}} end. -is_tcp_socket(Socket) -> {ok,[]} =/= inet:getopts(Socket, [delay_send]). - - +is_tcp_socket(Socket) -> + case inet:getopts(Socket, [delay_send]) of + {ok,[_]} -> true; + _ -> false + end. daemon_shell_opt(Options) -> case proplists:get_value(shell, Options) of -- cgit v1.2.3 From 8fbb5b7c55c78f5696a3c504a1f7c164d5be3dc3 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 17:00:09 +0100 Subject: ssh: handle return values and exceptions from ssh_acceptor:handle_connection --- lib/ssh/src/ssh.erl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 657cf4c62d..f572e02d5f 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -366,8 +366,7 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> - ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket), - Result; + call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, Result); Result = {error, _} -> Result catch @@ -380,8 +379,7 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> - ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket), - {ok, Sup}; + call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, {ok, Sup}); Other -> Other end @@ -451,6 +449,16 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> end end. +call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, DefaultResult) -> + try ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket) + of + {error,Error} -> {error,Error}; + _ -> DefaultResult + catch + C:R -> {error,{could_not_start_connection,{C,R}}} + end. + + sync_request_control(false) -> ok; sync_request_control({LSock,Callback}) -> -- cgit v1.2.3 From de437e3639912a0570541fa10c473ac0f0372806 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Feb 2017 14:08:15 +0100 Subject: ssh: replace byte-only function with element-size agnostic An error report on ssh_cli pointed to a usage of erlang:iolist_size/1. It is replaced by a specialized function. --- lib/ssh/src/ssh_cli.erl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 8af0ecc5f9..6f8c050486 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -453,14 +453,20 @@ move_cursor(From, To, #ssh_pty{width=Width, term=Type}) -> %% %%% make sure that there is data to send %% %%% before calling ssh_connection:send write_chars(ConnectionHandler, ChannelId, Chars) -> - case erlang:iolist_size(Chars) of - 0 -> - ok; - _ -> - ssh_connection:send(ConnectionHandler, ChannelId, - ?SSH_EXTENDED_DATA_DEFAULT, Chars) + case has_chars(Chars) of + false -> ok; + true -> ssh_connection:send(ConnectionHandler, + ChannelId, + ?SSH_EXTENDED_DATA_DEFAULT, + Chars) end. +has_chars([C|_]) when is_integer(C) -> true; +has_chars([H|T]) when is_list(H) ; is_binary(H) -> has_chars(H) orelse has_chars(T); +has_chars(<<_:8,_/binary>>) -> true; +has_chars(_) -> false. + + %%% tail, works with empty lists tl1([_|A]) -> A; tl1(_) -> []. -- cgit v1.2.3 From 89a829f32d855610b0bc0c3ea53e7c05454b7a24 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 16 Feb 2017 14:48:04 +0100 Subject: ssh: Initial commit of option handling changes --- lib/ssh/src/Makefile | 1 + lib/ssh/src/ssh.app.src | 1 + lib/ssh/src/ssh.erl | 817 ++++++------------------------ lib/ssh/src/ssh.hrl | 27 +- lib/ssh/src/ssh_acceptor.erl | 117 +++-- lib/ssh/src/ssh_acceptor_sup.erl | 28 +- lib/ssh/src/ssh_auth.erl | 79 ++- lib/ssh/src/ssh_cli.erl | 12 +- lib/ssh/src/ssh_connection.erl | 38 +- lib/ssh/src/ssh_connection_handler.erl | 124 +++-- lib/ssh/src/ssh_file.erl | 4 +- lib/ssh/src/ssh_io.erl | 16 +- lib/ssh/src/ssh_options.erl | 897 +++++++++++++++++++++++++++++++++ lib/ssh/src/ssh_sftp.erl | 43 +- lib/ssh/src/ssh_subsystem_sup.erl | 36 +- lib/ssh/src/ssh_system_sup.erl | 34 +- lib/ssh/src/ssh_transport.erl | 72 +-- lib/ssh/src/sshd_sup.erl | 22 +- 18 files changed, 1369 insertions(+), 999 deletions(-) create mode 100644 lib/ssh/src/ssh_options.erl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 7ab6f22424..f826fdfd9b 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -51,6 +51,7 @@ MODULES= \ ssh_sup \ sshc_sup \ sshd_sup \ + ssh_options \ ssh_connection_sup \ ssh_connection \ ssh_connection_handler \ diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 2bb7491b0c..95d2686094 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -7,6 +7,7 @@ ssh_app, ssh_acceptor, ssh_acceptor_sup, + ssh_options, ssh_auth, ssh_message, ssh_bits, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 68d98d3875..0186ac7922 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -41,7 +41,8 @@ %%% Type exports -export_type([connection_ref/0, - channel_id/0 + channel_id/0, + role/0 ]). %%-------------------------------------------------------------------- @@ -71,55 +72,63 @@ stop() -> application:stop(ssh). %%-------------------------------------------------------------------- --spec connect(port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec connect(inet:socket(), proplists:proplist()) -> ok_error(connection_ref()). --spec connect(port(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()} - ; (string(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec connect(inet:socket(), proplists:proplist(), timeout()) -> ok_error(connection_ref()) + ; (string(), inet:port_number(), proplists:proplist()) -> ok_error(connection_ref()). + +-spec connect(string(), inet:port_number(), proplists:proplist(), timeout()) -> ok_error(connection_ref()). --spec connect(string(), integer(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()}. %% %% Description: Starts an ssh connection. %%-------------------------------------------------------------------- -connect(Socket, Options) -> - connect(Socket, Options, infinity). +connect(Socket, UserOptions) when is_port(Socket), + is_list(UserOptions) -> + connect(Socket, UserOptions, infinity). -connect(Socket, Options, Timeout) when is_port(Socket) -> - case handle_options(Options) of +connect(Socket, UserOptions, Timeout) when is_port(Socket), + is_list(UserOptions) -> + case ssh_options:handle_options(client, UserOptions) of {error, Error} -> {error, Error}; - {_SocketOptions, SshOptions} -> - case valid_socket_to_use(Socket, Options) of + Options -> + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> {ok, {Host,_Port}} = inet:sockname(Socket), - Opts = [{user_pid,self()}, {host,fmt_host(Host)} | SshOptions], + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error,SockError} -> {error,SockError} end end; -connect(Host, Port, Options) when is_integer(Port), Port>0 -> - connect(Host, Port, Options, infinity). +connect(Host, Port, UserOptions) when is_integer(Port), + Port>0, + is_list(UserOptions) -> + connect(Host, Port, UserOptions, infinity). -connect(Host, Port, Options, Timeout) -> - case handle_options(Options) of +connect(Host, Port, UserOptions, Timeout) when is_integer(Port), + Port>0, + is_list(UserOptions) -> + case ssh_options:handle_options(client, UserOptions) of {error, _Reason} = Error -> Error; - {SocketOptions, SshOptions} -> - {_, Transport, _} = TransportOpts = - proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), - ConnectionTimeout = proplists:get_value(connect_timeout, Options, infinity), - try Transport:connect(Host, Port, [ {active, false} | SocketOptions], ConnectionTimeout) of + Options -> + {_, Transport, _} = TransportOpts = ?GET_OPT(transport, Options), + ConnectionTimeout = ?GET_OPT(connect_timeout, Options), + SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)], + try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> - Opts = [{user_pid,self()}, {host,Host} | SshOptions], + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error, Reason} -> {error, Reason} catch - exit:{function_clause, _} -> + exit:{function_clause, _F} -> + io:format('function_clause ~p~n',[_F]), {error, {options, {transport, TransportOpts}}}; exit:badarg -> - {error, {options, {socket_options, SocketOptions}}} + {error, {options, {socket_options, SocketOpts}}} end end. @@ -148,9 +157,11 @@ channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- --spec daemon(integer()) -> {ok, pid()} | {error, term()}. --spec daemon(integer()|port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. --spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec daemon(inet:port_number()) -> ok_error(pid()). +-spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(pid()). +-spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(pid()) + ;(socket, inet:socket(), proplists:proplist()) -> ok_error(pid()) + . %% Description: Starts a server listening for SSH connections %% on the given port. @@ -158,19 +169,21 @@ channel_info(ConnectionRef, ChannelId, Options) -> daemon(Port) -> daemon(Port, []). -daemon(Port, Options) when is_integer(Port) -> - daemon(any, Port, Options); -daemon(Socket, Options0) when is_port(Socket) -> - Options = daemon_shell_opt(Options0), - start_daemon(Socket, Options). +daemon(Port, UserOptions) when is_integer(Port), Port >= 0 -> + daemon(any, Port, UserOptions); + +daemon(Socket, UserOptions) when is_port(Socket) -> + daemon(socket, Socket, UserOptions). + -daemon(HostAddr, Port, Options0) -> - Options1 = daemon_shell_opt(Options0), - {Host, Inet, Options} = daemon_host_inet_opt(HostAddr, Options1), - start_daemon(Host, Port, Options, Inet). +daemon(Host0, Port, UserOptions0) -> + {Host, UserOptions} = handle_daemon_args(Host0, UserOptions0), + start_daemon(Host, Port, ssh_options:handle_options(server, UserOptions)). %%-------------------------------------------------------------------- +-spec daemon_info(pid()) -> ok_error( [{atom(), term()}] ). + daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> @@ -185,7 +198,7 @@ daemon_info(Pid) -> %%-------------------------------------------------------------------- -spec stop_listener(pid()) -> ok. --spec stop_listener(inet:ip_address(), integer()) -> ok. +-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. %% %% Description: Stops the listener, but leaves %% existing connections started by the listener up and running. @@ -199,7 +212,8 @@ stop_listener(Address, Port, Profile) -> %%-------------------------------------------------------------------- -spec stop_daemon(pid()) -> ok. --spec stop_daemon(inet:ip_address(), integer()) -> ok. +-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok. +-spec stop_daemon(inet:ip_address(), inet:port_number(), atom()) -> ok. %% %% Description: Stops the listener and all connections started by %% the listener. @@ -210,10 +224,11 @@ stop_daemon(Address, Port) -> ssh_system_sup:stop_system(Address, Port, ?DEFAULT_PROFILE). stop_daemon(Address, Port, Profile) -> ssh_system_sup:stop_system(Address, Port, Profile). + %%-------------------------------------------------------------------- --spec shell(port() | string()) -> _. --spec shell(port() | string(), proplists:proplist()) -> _. --spec shell(string(), integer(), proplists:proplist()) -> _. +-spec shell(inet:socket() | string()) -> _. +-spec shell(inet:socket() | string(), proplists:proplist()) -> _. +-spec shell(string(), inet:port_number(), proplists:proplist()) -> _. %% Host = string() %% Port = integer() @@ -261,112 +276,96 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -valid_socket_to_use(Socket, Options) -> - case proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}) of - {tcp,_,_} -> - %% Is this tcp-socket a valid socket? - case {is_tcp_socket(Socket), - {ok,[{active,false}]} == inet:getopts(Socket, [active]) - } - of - {true, true} -> - ok; - {true, false} -> - {error, not_passive_mode}; - _ -> - {error, not_tcp_socket} - end; - {L4,_,_} -> - {error, {unsupported,L4}} +handle_daemon_args(Host, UserOptions0) -> + case Host of + socket -> + {Host, UserOptions0}; + any -> + {ok, Host0} = inet:gethostname(), + Inet = proplists:get_value(inet, UserOptions0, inet), + {Host0, [Inet | UserOptions0]}; + {_,_,_,_} -> + {Host, [inet, {ip,Host} | UserOptions0]}; + {_,_,_,_,_,_,_,_} -> + {Host, [inet6, {ip,Host} | UserOptions0]}; + _ -> + error(badarg) end. +%%%---------------------------------------------------------------- +valid_socket_to_use(Socket, {tcp,_,_}) -> + %% Is this tcp-socket a valid socket? + case {is_tcp_socket(Socket), + {ok,[{active,false}]} == inet:getopts(Socket, [active]) + } + of + {true, true} -> + ok; + {true, false} -> + {error, not_passive_mode}; + _ -> + {error, not_tcp_socket} + end; + +valid_socket_to_use(_, {L4,_,_}) -> + {error, {unsupported,L4}}. + + is_tcp_socket(Socket) -> case inet:getopts(Socket, [delay_send]) of {ok,[_]} -> true; _ -> false end. -daemon_shell_opt(Options) -> - case proplists:get_value(shell, Options) of - undefined -> - [{shell, {shell, start, []}} | Options]; - _ -> - Options - end. - -daemon_host_inet_opt(HostAddr, Options1) -> - case HostAddr of - any -> - {ok, Host0} = inet:gethostname(), - {Host0, proplists:get_value(inet, Options1, inet), Options1}; - {_,_,_,_} -> - {HostAddr, inet, - [{ip, HostAddr} | Options1]}; - {_,_,_,_,_,_,_,_} -> - {HostAddr, inet6, - [{ip, HostAddr} | Options1]} - end. - +%%%---------------------------------------------------------------- +start_daemon(_, _, {error,Error}) -> + {error,Error}; + +start_daemon(socket, Socket, Options) -> + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of + ok -> + try + do_start_daemon(Socket, Options) + catch + throw:bad_fd -> {error,bad_fd}; + throw:bad_socket -> {error,bad_socket}; + _C:_E -> {error,{cannot_start_daemon,_C,_E}} + end; + {error,SockError} -> + {error,SockError} + end; -start_daemon(Socket, Options) -> - case handle_options(Options) of - {error, Error} -> - {error, Error}; - {SocketOptions, SshOptions} -> - case valid_socket_to_use(Socket, Options) of - ok -> - try - do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions) - catch - throw:bad_fd -> {error,bad_fd}; - throw:bad_socket -> {error,bad_socket}; - _C:_E -> {error,{cannot_start_daemon,_C,_E}} - end; - {error,SockError} -> - {error,SockError} - end +start_daemon(Host, Port, Options) -> + try + do_start_daemon(Host, Port, Options) + catch + throw:bad_fd -> {error,bad_fd}; + throw:bad_socket -> {error,bad_socket}; + _C:_E -> {error,{cannot_start_daemon,_C,_E}} end. -start_daemon(Host, Port, Options, Inet) -> - case handle_options(Options) of - {error, _Reason} = Error -> - Error; - {SocketOptions, SshOptions}-> - try - do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions]) - catch - throw:bad_fd -> {error,bad_fd}; - throw:bad_socket -> {error,bad_socket}; - _C:_E -> {error,{cannot_start_daemon,_C,_E}} - end - end. -do_start_daemon(Socket, SshOptions, SocketOptions) -> +do_start_daemon(Socket, Options) -> {ok, {IP,Port}} = try {ok,_} = inet:sockname(Socket) catch _:_ -> throw(bad_socket) end, Host = fmt_host(IP), - Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE), - Opts = [{asocket, Socket}, - {asock_owner,self()}, - {address, Host}, - {port, Port}, - {role, server}, - {socket_opts, SocketOptions}, - {ssh_opts, SshOptions}], - {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), + Opts = ?PUT_INTERNAL_OPT([{asocket, Socket}, + {asock_owner,self()}, + {address, Host}, + {port, Port}, + {role, server}], Options), + + Profile = ?GET_OPT(profile, Options), case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> - %% It would proably make more sense to call the - %% address option host but that is a too big change at the - %% monent. The name is a legacy name! try sshd_sup:start_child(Opts) of {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> - call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, Result); + call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, Result); Result = {error, _} -> Result catch @@ -379,56 +378,47 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> - call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, {ok, Sup}); + call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, {ok,Sup}); Other -> Other end end. -do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> +do_start_daemon(Host0, Port0, Options0) -> {Host,Port1} = try - case proplists:get_value(fd, SocketOptions) of + case ?GET_SOCKET_OPT(fd, Options0) of undefined -> {Host0,Port0}; Fd when Port0==0 -> - find_hostport(Fd); - _ -> - {Host0,Port0} + find_hostport(Fd) end catch _:_ -> throw(bad_fd) end, - Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE), - {Port, WaitRequestControl, Opts0} = + {Port, WaitRequestControl, Options1} = case Port1 of 0 -> %% Allocate the socket here to get the port number... - {_, Callback, _} = - proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), - {ok,LSock} = ssh_acceptor:callback_listen(Callback, 0, SocketOptions), + {ok,LSock} = ssh_acceptor:callback_listen(0, Options0), {ok,{_,LPort}} = inet:sockname(LSock), {LPort, - {LSock,Callback}, - [{lsocket,LSock},{lsock_owner,self()}] + LSock, + ?PUT_INTERNAL_OPT({lsocket,{LSock,self()}}, Options0) }; _ -> - {Port1, false, []} + {Port1, false, Options0} end, - Opts = [{address, Host}, - {port, Port}, - {role, server}, - {socket_opts, SocketOptions}, - {ssh_opts, SshOptions} | Opts0], + Options = ?PUT_INTERNAL_OPT([{address, Host}, + {port, Port}, + {role, server}], Options1), + Profile = ?GET_OPT(profile, Options0), case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> - %% It would proably make more sense to call the - %% address option host but that is a too big change at the - %% monent. The name is a legacy name! - try sshd_sup:start_child(Opts) of + try sshd_sup:start_child(Options) of {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> - sync_request_control(WaitRequestControl), + sync_request_control(WaitRequestControl, Options), Result; Result = {error, _} -> Result @@ -436,21 +426,22 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> exit:{noproc, _} -> {error, ssh_not_started} end; - Sup -> + Sup -> AccPid = ssh_system_sup:acceptor_supervisor(Sup), - case ssh_acceptor_sup:start_child(AccPid, Opts) of + case ssh_acceptor_sup:start_child(AccPid, Options) of {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> - sync_request_control(WaitRequestControl), + sync_request_control(WaitRequestControl, Options), {ok, Sup}; Other -> Other end end. -call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, DefaultResult) -> - try ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket) +call_ssh_acceptor_handle_connection(Host, Port, Options, Socket, DefaultResult) -> + {_, Callback, _} = ?GET_OPT(transport, Options), + try ssh_acceptor:handle_connection(Callback, Host, Port, Options, Socket) of {error,Error} -> {error,Error}; _ -> DefaultResult @@ -459,9 +450,10 @@ call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, DefaultR end. -sync_request_control(false) -> +sync_request_control(false, _Options) -> ok; -sync_request_control({LSock,Callback}) -> +sync_request_control(LSock, Options) -> + {_, Callback, _} = ?GET_OPT(transport, Options), receive {request_control,LSock,ReqPid} -> ok = Callback:controlling_process(LSock, ReqPid), @@ -477,523 +469,6 @@ find_hostport(Fd) -> ok = inet:close(S), HostPort. - -handle_options(Opts) -> - try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of - {Inet, Ssh} -> - {handle_ip(Inet), Ssh} - catch - throw:Error -> - Error - end. - - -algs_compatibility(Os0) -> - %% Take care of old options 'public_key_alg' and 'pref_public_key_algs' - case proplists:get_value(public_key_alg, Os0) of - undefined -> - Os0; - A when is_atom(A) -> - %% Skip public_key_alg if pref_public_key_algs is defined: - Os = lists:keydelete(public_key_alg, 1, Os0), - case proplists:get_value(pref_public_key_algs,Os) of - undefined when A == 'ssh-rsa' ; A==ssh_rsa -> - [{pref_public_key_algs,['ssh-rsa','ssh-dss']} | Os]; - undefined when A == 'ssh-dss' ; A==ssh_dsa -> - [{pref_public_key_algs,['ssh-dss','ssh-rsa']} | Os]; - undefined -> - throw({error, {eoptions, {public_key_alg,A} }}); - _ -> - Os - end; - V -> - throw({error, {eoptions, {public_key_alg,V} }}) - end. - - -handle_option([], SocketOptions, SshOptions) -> - {SocketOptions, SshOptions}; -handle_option([{system_dir, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_dir, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_dir_fun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{silently_accept_hosts, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_interaction, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{connect_timeout, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{dsa_pass_phrase, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{rsa_pass_phrase, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{password, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_passwords, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{pwdfun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{key_cb, {Module, Options}} | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option({key_cb, Module}), - handle_ssh_priv_option({key_cb_private, Options}) | - SshOptions]); -handle_option([{key_cb, Module} | Rest], SocketOptions, SshOptions) -> - handle_option([{key_cb, {Module, []}} | Rest], SocketOptions, SshOptions); -handle_option([{keyboard_interact_fun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -%%Backwards compatibility -handle_option([{allow_user_interaction, Value} | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option({user_interaction, Value}) | SshOptions]); -handle_option([{infofun, _} = Opt | Rest],SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{connectfun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{disconnectfun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{unexpectedfun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{failfun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{ssh_msg_debug_fun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -%%Backwards compatibility should not be underscore between ip and v6 in API -handle_option([{ip_v6_disabled, Value} | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option({ipv6_disabled, Value}) | SshOptions]); -handle_option([{ipv6_disabled, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{transport, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{subsystems, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{ssh_cli, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{shell, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{exec, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{auth_methods, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{auth_method_kb_interactive_data, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{pref_public_key_algs, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{preferred_algorithms,_} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{dh_gex_groups,_} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{dh_gex_limits,_} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{rekey_limit, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{max_sessions, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{max_channels, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{negotiation_timeout, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{parallel_login, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -%% (Is handled by proplists:unfold above:) -%% handle_option([parallel_login|Rest], SocketOptions, SshOptions) -> -%% handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | SshOptions]); -handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{id_string, _ID} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{profile, _ID} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{max_random_length_padding, _Bool} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{tstflg, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). - - -handle_ssh_option({tstflg,_F} = Opt) -> Opt; -handle_ssh_option({minimal_remote_max_packet_size, Value} = Opt) when is_integer(Value), Value >=0 -> - Opt; -handle_ssh_option({system_dir, Value} = Opt) when is_list(Value) -> - check_dir(Opt); -handle_ssh_option({user_dir, Value} = Opt) when is_list(Value) -> - check_dir(Opt); -handle_ssh_option({user_dir_fun, Value} = Opt) when is_function(Value) -> - Opt; -handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) -> - Opt; -handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_function(Value,2) -> - Opt; -handle_ssh_option({silently_accept_hosts, {DigestAlg,Value}} = Opt) when is_function(Value,2) -> - Algs = if is_atom(DigestAlg) -> [DigestAlg]; - is_list(DigestAlg) -> DigestAlg; - true -> throw({error, {eoptions, Opt}}) - end, - case [A || A <- Algs, - not lists:member(A, [md5, sha, sha224, sha256, sha384, sha512])] of - [_|_] = UnSup1 -> - throw({error, {{eoptions, Opt}, {not_fingerprint_algos,UnSup1}}}); - [] -> - CryptoHashAlgs = proplists:get_value(hashs, crypto:supports(), []), - case [A || A <- Algs, - not lists:member(A, CryptoHashAlgs)] of - [_|_] = UnSup2 -> - throw({error, {{eoptions, Opt}, {unsupported_algo,UnSup2}}}); - [] -> Opt - end - end; -handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> - Opt; -handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> - handle_pref_algs(Opt); - -handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> - {dh_gex_groups, - collect_per_size( - lists:foldl( - fun({N,G,P}, Acc) when is_integer(N),N>0, - is_integer(G),G>0, - is_integer(P),P>0 -> - [{N,{G,P}} | Acc]; - ({N,{G,P}}, Acc) when is_integer(N),N>0, - is_integer(G),G>0, - is_integer(P),P>0 -> - [{N,{G,P}} | Acc]; - ({N,GPs}, Acc) when is_list(GPs) -> - lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, - is_integer(Pi),Pi>0 -> - [{N,{Gi,Pi}} | Acci] - end, Acc, GPs) - end, [], L0))}; - -handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0, - Tag == file ; - Tag == ssh_moduli_file -> - {ok,GroupDefs} = - case Tag of - file -> - file:consult(File); - ssh_moduli_file -> - case file:open(File,[read]) of - {ok,D} -> - try - {ok,Moduli} = read_moduli_file(D, 1, []), - file:close(D), - {ok, Moduli} - catch - _:_ -> - throw({error, {{eoptions, Opt}, "Bad format in file "++File}}) - end; - {error,enoent} -> - throw({error, {{eoptions, Opt}, "File not found:"++File}}); - {error,Error} -> - throw({error, {{eoptions, Opt}, io_lib:format("Error reading file ~s: ~p",[File,Error])}}) - end - end, - - try - handle_ssh_option({dh_gex_groups,GroupDefs}) - catch - _:_ -> - throw({error, {{eoptions, Opt}, "Bad format in file: "++File}}) - end; - - -handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0, - is_integer(Max), Max>=Min -> - %% Server - Opt; -handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, - is_integer(I), I>=Min, - is_integer(Max), Max>=I -> - %% Client - Opt; -handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 -> - case handle_user_pref_pubkey_algs(Value, []) of - {true, NewOpts} -> - {pref_public_key_algs, NewOpts}; - _ -> - throw({error, {eoptions, Opt}}) - end; -handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> - Opt; -handle_ssh_option({max_sessions, Value} = Opt) when is_integer(Value), Value>0 -> - Opt; -handle_ssh_option({max_channels, Value} = Opt) when is_integer(Value), Value>0 -> - Opt; -handle_ssh_option({negotiation_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> - Opt; -handle_ssh_option({parallel_login, Value} = Opt) when Value==true ; Value==false -> - Opt; -handle_ssh_option({user, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({dsa_pass_phrase, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({rsa_pass_phrase, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({password, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({user_passwords, Value} = Opt) when is_list(Value)-> - Opt; -handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,2) -> - Opt; -handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,4) -> - Opt; -handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) -> - Opt; -handle_ssh_option({key_cb, {CallbackMod, CallbackOptions}} = Opt) when is_atom(CallbackMod), - is_list(CallbackOptions) -> - Opt; -handle_ssh_option({keyboard_interact_fun, Value} = Opt) when is_function(Value,3) -> - Opt; -handle_ssh_option({compression, Value} = Opt) when is_atom(Value) -> - Opt; -handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module), - is_atom(Function) -> - Opt; -handle_ssh_option({exec, Function} = Opt) when is_function(Function) -> - Opt; -handle_ssh_option({auth_methods, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({auth_method_kb_interactive_data, {Name,Instruction,Prompt,Echo}} = Opt) when is_list(Name), - is_list(Instruction), - is_list(Prompt), - is_boolean(Echo) -> - Opt; -handle_ssh_option({auth_method_kb_interactive_data, F} = Opt) when is_function(F,3) -> - Opt; -handle_ssh_option({infofun, Value} = Opt) when is_function(Value) -> - Opt; -handle_ssh_option({connectfun, Value} = Opt) when is_function(Value) -> - Opt; -handle_ssh_option({disconnectfun, Value} = Opt) when is_function(Value) -> - Opt; -handle_ssh_option({unexpectedfun, Value} = Opt) when is_function(Value,2) -> - Opt; -handle_ssh_option({failfun, Value} = Opt) when is_function(Value) -> - Opt; -handle_ssh_option({ssh_msg_debug_fun, Value} = Opt) when is_function(Value,4) -> - Opt; - -handle_ssh_option({ipv6_disabled, Value} = Opt) when is_boolean(Value) -> - throw({error, {{ipv6_disabled, Opt}, option_no_longer_valid_use_inet_option_instead}}); -handle_ssh_option({transport, {Protocol, Cb, ClosTag}} = Opt) when is_atom(Protocol), - is_atom(Cb), - is_atom(ClosTag) -> - Opt; -handle_ssh_option({subsystems, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({ssh_cli, {Cb, _}}= Opt) when is_atom(Cb) -> - Opt; -handle_ssh_option({ssh_cli, no_cli} = Opt) -> - Opt; -handle_ssh_option({shell, {Module, Function, _}} = Opt) when is_atom(Module), - is_atom(Function) -> - Opt; -handle_ssh_option({shell, Value} = Opt) when is_function(Value) -> - Opt; -handle_ssh_option({quiet_mode, Value} = Opt) when is_boolean(Value) -> - Opt; -handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> - Opt; -handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> - Opt; -handle_ssh_option({id_string, random}) -> - {id_string, {random,2,5}}; %% 2 - 5 random characters -handle_ssh_option({id_string, ID} = Opt) when is_list(ID) -> - Opt; -handle_ssh_option({max_random_length_padding, Value} = Opt) when is_integer(Value), - Value =< 255 -> - Opt; -handle_ssh_option({profile, Value} = Opt) when is_atom(Value) -> - Opt; -handle_ssh_option(Opt) -> - throw({error, {eoptions, Opt}}). - -handle_ssh_priv_option({key_cb_private, Value} = Opt) when is_list(Value) -> - Opt. - -handle_inet_option({active, _} = Opt) -> - throw({error, {{eoptions, Opt}, "SSH has built in flow control, " - "and active is handled internally, user is not allowed" - "to specify this option"}}); - -handle_inet_option({inet, Value}) when (Value == inet) or (Value == inet6) -> - Value; -handle_inet_option({reuseaddr, _} = Opt) -> - throw({error, {{eoptions, Opt},"Is set internally, user is not allowed" - "to specify this option"}}); -%% Option verified by inet -handle_inet_option(Opt) -> - Opt. - - -%% Check preferred algs - -handle_pref_algs({preferred_algorithms,Algs}) -> - try alg_duplicates(Algs, [], []) of - [] -> - {preferred_algorithms, - [try ssh_transport:supported_algorithms(Key) - of - DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs) - catch - _:_ -> throw({error, {{eoptions, {preferred_algorithms,Key}}, - "Bad preferred_algorithms key"}}) - end || {Key,Vals} <- Algs] - }; - - Dups -> - throw({error, {{eoptions, {preferred_algorithms,Dups}}, "Duplicates found"}}) - catch - _:_ -> - throw({error, {{eoptions, preferred_algorithms}, "Malformed"}}) - end. - -alg_duplicates([{K,V}|KVs], Ks, Dups0) -> - Dups = - case lists:member(K,Ks) of - true -> - [K|Dups0]; - false -> - Dups0 - end, - case V--lists:usort(V) of - [] -> - alg_duplicates(KVs, [K|Ks], Dups); - Ds -> - alg_duplicates(KVs, [K|Ks], Dups++Ds) - end; -alg_duplicates([], _Ks, Dups) -> - Dups. - -handle_pref_alg(Key, - Vs=[{client2server,C2Ss=[_|_]},{server2client,S2Cs=[_|_]}], - [{client2server,Sup_C2Ss},{server2client,Sup_S2Cs}] - ) -> - chk_alg_vs(Key, C2Ss, Sup_C2Ss), - chk_alg_vs(Key, S2Cs, Sup_S2Cs), - {Key, Vs}; - -handle_pref_alg(Key, - Vs=[{server2client,[_|_]},{client2server,[_|_]}], - Sup=[{client2server,_},{server2client,_}] - ) -> - handle_pref_alg(Key, lists:reverse(Vs), Sup); - -handle_pref_alg(Key, - Vs=[V|_], - Sup=[{client2server,_},{server2client,_}] - ) when is_atom(V) -> - handle_pref_alg(Key, [{client2server,Vs},{server2client,Vs}], Sup); - -handle_pref_alg(Key, - Vs=[V|_], - Sup=[S|_] - ) when is_atom(V), is_atom(S) -> - chk_alg_vs(Key, Vs, Sup), - {Key, Vs}; - -handle_pref_alg(Key, Vs, _) -> - throw({error, {{eoptions, {preferred_algorithms,[{Key,Vs}]}}, "Badly formed list"}}). - -chk_alg_vs(OptKey, Values, SupportedValues) -> - case (Values -- SupportedValues) of - [] -> Values; - Bad -> throw({error, {{eoptions, {OptKey,Bad}}, "Unsupported value(s) found"}}) - end. - -handle_ip(Inet) -> %% Default to ipv4 - case lists:member(inet, Inet) of - true -> - Inet; - false -> - case lists:member(inet6, Inet) of - true -> - Inet; - false -> - [inet | Inet] - end - end. - -check_dir({_,Dir} = Opt) -> - case directory_exist_readable(Dir) of - ok -> - Opt; - {error,Error} -> - throw({error, {eoptions,{Opt,Error}}}) - end. - -directory_exist_readable(Dir) -> - case file:read_file_info(Dir) of - {ok, #file_info{type = directory, - access = Access}} -> - case Access of - read -> ok; - read_write -> ok; - _ -> {error, eacces} - end; - - {ok, #file_info{}}-> - {error, enotdir}; - - {error, Error} -> - {error, Error} - end. - - - -collect_per_size(L) -> - lists:foldr( - fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc]; - ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc] - end, [], lists:sort(L)). - -read_moduli_file(D, I, Acc) -> - case io:get_line(D,"") of - {error,Error} -> - {error,Error}; - eof -> - {ok, Acc}; - "#" ++ _ -> read_moduli_file(D, I+1, Acc); - <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc); - Data -> - Line = if is_binary(Data) -> binary_to_list(Data); - is_list(Data) -> Data - end, - try - [_Time,_Type,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"), - M = {list_to_integer(Size), - {list_to_integer(G), list_to_integer(P,16)} - }, - read_moduli_file(D, I+1, [M|Acc]) - catch - _:_ -> - read_moduli_file(D, I+1, Acc) - end - end. - -handle_user_pref_pubkey_algs([], Acc) -> - {true, lists:reverse(Acc)}; -handle_user_pref_pubkey_algs([H|T], Acc) -> - case lists:member(H, ?SUPPORTED_USER_KEYS) of - true -> - handle_user_pref_pubkey_algs(T, [H| Acc]); - - false when H==ssh_dsa -> handle_user_pref_pubkey_algs(T, ['ssh-dss'| Acc]); - false when H==ssh_rsa -> handle_user_pref_pubkey_algs(T, ['ssh-rsa'| Acc]); - - false -> - false - end. - fmt_host({A,B,C,D}) -> lists:concat([A,".",B,".",C,".",D]); fmt_host(T={_,_,_,_,_,_,_,_}) -> diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 4cd91177f6..475534f572 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -33,6 +33,10 @@ -define(REKEY_DATA_TIMOUT, 60000). -define(DEFAULT_PROFILE, default). +-define(DEFAULT_TRANSPORT, {tcp, gen_tcp, tcp_closed} ). + +-define(MAX_RND_PADDING_LEN, 15). + -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). -define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']). @@ -64,10 +68,31 @@ -define(string_utf8(X), << ?STRING(unicode:characters_to_binary(X)) >> ). -define(binary(X), << ?STRING(X) >>). +%% Cipher details -define(SSH_CIPHER_NONE, 0). -define(SSH_CIPHER_3DES, 3). -define(SSH_CIPHER_AUTHFILE, ?SSH_CIPHER_3DES). +%% Option access macros +-define(do_get_opt(C,K,O), ssh_options:get_value(C,K,O, ?MODULE,?LINE)). +-define(do_get_opt(C,K,O,D), ssh_options:get_value(C,K,O,D,?MODULE,?LINE)). + +-define(GET_OPT(Key,Opts), ?do_get_opt(user_options, Key,Opts ) ). +-define(GET_INTERNAL_OPT(Key,Opts), ?do_get_opt(internal_options,Key,Opts ) ). +-define(GET_INTERNAL_OPT(Key,Opts,Def), ?do_get_opt(internal_options,Key,Opts,Def) ). +-define(GET_SOCKET_OPT(Key,Opts), ?do_get_opt(socket_options, Key,Opts ) ). +-define(GET_SOCKET_OPT(Key,Opts,Def), ?do_get_opt(socket_options, Key,Opts,Def) ). + +-define(do_put_opt(C,KV,O), ssh_options:put_value(C,KV,O, ?MODULE,?LINE)). + +-define(PUT_OPT(KeyVal,Opts), ?do_put_opt(user_options, KeyVal,Opts) ). +-define(PUT_INTERNAL_OPT(KeyVal,Opts), ?do_put_opt(internal_options,KeyVal,Opts) ). +-define(PUT_SOCKET_OPT(KeyVal,Opts), ?do_put_opt(socket_options, KeyVal,Opts) ). + +%% Types +-type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} . + +%% Records -record(ssh, { role, %% client | server @@ -127,7 +152,7 @@ recv_sequence = 0, keyex_key, keyex_info, - random_length_padding = 15, % From RFC 4253 section 6. + random_length_padding = ?MAX_RND_PADDING_LEN, % From RFC 4253 section 6. %% User auth user, diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 13c9d9af4a..42be18f2ad 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -25,56 +25,63 @@ -include("ssh.hrl"). %% Internal application API --export([start_link/5, +-export([start_link/4, number_of_connections/1, - callback_listen/3, + callback_listen/2, handle_connection/5]). %% spawn export --export([acceptor_init/6, acceptor_loop/6]). +-export([acceptor_init/5, acceptor_loop/6]). -define(SLEEP_TIME, 200). %%==================================================================== %% Internal application API %%==================================================================== -start_link(Port, Address, SockOpts, Opts, AcceptTimeout) -> - Args = [self(), Port, Address, SockOpts, Opts, AcceptTimeout], +start_link(Port, Address, Options, AcceptTimeout) -> + Args = [self(), Port, Address, Options, AcceptTimeout], proc_lib:start_link(?MODULE, acceptor_init, Args). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -acceptor_init(Parent, Port, Address, SockOpts, Opts, AcceptTimeout) -> - {_, Callback, _} = - proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}), - - SockOwner = proplists:get_value(lsock_owner, Opts), - LSock = proplists:get_value(lsocket, Opts), - UseExistingSocket = - case catch inet:sockname(LSock) of - {ok,{_,Port}} -> is_pid(SockOwner); - _ -> false - end, - - case UseExistingSocket of - true -> - proc_lib:init_ack(Parent, {ok, self()}), +acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + try + {LSock0,SockOwner0} = ?GET_INTERNAL_OPT(lsocket, Opts), + true = is_pid(SockOwner0), + {ok,{_,Port}} = inet:sockname(LSock0), + {LSock0, SockOwner0} + of + {LSock, SockOwner} -> + %% Use existing socket + proc_lib:init_ack(Parent, {ok, self()}), request_ownership(LSock, SockOwner), - acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout); - - false -> - case (catch do_socket_listen(Callback, Port, SockOpts)) of - {ok, ListenSocket} -> - proc_lib:init_ack(Parent, {ok, self()}), - acceptor_loop(Callback, - Port, Address, Opts, ListenSocket, AcceptTimeout); - Error -> - proc_lib:init_ack(Parent, Error), - error - end + acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout) + catch + error:{badkey,lsocket} -> + %% Open new socket + try + socket_listen(Port, Opts) + of + {ok, ListenSocket} -> + proc_lib:init_ack(Parent, {ok, self()}), + {_, Callback, _} = ?GET_OPT(transport, Opts), + acceptor_loop(Callback, + Port, Address, Opts, ListenSocket, AcceptTimeout); + {error,Error} -> + proc_lib:init_ack(Parent, Error), + {error,Error} + catch + _:_ -> + {error,listen_socket_failed} + end; + + _:_ -> + {error,use_existing_socket_failed} end. + request_ownership(LSock, SockOwner) -> SockOwner ! {request_control,LSock,self()}, receive @@ -82,23 +89,25 @@ request_ownership(LSock, SockOwner) -> end. -do_socket_listen(Callback, Port0, Opts) -> - Port = - case proplists:get_value(fd, Opts) of - undefined -> Port0; - _ -> 0 - end, - callback_listen(Callback, Port, Opts). - -callback_listen(Callback, Port, Opts0) -> - Opts = [{active, false}, {reuseaddr,true} | Opts0], - case Callback:listen(Port, Opts) of +socket_listen(Port0, Opts) -> + Port = case ?GET_SOCKET_OPT(fd, Opts) of + undefined -> Port0; + _ -> 0 + end, + callback_listen(Port, Opts). + + +callback_listen(Port, Opts0) -> + {_, Callback, _} = ?GET_OPT(transport, Opts0), + Opts = ?PUT_SOCKET_OPT([{active, false}, {reuseaddr,true}], Opts0), + SockOpts = ?GET_OPT(socket_options, Opts), + case Callback:listen(Port, SockOpts) of {error, nxdomain} -> - Callback:listen(Port, lists:delete(inet6, Opts)); + Callback:listen(Port, lists:delete(inet6, SockOpts)); {error, enetunreach} -> - Callback:listen(Port, lists:delete(inet6, Opts)); + Callback:listen(Port, lists:delete(inet6, SockOpts)); {error, eafnosupport} -> - Callback:listen(Port, lists:delete(inet6, Opts)); + Callback:listen(Port, lists:delete(inet6, SockOpts)); Other -> Other end. @@ -120,21 +129,21 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) -> end. handle_connection(Callback, Address, Port, Options, Socket) -> - SSHopts = proplists:get_value(ssh_opts, Options, []), - Profile = proplists:get_value(profile, SSHopts, ?DEFAULT_PROFILE), + Profile = ?GET_OPT(profile, Options), SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile), - MaxSessions = proplists:get_value(max_sessions,SSHopts,infinity), + MaxSessions = ?GET_OPT(max_sessions, Options), case number_of_connections(SystemSup) < MaxSessions of true -> {ok, SubSysSup} = ssh_system_sup:start_subsystem(SystemSup, Options), ConnectionSup = ssh_subsystem_sup:connection_supervisor(SubSysSup), - Timeout = proplists:get_value(negotiation_timeout, SSHopts, 2*60*1000), + NegTimeout = ?GET_OPT(negotiation_timeout, Options), ssh_connection_handler:start_connection(server, Socket, - [{supervisors, [{system_sup, SystemSup}, - {subsystem_sup, SubSysSup}, - {connection_sup, ConnectionSup}]} - | Options], Timeout); + ?PUT_INTERNAL_OPT( + {supervisors, [{system_sup, SystemSup}, + {subsystem_sup, SubSysSup}, + {connection_sup, ConnectionSup}]}, + Options), NegTimeout); false -> Callback:close(Socket), IPstr = if is_tuple(Address) -> inet:ntoa(Address); diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 129f85a3e0..77f7826918 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -44,14 +44,13 @@ start_link(Servers) -> supervisor:start_link(?MODULE, [Servers]). -start_child(AccSup, ServerOpts) -> - Spec = child_spec(ServerOpts), +start_child(AccSup, Options) -> + Spec = child_spec(Options), case supervisor:start_child(AccSup, Spec) of {error, already_present} -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Profile = proplists:get_value(profile, - proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Profile = ?GET_OPT(profile, Options), stop_child(AccSup, Address, Port, Profile), supervisor:start_child(AccSup, Spec); Reply -> @@ -70,24 +69,23 @@ stop_child(AccSup, Address, Port, Profile) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= -init([ServerOpts]) -> +init([Options]) -> RestartStrategy = one_for_one, MaxR = 10, MaxT = 3600, - Children = [child_spec(ServerOpts)], + Children = [child_spec(Options)], {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Timeout = proplists:get_value(timeout, ServerOpts, ?DEFAULT_TIMEOUT), - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +child_spec(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT), + Profile = ?GET_OPT(profile, Options), Name = id(Address, Port, Profile), - SocketOpts = proplists:get_value(socket_opts, ServerOpts), - StartFunc = {ssh_acceptor, start_link, [Port, Address, SocketOpts, ServerOpts, Timeout]}, + StartFunc = {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, Restart = transient, Shutdown = brutal_kill, Modules = [ssh_acceptor], diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 9b54ecb2dd..88c8144063 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -96,14 +96,14 @@ unique(L) -> password_msg([#ssh{opts = Opts, io_cb = IoCb, user = User, service = Service} = Ssh0]) -> {Password,Ssh} = - case proplists:get_value(password, Opts) of + case ?GET_OPT(password, Opts) of undefined when IoCb == ssh_no_io -> {not_ok, Ssh0}; undefined -> - {IoCb:read_password("ssh password: ",Ssh0), Ssh0}; + {IoCb:read_password("ssh password: ",Opts), Ssh0}; PW -> %% If "password" option is given it should not be tried again - {PW, Ssh0#ssh{opts = lists:keyreplace(password,1,Opts,{password,not_ok})}} + {PW, Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} end, case Password of not_ok -> @@ -123,7 +123,7 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb, keyboard_interactive_msg([#ssh{user = User, opts = Opts, service = Service} = Ssh]) -> - case proplists:get_value(password, Opts) of + case ?GET_OPT(password, Opts) of not_ok -> {not_ok,Ssh}; % No need to use a failed pwd once more _ -> @@ -141,8 +141,9 @@ publickey_msg([Alg, #ssh{user = User, service = Service, opts = Opts} = Ssh]) -> Hash = ssh_transport:sha(Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - case KeyCb:user_key(Alg, Opts) of + {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), + UserOpts = ?GET_OPT(user_options, Opts), + case KeyCb:user_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of {ok, PrivKey} -> StrAlgo = atom_to_list(Alg), case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of @@ -174,13 +175,19 @@ service_request_msg(Ssh) -> %%%---------------------------------------------------------------- init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> - case user_name(Opts) of - {ok, User} -> + case ?GET_OPT(user, Opts) of + undefined -> + ErrStr = "Could not determine the users name", + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, + description = ErrStr}); + + User -> Msg = #ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "none", data = <<>>}, - Algs0 = proplists:get_value(pref_public_key_algs, Opts, ?SUPPORTED_USER_KEYS), + Algs0 = ?GET_OPT(pref_public_key_algs, Opts), %% The following line is not strictly correct. The call returns the %% supported HOST key types while we are interested in USER keys. However, %% they "happens" to be the same (for now). This could change.... @@ -194,12 +201,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, userauth_preference = Prefs, userauth_methods = none, - service = "ssh-connection"}); - {error, no_user} -> - ErrStr = "Could not determine the users name", - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, - description = ErrStr}) + service = "ssh-connection"}) end. %%%---------------------------------------------------------------- @@ -342,7 +344,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, false}, {Name, Instruction, Prompt, Echo} = - case proplists:get_value(auth_method_kb_interactive_data, Opts) of + case ?GET_OPT(auth_method_kb_interactive_data, Opts) of undefined -> Default; {_,_,_,_}=V -> @@ -407,9 +409,9 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, user = User, userauth_supported_methods = Methods} = Ssh) -> SendOneEmpty = - (proplists:get_value(tstflg,Opts) == one_empty) + (?GET_OPT(tstflg,Opts) == one_empty) orelse - proplists:get_value(one_empty, proplists:get_value(tstflg,Opts,[]), false), + proplists:get_value(one_empty, ?GET_OPT(tstflg,Opts), false), case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of {true,Ssh1} when SendOneEmpty==true -> @@ -460,27 +462,8 @@ method_preference(Algs) -> ], Algs). -user_name(Opts) -> - Env = case os:type() of - {win32, _} -> - "USERNAME"; - {unix, _} -> - "LOGNAME" - end, - case proplists:get_value(user, Opts, os:getenv(Env)) of - false -> - case os:getenv("USER") of - false -> - {error, no_user}; - User -> - {ok, User} - end; - User -> - {ok, User} - end. - check_password(User, Password, Opts, Ssh) -> - case proplists:get_value(pwdfun, Opts) of + case ?GET_OPT(pwdfun, Opts) of undefined -> Static = get_password_option(Opts, User), {Password == Static, Ssh}; @@ -510,17 +493,18 @@ check_password(User, Password, Opts, Ssh) -> end. get_password_option(Opts, User) -> - Passwords = proplists:get_value(user_passwords, Opts, []), + Passwords = ?GET_OPT(user_passwords, Opts), case lists:keysearch(User, 1, Passwords) of {value, {User, Pw}} -> Pw; - false -> proplists:get_value(password, Opts, false) + false -> ?GET_OPT(password, Opts) end. pre_verify_sig(User, Alg, KeyBlob, Opts) -> try {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - KeyCb:is_auth_key(Key, User, Opts) + {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), + UserOpts = ?GET_OPT(user_options, Opts), + KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) catch _:_ -> false @@ -529,9 +513,10 @@ pre_verify_sig(User, Alg, KeyBlob, Opts) -> verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> try {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - case KeyCb:is_auth_key(Key, User, Opts) of + {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), + UserOpts = ?GET_OPT(user_options, Opts), + case KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) of true -> PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg), @@ -565,9 +550,9 @@ decode_keyboard_interactive_prompts(_NumPrompts, Data) -> keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> NumPrompts = length(PromptInfos), - keyboard_interact_get_responses(proplists:get_value(user_interaction, Opts, true), - proplists:get_value(keyboard_interact_fun, Opts), - proplists:get_value(password, Opts, undefined), IoCb, Name, + keyboard_interact_get_responses(?GET_OPT(user_interaction, Opts), + ?GET_OPT(keyboard_interact_fun, Opts), + ?GET_OPT(password, Opts), IoCb, Name, Instr, PromptInfos, Opts, NumPrompts). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 6f8c050486..4c4f61e036 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -499,14 +499,12 @@ start_shell(ConnectionHandler, State) -> [peer, user]), ShellFun = case is_function(Shell) of true -> - User = - proplists:get_value(user, ConnectionInfo), + User = proplists:get_value(user, ConnectionInfo), case erlang:fun_info(Shell, arity) of {arity, 1} -> fun() -> Shell(User) end; {arity, 2} -> - {_, PeerAddr} = - proplists:get_value(peer, ConnectionInfo), + {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), fun() -> Shell(User, PeerAddr) end; _ -> Shell @@ -525,8 +523,7 @@ start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]), - User = - proplists:get_value(user, ConnectionInfo), + User = proplists:get_value(user, ConnectionInfo), ShellFun = case erlang:fun_info(Shell, arity) of {arity, 1} -> @@ -534,8 +531,7 @@ start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function {arity, 2} -> fun() -> Shell(Cmd, User) end; {arity, 3} -> - {_, PeerAddr} = - proplists:get_value(peer, ConnectionInfo), + {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), fun() -> Shell(Cmd, User, PeerAddr) end; _ -> Shell diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index c7a2c92670..6a48ed581c 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -197,16 +197,16 @@ reply_request(_,false, _, _) -> ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> - Options = backwards_compatible(Options0, []), - {Width, PixWidth} = pty_default_dimensions(width, Options), - {Height, PixHeight} = pty_default_dimensions(height, Options), + TermData = backwards_compatible(Options0, []), % FIXME + {Width, PixWidth} = pty_default_dimensions(width, TermData), + {Height, PixHeight} = pty_default_dimensions(height, TermData), pty_req(ConnectionHandler, Channel, - proplists:get_value(term, Options, os:getenv("TERM", ?DEFAULT_TERMINAL)), - proplists:get_value(width, Options, Width), - proplists:get_value(height, Options, Height), - proplists:get_value(pixel_widh, Options, PixWidth), - proplists:get_value(pixel_height, Options, PixHeight), - proplists:get_value(pty_opts, Options, []), TimeOut + proplists:get_value(term, TermData, os:getenv("TERM", ?DEFAULT_TERMINAL)), + proplists:get_value(width, TermData, Width), + proplists:get_value(height, TermData, Height), + proplists:get_value(pixel_widh, TermData, PixWidth), + proplists:get_value(pixel_height, TermData, PixHeight), + proplists:get_value(pty_opts, TermData, []), TimeOut ). %%-------------------------------------------------------------------- %% Not yet officialy supported! The following functions are part of the @@ -417,7 +417,8 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, maximum_packet_size = PacketSz}, #connection{options = SSHopts} = Connection0, server) -> - MinAcceptedPackSz = proplists:get_value(minimal_remote_max_packet_size, SSHopts, 0), + MinAcceptedPackSz = + ?GET_OPT(minimal_remote_max_packet_size, SSHopts), if MinAcceptedPackSz =< PacketSz -> @@ -574,7 +575,6 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, PixWidth, PixHeight, decode_pty_opts(Modes)}, Channel = ssh_channel:cache_lookup(Cache, ChannelId), - handle_cli_msg(Connection, Channel, {pty, ChannelId, WantReply, PtyRequest}); @@ -691,7 +691,6 @@ handle_cli_msg(#connection{channel_cache = Cache} = Connection, #channel{user = undefined, remote_id = RemoteId, local_id = ChannelId} = Channel0, Reply0) -> - case (catch start_cli(Connection, ChannelId)) of {ok, Pid} -> erlang:monitor(process, Pid), @@ -819,7 +818,7 @@ start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> ssh_channel_sup:start_child(ChannelSup, ChildSpec). assert_limit_num_channels_not_exceeded(ChannelSup, Opts) -> - MaxNumChannels = proplists:get_value(max_channels, Opts, infinity), + MaxNumChannels = ?GET_OPT(max_channels, Opts), NumChannels = length([x || {_,_,worker,[ssh_channel]} <- supervisor:which_children(ChannelSup)]), if @@ -858,8 +857,8 @@ setup_session(#connection{channel_cache = Cache check_subsystem("sftp"= SsName, Options) -> - case proplists:get_value(subsystems, Options, no_subsys) of - no_subsys -> + case ?GET_OPT(subsystems, Options) of + no_subsys -> % FIXME: Can 'no_subsys' ever be matched? {SsName, {Cb, Opts}} = ssh_sftpd:subsystem_spec([]), {Cb, Opts}; SubSystems -> @@ -867,7 +866,7 @@ check_subsystem("sftp"= SsName, Options) -> end; check_subsystem(SsName, Options) -> - Subsystems = proplists:get_value(subsystems, Options, []), + Subsystems = ?GET_OPT(subsystems, Options), case proplists:get_value(SsName, Subsystems, {none, []}) of Fun when is_function(Fun) -> {Fun, []}; @@ -1022,12 +1021,13 @@ pty_req(ConnectionHandler, Channel, Term, Width, Height, ?uint32(PixWidth),?uint32(PixHeight), encode_pty_opts(PtyOpts)], TimeOut). -pty_default_dimensions(Dimension, Options) -> - case proplists:get_value(Dimension, Options, 0) of +pty_default_dimensions(Dimension, TermData) -> + case proplists:get_value(Dimension, TermData, 0) of N when is_integer(N), N > 0 -> {N, 0}; _ -> - case proplists:get_value(list_to_atom("pixel_" ++ atom_to_list(Dimension)), Options, 0) of + PixelDim = list_to_atom("pixel_" ++ atom_to_list(Dimension)), + case proplists:get_value(PixelDim, TermData, 0) of N when is_integer(N), N > 0 -> {0, N}; _ -> diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index dcf509ca09..706b68d78b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -76,7 +76,7 @@ %%-------------------------------------------------------------------- -spec start_link(role(), inet:socket(), - proplists:proplist() + ssh_options:options() ) -> {ok, pid()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . start_link(Role, Socket, Options) -> @@ -99,12 +99,10 @@ stop(ConnectionHandler)-> %% Internal application API %%==================================================================== --define(DefaultTransport, {tcp, gen_tcp, tcp_closed} ). - %%-------------------------------------------------------------------- -spec start_connection(role(), inet:socket(), - proplists:proplist(), + ssh_options:options(), timeout() ) -> {ok, connection_ref()} | {error, term()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -121,9 +119,8 @@ start_connection(client = Role, Socket, Options, Timeout) -> end; start_connection(server = Role, Socket, Options, Timeout) -> - SSH_Opts = proplists:get_value(ssh_opts, Options, []), try - case proplists:get_value(parallel_login, SSH_Opts, false) of + case ?GET_OPT(parallel_login, Options) of true -> HandshakerPid = spawn_link(fun() -> @@ -346,7 +343,7 @@ renegotiate_data(ConnectionHandler) -> | undefined, last_size_rekey = 0 :: non_neg_integer(), event_queue = [] :: list(), - opts :: proplists:proplist(), + opts :: ssh_options:options(), inet_initial_recbuf_size :: pos_integer() | undefined }). @@ -357,15 +354,14 @@ renegotiate_data(ConnectionHandler) -> %%-------------------------------------------------------------------- -spec init_connection_handler(role(), inet:socket(), - proplists:proplist() + ssh_options:options() ) -> no_return(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . init_connection_handler(Role, Socket, Opts) -> process_flag(trap_exit, true), S0 = init_process_state(Role, Socket, Opts), try - {Protocol, Callback, CloseTag} = - proplists:get_value(transport, Opts, ?DefaultTransport), + {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), transport_protocol = Protocol, transport_cb = Callback, @@ -393,7 +389,7 @@ init_process_state(Role, Socket, Opts) -> port_bindings = [], requests = [], options = Opts}, - starter = proplists:get_value(user_pid, Opts), + starter = ?GET_INTERNAL_OPT(user_pid, Opts), socket = Socket, opts = Opts }, @@ -409,13 +405,18 @@ init_process_state(Role, Socket, Opts) -> init_connection(server, C = #connection{}, Opts) -> - Sups = proplists:get_value(supervisors, Opts), - SystemSup = proplists:get_value(system_sup, Sups), - SubSystemSup = proplists:get_value(subsystem_sup, Sups), + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + + SystemSup = proplists:get_value(system_sup, Sups), + SubSystemSup = proplists:get_value(subsystem_sup, Sups), ConnectionSup = proplists:get_value(connection_sup, Sups), - Shell = proplists:get_value(shell, Opts), - Exec = proplists:get_value(exec, Opts), - CliSpec = proplists:get_value(ssh_cli, Opts, {ssh_cli, [Shell]}), + + Shell = ?GET_OPT(shell, Opts), + Exec = ?GET_OPT(exec, Opts), + CliSpec = case ?GET_OPT(ssh_cli, Opts) of + undefined -> {ssh_cli, [Shell]}; + Spec -> Spec + end, C#connection{cli_spec = CliSpec, exec = Exec, system_supervisor = SystemSup, @@ -426,41 +427,38 @@ init_connection(server, C = #connection{}, Opts) -> init_ssh_record(Role, Socket, Opts) -> {ok, PeerAddr} = inet:peername(Socket), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - AuthMethods = proplists:get_value(auth_methods, - Opts, - case Role of - server -> ?SUPPORTED_AUTH_METHODS; - client -> undefined - end), + KeyCb = ?GET_OPT(key_cb, Opts), + AuthMethods = + case Role of + server -> ?GET_OPT(auth_methods, Opts); + client -> undefined + end, S0 = #ssh{role = Role, key_cb = KeyCb, opts = Opts, userauth_supported_methods = AuthMethods, available_host_keys = supported_host_keys(Role, KeyCb, Opts), - random_length_padding = proplists:get_value(max_random_length_padding, - Opts, - (#ssh{})#ssh.random_length_padding) + random_length_padding = ?GET_OPT(max_random_length_padding, Opts) }, {Vsn, Version} = ssh_transport:versions(Role, Opts), case Role of client -> - PeerName = proplists:get_value(host, Opts), + PeerName = ?GET_INTERNAL_OPT(host, Opts), S0#ssh{c_vsn = Vsn, c_version = Version, - io_cb = case proplists:get_value(user_interaction, Opts, true) of + io_cb = case ?GET_OPT(user_interaction, Opts) of true -> ssh_io; false -> ssh_no_io end, - userauth_quiet_mode = proplists:get_value(quiet_mode, Opts, false), + userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), peer = {PeerName, PeerAddr} }; server -> S0#ssh{s_vsn = Vsn, s_version = Version, - io_cb = proplists:get_value(io_cb, Opts, ssh_io), + io_cb = ?GET_INTERNAL_OPT(io_cb, Opts, ssh_io), userauth_methods = string:tokens(AuthMethods, ","), kb_tries_left = 3, peer = {undefined, PeerAddr} @@ -849,14 +847,12 @@ handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactiv handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, #data{ssh_params = Ssh0} = D0) -> Opts = Ssh0#ssh.opts, - D = case proplists:get_value(password, Opts) of + D = case ?GET_OPT(password, Opts) of undefined -> D0; _ -> D0#data{ssh_params = - Ssh0#ssh{opts = - lists:keyreplace(password,1,Opts, - {password,not_ok})}} % FIXME:intermodule dependency + Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} % FIXME:intermodule dependency end, {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; @@ -954,7 +950,7 @@ handle_event(cast, renegotiate, _, _) -> handle_event(cast, data_size, {connected,Role}, D) -> {ok, [{send_oct,Sent0}]} = inet:getstat(D#data.socket, [send_oct]), Sent = Sent0 - D#data.last_size_rekey, - MaxSent = proplists:get_value(rekey_limit, D#data.opts, 1024000000), + MaxSent = ?GET_OPT(rekey_limit, D#data.opts), timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), case Sent >= MaxSent of true -> @@ -1294,11 +1290,12 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> "Unexpected message '~p' received in state '~p'\n" "Role: ~p\n" "Peer: ~p\n" - "Local Address: ~p\n", [UnexpectedMessage, - StateName, - Ssh#ssh.role, - Ssh#ssh.peer, - proplists:get_value(address, Ssh#ssh.opts)])), + "Local Address: ~p\n", + [UnexpectedMessage, + StateName, + Ssh#ssh.role, + Ssh#ssh.peer, + ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)])), error_logger:info_report(Msg), keep_state_and_data; @@ -1312,11 +1309,12 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> "Message: ~p\n" "Role: ~p\n" "Peer: ~p\n" - "Local Address: ~p\n", [Other, - UnexpectedMessage, - Ssh#ssh.role, - element(2,Ssh#ssh.peer), - proplists:get_value(address, Ssh#ssh.opts)] + "Local Address: ~p\n", + [Other, + UnexpectedMessage, + Ssh#ssh.role, + element(2,Ssh#ssh.peer), + ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)] )), error_logger:error_report(Msg), keep_state_and_data @@ -1438,11 +1436,11 @@ code_change(_OldVsn, StateName, State, _Extra) -> %%-------------------------------------------------------------------- %% Starting -start_the_connection_child(UserPid, Role, Socket, Options) -> - Sups = proplists:get_value(supervisors, Options), +start_the_connection_child(UserPid, Role, Socket, Options0) -> + Sups = ?GET_INTERNAL_OPT(supervisors, Options0), ConnectionSup = proplists:get_value(connection_sup, Sups), - Opts = [{supervisors, Sups}, {user_pid, UserPid} | proplists:get_value(ssh_opts, Options, [])], - {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Opts]), + Options = ?PUT_INTERNAL_OPT({user_pid,UserPid}, Options0), + {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Options]), ok = socket_control(Socket, Pid, Options), Pid. @@ -1499,7 +1497,7 @@ supported_host_keys(server, KeyCb, Options) -> find_sup_hkeys(Options) -> case proplists:get_value(public_key, - proplists:get_value(preferred_algorithms,Options,[]) + ?GET_OPT(preferred_algorithms,Options) ) of undefined -> @@ -1512,9 +1510,10 @@ find_sup_hkeys(Options) -> %% Alg :: atom() -available_host_key(KeyCb, Alg, Opts) -> - element(1, catch KeyCb:host_key(Alg, Opts)) == ok. - +available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) -> + UserOpts = ?GET_OPT(user_options, Opts), + element(1, + catch KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts])) == ok. send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), @@ -1773,7 +1772,7 @@ get_repl(X, Acc) -> disconnect_fun({disconnect,Msg}, D) -> disconnect_fun(Msg, D); disconnect_fun(Reason, #data{opts=Opts}) -> - case proplists:get_value(disconnectfun, Opts) of + case ?GET_OPT(disconnectfun, Opts) of undefined -> ok; Fun -> @@ -1783,7 +1782,7 @@ disconnect_fun(Reason, #data{opts=Opts}) -> unexpected_fun(UnexpectedMessage, #data{opts = Opts, ssh_params = #ssh{peer = {_,Peer} } } ) -> - case proplists:get_value(unexpectedfun, Opts) of + case ?GET_OPT(unexpectedfun, Opts) of undefined -> report; Fun -> @@ -1795,7 +1794,7 @@ debug_fun(#ssh_msg_debug{always_display = Display, message = DbgMsg, language = Lang}, #data{opts = Opts}) -> - case proplists:get_value(ssh_msg_debug_fun, Opts) of + case ?GET_OPT(ssh_msg_debug_fun, Opts) of undefined -> ok; Fun -> @@ -1805,7 +1804,7 @@ debug_fun(#ssh_msg_debug{always_display = Display, connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}, opts = Opts}) -> - case proplists:get_value(connectfun, Opts) of + case ?GET_OPT(connectfun, Opts) of undefined -> ok; Fun -> @@ -1824,7 +1823,7 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts, _ -> {infofun, Reason} end, - Fun = proplists:get_value(Tag, Opts, fun(_,_)-> ok end), + Fun = ?GET_OPT(Tag, Opts), try erlang:fun_info(Fun, arity) of {arity, 2} -> %% Backwards compatible @@ -1843,7 +1842,7 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts, %%% channels open for a while. cache_init_idle_timer(D) -> - case proplists:get_value(idle_time, D#data.opts, infinity) of + case ?GET_OPT(idle_time, D#data.opts) of infinity -> D#data{idle_timer_value = infinity, idle_timer_ref = infinity % A flag used later... @@ -1906,9 +1905,8 @@ start_channel_request_timer(Channel, From, Time) -> %%% Connection start and initalization helpers socket_control(Socket, Pid, Options) -> - {_, TransportCallback, _} = % For example {_,gen_tcp,_} - proplists:get_value(transport, Options, ?DefaultTransport), - case TransportCallback:controlling_process(Socket, Pid) of + {_, Callback, _} = ?GET_OPT(transport, Options), + case Callback:controlling_process(Socket, Pid) of ok -> gen_statem:cast(Pid, socket_control); {error, Reason} -> diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 216f65f33a..898b4cc5c4 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -192,8 +192,8 @@ lookup_user_key(Key, User, Opts) -> ssh_dir({remoteuser, User}, Opts) -> case proplists:get_value(user_dir_fun, Opts) of undefined -> - case proplists:get_value(user_dir, Opts) of - undefined -> + case proplists:get_value(user_dir, Opts, false) of + false -> default_user_dir(); Dir -> Dir diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 1d8f370884..6828fd4760 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -27,17 +27,17 @@ -export([yes_no/2, read_password/2, read_line/2, format/2]). -include("ssh.hrl"). -read_line(Prompt, Ssh) -> +read_line(Prompt, Opts) -> format("~s", [listify(Prompt)]), - proplists:get_value(user_pid, Ssh) ! {self(), question}, + ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question}, receive Answer when is_list(Answer) -> Answer end. -yes_no(Prompt, Ssh) -> +yes_no(Prompt, Opts) -> format("~s [y/n]?", [Prompt]), - proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question}, + ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question}, receive %% I can't see that the atoms y and n are ever received, but it must %% be investigated before removing @@ -52,15 +52,13 @@ yes_no(Prompt, Ssh) -> "N" -> no; _ -> format("please answer y or n\n",[]), - yes_no(Prompt, Ssh) + yes_no(Prompt, Opts) end end. - -read_password(Prompt, #ssh{opts=Opts}) -> read_password(Prompt, Opts); -read_password(Prompt, Opts) when is_list(Opts) -> +read_password(Prompt, Opts) -> format("~s", [listify(Prompt)]), - proplists:get_value(user_pid, Opts) ! {self(), user_password}, + ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), user_password}, receive Answer when is_list(Answer) -> case trim(Answer) of diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl new file mode 100644 index 0000000000..52dea5d183 --- /dev/null +++ b/lib/ssh/src/ssh_options.erl @@ -0,0 +1,897 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssh_options). + +-include("ssh.hrl"). +-include_lib("kernel/include/file.hrl"). + +-export([default/1, + get_value/5, get_value/6, + put_value/5, + handle_options/2 + ]). + +-export_type([options/0 + ]). + +%%%================================================================ +%%% Types + +-type options() :: #{socket_options := socket_options(), + internal_options := internal_options(), + option_key() => any() + }. + +-type socket_options() :: proplists:proplist(). +-type internal_options() :: #{option_key() => any()}. + +-type option_key() :: atom(). + +-type option_in() :: proplists:property() | proplists:proplist() . + +-type option_class() :: internal_options | socket_options | user_options . + +-type option_declaration() :: #{class := user_options, + chk := fun((any) -> boolean() | {true,any()}), + default => any() + }. + +-type option_declarations() :: #{ {option_key(),def} := option_declaration() }. + +-type role() :: client | server . + +-type error() :: {error,{eoptions,any()}} . + +%%%================================================================ +%%% +%%% Get an option +%%% + +-spec get_value(option_class(), option_key(), options(), + atom(), non_neg_integer()) -> any() | no_return(). + +get_value(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + case Class of + internal_options -> maps:get(Key, maps:get(internal_options,Opts)); + socket_options -> proplists:get_value(Key, maps:get(socket_options,Opts)); + user_options -> maps:get(Key, Opts) + end; +get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> + io:format("*** Bad Opts GET OPT ~p ~p:~p Key=~p,~n Opts=~p~n",[Class,_CallerMod,_CallerLine,Key,Opts]), + error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). + + +-spec get_value(option_class(), option_key(), options(), any(), + atom(), non_neg_integer()) -> any() | no_return(). + +get_value(socket_options, Key, Opts, Def, _CallerMod, _CallerLine) when is_map(Opts) -> + proplists:get_value(Key, maps:get(socket_options,Opts), Def); +get_value(Class, Key, Opts, Def, CallerMod, CallerLine) when is_map(Opts) -> + try get_value(Class, Key, Opts, CallerMod, CallerLine) + catch + error:{badkey,Key} -> Def + end; +get_value(Class, Key, Opts, _Def, _CallerMod, _CallerLine) -> + io:format("*** Bad Opts GET OPT ~p ~p:~p Key=~p,~n Opts=~p~n",[Class,_CallerMod,_CallerLine,Key,Opts]), + error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). + + +%%%================================================================ +%%% +%%% Put an option +%%% + +-spec put_value(option_class(), option_in(), options(), + atom(), non_neg_integer()) -> options(). + +put_value(user_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + put_user_value(KeyVal, Opts); + +put_value(internal_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + InternalOpts = maps:get(internal_options,Opts), + Opts#{internal_options := put_internal_value(KeyVal, InternalOpts)}; + +put_value(socket_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + SocketOpts = maps:get(socket_options,Opts), + Opts#{socket_options := put_socket_value(KeyVal, SocketOpts)}. + + +%%%---------------- +put_user_value(L, Opts) when is_list(L) -> + lists:foldl(fun put_user_value/2, Opts, L); +put_user_value({Key,Value}, Opts) -> + Opts#{Key := Value}. + +%%%---------------- +put_internal_value(L, IntOpts) when is_list(L) -> + lists:foldl(fun put_internal_value/2, IntOpts, L); +put_internal_value({Key,Value}, IntOpts) -> + IntOpts#{Key => Value}. + +%%%---------------- +put_socket_value(L, SockOpts) when is_list(L) -> + L ++ SockOpts; +put_socket_value({Key,Value}, SockOpts) -> + [{Key,Value} | SockOpts]; +put_socket_value(A, SockOpts) when is_atom(A) -> + [A | SockOpts]. + +%%%================================================================ +%%% +%%% Initialize the options +%%% + +-spec handle_options(role(), proplists:proplist()) -> options() | error() . + +-spec handle_options(role(), proplists:proplist(), options()) -> options() | error() . + +handle_options(Role, PropList0) -> + handle_options(Role, PropList0, #{socket_options => [], + internal_options => #{}, + user_options => [] + }). + +handle_options(Role, PropList0, Opts0) when is_map(Opts0), + is_list(PropList0) -> + PropList1 = proplists:unfold(PropList0), + try + OptionDefinitions = default(Role), + InitialMap = + maps:fold( + fun({K,def}, #{default:=V}, M) -> M#{K=>V}; + (_,_,M) -> M + end, + Opts0#{user_options => + maps:get(user_options,Opts0) ++ PropList1 + }, + OptionDefinitions), + %% Enter the user's values into the map; unknown keys are + %% treated as socket options + lists:foldl(fun(KV, Vals) -> + save(KV, OptionDefinitions, Vals) + end, InitialMap, PropList1) + catch + error:{eoptions, KV, undefined} -> + {error, {eoptions,KV}}; + + error:{eoptions, KV, Txt} when is_list(Txt) -> + {error, {eoptions,{KV,lists:flatten(Txt)}}}; + + error:{eoptions, KV, Extra} -> + {error, {eoptions,{KV,Extra}}} + end. + + +check_fun(Key, Defs) -> + #{chk := Fun} = maps:get({Key,def}, Defs), + Fun. + +%%%================================================================ +%%% +%%% Check and save one option +%%% + + +%%% First some prohibited inet options: +save({K,V}, _, _) when K == reuseaddr ; + K == active + -> + forbidden_option(K, V); + +%%% then compatibility conversions: +save({allow_user_interaction,V}, Opts, Vals) -> + save({user_interaction,V}, Opts, Vals); + +save({public_key_alg,V}, Defs, Vals) -> % To remove in OTP-20 + New = case V of + 'ssh-rsa' -> ['ssh-rsa', 'ssh-dss']; + ssh_rsa -> ['ssh-rsa', 'ssh-dss']; + 'ssh-dss' -> ['ssh-dss', 'ssh-rsa']; + ssh_dsa -> ['ssh-dss', 'ssh-rsa']; + _ -> error({eoptions, {public_key_alg,V}, + "Unknown algorithm, try pref_public_key_algs instead"}) + end, + save({pref_public_key_algs,New}, Defs, Vals); + +%% Special case for socket options 'inet' and 'inet6' +save(Inet, Defs, OptMap) when Inet==inet ; Inet==inet6 -> + save({inet,Inet}, Defs, OptMap); + +%% Two clauses to prepare for a proplists:unfold +save({Inet,true}, Defs, OptMap) when Inet==inet ; Inet==inet6 -> save({inet,Inet}, Defs, OptMap); +save({Inet,false}, _Defs, OptMap) when Inet==inet ; Inet==inet6 -> OptMap; + +%% and finaly the 'real stuff': +save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> + try (check_fun(Key,Defs))(Value) + of + true -> + OptMap#{Key := Value}; + {true, ModifiedValue} -> + OptMap#{Key := ModifiedValue}; + false -> + error({eoptions, {Key,Value}, "Bad value"}) + catch + %% An unknown Key (= not in the definition map) is + %% regarded as an inet option: + error:{badkey,{inet,def}} -> + %% atomic (= non-tuple) options 'inet' and 'inet6': + OptMap#{socket_options := [Value | maps:get(socket_options,OptMap)]}; + error:{badkey,{Key,def}} -> + OptMap#{socket_options := [{Key,Value} | maps:get(socket_options,OptMap)]}; + + %% But a Key that is known but the value does not validate + %% by the check fun will give an error exception: + error:{check,{BadValue,Extra}} -> + error({eoptions, {Key,BadValue}, Extra}) + end. + +%%%================================================================ +%%% +%%% Default options +%%% + +-spec default(role() | common) -> option_declarations() . + +default(server) -> + (default(common)) + #{ + {subsystems, def} => + #{default => [ssh_sftpd:subsystem_spec([])], + chk => fun(L) -> + is_list(L) andalso + lists:all(fun({Name,{CB,Args}}) -> + check_string(Name) andalso + is_atom(CB) andalso + is_list(Args); + (_) -> + false + end, L) + end, + class => user_options + }, + + {shell, def} => + #{default => {shell, start, []}, + chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A); + (V) -> check_function1(V) orelse check_function2(V) + end, + class => user_options + }, + + {exec, def} => % FIXME: need some archeology.... + #{default => undefined, + chk => fun({M,F,_}) -> is_atom(M) andalso is_atom(F); + (V) -> is_function(V) + end, + class => user_options + }, + + {ssh_cli, def} => + #{default => undefined, + chk => fun({Cb, As}) -> is_atom(Cb) andalso is_list(As); + (V) -> V == no_cli + end, + class => user_options + }, + + {system_dir, def} => + #{default => "/etc/ssh", + chk => fun(V) -> check_string(V) andalso check_dir(V) end, + class => user_options + }, + + {auth_methods, def} => + #{default => ?SUPPORTED_AUTH_METHODS, + chk => fun check_string/1, + class => user_options + }, + + {auth_method_kb_interactive_data, def} => + #{default => undefined, % Default value can be constructed when User is known + chk => fun({S1,S2,S3,B}) -> + check_string(S1) andalso + check_string(S2) andalso + check_string(S3) andalso + is_boolean(B); + (F) -> + check_function3(F) + end, + class => user_options + }, + + {user_passwords, def} => + #{default => [], + chk => fun(V) -> + is_list(V) andalso + lists:all(fun({S1,S2}) -> + check_string(S1) andalso + check_string(S2) + end, V) + end, + class => user_options + }, + + {password, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + + {dh_gex_groups, def} => + #{default => undefined, + chk => fun check_dh_gex_groups/1, + class => user_options + }, + + {dh_gex_limits, def} => + #{default => {0, infinity}, + chk => fun({I1,I2}) -> + check_pos_integer(I1) andalso + check_pos_integer(I2) andalso + I1 < I2; + (_) -> + false + end, + class => user_options + }, + + {pwdfun, def} => + #{default => undefined, + chk => fun(V) -> check_function4(V) orelse check_function2(V) end, + class => user_options + }, + + {negotiation_timeout, def} => + #{default => 2*60*1000, + chk => fun check_timeout/1, + class => user_options + }, + + {max_sessions, def} => + #{default => infinity, + chk => fun check_pos_integer/1, + class => user_options + }, + + {max_channels, def} => + #{default => infinity, + chk => fun check_pos_integer/1, + class => user_options + }, + + {parallel_login, def} => + #{default => false, + chk => fun erlang:is_boolean/1, + class => user_options + }, + + {minimal_remote_max_packet_size, def} => + #{default => 0, + chk => fun check_pos_integer/1, + class => user_options + }, + + {failfun, def} => + #{default => fun(_,_,_) -> void end, + chk => fun(V) -> check_function3(V) orelse + check_function2(V) % Backwards compatibility + end, + class => user_options + }, + + {connectfun, def} => + #{default => fun(_,_,_) -> void end, + chk => fun check_function3/1, + class => user_options + }, + +%%%%% Undocumented + {infofun, def} => + #{default => fun(_,_,_) -> void end, + chk => fun(V) -> check_function3(V) orelse + check_function2(V) % Backwards compatibility + end, + class => user_options + } + }; + +default(client) -> + (default(common)) + #{ + {dsa_pass_phrase, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + + {rsa_pass_phrase, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + + {silently_accept_hosts, def} => + #{default => false, + chk => fun check_silently_accept_hosts/1, + class => user_options + }, + + {user_interaction, def} => + #{default => true, + chk => fun erlang:is_boolean/1, + class => user_options + }, + + {pref_public_key_algs, def} => + #{default => + %% Get dynamically supported keys in the order of the ?SUPPORTED_USER_KEYS + [A || A <- ?SUPPORTED_USER_KEYS, + lists:member(A, ssh_transport:supported_algorithms(public_key))], + chk => + fun check_pref_public_key_algs/1, + class => + ssh + }, + + {dh_gex_limits, def} => + #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? + chk => fun({Min,I,Max}) -> + lists:all(fun check_pos_integer/1, + [Min,I,Max]); + (_) -> false + end, + class => user_options + }, + + {connect_timeout, def} => + #{default => infinity, + chk => fun check_timeout/1, + class => user_options + }, + + {user, def} => + #{default => + begin + Env = case os:type() of + {win32, _} -> "USERNAME"; + {unix, _} -> "LOGNAME" + end, + case os:getenv(Env) of + false -> + case os:getenv("USER") of + false -> undefined; + User -> User + end; + User -> + User + end + end, + chk => fun check_string/1, + class => user_options + }, + + {password, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + + {quiet_mode, def} => + #{default => false, + chk => fun erlang:is_boolean/1, + class => user_options + }, + + {idle_time, def} => + #{default => infinity, + chk => fun check_timeout/1, + class => user_options + }, + +%%%%% Undocumented + {keyboard_interact_fun, def} => + #{default => undefined, + chk => fun check_function3/1, + class => user_options + } + }; + +default(common) -> + #{ + {user_dir, def} => + #{default => false, % FIXME: TBD ~/.ssh at time of call when user is known + chk => fun(V) -> check_string(V) andalso check_dir(V) end, + class => user_options + }, + + {preferred_algorithms, def} => + #{default => ssh:default_algorithms(), + chk => fun check_preferred_algorithms/1, + class => user_options + }, + + {id_string, def} => + #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 + chk => fun(random) -> + {true, {random,2,5}}; % 2 - 5 random characters + ({random,I1,I2}) -> + %% Undocumented + check_pos_integer(I1) andalso + check_pos_integer(I2) andalso + I1= + check_string(V) + end, + class => user_options + }, + + {key_cb, def} => + #{default => {ssh_file, []}, + chk => fun({Mod,Opts}) -> is_atom(Mod) andalso is_list(Opts); + (Mod) when is_atom(Mod) -> {true, {Mod,[]}}; + (_) -> false + end, + class => user_options + }, + + {profile, def} => + #{default => ?DEFAULT_PROFILE, + chk => fun erlang:is_atom/1, + class => user_options + }, + + %% This is a "SocketOption"... + %% {fd, def} => + %% #{default => undefined, + %% chk => fun erlang:is_integer/1, + %% class => user_options + %% }, + + {disconnectfun, def} => + #{default => fun(_) -> void end, + chk => fun check_function1/1, + class => user_options + }, + + {unexpectedfun, def} => + #{default => fun(_,_) -> report end, + chk => fun check_function2/1, + class => user_options + }, + + {ssh_msg_debug_fun, def} => + #{default => fun(_,_,_,_) -> void end, + chk => fun check_function4/1, + class => user_options + }, + + {rekey_limit, def} => % FIXME: Why not common? + #{default => 1024000000, + chk => fun check_non_neg_integer/1, + class => user_options + }, + +%%%%% Undocumented + {transport, def} => + #{default => ?DEFAULT_TRANSPORT, + chk => fun({A,B,C}) -> + is_atom(A) andalso is_atom(B) andalso is_atom(C) + end, + class => user_options + }, + + {vsn, def} => + #{default => {2,0}, + chk => fun({Maj,Min}) -> check_non_neg_integer(Maj) andalso check_non_neg_integer(Min); + (_) -> false + end, + class => user_options + }, + + {tstflg, def} => + #{default => [], + chk => fun erlang:is_list/1, + class => user_options + }, + + {user_dir_fun, def} => + #{default => undefined, + chk => fun check_function1/1, + class => user_options + }, + + {max_random_length_padding, def} => + #{default => ?MAX_RND_PADDING_LEN, + chk => fun check_non_neg_integer/1, + class => user_options + } + }. + + +%%%================================================================ +%%%================================================================ +%%%================================================================ + +%%% +%%% check_*/1 -> true | false | error({check,Spec}) +%%% See error_in_check/2,3 +%%% + +%%% error_in_check(BadValue) -> error_in_check(BadValue, undefined). + +error_in_check(BadValue, Extra) -> error({check,{BadValue,Extra}}). + + +%%%---------------------------------------------------------------- +check_timeout(infinity) -> true; +check_timeout(I) -> check_pos_integer(I). + +%%%---------------------------------------------------------------- +check_pos_integer(I) -> is_integer(I) andalso I>0. + +%%%---------------------------------------------------------------- +check_non_neg_integer(I) -> is_integer(I) andalso I>=0. + +%%%---------------------------------------------------------------- +check_function1(F) -> is_function(F,1). +check_function2(F) -> is_function(F,2). +check_function3(F) -> is_function(F,3). +check_function4(F) -> is_function(F,4). + +%%%---------------------------------------------------------------- +check_pref_public_key_algs(V) -> + %% Get the dynamically supported keys, that is, thoose + %% that are stored + PKs = ssh_transport:supported_algorithms(public_key), + CHK = fun(A, Ack) -> + case lists:member(A, PKs) of + true -> + [A|Ack]; + false -> + %% Check with the documented options, that is, + %% the one we can handle + case lists:member(A,?SUPPORTED_USER_KEYS) of + false -> + %% An algorithm ssh never can handle + error_in_check(A, "Not supported public key"); + true -> + %% An algorithm ssh can handle, but not in + %% this very call + Ack + end + end + end, + case lists:foldr( + fun(ssh_dsa, Ack) -> CHK('ssh-dss', Ack); % compatibility + (ssh_rsa, Ack) -> CHK('ssh-rsa', Ack); % compatibility + (X, Ack) -> CHK(X, Ack) + end, [], V) + of + V -> true; + [] -> false; + V1 -> {true,V1} + end. + + +%%%---------------------------------------------------------------- +%% Check that it is a directory and is readable +check_dir(Dir) -> + case file:read_file_info(Dir) of + {ok, #file_info{type = directory, + access = Access}} -> + case Access of + read -> true; + read_write -> true; + _ -> error_in_check(Dir, eacces) + end; + + {ok, #file_info{}}-> + error_in_check(Dir, enotdir); + + {error, Error} -> + error_in_check(Dir, Error) + end. + +%%%---------------------------------------------------------------- +check_string(S) -> is_list(S). % FIXME: stub + +%%%---------------------------------------------------------------- +check_dh_gex_groups({file,File}) when is_list(File) -> + case file:consult(File) of + {ok, GroupDefs} -> + check_dh_gex_groups(GroupDefs); + {error, Error} -> + error_in_check({file,File},Error) + end; + +check_dh_gex_groups({ssh_moduli_file,File}) when is_list(File) -> + case file:open(File,[read]) of + {ok,D} -> + try + read_moduli_file(D, 1, []) + of + {ok,Moduli} -> + check_dh_gex_groups(Moduli); + {error,Error} -> + error_in_check({ssh_moduli_file,File}, Error) + catch + _:_ -> + error_in_check({ssh_moduli_file,File}, "Bad format in file "++File) + after + file:close(D) + end; + + {error, Error} -> + error_in_check({ssh_moduli_file,File}, Error) + end; + +check_dh_gex_groups(L0) when is_list(L0), is_tuple(hd(L0)) -> + {true, + collect_per_size( + lists:foldl( + fun({N,G,P}, Acc) when is_integer(N),N>0, + is_integer(G),G>0, + is_integer(P),P>0 -> + [{N,{G,P}} | Acc]; + ({N,{G,P}}, Acc) when is_integer(N),N>0, + is_integer(G),G>0, + is_integer(P),P>0 -> + [{N,{G,P}} | Acc]; + ({N,GPs}, Acc) when is_list(GPs) -> + lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, + is_integer(Pi),Pi>0 -> + [{N,{Gi,Pi}} | Acci] + end, Acc, GPs) + end, [], L0))}; + +check_dh_gex_groups(_) -> + false. + + + +collect_per_size(L) -> + lists:foldr( + fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc]; + ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc] + end, [], lists:sort(L)). + +read_moduli_file(D, I, Acc) -> + case io:get_line(D,"") of + {error,Error} -> + {error,Error}; + eof -> + {ok, Acc}; + "#" ++ _ -> read_moduli_file(D, I+1, Acc); + <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc); + Data -> + Line = if is_binary(Data) -> binary_to_list(Data); + is_list(Data) -> Data + end, + try + [_Time,_Class,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"), + M = {list_to_integer(Size), + {list_to_integer(G), list_to_integer(P,16)} + }, + read_moduli_file(D, I+1, [M|Acc]) + catch + _:_ -> + read_moduli_file(D, I+1, Acc) + end + end. + +%%%---------------------------------------------------------------- +-define(SHAs, [md5, sha, sha224, sha256, sha384, sha512]). + +check_silently_accept_hosts(B) when is_boolean(B) -> true; +check_silently_accept_hosts(F) when is_function(F,2) -> true; +check_silently_accept_hosts({S,F}) when is_atom(S), + is_function(F,2) -> + lists:member(S, ?SHAs) andalso + lists:member(S, proplists:get_value(hashs,crypto:supports())); +check_silently_accept_hosts({L,F}) when is_list(L), + is_function(F,2) -> + lists:all(fun(S) -> + lists:member(S, ?SHAs) andalso + lists:member(S, proplists:get_value(hashs,crypto:supports())) + end, L); +check_silently_accept_hosts(_) -> false. + +%%%---------------------------------------------------------------- +check_preferred_algorithms(Algs) -> + try alg_duplicates(Algs, [], []) + of + [] -> + {true, + [try ssh_transport:supported_algorithms(Key) + of + DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs) + catch + _:_ -> error_in_check(Key,"Bad preferred_algorithms key") + end || {Key,Vals} <- Algs] + }; + + Dups -> + error_in_check(Dups, "Duplicates") + catch + _:_ -> + false + end. + +alg_duplicates([{K,V}|KVs], Ks, Dups0) -> + Dups = + case lists:member(K,Ks) of + true -> [K|Dups0]; + false -> Dups0 + end, + case V--lists:usort(V) of + [] -> alg_duplicates(KVs, [K|Ks], Dups); + Ds -> alg_duplicates(KVs, [K|Ks], Dups++Ds) + end; +alg_duplicates([], _Ks, Dups) -> + Dups. + +handle_pref_alg(Key, + Vs=[{client2server,C2Ss=[_|_]},{server2client,S2Cs=[_|_]}], + [{client2server,Sup_C2Ss},{server2client,Sup_S2Cs}] + ) -> + chk_alg_vs(Key, C2Ss, Sup_C2Ss), + chk_alg_vs(Key, S2Cs, Sup_S2Cs), + {Key, Vs}; + +handle_pref_alg(Key, + Vs=[{server2client,[_|_]},{client2server,[_|_]}], + Sup=[{client2server,_},{server2client,_}] + ) -> + handle_pref_alg(Key, lists:reverse(Vs), Sup); + +handle_pref_alg(Key, + Vs=[V|_], + Sup=[{client2server,_},{server2client,_}] + ) when is_atom(V) -> + handle_pref_alg(Key, [{client2server,Vs},{server2client,Vs}], Sup); + +handle_pref_alg(Key, + Vs=[V|_], + Sup=[S|_] + ) when is_atom(V), is_atom(S) -> + chk_alg_vs(Key, Vs, Sup), + {Key, Vs}; + +handle_pref_alg(Key, Vs, _) -> + error_in_check({Key,Vs}, "Badly formed list"). + +chk_alg_vs(OptKey, Values, SupportedValues) -> + case (Values -- SupportedValues) of + [] -> Values; + Bad -> error_in_check({OptKey,Bad}, "Unsupported value(s) found") + end. + +%%%---------------------------------------------------------------- +forbidden_option(K,V) -> + Txt = io_lib:format("The option '~s' is used internally. The " + "user is not allowed to specify this option.", + [K]), + error({eoptions, {K,V}, Txt}). + +%%%---------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 8d994cdb43..140856c8e3 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -100,18 +100,14 @@ start_channel(Socket) when is_port(Socket) -> start_channel(Host) when is_list(Host) -> start_channel(Host, []). -start_channel(Socket, Options) when is_port(Socket) -> - Timeout = - %% A mixture of ssh:connect and ssh_sftp:start_channel: - case proplists:get_value(connect_timeout, Options, undefined) of - undefined -> - proplists:get_value(timeout, Options, infinity); - TO -> - TO - end, - case ssh:connect(Socket, Options, Timeout) of +start_channel(Socket, UserOptions) when is_port(Socket) -> + {SshOpts, _ChanOpts, SftpOpts} = handle_options(UserOptions), + Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: + proplists:get_value(connect_timeout, SshOpts, + proplists:get_value(timeout, SftpOpts, infinity)), + case ssh:connect(Socket, SshOpts, Timeout) of {ok,Cm} -> - case start_channel(Cm, Options) of + case start_channel(Cm, UserOptions) of {ok, Pid} -> {ok, Pid, Cm}; Error -> @@ -120,9 +116,9 @@ start_channel(Socket, Options) when is_port(Socket) -> Error -> Error end; -start_channel(Cm, Opts) when is_pid(Cm) -> - Timeout = proplists:get_value(timeout, Opts, infinity), - {_, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []), +start_channel(Cm, UserOptions) when is_pid(Cm) -> + Timeout = proplists:get_value(timeout, UserOptions, infinity), + {_SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), case ssh_xfer:attach(Cm, [], ChanOpts) of {ok, ChannelId, Cm} -> case ssh_channel:start(Cm, ChannelId, @@ -143,15 +139,17 @@ start_channel(Cm, Opts) when is_pid(Cm) -> Error end; -start_channel(Host, Opts) -> - start_channel(Host, 22, Opts). -start_channel(Host, Port, Opts) -> - {SshOpts, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []), - Timeout = proplists:get_value(timeout, SftpOpts, infinity), +start_channel(Host, UserOptions) -> + start_channel(Host, 22, UserOptions). + +start_channel(Host, Port, UserOptions) -> + {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), + Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: + proplists:get_value(connect_timeout, SshOpts, + proplists:get_value(timeout, SftpOpts, infinity)), case ssh_xfer:connect(Host, Port, SshOpts, ChanOpts, Timeout) of {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, - ChannelId, SftpOpts]) of + case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,ChannelId,SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> @@ -865,6 +863,9 @@ terminate(_Reason, State) -> %%==================================================================== %% Internal functions %%==================================================================== +handle_options(UserOptions) -> + handle_options(UserOptions, [], [], []). + handle_options([], Sftp, Chan, Ssh) -> {Ssh, Chan, Sftp}; handle_options([{timeout, _} = Opt | Rest], Sftp, Chan, Ssh) -> diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index 637f5f398f..cf82db458f 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -26,6 +26,8 @@ -behaviour(supervisor). +-include("ssh.hrl"). + -export([start_link/1, connection_supervisor/1, channel_supervisor/1 @@ -37,8 +39,8 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Opts) -> - supervisor:start_link(?MODULE, [Opts]). +start_link(Options) -> + supervisor:start_link(?MODULE, [Options]). connection_supervisor(SupPid) -> Children = supervisor:which_children(SupPid), @@ -53,42 +55,42 @@ channel_supervisor(SupPid) -> %%%========================================================================= -spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . -init([Opts]) -> +init([Options]) -> RestartStrategy = one_for_all, MaxR = 0, MaxT = 3600, - Children = child_specs(Opts), + Children = child_specs(Options), {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(Opts) -> - case proplists:get_value(role, Opts) of +child_specs(Options) -> + case ?GET_INTERNAL_OPT(role, Options) of client -> []; server -> - [ssh_channel_child_spec(Opts), ssh_connectinon_child_spec(Opts)] + [ssh_channel_child_spec(Options), ssh_connectinon_child_spec(Options)] end. -ssh_connectinon_child_spec(Opts) -> - Address = proplists:get_value(address, Opts), - Port = proplists:get_value(port, Opts), - Role = proplists:get_value(role, Opts), +ssh_connectinon_child_spec(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Role = ?GET_INTERNAL_OPT(role, Options), Name = id(Role, ssh_connection_sup, Address, Port), - StartFunc = {ssh_connection_sup, start_link, [Opts]}, + StartFunc = {ssh_connection_sup, start_link, [Options]}, Restart = temporary, Shutdown = 5000, Modules = [ssh_connection_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_channel_child_spec(Opts) -> - Address = proplists:get_value(address, Opts), - Port = proplists:get_value(port, Opts), - Role = proplists:get_value(role, Opts), +ssh_channel_child_spec(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Role = ?GET_INTERNAL_OPT(role, Options), Name = id(Role, ssh_channel_sup, Address, Port), - StartFunc = {ssh_channel_sup, start_link, [Opts]}, + StartFunc = {ssh_channel_sup, start_link, [Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_channel_sup], diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index e97ac7b01a..b0bbd3aae5 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -45,12 +45,12 @@ %%%========================================================================= %%% Internal API %%%========================================================================= -start_link(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +start_link(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Profile = ?GET_OPT(profile, Options), Name = make_name(Address, Port, Profile), - supervisor:start_link({local, Name}, ?MODULE, [ServerOpts]). + supervisor:start_link({local, Name}, ?MODULE, [Options]). stop_listener(SysSup) -> stop_acceptor(SysSup). @@ -127,12 +127,12 @@ restart_acceptor(Address, Port, Profile) -> %%%========================================================================= -spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . -init([ServerOpts]) -> +init([Options]) -> RestartStrategy = one_for_one, MaxR = 0, MaxT = 3600, - Children = case proplists:get_value(asocket,ServerOpts) of - undefined -> child_specs(ServerOpts); + Children = case ?GET_INTERNAL_OPT(asocket,Options,undefined) of + undefined -> child_specs(Options); _ -> [] end, {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. @@ -140,24 +140,24 @@ init([ServerOpts]) -> %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(ServerOpts) -> - [ssh_acceptor_child_spec(ServerOpts)]. +child_specs(Options) -> + [ssh_acceptor_child_spec(Options)]. -ssh_acceptor_child_spec(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +ssh_acceptor_child_spec(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Profile = ?GET_OPT(profile, Options), Name = id(ssh_acceptor_sup, Address, Port, Profile), - StartFunc = {ssh_acceptor_sup, start_link, [ServerOpts]}, + StartFunc = {ssh_acceptor_sup, start_link, [Options]}, Restart = transient, Shutdown = infinity, Modules = [ssh_acceptor_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_subsystem_child_spec(ServerOpts) -> +ssh_subsystem_child_spec(Options) -> Name = make_ref(), - StartFunc = {ssh_subsystem_sup, start_link, [ServerOpts]}, + StartFunc = {ssh_subsystem_sup, start_link, [Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_subsystem_sup], diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index a17ad560d1..02c995399a 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -153,14 +153,14 @@ supported_algorithms(compression) -> %%%---------------------------------------------------------------------------- versions(client, Options)-> - Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION), + Vsn = ?GET_INTERNAL_OPT(vsn, Options, ?DEFAULT_CLIENT_VERSION), {Vsn, format_version(Vsn, software_version(Options))}; versions(server, Options) -> - Vsn = proplists:get_value(vsn, Options, ?DEFAULT_SERVER_VERSION), + Vsn = ?GET_INTERNAL_OPT(vsn, Options, ?DEFAULT_SERVER_VERSION), {Vsn, format_version(Vsn, software_version(Options))}. software_version(Options) -> - case proplists:get_value(id_string, Options) of + case ?GET_OPT(id_string, Options) of undefined -> "Erlang"++ssh_vsn(); {random,Nlo,Nup} -> @@ -171,7 +171,7 @@ software_version(Options) -> ssh_vsn() -> try {ok,L} = application:get_all_key(ssh), - proplists:get_value(vsn,L,"") + proplists:get_value(vsn, L, "") of "" -> ""; VSN when is_list(VSN) -> "/" ++ VSN; @@ -232,13 +232,7 @@ key_exchange_init_msg(Ssh0) -> kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) -> Random = ssh_bits:random(16), - PrefAlgs = - case proplists:get_value(preferred_algorithms,Opts) of - undefined -> - default_algorithms(); - Algs0 -> - Algs0 - end, + PrefAlgs = ?GET_OPT(preferred_algorithms, Opts), kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs). key_init(client, Ssh, Value) -> @@ -341,10 +335,7 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ; Kex == 'diffie-hellman-group-exchange-sha256' -> - {Min,NBits0,Max} = - proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN, - ?DEFAULT_DH_GROUP_NBITS, - ?DEFAULT_DH_GROUP_MAX}), + {Min,NBits0,Max} = ?GET_OPT(dh_gex_limits, Opts), DhBits = dh_bits(Ssh0#ssh.algorithms), NBits1 = %% NIST Special Publication 800-57 Part 1 Revision 4: Recommendation for Key Management @@ -458,7 +449,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, %% server {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, - proplists:get_value(dh_gex_groups,Opts)) of + ?GET_OPT(dh_gex_groups,Opts)) of {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), @@ -492,7 +483,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, Max0 = 8192, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, - proplists:get_value(dh_gex_groups,Opts)) of + ?GET_OPT(dh_gex_groups,Opts)) of {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), @@ -517,22 +508,18 @@ handle_kex_dh_gex_request(_, _) -> adjust_gex_min_max(Min0, Max0, Opts) -> - case proplists:get_value(dh_gex_limits, Opts) of - undefined -> - {Min0, Max0}; - {Min1, Max1} -> - Min2 = max(Min0, Min1), - Max2 = min(Max0, Max1), - if - Min2 =< Max2 -> - {Min2, Max2}; - Max2 < Min2 -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "No possible diffie-hellman-group-exchange group possible" - }) - end + {Min1, Max1} = ?GET_OPT(dh_gex_limits, Opts), + Min2 = max(Min0, Min1), + Max2 = min(Max0, Max1), + if + Min2 =< Max2 -> + {Min2, Max2}; + Max2 < Min2 -> + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "No possible diffie-hellman-group-exchange group possible" + }) end. @@ -719,9 +706,9 @@ sid(#ssh{session_id = Id}, _) -> %% The host key should be read from storage %% get_host_key(SSH) -> - #ssh{key_cb = Mod, opts = Opts, algorithms = ALG} = SSH, - - case Mod:host_key(ALG#alg.hkey, Opts) of + #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts, algorithms = ALG} = SSH, + UserOpts = ?GET_OPT(user_options, Opts), + case KeyCb:host_key(ALG#alg.hkey, [{key_cb_private,KeyCbOpts}|UserOpts]) of {ok, #'RSAPrivateKey'{} = Key} -> Key; {ok, #'DSAPrivateKey'{} = Key} -> Key; {ok, #'ECPrivateKey'{} = Key} -> Key; @@ -767,7 +754,7 @@ public_algo({#'ECPoint'{},{namedCurve,OID}}) -> accepted_host(Ssh, PeerName, Public, Opts) -> - case proplists:get_value(silently_accept_hosts, Opts, false) of + case ?GET_OPT(silently_accept_hosts, Opts) of F when is_function(F,2) -> true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public))); {DigestAlg,F} when is_function(F,2) -> @@ -778,15 +765,16 @@ accepted_host(Ssh, PeerName, Public, Opts) -> yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") end. -known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = {PeerName,_}} = Ssh, +known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}} = Ssh, Public, Alg) -> - case Mod:is_host_key(Public, PeerName, Alg, Opts) of + UserOpts = ?GET_OPT(user_options, Opts), + case KeyCb:is_host_key(Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of true -> ok; false -> case accepted_host(Ssh, PeerName, Public, Opts) of true -> - Mod:add_host_key(PeerName, Public, Opts); + KeyCb:add_host_key(PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]); false -> {error, rejected} end @@ -1822,10 +1810,6 @@ len_supported(Name, Len) -> same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. - -%% default_algorithms(kex) -> % Example of how to disable an algorithm -%% supported_algorithms(kex, ['ecdh-sha2-nistp521']); - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% Other utils diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 04d2df30f7..14f1937abd 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -41,13 +41,13 @@ start_link(Servers) -> supervisor:start_link({local, ?MODULE}, ?MODULE, [Servers]). -start_child(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +start_child(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Profile = ?GET_OPT(profile, Options), case ssh_system_sup:system_supervisor(Address, Port, Profile) of undefined -> - Spec = child_spec(Address, Port, ServerOpts), + Spec = child_spec(Address, Port, Options), case supervisor:start_child(?MODULE, Spec) of {error, already_present} -> Name = id(Address, Port, Profile), @@ -58,7 +58,7 @@ start_child(ServerOpts) -> end; Pid -> AccPid = ssh_system_sup:acceptor_supervisor(Pid), - ssh_acceptor_sup:start_child(AccPid, ServerOpts) + ssh_acceptor_sup:start_child(AccPid, Options) end. stop_child(Name) -> @@ -82,8 +82,8 @@ init([Servers]) -> MaxR = 10, MaxT = 3600, Fun = fun(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), + Address = ?GET_INTERNAL_OPT(address, ServerOpts), + Port = ?GET_INTERNAL_OPT(port, ServerOpts), child_spec(Address, Port, ServerOpts) end, Children = lists:map(Fun, Servers), @@ -92,10 +92,10 @@ init([Servers]) -> %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(Address, Port, ServerOpts) -> - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +child_spec(Address, Port, Options) -> + Profile = ?GET_OPT(profile, Options), Name = id(Address, Port,Profile), - StartFunc = {ssh_system_sup, start_link, [ServerOpts]}, + StartFunc = {ssh_system_sup, start_link, [Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_system_sup], -- cgit v1.2.3 From 7e2ceb5d44dc5004ea4d8271ee1e961bfa4987fd Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 23 Feb 2017 15:57:12 +0100 Subject: ssh: Types and spec fixes to conform to the ref manual --- lib/ssh/src/ssh.erl | 34 ++++++++++++++++++++++++---------- lib/ssh/src/ssh.hrl | 18 ++++++++++++++++++ lib/ssh/src/ssh_connect.hrl | 4 ++-- lib/ssh/src/ssh_connection.erl | 30 +++++++++++++++--------------- lib/ssh/src/ssh_options.erl | 2 -- 5 files changed, 59 insertions(+), 29 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 0186ac7922..53aba14458 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -40,11 +40,24 @@ ]). %%% Type exports --export_type([connection_ref/0, - channel_id/0, - role/0 +-export_type([ssh_daemon_ref/0, + ssh_connection_ref/0, + ssh_channel_id/0, + role/0, + subsystem_spec/0, + subsystem_name/0, + channel_callback/0, + channel_init_args/0, + algs_list/0, + alg_entry/0, + simple_algs/0, + double_algs/0 ]). +-opaque ssh_daemon_ref() :: daemon_ref() . +-opaque ssh_connection_ref() :: connection_ref() . +-opaque ssh_channel_id() :: channel_id(). + %%-------------------------------------------------------------------- -spec start() -> ok | {error, term()}. -spec start(permanent | transient | temporary) -> ok | {error, term()}. @@ -157,10 +170,10 @@ channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- --spec daemon(inet:port_number()) -> ok_error(pid()). --spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(pid()). --spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(pid()) - ;(socket, inet:socket(), proplists:proplist()) -> ok_error(pid()) +-spec daemon(inet:port_number()) -> ok_error(daemon_ref()). +-spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(daemon_ref()). +-spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(daemon_ref()) + ;(socket, inet:socket(), proplists:proplist()) -> ok_error(daemon_ref()) . %% Description: Starts a server listening for SSH connections @@ -182,7 +195,7 @@ daemon(Host0, Port, UserOptions0) -> start_daemon(Host, Port, ssh_options:handle_options(server, UserOptions)). %%-------------------------------------------------------------------- --spec daemon_info(pid()) -> ok_error( [{atom(), term()}] ). +-spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ). daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of @@ -197,7 +210,7 @@ daemon_info(Pid) -> end. %%-------------------------------------------------------------------- --spec stop_listener(pid()) -> ok. +-spec stop_listener(daemon_ref()) -> ok. -spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. %% %% Description: Stops the listener, but leaves @@ -211,7 +224,7 @@ stop_listener(Address, Port, Profile) -> ssh_system_sup:stop_listener(Address, Port, Profile). %%-------------------------------------------------------------------- --spec stop_daemon(pid()) -> ok. +-spec stop_daemon(daemon_ref()) -> ok. -spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok. -spec stop_daemon(inet:ip_address(), inet:port_number(), atom()) -> ok. %% @@ -269,6 +282,7 @@ start_shell(Error) -> Error. %%-------------------------------------------------------------------- +-spec default_algorithms() -> algs_list() . %%-------------------------------------------------------------------- default_algorithms() -> ssh_transport:default_algorithms(). diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 475534f572..c1ba58ed40 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -90,7 +90,25 @@ -define(PUT_SOCKET_OPT(KeyVal,Opts), ?do_put_opt(socket_options, KeyVal,Opts) ). %% Types +-type role() :: client | server . -type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} . +-type daemon_ref() :: pid() . + +-type subsystem_spec() :: {subsystem_name(), {channel_callback(), channel_init_args()}} . +-type subsystem_name() :: string() . +-type channel_callback() :: atom() . +-type channel_init_args() :: list() . + +-type algs_list() :: list( alg_entry() ). +-type alg_entry() :: {kex, simple_algs()} + | {public_key, simple_algs()} + | {cipher, double_algs()} + | {mac, double_algs()} + | {compression, double_algs()} . +-type simple_algs() :: list( atom() ) . +-type double_algs() :: list( {client2serverlist,simple_algs()} | {server2client,simple_algs()} ) + | simple_algs() . + %% Records -record(ssh, diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 4fb6bc39f3..c91c56435e 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -22,9 +22,9 @@ %%% Description : SSH connection protocol --type role() :: client | server . --type connection_ref() :: pid(). -type channel_id() :: pos_integer(). +-type connection_ref() :: pid(). + -define(DEFAULT_PACKET_SIZE, 65536). -define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE). diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 6a48ed581c..930ccecb4c 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -56,8 +56,8 @@ %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec session_channel(pid(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. --spec session_channel(pid(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. +-spec session_channel(connection_ref(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. +-spec session_channel(connection_ref(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. %% Description: Opens a channel for a ssh session. A session is a %% remote execution of a program. The program may be a shell, an @@ -81,7 +81,7 @@ session_channel(ConnectionHandler, InitialWindowSize, end. %%-------------------------------------------------------------------- --spec exec(pid(), channel_id(), string(), timeout()) -> +-spec exec(connection_ref(), channel_id(), string(), timeout()) -> success | failure | {error, timeout | closed}. %% Description: Will request that the server start the @@ -92,7 +92,7 @@ exec(ConnectionHandler, ChannelId, Command, TimeOut) -> true, [?string(Command)], TimeOut). %%-------------------------------------------------------------------- --spec shell(pid(), channel_id()) -> _. +-spec shell(connection_ref(), channel_id()) -> _. %% Description: Will request that the user's default shell (typically %% defined in /etc/passwd in UNIX systems) be started at the other @@ -102,7 +102,7 @@ shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "shell", false, <<>>, 0). %%-------------------------------------------------------------------- --spec subsystem(pid(), channel_id(), string(), timeout()) -> +-spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> success | failure | {error, timeout | closed}. %% %% Description: Executes a predefined subsystem. @@ -112,11 +112,11 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ChannelId, "subsystem", true, [?string(SubSystem)], TimeOut). %%-------------------------------------------------------------------- --spec send(pid(), channel_id(), iodata()) -> +-spec send(connection_ref(), channel_id(), iodata()) -> ok | {error, closed}. --spec send(pid(), channel_id(), integer()| iodata(), timeout() | iodata()) -> +-spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> ok | {error, timeout} | {error, closed}. --spec send(pid(), channel_id(), integer(), iodata(), timeout()) -> +-spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> ok | {error, timeout} | {error, closed}. %% %% @@ -134,7 +134,7 @@ send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). %%-------------------------------------------------------------------- --spec send_eof(pid(), channel_id()) -> ok | {error, closed}. +-spec send_eof(connection_ref(), channel_id()) -> ok | {error, closed}. %% %% %% Description: Sends eof on the channel . @@ -143,7 +143,7 @@ send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- --spec adjust_window(pid(), channel_id(), integer()) -> ok | {error, closed}. +-spec adjust_window(connection_ref(), channel_id(), integer()) -> ok | {error, closed}. %% %% %% Description: Adjusts the ssh flowcontrol window. @@ -152,7 +152,7 @@ adjust_window(ConnectionHandler, Channel, Bytes) -> ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes). %%-------------------------------------------------------------------- --spec setenv(pid(), channel_id(), string(), string(), timeout()) -> +-spec setenv(connection_ref(), channel_id(), string(), string(), timeout()) -> success | failure | {error, timeout | closed}. %% %% @@ -165,7 +165,7 @@ setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) -> %%-------------------------------------------------------------------- --spec close(pid(), channel_id()) -> ok. +-spec close(connection_ref(), channel_id()) -> ok. %% %% %% Description: Sends a close message on the channel . @@ -174,7 +174,7 @@ close(ConnectionHandler, ChannelId) -> ssh_connection_handler:close(ConnectionHandler, ChannelId). %%-------------------------------------------------------------------- --spec reply_request(pid(), boolean(), success | failure, channel_id()) -> ok. +-spec reply_request(connection_ref(), boolean(), success | failure, channel_id()) -> ok. %% %% %% Description: Send status replies to requests that want such replies. @@ -185,9 +185,9 @@ reply_request(_,false, _, _) -> ok. %%-------------------------------------------------------------------- --spec ptty_alloc(pid(), channel_id(), proplists:proplist()) -> +-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> success | failiure | {error, closed}. --spec ptty_alloc(pid(), channel_id(), proplists:proplist(), timeout()) -> +-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> success | failiure | {error, timeout} | {error, closed}. %% diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 52dea5d183..395be6b220 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -58,8 +58,6 @@ -type option_declarations() :: #{ {option_key(),def} := option_declaration() }. --type role() :: client | server . - -type error() :: {error,{eoptions,any()}} . %%%================================================================ -- cgit v1.2.3 From 931df53bc431c47140620864b04d4622f9e41421 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 23 Feb 2017 17:53:46 +0100 Subject: ssh: Simplify calling of user's callback funs Since the Options now are initialized with a correct fun (that does nothing), we can just call it without tests --- lib/ssh/src/ssh_connection_handler.erl | 45 +++++++++------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 706b68d78b..b9c643c77e 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1769,47 +1769,24 @@ get_repl(X, Acc) -> exit({get_repl,X,Acc}). %%%---------------------------------------------------------------- -disconnect_fun({disconnect,Msg}, D) -> - disconnect_fun(Msg, D); -disconnect_fun(Reason, #data{opts=Opts}) -> - case ?GET_OPT(disconnectfun, Opts) of - undefined -> - ok; - Fun -> - catch Fun(Reason) - end. - -unexpected_fun(UnexpectedMessage, #data{opts = Opts, - ssh_params = #ssh{peer = {_,Peer} } - } ) -> - case ?GET_OPT(unexpectedfun, Opts) of - undefined -> - report; - Fun -> - catch Fun(UnexpectedMessage, Peer) - end. +-define(CALL_FUN(Key,D), catch (?GET_OPT(Key, D#data.opts)) ). + +disconnect_fun({disconnect,Msg}, D) -> ?CALL_FUN(disconnectfun,D)(Msg); +disconnect_fun(Reason, D) -> ?CALL_FUN(disconnectfun,D)(Reason). +unexpected_fun(UnexpectedMessage, #data{ssh_params = #ssh{peer = {_,Peer} }} = D) -> + ?CALL_FUN(unexpectedfun,D)(UnexpectedMessage, Peer). debug_fun(#ssh_msg_debug{always_display = Display, message = DbgMsg, language = Lang}, - #data{opts = Opts}) -> - case ?GET_OPT(ssh_msg_debug_fun, Opts) of - undefined -> - ok; - Fun -> - catch Fun(self(), Display, DbgMsg, Lang) - end. + D) -> + ?CALL_FUN(ssh_msg_debug_fun,D)(self(), Display, DbgMsg, Lang). -connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}, - opts = Opts}) -> - case ?GET_OPT(connectfun, Opts) of - undefined -> - ok; - Fun -> - catch Fun(User, Peer, Method) - end. +connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}} = D) -> + ?CALL_FUN(connectfun,D)(User, Peer, Method). + retry_fun(_, undefined, _) -> ok; -- cgit v1.2.3 From 6fb1523e86bc3561f3fd397b3b9cbad5e3ca90a0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 8 Mar 2017 16:45:08 +0100 Subject: ssh: Update .app file --- lib/ssh/src/ssh.app.src | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 2bb7491b0c..7859ab4064 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -41,10 +41,10 @@ {env, []}, {mod, {ssh_app, []}}, {runtime_dependencies, [ - "crypto-3.3", + "crypto-3.7.3", "erts-6.0", "kernel-3.0", - "public_key-1.1", - "stdlib-3.1" + "public_key-1.4", + "stdlib-3.3" ]}]}. -- cgit v1.2.3 From 1af346afd2d8cabc48bf9673dc62672e36b7b8a2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 6 Mar 2017 16:24:03 +0100 Subject: ssh: Host and Profile info returned in ssh:daemon_info --- lib/ssh/src/ssh.erl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 53aba14458..e2a289d737 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -200,11 +200,13 @@ daemon(Host0, Port, UserOptions0) -> daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> - [Port] = - [Prt || {{ssh_acceptor_sup,any,Prt,default}, + [{ListenAddr,Port,Profile}] = + [{LA,Prt,Prf} || {{ssh_acceptor_sup,LA,Prt,Prf}, _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)], - {ok, [{port,Port}]}; - + {ok, [{port,Port}, + {listen_address,ListenAddr}, + {profile,Profile} + ]}; _ -> {error,bad_daemon_ref} end. -- cgit v1.2.3 From 26c3cd82529836cb5b6eefbf7f92f318fd91f847 Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Fri, 10 Mar 2017 15:00:46 +0100 Subject: Update copyright year --- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh_cli.erl | 2 +- lib/ssh/src/ssh_connection_handler.erl | 2 +- lib/ssh/src/ssh_dbg.erl | 2 +- lib/ssh/src/ssh_sftpd.erl | 2 +- lib/ssh/src/ssh_sftpd_file_api.erl | 2 +- lib/ssh/src/ssh_transport.erl | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 68d98d3875..1f3f77a4e4 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -1,7 +1,7 @@ % %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 6f8c050486..7c7b9e7922 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4496c657c3..fc75945a5b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 0345bbdea7..251741da7e 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 9352046795..fb680fe11c 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl index e444e52ac0..81f181f1fc 100644 --- a/lib/ssh/src/ssh_sftpd_file_api.erl +++ b/lib/ssh/src/ssh_sftpd_file_api.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index a7cc4cd52c..9bebaf2d9b 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From a4edbd619c624d2ca2f343da19a88fba74470e93 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Mar 2017 13:23:42 +0100 Subject: ssh: remove from code --- lib/ssh/src/ssh_options.erl | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 395be6b220..a882a01eaf 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -200,17 +200,6 @@ save({K,V}, _, _) when K == reuseaddr ; save({allow_user_interaction,V}, Opts, Vals) -> save({user_interaction,V}, Opts, Vals); -save({public_key_alg,V}, Defs, Vals) -> % To remove in OTP-20 - New = case V of - 'ssh-rsa' -> ['ssh-rsa', 'ssh-dss']; - ssh_rsa -> ['ssh-rsa', 'ssh-dss']; - 'ssh-dss' -> ['ssh-dss', 'ssh-rsa']; - ssh_dsa -> ['ssh-dss', 'ssh-rsa']; - _ -> error({eoptions, {public_key_alg,V}, - "Unknown algorithm, try pref_public_key_algs instead"}) - end, - save({pref_public_key_algs,New}, Defs, Vals); - %% Special case for socket options 'inet' and 'inet6' save(Inet, Defs, OptMap) when Inet==inet ; Inet==inet6 -> save({inet,Inet}, Defs, OptMap); -- cgit v1.2.3 From 7f5d5119d59e1741aac6b622880dbc2f08b394de Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 24 Mar 2017 16:02:15 +0100 Subject: ssh: fixed crash in ssh:daemon_info --- lib/ssh/src/ssh.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 1f3f77a4e4..290525cec0 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -175,7 +175,7 @@ daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> [Port] = - [Prt || {{ssh_acceptor_sup,any,Prt,default}, + [Prt || {{ssh_acceptor_sup,_,Prt,_}, _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)], {ok, [{port,Port}]}; -- cgit v1.2.3 From 1cc5affef1dee69e5cb4b7d4aca0465e1072d4d9 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 30 Mar 2017 17:17:34 +0200 Subject: ssh: idle_timer on daemon - implementation --- lib/ssh/src/ssh_connection_handler.erl | 8 +++++++- lib/ssh/src/ssh_options.erl | 12 ++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index b9c643c77e..15a05a1b85 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -400,7 +400,9 @@ init_process_state(Role, Socket, Opts) -> timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), cache_init_idle_timer(D); server -> - D#data{connection_state = init_connection(Role, C, Opts)} + cache_init_idle_timer( + D#data{connection_state = init_connection(Role, C, Opts)} + ) end. @@ -919,6 +921,9 @@ handle_event(internal, Msg=#ssh_msg_channel_extended_data{}, StateName, D) - handle_event(internal, Msg=#ssh_msg_channel_eof{}, StateName, D) -> handle_connection_msg(Msg, StateName, D); +handle_event(internal, Msg=#ssh_msg_channel_close{}, {connected,server} = StateName, D) -> + handle_connection_msg(Msg, StateName, cache_request_idle_timer_check(D)); + handle_event(internal, Msg=#ssh_msg_channel_close{}, StateName, D) -> handle_connection_msg(Msg, StateName, D); @@ -1280,6 +1285,7 @@ handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> {stop, {shutdown, Reason}}; handle_event(info, check_cache, _, D) -> +ct:pal("check_cache",[]), {keep_state, cache_check_set_idle_timer(D)}; handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index a882a01eaf..55f9c6bdc8 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -490,12 +490,6 @@ default(client) -> class => user_options }, - {idle_time, def} => - #{default => infinity, - chk => fun check_timeout/1, - class => user_options - }, - %%%%% Undocumented {keyboard_interact_fun, def} => #{default => undefined, @@ -548,6 +542,12 @@ default(common) -> class => user_options }, + {idle_time, def} => + #{default => infinity, + chk => fun check_timeout/1, + class => user_options + }, + %% This is a "SocketOption"... %% {fd, def} => %% #{default => undefined, -- cgit v1.2.3 From 9d174dd62112ec5129b66ffafc1d99650f037d54 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 6 Apr 2017 15:50:38 +0200 Subject: ssh: remove log printout --- lib/ssh/src/ssh_connection_handler.erl | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 15a05a1b85..5a13209ae3 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1285,7 +1285,6 @@ handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> {stop, {shutdown, Reason}}; handle_event(info, check_cache, _, D) -> -ct:pal("check_cache",[]), {keep_state, cache_check_set_idle_timer(D)}; handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> -- cgit v1.2.3 From 7b21b7b5fa0b6da173080bc8322e99eead905191 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 10 Mar 2017 12:56:33 +0100 Subject: ssh: Bug fix when calling ssh_io:yes_no This was introduced by the new option handling in commit 89a829f32d855610b0bc0c3ea53e7c05454b7a24 --- lib/ssh/src/ssh_transport.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 02c995399a..5d896e02a2 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -201,7 +201,7 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm, Mac == mac(Algorithm, Key, SeqNum, Data). yes_no(Ssh, Prompt) -> - (Ssh#ssh.io_cb):yes_no(Prompt, Ssh). + (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts). format_version({Major,Minor}, SoftwareVersion) -> "SSH-" ++ integer_to_list(Major) ++ "." ++ -- cgit v1.2.3 From 7925b59450dd6f34b756da7b10dd10af95304d94 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 28 Feb 2017 12:19:37 +0100 Subject: ssh: Option pruning --- lib/ssh/src/ssh.erl | 3 +-- lib/ssh/src/ssh_acceptor.erl | 1 - lib/ssh/src/ssh_system_sup.erl | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 369a00ac40..c1be9f732d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -368,8 +368,7 @@ do_start_daemon(Socket, Options) -> _:_ -> throw(bad_socket) end, Host = fmt_host(IP), - Opts = ?PUT_INTERNAL_OPT([{asocket, Socket}, - {asock_owner,self()}, + Opts = ?PUT_INTERNAL_OPT([{connected_socket, Socket}, {address, Host}, {port, Port}, {role, server}], Options), diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 42be18f2ad..4943f062b4 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -66,7 +66,6 @@ acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) -> of {ok, ListenSocket} -> proc_lib:init_ack(Parent, {ok, self()}), - {_, Callback, _} = ?GET_OPT(transport, Opts), acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout); {error,Error} -> diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index b0bbd3aae5..5a58ef1c44 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -131,7 +131,7 @@ init([Options]) -> RestartStrategy = one_for_one, MaxR = 0, MaxT = 3600, - Children = case ?GET_INTERNAL_OPT(asocket,Options,undefined) of + Children = case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of undefined -> child_specs(Options); _ -> [] end, -- cgit v1.2.3 From ccef9f6e379a2cee828a9b914a49a4ebc831f936 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 2 Mar 2017 20:02:50 +0100 Subject: ssh: Make an internal option delete function --- lib/ssh/src/ssh.hrl | 4 ++++ lib/ssh/src/ssh_options.erl | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index c1ba58ed40..63eeb0bd0a 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -89,6 +89,10 @@ -define(PUT_INTERNAL_OPT(KeyVal,Opts), ?do_put_opt(internal_options,KeyVal,Opts) ). -define(PUT_SOCKET_OPT(KeyVal,Opts), ?do_put_opt(socket_options, KeyVal,Opts) ). +-define(do_del_opt(C,K,O), ssh_options:delete_key(C,K,O, ?MODULE,?LINE)). +-define(DELETE_INTERNAL_OPT(Key,Opts), ?do_del_opt(internal_options,Key,Opts) ). + + %% Types -type role() :: client | server . -type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} . diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 55f9c6bdc8..512aefa76d 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -28,6 +28,7 @@ -export([default/1, get_value/5, get_value/6, put_value/5, + delete_key/5, handle_options/2 ]). @@ -75,7 +76,6 @@ get_value(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> user_options -> maps:get(Key, Opts) end; get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> - io:format("*** Bad Opts GET OPT ~p ~p:~p Key=~p,~n Opts=~p~n",[Class,_CallerMod,_CallerLine,Key,Opts]), error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). @@ -90,7 +90,6 @@ get_value(Class, Key, Opts, Def, CallerMod, CallerLine) when is_map(Opts) -> error:{badkey,Key} -> Def end; get_value(Class, Key, Opts, _Def, _CallerMod, _CallerLine) -> - io:format("*** Bad Opts GET OPT ~p ~p:~p Key=~p,~n Opts=~p~n",[Class,_CallerMod,_CallerLine,Key,Opts]), error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). @@ -134,6 +133,19 @@ put_socket_value({Key,Value}, SockOpts) -> put_socket_value(A, SockOpts) when is_atom(A) -> [A | SockOpts]. +%%%================================================================ +%%% +%%% Delete an option +%%% + +-spec delete_key(option_class(), option_key(), options(), + atom(), non_neg_integer()) -> options(). + +delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + InternalOpts = maps:get(internal_options,Opts), + Opts#{internal_options := maps:remove(Key, InternalOpts)}. + + %%%================================================================ %%% %%% Initialize the options -- cgit v1.2.3 From 2f212c1a3e8bc3070b51dfc5607f30e501ba24ea Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 10 Mar 2017 13:08:08 +0100 Subject: ssh: option 'silently_accept_hosts' reworked New (yet) undocumented option value {false,Alg} where Alg :: md5 | sha | sha224 | sha256 | sha384 | sha512 This option includes the fingerprint value in the accept question to the user. The fingerprint is calculated with the Alg provided --- lib/ssh/src/ssh_options.erl | 21 ++++++++++----------- lib/ssh/src/ssh_transport.erl | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 20 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 512aefa76d..6a2e7ce696 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -431,7 +431,7 @@ default(client) -> }, {silently_accept_hosts, def} => - #{default => false, + #{default => {false,none}, chk => fun check_silently_accept_hosts/1, class => user_options }, @@ -804,18 +804,17 @@ read_moduli_file(D, I, Acc) -> check_silently_accept_hosts(B) when is_boolean(B) -> true; check_silently_accept_hosts(F) when is_function(F,2) -> true; -check_silently_accept_hosts({S,F}) when is_atom(S), - is_function(F,2) -> - lists:member(S, ?SHAs) andalso - lists:member(S, proplists:get_value(hashs,crypto:supports())); -check_silently_accept_hosts({L,F}) when is_list(L), - is_function(F,2) -> - lists:all(fun(S) -> - lists:member(S, ?SHAs) andalso - lists:member(S, proplists:get_value(hashs,crypto:supports())) - end, L); +check_silently_accept_hosts({false,S}) when is_atom(S) -> valid_hash(S); +check_silently_accept_hosts({S,F}) when is_function(F,2) -> valid_hash(S); check_silently_accept_hosts(_) -> false. + +valid_hash(S) -> valid_hash(S, proplists:get_value(hashs,crypto:supports())). + +valid_hash(S, Ss) when is_atom(S) -> lists:member(S, ?SHAs) andalso lists:member(S, Ss); +valid_hash(L, Ss) when is_list(L) -> lists:all(fun(S) -> valid_hash(S,Ss) end, L); +valid_hash(X, _) -> error_in_check(X, "Expect atom or list in fingerprint spec"). + %%%---------------------------------------------------------------- check_preferred_algorithms(Algs) -> try alg_duplicates(Algs, [], []) diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 5d896e02a2..54ea80c727 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -200,9 +200,6 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm, recv_mac_key = Key, recv_sequence = SeqNum}) -> Mac == mac(Algorithm, Key, SeqNum, Data). -yes_no(Ssh, Prompt) -> - (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts). - format_version({Major,Minor}, SoftwareVersion) -> "SSH-" ++ integer_to_list(Major) ++ "." ++ integer_to_list(Minor) ++ "-" ++ SoftwareVersion. @@ -755,16 +752,44 @@ public_algo({#'ECPoint'{},{namedCurve,OID}}) -> accepted_host(Ssh, PeerName, Public, Opts) -> case ?GET_OPT(silently_accept_hosts, Opts) of - F when is_function(F,2) -> + + %% Original option values; User question and no host key fingerprints known. + %% Keep the original question unchanged: + false -> yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept"); + true -> true; + + %% Variant: User question but with host key fingerprint in the question: + {false,Alg} -> + HostKeyAlg = (Ssh#ssh.algorithms)#alg.hkey, + Prompt = io_lib:format("The authenticity of the host can't be established.~n" + "~s host key fingerprint is ~s.~n" + "New host ~p accept", + [fmt_hostkey(HostKeyAlg), + public_key:ssh_hostkey_fingerprint(Alg,Public), + PeerName]), + yes == yes_no(Ssh, Prompt); + + %% Call-back alternatives: A user provided fun is called for the decision: + F when is_function(F,2) -> true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public))); + {DigestAlg,F} when is_function(F,2) -> - true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))); - true -> - true; - false -> - yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") + true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))) + end. + +yes_no(Ssh, Prompt) -> + (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts). + + +fmt_hostkey('ssh-rsa') -> "RSA"; +fmt_hostkey('ssh-dss') -> "DSA"; +fmt_hostkey(A) when is_atom(A) -> fmt_hostkey(atom_to_list(A)); +fmt_hostkey("ecdsa"++_) -> "ECDSA"; +fmt_hostkey(X) -> X. + + known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}} = Ssh, Public, Alg) -> UserOpts = ?GET_OPT(user_options, Opts), -- cgit v1.2.3 From 6ff33b1548a24d9f195c27a1ee5bcfcdb1b892d8 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 16 Mar 2017 14:42:30 +0100 Subject: ssh: enable 'none' as a secret accepted value in negotiation This is for testing only to disable e.g. encryption/decryption is measurements. The value must be explicitly enabled like {preferred_algorithms,[{cipher,[none]}]} --- lib/ssh/src/ssh_options.erl | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6a2e7ce696..cb3f63103c 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -882,6 +882,7 @@ handle_pref_alg(Key, Vs, _) -> chk_alg_vs(OptKey, Values, SupportedValues) -> case (Values -- SupportedValues) of [] -> Values; + [none] -> [none]; % for testing only Bad -> error_in_check({OptKey,Bad}, "Unsupported value(s) found") end. -- cgit v1.2.3 From da7902412f1e77b8241c0bacbeac2d6013e8f345 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 2 Mar 2017 16:37:54 +0100 Subject: ssh: Unified way of starting listening sockets --- lib/ssh/src/ssh.erl | 215 ++++++++++++++++++------------------------- lib/ssh/src/ssh_acceptor.erl | 106 ++++++++++----------- 2 files changed, 136 insertions(+), 185 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index c1be9f732d..c139556791 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -132,13 +132,12 @@ connect(Host, Port, UserOptions, Timeout) when is_integer(Port), SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)], try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> - Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error, Reason} -> {error, Reason} catch exit:{function_clause, _F} -> - io:format('function_clause ~p~n',[_F]), {error, {options, {transport, TransportOpts}}}; exit:badarg -> {error, {options, {socket_options, SocketOpts}}} @@ -311,16 +310,15 @@ handle_daemon_args(Host, UserOptions0) -> %%%---------------------------------------------------------------- valid_socket_to_use(Socket, {tcp,_,_}) -> %% Is this tcp-socket a valid socket? - case {is_tcp_socket(Socket), - {ok,[{active,false}]} == inet:getopts(Socket, [active]) - } + try {is_tcp_socket(Socket), + {ok,[{active,false}]} == inet:getopts(Socket, [active]) + } of - {true, true} -> - ok; - {true, false} -> - {error, not_passive_mode}; - _ -> - {error, not_tcp_socket} + {true, true} -> ok; + {true, false} -> {error, not_passive_mode}; + _ -> {error, not_tcp_socket} + catch + _:_ -> {error, bad_socket} end; valid_socket_to_use(_, {L4,_,_}) -> @@ -340,13 +338,7 @@ start_daemon(_, _, {error,Error}) -> start_daemon(socket, Socket, Options) -> case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> - try - do_start_daemon(Socket, Options) - catch - throw:bad_fd -> {error,bad_fd}; - throw:bad_socket -> {error,bad_socket}; - _C:_E -> {error,{cannot_start_daemon,_C,_E}} - end; + start_daemon(inet:sockname(Socket), Socket, Options); {error,SockError} -> {error,SockError} end; @@ -355,136 +347,107 @@ start_daemon(Host, Port, Options) -> try do_start_daemon(Host, Port, Options) catch - throw:bad_fd -> {error,bad_fd}; - throw:bad_socket -> {error,bad_socket}; - _C:_E -> {error,{cannot_start_daemon,_C,_E}} + throw:bad_fd -> + {error,bad_fd}; + throw:bad_socket -> + {error,bad_socket}; + error:{badmatch,{error,Error}} -> + {error,Error}; + _C:_E -> + {error,{cannot_start_daemon,_C,_E}} end. +%%%---------------------------------------------------------------- +do_start_daemon({error,Error}, _, _) -> + {error,Error}; -do_start_daemon(Socket, Options) -> - {ok, {IP,Port}} = - try {ok,_} = inet:sockname(Socket) - catch - _:_ -> throw(bad_socket) - end, - Host = fmt_host(IP), - Opts = ?PUT_INTERNAL_OPT([{connected_socket, Socket}, - {address, Host}, - {port, Port}, - {role, server}], Options), - - Profile = ?GET_OPT(profile, Options), - case ssh_system_sup:system_supervisor(Host, Port, Profile) of - undefined -> - try sshd_sup:start_child(Opts) of - {error, {already_started, _}} -> - {error, eaddrinuse}; - Result = {ok,_} -> - call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, Result); - Result = {error, _} -> - Result - catch - exit:{noproc, _} -> - {error, ssh_not_started} - end; - Sup -> - AccPid = ssh_system_sup:acceptor_supervisor(Sup), - case ssh_acceptor_sup:start_child(AccPid, Opts) of - {error, {already_started, _}} -> - {error, eaddrinuse}; - {ok, _} -> - call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, {ok,Sup}); - Other -> - Other - end - end. +do_start_daemon({ok, {IP,Port}}, Socket, Options0) -> + finalize_start(fmt_host(IP), + Port, + ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options0), + fun(Opts, DefaultResult) -> + try ssh_acceptor:handle_established_connection( + ?GET_INTERNAL_OPT(address, Opts), + ?GET_INTERNAL_OPT(port, Opts), + Opts, + Socket) + of + {error,Error} -> + {error,Error}; + _ -> + DefaultResult + catch + C:R -> + {error,{could_not_start_connection,{C,R}}} + end + end); do_start_daemon(Host0, Port0, Options0) -> - {Host,Port1} = - try - case ?GET_SOCKET_OPT(fd, Options0) of - undefined -> - {Host0,Port0}; - Fd when Port0==0 -> - find_hostport(Fd) - end - catch - _:_ -> throw(bad_fd) - end, - {Port, WaitRequestControl, Options1} = - case Port1 of - 0 -> %% Allocate the socket here to get the port number... - {ok,LSock} = ssh_acceptor:callback_listen(0, Options0), - {ok,{_,LPort}} = inet:sockname(LSock), - {LPort, - LSock, - ?PUT_INTERNAL_OPT({lsocket,{LSock,self()}}, Options0) - }; - _ -> - {Port1, false, Options0} - end, + {{Host,Port}, ListenSocket} = + open_listen_socket(Host0, Port0, Options0), + + %% Now Host,Port is what to use for the supervisor to register its name, + %% and ListenSocket is for listening on connections. But it is still owned + %% by self()... + + finalize_start(Host, Port, + ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), + fun(Opts, Result) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + receive + {request_control, ListenSocket, ReqPid} -> + ok = Callback:controlling_process(ListenSocket, ReqPid), + ReqPid ! {its_yours,ListenSocket}, + Result + end + end). + + +open_listen_socket(Host0, Port0, Options0) -> + case ?GET_SOCKET_OPT(fd, Options0) of + undefined -> + {ok,LSock} = ssh_acceptor:listen(Port0, Options0), + {ok,{_,LPort}} = inet:sockname(LSock), + {{fmt_host(Host0),LPort}, LSock}; + + Fd when is_integer(Fd) -> + %% Do gen_tcp:listen with the option {fd,Fd}: + {ok,LSock} = ssh_acceptor:listen(0, Options0), + {ok,{LHost,LPort}} = inet:sockname(LSock), + {{fmt_host(LHost),LPort}, LSock} + end. + +%%%---------------------------------------------------------------- +finalize_start(Host, Port, Options0, F) -> Options = ?PUT_INTERNAL_OPT([{address, Host}, {port, Port}, - {role, server}], Options1), - Profile = ?GET_OPT(profile, Options0), + {role, server}], Options0), + Profile = ?GET_OPT(profile, Options), case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> try sshd_sup:start_child(Options) of {error, {already_started, _}} -> {error, eaddrinuse}; + {error, Error} -> + {error, Error}; Result = {ok,_} -> - sync_request_control(WaitRequestControl, Options), - Result; - Result = {error, _} -> - Result + F(Options, Result) catch exit:{noproc, _} -> {error, ssh_not_started} end; - Sup -> + Sup -> AccPid = ssh_system_sup:acceptor_supervisor(Sup), case ssh_acceptor_sup:start_child(AccPid, Options) of {error, {already_started, _}} -> {error, eaddrinuse}; + {error, Error} -> + {error, Error}; {ok, _} -> - sync_request_control(WaitRequestControl, Options), - {ok, Sup}; - Other -> - Other + F(Options, {ok,Sup}) end end. -call_ssh_acceptor_handle_connection(Host, Port, Options, Socket, DefaultResult) -> - {_, Callback, _} = ?GET_OPT(transport, Options), - try ssh_acceptor:handle_connection(Callback, Host, Port, Options, Socket) - of - {error,Error} -> {error,Error}; - _ -> DefaultResult - catch - C:R -> {error,{could_not_start_connection,{C,R}}} - end. - - -sync_request_control(false, _Options) -> - ok; -sync_request_control(LSock, Options) -> - {_, Callback, _} = ?GET_OPT(transport, Options), - receive - {request_control,LSock,ReqPid} -> - ok = Callback:controlling_process(LSock, ReqPid), - ReqPid ! {its_yours,LSock}, - ok - end. - -find_hostport(Fd) -> - %% Using internal functions inet:open/8 and inet:close/0. - %% Don't try this at home unless you know what you are doing! - {ok,S} = inet:open(Fd, {0,0,0,0}, 0, [], tcp, inet, stream, inet_tcp), - {ok, HostPort} = inet:sockname(S), - ok = inet:close(S), - HostPort. - -fmt_host({A,B,C,D}) -> - lists:concat([A,".",B,".",C,".",D]); -fmt_host(T={_,_,_,_,_,_,_,_}) -> - lists:flatten(string:join([io_lib:format("~.16B",[A]) || A <- tuple_to_list(T)], ":")). +%%%---------------------------------------------------------------- +fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); +fmt_host(Str) when is_list(Str) -> Str. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 4943f062b4..f9e2280212 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -27,8 +27,8 @@ %% Internal application API -export([start_link/4, number_of_connections/1, - callback_listen/2, - handle_connection/5]). + listen/2, + handle_established_connection/4]). %% spawn export -export([acceptor_init/5, acceptor_loop/6]). @@ -42,40 +42,57 @@ start_link(Port, Address, Options, AcceptTimeout) -> Args = [self(), Port, Address, Options, AcceptTimeout], proc_lib:start_link(?MODULE, acceptor_init, Args). +%%%---------------------------------------------------------------- +number_of_connections(SystemSup) -> + length([X || + {R,X,supervisor,[ssh_subsystem_sup]} <- supervisor:which_children(SystemSup), + is_pid(X), + is_reference(R) + ]). + +%%%---------------------------------------------------------------- +listen(Port, Options) -> + {_, Callback, _} = ?GET_OPT(transport, Options), + SockOpts = [{active, false}, {reuseaddr,true} | ?GET_OPT(socket_options, Options)], + case Callback:listen(Port, SockOpts) of + {error, nxdomain} -> + Callback:listen(Port, lists:delete(inet6, SockOpts)); + {error, enetunreach} -> + Callback:listen(Port, lists:delete(inet6, SockOpts)); + {error, eafnosupport} -> + Callback:listen(Port, lists:delete(inet6, SockOpts)); + Other -> + Other + end. + +%%%---------------------------------------------------------------- +handle_established_connection(Address, Port, Options, Socket) -> + {_, Callback, _} = ?GET_OPT(transport, Options), + handle_connection(Callback, Address, Port, Options, Socket). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) -> - {_, Callback, _} = ?GET_OPT(transport, Opts), try - {LSock0,SockOwner0} = ?GET_INTERNAL_OPT(lsocket, Opts), - true = is_pid(SockOwner0), - {ok,{_,Port}} = inet:sockname(LSock0), - {LSock0, SockOwner0} + ?GET_INTERNAL_OPT(lsocket, Opts) of {LSock, SockOwner} -> - %% Use existing socket - proc_lib:init_ack(Parent, {ok, self()}), - request_ownership(LSock, SockOwner), - acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout) - catch - error:{badkey,lsocket} -> - %% Open new socket - try - socket_listen(Port, Opts) - of - {ok, ListenSocket} -> + case inet:sockname(LSock) of + {ok,{_,Port}} -> % A usable, open LSock proc_lib:init_ack(Parent, {ok, self()}), - acceptor_loop(Callback, - Port, Address, Opts, ListenSocket, AcceptTimeout); - {error,Error} -> - proc_lib:init_ack(Parent, Error), - {error,Error} - catch - _:_ -> - {error,listen_socket_failed} - end; + request_ownership(LSock, SockOwner), + {_, Callback, _} = ?GET_OPT(transport, Opts), + acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout); + {error,_} -> % Not open, a restart + {ok,NewLSock} = listen(Port, Opts), + proc_lib:init_ack(Parent, {ok, self()}), + Opts1 = ?DELETE_INTERNAL_OPT(lsocket, Opts), + {_, Callback, _} = ?GET_OPT(transport, Opts1), + acceptor_loop(Callback, Port, Address, Opts1, NewLSock, AcceptTimeout) + end + catch _:_ -> {error,use_existing_socket_failed} end. @@ -87,30 +104,7 @@ request_ownership(LSock, SockOwner) -> {its_yours,LSock} -> ok end. - -socket_listen(Port0, Opts) -> - Port = case ?GET_SOCKET_OPT(fd, Opts) of - undefined -> Port0; - _ -> 0 - end, - callback_listen(Port, Opts). - - -callback_listen(Port, Opts0) -> - {_, Callback, _} = ?GET_OPT(transport, Opts0), - Opts = ?PUT_SOCKET_OPT([{active, false}, {reuseaddr,true}], Opts0), - SockOpts = ?GET_OPT(socket_options, Opts), - case Callback:listen(Port, SockOpts) of - {error, nxdomain} -> - Callback:listen(Port, lists:delete(inet6, SockOpts)); - {error, enetunreach} -> - Callback:listen(Port, lists:delete(inet6, SockOpts)); - {error, eafnosupport} -> - Callback:listen(Port, lists:delete(inet6, SockOpts)); - Other -> - Other - end. - +%%%---------------------------------------------------------------- acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) -> case (catch Callback:accept(ListenSocket, AcceptTimeout)) of {ok, Socket} -> @@ -127,6 +121,7 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) -> ListenSocket, AcceptTimeout) end. +%%%---------------------------------------------------------------- handle_connection(Callback, Address, Port, Options, Socket) -> Profile = ?GET_OPT(profile, Options), SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile), @@ -158,7 +153,7 @@ handle_connection(Callback, Address, Port, Options, Socket) -> {error,max_sessions} end. - +%%%---------------------------------------------------------------- handle_error(timeout) -> ok; @@ -185,10 +180,3 @@ handle_error(Reason) -> error_logger:error_report(String), exit({accept_failed, String}). - -number_of_connections(SystemSup) -> - length([X || - {R,X,supervisor,[ssh_subsystem_sup]} <- supervisor:which_children(SystemSup), - is_pid(X), - is_reference(R) - ]). -- cgit v1.2.3 From e20ce5b9174e5ac0e1279a1af5be80f9c1b35caa Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 15:21:46 +0100 Subject: ssh: handle HostAddr arg and ip-option for daemons --- lib/ssh/src/ssh.erl | 221 +++++++++++++++++++++++++++++----------------------- 1 file changed, 124 insertions(+), 97 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index c139556791..ff424b738c 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -182,16 +182,86 @@ daemon(Port) -> daemon(Port, []). -daemon(Port, UserOptions) when is_integer(Port), Port >= 0 -> - daemon(any, Port, UserOptions); - daemon(Socket, UserOptions) when is_port(Socket) -> - daemon(socket, Socket, UserOptions). + try + #{} = Options = ssh_options:handle_options(server, UserOptions), + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of + ok -> + {ok, {IP,Port}} = inet:sockname(Socket), + finalize_start(fmt_host(IP), Port, ?GET_OPT(profile, Options), + ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options), + fun(Opts, DefaultResult) -> + try ssh_acceptor:handle_established_connection( + ?GET_INTERNAL_OPT(address, Opts), + ?GET_INTERNAL_OPT(port, Opts), + Opts, + Socket) + of + {error,Error} -> + {error,Error}; + _ -> + DefaultResult + catch + C:R -> + {error,{could_not_start_connection,{C,R}}} + end + end); + {error,SockError} -> + {error,SockError} + end + catch + throw:bad_fd -> + {error,bad_fd}; + throw:bad_socket -> + {error,bad_socket}; + error:{badmatch,{error,Error}} -> + {error,Error}; + error:Error -> + {error,Error}; + _C:_E -> + {error,{cannot_start_daemon,_C,_E}} + end; + +daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 -> + daemon(any, Port, UserOptions). + +daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> + try + {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), + #{} = Options0 = ssh_options:handle_options(server, UserOptions), + + {{Host,Port}, ListenSocket} = + open_listen_socket(Host1, Port0, Options0), + + %% Now Host,Port is what to use for the supervisor to register its name, + %% and ListenSocket is for listening on connections. But it is still owned + %% by self()... + + finalize_start(fmt_host(Host), Port, ?GET_OPT(profile, Options0), + ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), + fun(Opts, Result) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + receive + {request_control, ListenSocket, ReqPid} -> + ok = Callback:controlling_process(ListenSocket, ReqPid), + ReqPid ! {its_yours,ListenSocket}, + Result + end + end) + catch + throw:bad_fd -> + {error,bad_fd}; + throw:bad_socket -> + {error,bad_socket}; + error:{badmatch,{error,Error}} -> + {error,Error}; + error:Error -> + {error,Error}; + _C:_E -> + {error,{cannot_start_daemon,_C,_E}} + end. -daemon(Host0, Port, UserOptions0) -> - {Host, UserOptions} = handle_daemon_args(Host0, UserOptions0), - start_daemon(Host, Port, ssh_options:handle_options(server, UserOptions)). %%-------------------------------------------------------------------- -spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ). @@ -291,21 +361,49 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -handle_daemon_args(Host, UserOptions0) -> - case Host of - socket -> - {Host, UserOptions0}; - any -> - {ok, Host0} = inet:gethostname(), - Inet = proplists:get_value(inet, UserOptions0, inet), - {Host0, [Inet | UserOptions0]}; - {_,_,_,_} -> - {Host, [inet, {ip,Host} | UserOptions0]}; - {_,_,_,_,_,_,_,_} -> - {Host, [inet6, {ip,Host} | UserOptions0]}; - _ -> - error(badarg) - end. +handle_daemon_args(HostAddr, Opts) -> + IP = proplists:get_value(ip, Opts), + IPh = case inet:parse_strict_address(HostAddr) of + {ok, IPtuple} -> IPtuple; + {error, einval} when is_tuple(HostAddr), + size(HostAddr)==4 ; size(HostAddr)==6 -> HostAddr; + _ -> undefined + end, + handle_daemon_args(HostAddr, IPh, IP, Opts). + + +%% HostAddr is 'any' +handle_daemon_args(any, undefined, undefined, Opts) -> {any, Opts}; +handle_daemon_args(any, undefined, IP, Opts) -> {IP, Opts}; + +%% HostAddr is 'loopback' or "localhost" +handle_daemon_args(loopback, undefined, {127,_,_,_}=IP, Opts) -> {IP, Opts}; +handle_daemon_args(loopback, undefined, {0,0,0,0,0,0,0,1}=IP, Opts) -> {IP, Opts}; +handle_daemon_args(loopback, undefined, undefined, Opts) -> + IP = case proplists:get_value(inet,Opts) of + true -> {127,0,0,1}; + inet -> {127,0,0,1}; + inet6 -> {0,0,0,0,0,0,0,1}; + _ -> case proplists:get_value(inet6,Opts) of + true -> {0,0,0,0,0,0,0,1}; + _ -> {127,0,0,1} % default if no 'inet' nor 'inet6' + end + end, + {IP, [{ip,IP}|Opts]}; +handle_daemon_args("localhost", IPh, IP, Opts) -> + handle_daemon_args(loopback, IPh, IP, Opts); + +%% HostAddr is ip and no ip-option +handle_daemon_args(_, IP, undefined, Opts) when is_tuple(IP) -> {IP, [{ip,IP}|Opts]}; + +%% HostAddr and ip-option are equal +handle_daemon_args(_, IP, IP, Opts) when is_tuple(IP) -> {IP, Opts}; + +%% HostAddr is ip, but ip-option is different! +handle_daemon_args(_, IPh, IPo, _) when is_tuple(IPh), is_tuple(IPo) -> error({eoption,{ip,IPo}}); + +%% Something else. Whatever it is, it is wrong. +handle_daemon_args(_, _, _, _) -> error(badarg). %%%---------------------------------------------------------------- valid_socket_to_use(Socket, {tcp,_,_}) -> @@ -332,97 +430,25 @@ is_tcp_socket(Socket) -> end. %%%---------------------------------------------------------------- -start_daemon(_, _, {error,Error}) -> - {error,Error}; - -start_daemon(socket, Socket, Options) -> - case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of - ok -> - start_daemon(inet:sockname(Socket), Socket, Options); - {error,SockError} -> - {error,SockError} - end; - -start_daemon(Host, Port, Options) -> - try - do_start_daemon(Host, Port, Options) - catch - throw:bad_fd -> - {error,bad_fd}; - throw:bad_socket -> - {error,bad_socket}; - error:{badmatch,{error,Error}} -> - {error,Error}; - _C:_E -> - {error,{cannot_start_daemon,_C,_E}} - end. - -%%%---------------------------------------------------------------- -do_start_daemon({error,Error}, _, _) -> - {error,Error}; - -do_start_daemon({ok, {IP,Port}}, Socket, Options0) -> - finalize_start(fmt_host(IP), - Port, - ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options0), - fun(Opts, DefaultResult) -> - try ssh_acceptor:handle_established_connection( - ?GET_INTERNAL_OPT(address, Opts), - ?GET_INTERNAL_OPT(port, Opts), - Opts, - Socket) - of - {error,Error} -> - {error,Error}; - _ -> - DefaultResult - catch - C:R -> - {error,{could_not_start_connection,{C,R}}} - end - end); - -do_start_daemon(Host0, Port0, Options0) -> - {{Host,Port}, ListenSocket} = - open_listen_socket(Host0, Port0, Options0), - - %% Now Host,Port is what to use for the supervisor to register its name, - %% and ListenSocket is for listening on connections. But it is still owned - %% by self()... - - finalize_start(Host, Port, - ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), - fun(Opts, Result) -> - {_, Callback, _} = ?GET_OPT(transport, Opts), - receive - {request_control, ListenSocket, ReqPid} -> - ok = Callback:controlling_process(ListenSocket, ReqPid), - ReqPid ! {its_yours,ListenSocket}, - Result - end - end). - - open_listen_socket(Host0, Port0, Options0) -> case ?GET_SOCKET_OPT(fd, Options0) of undefined -> {ok,LSock} = ssh_acceptor:listen(Port0, Options0), {ok,{_,LPort}} = inet:sockname(LSock), - {{fmt_host(Host0),LPort}, LSock}; + {{Host0,LPort}, LSock}; Fd when is_integer(Fd) -> %% Do gen_tcp:listen with the option {fd,Fd}: {ok,LSock} = ssh_acceptor:listen(0, Options0), {ok,{LHost,LPort}} = inet:sockname(LSock), - {{fmt_host(LHost),LPort}, LSock} + {{LHost,LPort}, LSock} end. %%%---------------------------------------------------------------- -finalize_start(Host, Port, Options0, F) -> +finalize_start(Host, Port, Profile, Options0, F) -> Options = ?PUT_INTERNAL_OPT([{address, Host}, {port, Port}, {role, server}], Options0), - Profile = ?GET_OPT(profile, Options), case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> try sshd_sup:start_child(Options) of @@ -449,5 +475,6 @@ finalize_start(Host, Port, Options0, F) -> end. %%%---------------------------------------------------------------- +fmt_host(any) -> any; fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); fmt_host(Str) when is_list(Str) -> Str. -- cgit v1.2.3 From ee8a5fa4da90016d6f17db2aa9f43bd98ca04985 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 13 Mar 2017 14:54:40 +0100 Subject: ssh: Removed outdated comment in ssh_sftpd.erl --- lib/ssh/src/ssh_sftp.erl | 9 --------- 1 file changed, 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 140856c8e3..f1f7b57e8d 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1063,15 +1063,6 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) -> gid = A#ssh_xfer_attr.group}. -%% Added workaround for sftp timestam problem. (Timestamps should be -%% in UTC but they where not) . The workaround uses a deprecated -%% function i calandar. This will work as expected most of the time -%% but has problems for the same reason as -%% calendar:local_time_to_universal_time/1. We consider it better that -%% the timestamps work as expected most of the time instead of none of -%% the time. Hopfully the file-api will be updated so that we can -%% solve this problem in a better way in the future. - unix_to_datetime(undefined) -> undefined; unix_to_datetime(UTCSecs) -> -- cgit v1.2.3 From f64dc7858c06d3096b47532270d9f6b732aa7ece Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 20 Mar 2017 13:15:34 +0100 Subject: ssh: remove deprecated ssh_sftpd:listen and ssh_sftpd:stop --- lib/ssh/src/ssh_sftpd.erl | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 9352046795..b879116393 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -34,8 +34,7 @@ %%-------------------------------------------------------------------- %% External exports --export([subsystem_spec/1, - listen/1, listen/2, listen/3, stop/1]). +-export([subsystem_spec/1]). -export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). @@ -76,29 +75,6 @@ subsystem_spec(Options) -> {"sftp", {?MODULE, Options}}. -%%% DEPRECATED START %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%-------------------------------------------------------------------- -%% Function: listen() -> Pid | {error,Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- -listen(Port) -> - listen(any, Port, []). -listen(Port, Options) -> - listen(any, Port, Options). -listen(Addr, Port, Options) -> - SubSystems = [subsystem_spec(Options)], - ssh:daemon(Addr, Port, [{subsystems, SubSystems} |Options]). - -%%-------------------------------------------------------------------- -%% Function: stop(Pid) -> ok -%% Description: Stops the listener -%%-------------------------------------------------------------------- -stop(Pid) -> - ssh:stop_listener(Pid). - - -%%% DEPRECATED END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%==================================================================== %% subsystem callbacks -- cgit v1.2.3 From 3bbb2c9d5f92205f91cc68b9cebe263b84afe3e2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 15:20:58 +0100 Subject: ssh: -type and -spec adjustments --- lib/ssh/src/ssh.hrl | 17 +++++++++++++++-- lib/ssh/src/ssh_options.erl | 12 +----------- 2 files changed, 16 insertions(+), 13 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 63eeb0bd0a..e03c15454c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -113,12 +113,25 @@ -type double_algs() :: list( {client2serverlist,simple_algs()} | {server2client,simple_algs()} ) | simple_algs() . +-type options() :: #{socket_options := socket_options(), + internal_options := internal_options(), + option_key() => any() + }. + +-type socket_options() :: proplists:proplist(). +-type internal_options() :: #{option_key() => any()}. + +-type option_key() :: atom(). + + %% Records -record(ssh, { - role, %% client | server - peer, %% string version of peer address + role :: client | role(), + peer :: undefined | + {inet:hostname(), + {inet:ip_adress(),inet:port_number()}}, %% string version of peer address c_vsn, %% client version {Major,Minor} s_vsn, %% server version {Major,Minor} diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index cb3f63103c..febd3f6eef 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -38,16 +38,6 @@ %%%================================================================ %%% Types --type options() :: #{socket_options := socket_options(), - internal_options := internal_options(), - option_key() => any() - }. - --type socket_options() :: proplists:proplist(). --type internal_options() :: #{option_key() => any()}. - --type option_key() :: atom(). - -type option_in() :: proplists:property() | proplists:proplist() . -type option_class() :: internal_options | socket_options | user_options . @@ -431,7 +421,7 @@ default(client) -> }, {silently_accept_hosts, def} => - #{default => {false,none}, + #{default => false, chk => fun check_silently_accept_hosts/1, class => user_options }, -- cgit v1.2.3 From 4d6393bc4df58defbc22c5d97e28bbfdd8794fc6 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 6 Apr 2017 19:31:13 +0200 Subject: ssh: Lazy default in get options macro --- lib/ssh/src/ssh.hrl | 5 ++++- lib/ssh/src/ssh_connection_handler.erl | 10 ++-------- lib/ssh/src/ssh_options.erl | 15 +++++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index e03c15454c..315310f700 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -75,9 +75,12 @@ %% Option access macros -define(do_get_opt(C,K,O), ssh_options:get_value(C,K,O, ?MODULE,?LINE)). --define(do_get_opt(C,K,O,D), ssh_options:get_value(C,K,O,D,?MODULE,?LINE)). +-define(do_get_opt(C,K,O,D), ssh_options:get_value(C,K,O,?LAZY(D),?MODULE,?LINE)). + +-define(LAZY(D), fun()-> D end). -define(GET_OPT(Key,Opts), ?do_get_opt(user_options, Key,Opts ) ). +-define(GET_OPT(Key,Opts,Def), ?do_get_opt(user_options, Key,Opts,Def) ). -define(GET_INTERNAL_OPT(Key,Opts), ?do_get_opt(internal_options,Key,Opts ) ). -define(GET_INTERNAL_OPT(Key,Opts,Def), ?do_get_opt(internal_options,Key,Opts,Def) ). -define(GET_SOCKET_OPT(Key,Opts), ?do_get_opt(socket_options, Key,Opts ) ). diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 5a13209ae3..50a29bbb53 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -413,14 +413,8 @@ init_connection(server, C = #connection{}, Opts) -> SubSystemSup = proplists:get_value(subsystem_sup, Sups), ConnectionSup = proplists:get_value(connection_sup, Sups), - Shell = ?GET_OPT(shell, Opts), - Exec = ?GET_OPT(exec, Opts), - CliSpec = case ?GET_OPT(ssh_cli, Opts) of - undefined -> {ssh_cli, [Shell]}; - Spec -> Spec - end, - C#connection{cli_spec = CliSpec, - exec = Exec, + C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), + exec = ?GET_OPT(exec, Opts), system_supervisor = SystemSup, sub_system_supervisor = SubSystemSup, connection_supervisor = ConnectionSup diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index febd3f6eef..ee3cdbb8a0 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -69,17 +69,20 @@ get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). --spec get_value(option_class(), option_key(), options(), any(), +-spec get_value(option_class(), option_key(), options(), fun(() -> any()), atom(), non_neg_integer()) -> any() | no_return(). -get_value(socket_options, Key, Opts, Def, _CallerMod, _CallerLine) when is_map(Opts) -> - proplists:get_value(Key, maps:get(socket_options,Opts), Def); -get_value(Class, Key, Opts, Def, CallerMod, CallerLine) when is_map(Opts) -> +get_value(socket_options, Key, Opts, DefFun, _CallerMod, _CallerLine) when is_map(Opts) -> + proplists:get_value(Key, maps:get(socket_options,Opts), DefFun); +get_value(Class, Key, Opts, DefFun, CallerMod, CallerLine) when is_map(Opts) -> try get_value(Class, Key, Opts, CallerMod, CallerLine) + of + undefined -> DefFun(); + Value -> Value catch - error:{badkey,Key} -> Def + error:{badkey,Key} -> DefFun() end; -get_value(Class, Key, Opts, _Def, _CallerMod, _CallerLine) -> +get_value(Class, Key, Opts, _DefFun, _CallerMod, _CallerLine) -> error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). -- cgit v1.2.3 From 57d994270d63e7a9ce80eece3c1c3aeca79d3ea4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 15:44:44 +0100 Subject: ssh: fix ssh_system_sup naming of Host-Port-Profile --- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh_acceptor_sup.erl | 7 +------ lib/ssh/src/ssh_system_sup.erl | 16 ++-------------- lib/ssh/src/sshd_sup.erl | 7 +------ 4 files changed, 5 insertions(+), 27 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index ff424b738c..9047b7e0f0 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -475,6 +475,6 @@ finalize_start(Host, Port, Profile, Options0, F) -> end. %%%---------------------------------------------------------------- -fmt_host(any) -> any; +fmt_host(any) -> "any"; fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); fmt_host(Str) when is_list(Str) -> Str. diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 77f7826918..613d8fbc75 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -93,10 +93,5 @@ child_spec(Options) -> {Name, StartFunc, Restart, Shutdown, Type, Modules}. id(Address, Port, Profile) -> - case is_list(Address) of - true -> - {ssh_acceptor_sup, any, Port, Profile}; - false -> - {ssh_acceptor_sup, Address, Port, Profile} - end. + {ssh_acceptor_sup, Address, Port, Profile}. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 5a58ef1c44..4083f666c3 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -166,22 +166,10 @@ ssh_subsystem_child_spec(Options) -> id(Sup, Address, Port, Profile) -> - case is_list(Address) of - true -> - {Sup, any, Port, Profile}; - false -> - {Sup, Address, Port, Profile} - end. + {Sup, Address, Port, Profile}. make_name(Address, Port, Profile) -> - case is_list(Address) of - true -> - list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_~p_sup", - [any, Port, Profile]))); - false -> - list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_~p_sup", - [Address, Port, Profile]))) - end. + list_to_atom(lists:flatten(io_lib:format("ssh_system_~s_~p_~p_sup", [Address, Port, Profile]))). ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) -> Child; diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 14f1937abd..791456839d 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -103,12 +103,7 @@ child_spec(Address, Port, Options) -> {Name, StartFunc, Restart, Shutdown, Type, Modules}. id(Address, Port, Profile) -> - case is_list(Address) of - true -> - {server, ssh_system_sup, any, Port, Profile}; - false -> - {server, ssh_system_sup, Address, Port, Profile} - end. + {server, ssh_system_sup, Address, Port, Profile}. system_name([], _ ) -> undefined; -- cgit v1.2.3 From 6158cb432092c47e178b4dc1177b46cb8c310ab4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 19:50:49 +0100 Subject: ssh: Fix supervisors, start daemon and connect code Remove many internal options and made them as explicit arguments. --- lib/ssh/src/ssh.erl | 240 +++++++++++++++++++++------------ lib/ssh/src/ssh_acceptor.erl | 3 +- lib/ssh/src/ssh_acceptor_sup.erl | 22 ++- lib/ssh/src/ssh_connection_handler.erl | 7 +- lib/ssh/src/ssh_file.erl | 2 + lib/ssh/src/ssh_subsystem_sup.erl | 34 ++--- lib/ssh/src/ssh_sup.erl | 53 +------- lib/ssh/src/ssh_system_sup.erl | 43 +++--- lib/ssh/src/sshc_sup.erl | 14 +- lib/ssh/src/sshd_sup.erl | 55 +++----- 10 files changed, 237 insertions(+), 236 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 9047b7e0f0..680047dffd 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -108,7 +108,7 @@ connect(Socket, UserOptions, Timeout) when is_port(Socket), case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> {ok, {Host,_Port}} = inet:sockname(Socket), - Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options), + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error,SockError} -> {error,SockError} @@ -132,7 +132,7 @@ connect(Host, Port, UserOptions, Timeout) when is_integer(Port), SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)], try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> - Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options), + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error, Reason} -> {error, Reason} @@ -188,14 +188,11 @@ daemon(Socket, UserOptions) when is_port(Socket) -> case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> {ok, {IP,Port}} = inet:sockname(Socket), - finalize_start(fmt_host(IP), Port, ?GET_OPT(profile, Options), + finalize_start(IP, Port, ?GET_OPT(profile, Options), ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options), fun(Opts, DefaultResult) -> try ssh_acceptor:handle_established_connection( - ?GET_INTERNAL_OPT(address, Opts), - ?GET_INTERNAL_OPT(port, Opts), - Opts, - Socket) + IP, Port, Opts, Socket) of {error,Error} -> {error,Error}; @@ -238,7 +235,7 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> %% and ListenSocket is for listening on connections. But it is still owned %% by self()... - finalize_start(fmt_host(Host), Port, ?GET_OPT(profile, Options0), + finalize_start(Host, Port, ?GET_OPT(profile, Options0), ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), fun(Opts, Result) -> {_, Callback, _} = ?GET_OPT(transport, Opts), @@ -269,17 +266,27 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> - [{ListenAddr,Port,Profile}] = - [{LA,Prt,Prf} || {{ssh_acceptor_sup,LA,Prt,Prf}, - _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)], + [{Name,Port,Profile}] = + [{Nam,Prt,Prf} + || {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]} + <- supervisor:which_children(AsupPid), + Nam <- [case inet:parse_strict_address(Hst) of + {ok,IP} -> IP; + _ when Hst=="any" -> any; + _ when Hst=="loopback" -> loopback; + _ -> Hst + end] + ], {ok, [{port,Port}, - {listen_address,ListenAddr}, + {name,Name}, {profile,Profile} ]}; _ -> {error,bad_daemon_ref} end. + + %%-------------------------------------------------------------------- -spec stop_listener(daemon_ref()) -> ok. -spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. @@ -361,49 +368,128 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -handle_daemon_args(HostAddr, Opts) -> + +%% - if Address is 'any' and no ip-option is present, the name is +%% 'any' and the socket will listen to all addresses +%% +%% - if Address is 'any' and an ip-option is present, the name is +%% set to the value of the ip-option and the socket will listen +%% to that address +%% +%% - if Address is 'loopback' and no ip-option is present, the name +%% is 'loopback' and an loopback address will be choosen by the +%% underlying layers +%% +%% - if Address is 'loopback' and an ip-option is present, the name +%% is set to the value of the ip-option kept and the socket will +%% listen to that address +%% +%% - if Address is an ip-address, that ip-address is the name and +%% the listening address. An ip-option will be discarded. +%% +%% - if Address is a HostName, and that resolves to an ip-address, +%% that ip-address is the name and the listening address. An +%% ip-option will be discarded. +%% +%% - if Address is a string or an atom other than thoose defined +%% above, that Address will be the name and the listening address +%% will be choosen by the lower layers taking an ip-option in +%% consideration +%% + +handle_daemon_args(any, Opts) -> + case proplists:get_value(ip, Opts) of + undefined -> {any, Opts}; + IP -> {IP, Opts} + end; + +handle_daemon_args(loopback, Opts) -> + case proplists:get_value(ip, Opts) of + undefined -> {loopback, [{ip,loopback}|Opts]}; + IP -> {IP, Opts} + end; + +handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) -> + case proplists:get_value(ip, Opts) of + undefined -> {IPaddr, [{ip,IPaddr}|Opts]}; + IPaddr -> {IPaddr, Opts}; + IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility + end; + +handle_daemon_args(Address, Opts) when is_list(Address) ; is_atom(Address) -> IP = proplists:get_value(ip, Opts), - IPh = case inet:parse_strict_address(HostAddr) of - {ok, IPtuple} -> IPtuple; - {error, einval} when is_tuple(HostAddr), - size(HostAddr)==4 ; size(HostAddr)==6 -> HostAddr; - _ -> undefined - end, - handle_daemon_args(HostAddr, IPh, IP, Opts). - - -%% HostAddr is 'any' -handle_daemon_args(any, undefined, undefined, Opts) -> {any, Opts}; -handle_daemon_args(any, undefined, IP, Opts) -> {IP, Opts}; - -%% HostAddr is 'loopback' or "localhost" -handle_daemon_args(loopback, undefined, {127,_,_,_}=IP, Opts) -> {IP, Opts}; -handle_daemon_args(loopback, undefined, {0,0,0,0,0,0,0,1}=IP, Opts) -> {IP, Opts}; -handle_daemon_args(loopback, undefined, undefined, Opts) -> - IP = case proplists:get_value(inet,Opts) of - true -> {127,0,0,1}; - inet -> {127,0,0,1}; - inet6 -> {0,0,0,0,0,0,0,1}; - _ -> case proplists:get_value(inet6,Opts) of - true -> {0,0,0,0,0,0,0,1}; - _ -> {127,0,0,1} % default if no 'inet' nor 'inet6' - end - end, - {IP, [{ip,IP}|Opts]}; -handle_daemon_args("localhost", IPh, IP, Opts) -> - handle_daemon_args(loopback, IPh, IP, Opts); - -%% HostAddr is ip and no ip-option -handle_daemon_args(_, IP, undefined, Opts) when is_tuple(IP) -> {IP, [{ip,IP}|Opts]}; - -%% HostAddr and ip-option are equal -handle_daemon_args(_, IP, IP, Opts) when is_tuple(IP) -> {IP, Opts}; - -%% HostAddr is ip, but ip-option is different! -handle_daemon_args(_, IPh, IPo, _) when is_tuple(IPh), is_tuple(IPo) -> error({eoption,{ip,IPo}}); - -%% Something else. Whatever it is, it is wrong. -handle_daemon_args(_, _, _, _) -> error(badarg). + case inet:parse_strict_address(Address) of + {ok, IP} -> {IP, Opts}; + {ok, OtherIP} -> {OtherIP, [{ip,OtherIP}|Opts--[{ip,IP}]]}; + _ -> + case inet:getaddr(Address, family(Opts)) of + {ok, IP} -> {Address, Opts}; + {ok, OtherIP} -> {Address, [{ip,OtherIP}|Opts--[{ip,IP}]]}; + _ -> {Address, Opts} + end + end. + + +-ifdef(hulahopp). +%% Check the Address parameter and set an ip-option in some cases. The +%% Address parameter is left unchanged because ssh:stop_listener and +%% ssh:stop_daemon needs to find the system supervisor by name + +handle_daemon_args(any, Opts) -> + %% Listen to 0.0.0.0. The caller may have set an ip-option. Trust + %% that one in such a case. + {any, Opts}; + +handle_daemon_args(loopback, Opts) -> + %% Listen to a loopback address. Let the underlying layers decide + %% in case the caller hasn't set the ip-option. + {loopback, ensure_ip_option(loopback,Opts)}; + +handle_daemon_args(IP, Opts) when is_tuple(IP) -> + %% An IP address in Erlang tuple format: + {IP, ensure_ip_option(IP,Opts)}; + +handle_daemon_args(Address, Opts) when is_list(Address) ; is_atom(Address) -> + %% This might be a host name, an FQDN, an IP address in string format ("127.1.1.1") + %% etc. It might be a string or an atom since inet:hostname() is defined in that way + case inet:parse_strict_address(Address) of + {ok, IP} -> + {Address, ensure_ip_option(IP,Opts)}; + _ -> + %% Try to lookup as a hostname: + case inet:getaddr(Address, family(Opts)) of + {ok, IP} -> + {Address, ensure_ip_option(IP,Opts)}; + _ -> + %% Give up and let the underlying system handle this + {Address, Opts} + end + end. + + +%% Add an ip-option if not already present. +ensure_ip_option(Address, Opts) -> + case proplists:get_value(ip, Opts) of + undefined -> [{ip,Address}|Opts]; + _ -> Opts + end. +-endif. + + +%% Has the caller indicated the address family? +family(Opts) -> + family(Opts, inet). + +family(Opts, Default) -> + case proplists:get_value(inet,Opts) of + true -> inet; + inet -> inet; + inet6 -> inet6; + _ -> case proplists:get_value(inet6,Opts) of + true -> inet6; + _ -> Default + end + end. %%%---------------------------------------------------------------- valid_socket_to_use(Socket, {tcp,_,_}) -> @@ -434,8 +520,9 @@ open_listen_socket(Host0, Port0, Options0) -> case ?GET_SOCKET_OPT(fd, Options0) of undefined -> {ok,LSock} = ssh_acceptor:listen(Port0, Options0), - {ok,{_,LPort}} = inet:sockname(LSock), - {{Host0,LPort}, LSock}; + {ok,{_LHost,LPort}} = inet:sockname(LSock), + {{_LHost,LPort}, LSock}; +%% {{Host0,LPort}, LSock}; Fd when is_integer(Fd) -> %% Do gen_tcp:listen with the option {fd,Fd}: @@ -446,35 +533,18 @@ open_listen_socket(Host0, Port0, Options0) -> %%%---------------------------------------------------------------- finalize_start(Host, Port, Profile, Options0, F) -> - Options = ?PUT_INTERNAL_OPT([{address, Host}, - {port, Port}, - {role, server}], Options0), - case ssh_system_sup:system_supervisor(Host, Port, Profile) of - undefined -> - try sshd_sup:start_child(Options) of - {error, {already_started, _}} -> - {error, eaddrinuse}; - {error, Error} -> - {error, Error}; - Result = {ok,_} -> - F(Options, Result) - catch - exit:{noproc, _} -> - {error, ssh_not_started} - end; - Sup -> - AccPid = ssh_system_sup:acceptor_supervisor(Sup), - case ssh_acceptor_sup:start_child(AccPid, Options) of - {error, {already_started, _}} -> - {error, eaddrinuse}; - {error, Error} -> - {error, Error}; - {ok, _} -> - F(Options, {ok,Sup}) - end + try + sshd_sup:start_child(Host, Port, Profile, Options0) + of + {error, {already_started, _}} -> + {error, eaddrinuse}; + {error, Error} -> + {error, Error}; + Result = {ok,_} -> + F(Options0, Result) + catch + exit:{noproc, _} -> + {error, ssh_not_started} end. %%%---------------------------------------------------------------- -fmt_host(any) -> "any"; -fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); -fmt_host(Str) when is_list(Str) -> Str. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index f9e2280212..f7fbd7ccad 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -129,7 +129,8 @@ handle_connection(Callback, Address, Port, Options, Socket) -> MaxSessions = ?GET_OPT(max_sessions, Options), case number_of_connections(SystemSup) < MaxSessions of true -> - {ok, SubSysSup} = ssh_system_sup:start_subsystem(SystemSup, Options), + {ok, SubSysSup} = + ssh_system_sup:start_subsystem(SystemSup, server, Address, Port, Profile, Options), ConnectionSup = ssh_subsystem_sup:connection_supervisor(SubSysSup), NegTimeout = ?GET_OPT(negotiation_timeout, Options), ssh_connection_handler:start_connection(server, Socket, diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 613d8fbc75..4606107f56 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -29,7 +29,7 @@ -include("ssh.hrl"). --export([start_link/1, start_child/2, stop_child/4]). +-export([start_link/4, start_child/5, stop_child/4]). %% Supervisor callback -export([init/1]). @@ -41,16 +41,13 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Servers) -> - supervisor:start_link(?MODULE, [Servers]). +start_link(Address, Port, Profile, Options) -> + supervisor:start_link(?MODULE, [Address, Port, Profile, Options]). -start_child(AccSup, Options) -> - Spec = child_spec(Options), +start_child(AccSup, Address, Port, Profile, Options) -> + Spec = child_spec(Address, Port, Profile, Options), case supervisor:start_child(AccSup, Spec) of {error, already_present} -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Profile = ?GET_OPT(profile, Options), stop_child(AccSup, Address, Port, Profile), supervisor:start_child(AccSup, Spec); Reply -> @@ -69,21 +66,18 @@ stop_child(AccSup, Address, Port, Profile) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= -init([Options]) -> +init([Address, Port, Profile, Options]) -> RestartStrategy = one_for_one, MaxR = 10, MaxT = 3600, - Children = [child_spec(Options)], + Children = [child_spec(Address, Port, Profile, Options)], {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), +child_spec(Address, Port, Profile, Options) -> Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT), - Profile = ?GET_OPT(profile, Options), Name = id(Address, Port, Profile), StartFunc = {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, Restart = transient, diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 50a29bbb53..ff94e5dfb6 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -440,7 +440,12 @@ init_ssh_record(Role, Socket, Opts) -> {Vsn, Version} = ssh_transport:versions(Role, Opts), case Role of client -> - PeerName = ?GET_INTERNAL_OPT(host, Opts), + PeerName = case ?GET_INTERNAL_OPT(host, Opts) of + PeerIP when is_tuple(PeerIP) -> + inet_parse:ntoa(PeerIP); + PeerName0 -> + PeerName0 + end, S0#ssh{c_vsn = Vsn, c_version = Version, io_cb = case ?GET_OPT(user_interaction, Opts) of diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 898b4cc5c4..88f4d10792 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -221,6 +221,8 @@ file_name(Type, Name, Opts) -> %% in: "host" out: "host,1.2.3.4. +add_ip(IP) when is_tuple(IP) -> + ssh_connection:encode_ip(IP); add_ip(Host) -> case inet:getaddr(Host, inet) of {ok, Addr} -> diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index cf82db458f..c5ab422265 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -28,7 +28,7 @@ -include("ssh.hrl"). --export([start_link/1, +-export([start_link/5, connection_supervisor/1, channel_supervisor/1 ]). @@ -39,8 +39,8 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Options) -> - supervisor:start_link(?MODULE, [Options]). +start_link(Role, Address, Port, Profile, Options) -> + supervisor:start_link(?MODULE, [Role, Address, Port, Profile, Options]). connection_supervisor(SupPid) -> Children = supervisor:which_children(SupPid), @@ -53,30 +53,23 @@ channel_supervisor(SupPid) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init([Options]) -> +init([Role, Address, Port, Profile, Options]) -> RestartStrategy = one_for_all, MaxR = 0, MaxT = 3600, - Children = child_specs(Options), + Children = child_specs(Role, Address, Port, Profile, Options), {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(Options) -> - case ?GET_INTERNAL_OPT(role, Options) of - client -> - []; - server -> - [ssh_channel_child_spec(Options), ssh_connectinon_child_spec(Options)] - end. +child_specs(client, _Address, _Port, _Profile, _Options) -> + []; +child_specs(server, Address, Port, Profile, Options) -> + [ssh_channel_child_spec(server, Address, Port, Profile, Options), + ssh_connection_child_spec(server, Address, Port, Profile, Options)]. -ssh_connectinon_child_spec(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Role = ?GET_INTERNAL_OPT(role, Options), +ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> Name = id(Role, ssh_connection_sup, Address, Port), StartFunc = {ssh_connection_sup, start_link, [Options]}, Restart = temporary, @@ -85,10 +78,7 @@ ssh_connectinon_child_spec(Options) -> Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_channel_child_spec(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Role = ?GET_INTERNAL_OPT(role, Options), +ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> Name = id(Role, ssh_channel_sup, Address, Port), StartFunc = {ssh_channel_sup, start_link, [Options]}, Restart = temporary, diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 8b57387589..5463401dcd 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -31,63 +31,20 @@ %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init([]) -> +init(_) -> SupFlags = {one_for_one, 10, 3600}, - Children = children(), + Children = [child_spec(sshd_sup), child_spec(sshc_sup)], %%children(), {ok, {SupFlags, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -get_services() -> - case (catch application:get_env(ssh, services)) of - {ok, Services} -> - Services; - _ -> - [] - end. - -children() -> - Services = get_services(), - Clients = [Service || Service <- Services, is_client(Service)], - Servers = [Service || Service <- Services, is_server(Service)], - - [server_child_spec(Servers), client_child_spec(Clients)]. - -server_child_spec(Servers) -> - Name = sshd_sup, - StartFunc = {sshd_sup, start_link, [Servers]}, +child_spec(Name) -> + StartFunc = {Name, start_link, []}, Restart = permanent, Shutdown = infinity, - Modules = [sshd_sup], + Modules = [Name], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -client_child_spec(Clients) -> - Name = sshc_sup, - StartFunc = {sshc_sup, start_link, [Clients]}, - Restart = permanent, - Shutdown = infinity, - Modules = [sshc_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -is_server({sftpd, _}) -> - true; -is_server({shelld, _}) -> - true; -is_server(_) -> - false. - -is_client({sftpc, _}) -> - true; -is_client({shellc, _}) -> - true; -is_client(_) -> - false. - - - diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 4083f666c3..a923b5ef71 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -31,12 +31,12 @@ -include("ssh.hrl"). --export([start_link/1, stop_listener/1, +-export([start_link/4, stop_listener/1, stop_listener/3, stop_system/1, stop_system/3, system_supervisor/3, subsystem_supervisor/1, channel_supervisor/1, connection_supervisor/1, - acceptor_supervisor/1, start_subsystem/2, restart_subsystem/3, + acceptor_supervisor/1, start_subsystem/6, restart_subsystem/3, restart_acceptor/3, stop_subsystem/2]). %% Supervisor callback @@ -45,12 +45,9 @@ %%%========================================================================= %%% Internal API %%%========================================================================= -start_link(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Profile = ?GET_OPT(profile, Options), +start_link(Address, Port, Profile, Options) -> Name = make_name(Address, Port, Profile), - supervisor:start_link({local, Name}, ?MODULE, [Options]). + supervisor:start_link({local, Name}, ?MODULE, [Address, Port, Profile, Options]). stop_listener(SysSup) -> stop_acceptor(SysSup). @@ -86,8 +83,8 @@ connection_supervisor(SystemSup) -> acceptor_supervisor(SystemSup) -> ssh_acceptor_sup(supervisor:which_children(SystemSup)). -start_subsystem(SystemSup, Options) -> - Spec = ssh_subsystem_child_spec(Options), +start_subsystem(SystemSup, Role, Address, Port, Profile, Options) -> + Spec = ssh_subsystem_child_spec(Role, Address, Port, Profile, Options), supervisor:start_child(SystemSup, Spec). stop_subsystem(SystemSup, SubSys) -> @@ -125,14 +122,12 @@ restart_acceptor(Address, Port, Profile) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init([Options]) -> +init([Address, Port, Profile, Options]) -> RestartStrategy = one_for_one, MaxR = 0, MaxT = 3600, Children = case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of - undefined -> child_specs(Options); + undefined -> child_specs(Address, Port, Profile, Options); _ -> [] end, {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. @@ -140,24 +135,21 @@ init([Options]) -> %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(Options) -> - [ssh_acceptor_child_spec(Options)]. +child_specs(Address, Port, Profile, Options) -> + [ssh_acceptor_child_spec(Address, Port, Profile, Options)]. -ssh_acceptor_child_spec(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Profile = ?GET_OPT(profile, Options), +ssh_acceptor_child_spec(Address, Port, Profile, Options) -> Name = id(ssh_acceptor_sup, Address, Port, Profile), - StartFunc = {ssh_acceptor_sup, start_link, [Options]}, + StartFunc = {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}, Restart = transient, Shutdown = infinity, Modules = [ssh_acceptor_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_subsystem_child_spec(Options) -> +ssh_subsystem_child_spec(Role, Address, Port, Profile, Options) -> Name = make_ref(), - StartFunc = {ssh_subsystem_sup, start_link, [Options]}, + StartFunc = {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_subsystem_sup], @@ -169,7 +161,12 @@ id(Sup, Address, Port, Profile) -> {Sup, Address, Port, Profile}. make_name(Address, Port, Profile) -> - list_to_atom(lists:flatten(io_lib:format("ssh_system_~s_~p_~p_sup", [Address, Port, Profile]))). + list_to_atom(lists:flatten(io_lib:format("ssh_system_~s_~p_~p_sup", [fmt_host(Address), Port, Profile]))). + +fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); +fmt_host(A) when is_atom(A) -> A; +fmt_host(S) when is_list(S) -> S. + ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) -> Child; diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 15858f36e1..9aab9d57e9 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -27,7 +27,7 @@ -behaviour(supervisor). --export([start_link/1, start_child/1, stop_child/1]). +-export([start_link/0, start_child/1, stop_child/1]). %% Supervisor callback -export([init/1]). @@ -35,8 +35,8 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Args) -> - supervisor:start_link({local, ?MODULE}, ?MODULE, [Args]). +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). start_child(Args) -> supervisor:start_child(?MODULE, Args). @@ -51,18 +51,16 @@ stop_child(Client) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init(Args) -> +init(_) -> RestartStrategy = simple_one_for_one, MaxR = 0, MaxT = 3600, - {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec(Args)]}}. + {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec()]}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(_) -> +child_spec() -> Name = undefined, % As simple_one_for_one is used. StartFunc = {ssh_connection_handler, start_link, []}, Restart = temporary, diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 791456839d..d4805e9465 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -29,8 +29,11 @@ -include("ssh.hrl"). --export([start_link/1, start_child/1, stop_child/1, - stop_child/3, system_name/1]). +-export([start_link/0, + start_child/4, + stop_child/1, + stop_child/3, + system_name/1]). %% Supervisor callback -export([init/1]). @@ -38,27 +41,23 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Servers) -> - supervisor:start_link({local, ?MODULE}, ?MODULE, [Servers]). +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). -start_child(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Profile = ?GET_OPT(profile, Options), +start_child(Address, Port, Profile, Options) -> +io:format("~p:~p ~p:~p~n",[?MODULE,?LINE,Address, Port]), case ssh_system_sup:system_supervisor(Address, Port, Profile) of undefined -> - Spec = child_spec(Address, Port, Options), - case supervisor:start_child(?MODULE, Spec) of - {error, already_present} -> - Name = id(Address, Port, Profile), - supervisor:delete_child(?MODULE, Name), - supervisor:start_child(?MODULE, Spec); - Reply -> - Reply - end; +io:format("~p:~p undefined~n",[?MODULE,?LINE]), + Spec = child_spec(Address, Port, Profile, Options), + Reply = supervisor:start_child(?MODULE, Spec), +io:format("~p:~p Reply=~p~n",[?MODULE,?LINE,Reply]), + Reply; Pid -> +io:format("~p:~p Pid=~p~n",[?MODULE,?LINE,Pid]), AccPid = ssh_system_sup:acceptor_supervisor(Pid), - ssh_acceptor_sup:start_child(AccPid, Options) + ssh_acceptor_sup:start_child(AccPid, Address, Port, Profile, Options), + {ok,Pid} end. stop_child(Name) -> @@ -75,27 +74,15 @@ system_name(SysSup) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init([Servers]) -> - RestartStrategy = one_for_one, - MaxR = 10, - MaxT = 3600, - Fun = fun(ServerOpts) -> - Address = ?GET_INTERNAL_OPT(address, ServerOpts), - Port = ?GET_INTERNAL_OPT(port, ServerOpts), - child_spec(Address, Port, ServerOpts) - end, - Children = lists:map(Fun, Servers), - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. +init(_) -> + {ok, {{one_for_one, 10, 3600}, []}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(Address, Port, Options) -> - Profile = ?GET_OPT(profile, Options), +child_spec(Address, Port, Profile, Options) -> Name = id(Address, Port,Profile), - StartFunc = {ssh_system_sup, start_link, [Options]}, + StartFunc = {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_system_sup], -- cgit v1.2.3 From 8f4bb9b0bd3aed663521371726ea3ec460e231a0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 23 Mar 2017 16:53:53 +0100 Subject: ssh: Mappify supervisors --- lib/ssh/src/ssh_acceptor_sup.erl | 30 ++++--- lib/ssh/src/ssh_connection_sup.erl | 28 +++---- lib/ssh/src/ssh_subsystem_sup.erl | 39 ++++----- lib/ssh/src/ssh_sup.erl | 30 +++---- lib/ssh/src/ssh_system_sup.erl | 159 ++++++++++++++++--------------------- lib/ssh/src/sshc_sup.erl | 35 ++++---- lib/ssh/src/sshd_sup.erl | 73 +++++++++-------- 7 files changed, 190 insertions(+), 204 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 4606107f56..3ad842f98c 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -48,9 +48,12 @@ start_child(AccSup, Address, Port, Profile, Options) -> Spec = child_spec(Address, Port, Profile, Options), case supervisor:start_child(AccSup, Spec) of {error, already_present} -> + %% Is this ever called? stop_child(AccSup, Address, Port, Profile), supervisor:start_child(AccSup, Spec); Reply -> + %% Reply = {ok,SystemSupPid} when the user calls ssh:daemon + %% after having called ssh:stop_listening Reply end. @@ -67,24 +70,27 @@ stop_child(AccSup, Address, Port, Profile) -> %%% Supervisor callback %%%========================================================================= init([Address, Port, Profile, Options]) -> - RestartStrategy = one_for_one, - MaxR = 10, - MaxT = 3600, - Children = [child_spec(Address, Port, Profile, Options)], - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. + %% Initial start of ssh_acceptor_sup for this port or new start after + %% ssh:stop_daemon + SupFlags = #{strategy => one_for_one, + intensity => 10, + period => 3600 + }, + ChildSpecs = [child_spec(Address, Port, Profile, Options)], + {ok, {SupFlags,ChildSpecs}}. %%%========================================================================= %%% Internal functions %%%========================================================================= child_spec(Address, Port, Profile, Options) -> Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT), - Name = id(Address, Port, Profile), - StartFunc = {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, - Restart = transient, - Shutdown = brutal_kill, - Modules = [ssh_acceptor], - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + #{id => id(Address, Port, Profile), + start => {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, + restart => transient, + shutdown => brutal_kill, + type => worker, + modules => [ssh_acceptor] + }. id(Address, Port, Profile) -> {ssh_acceptor_sup, Address, Port, Profile}. diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl index 0f54053f52..fad796f196 100644 --- a/lib/ssh/src/ssh_connection_sup.erl +++ b/lib/ssh/src/ssh_connection_sup.erl @@ -45,19 +45,17 @@ start_child(Sup, Args) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - init(_) -> - RestartStrategy = simple_one_for_one, - MaxR = 0, - MaxT = 3600, - - Name = undefined, % As simple_one_for_one is used. - StartFunc = {ssh_connection_handler, start_link, []}, - Restart = temporary, % E.g. should not be restarted - Shutdown = 4000, - Modules = [ssh_connection_handler], - Type = worker, - - ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, - {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. + SupFlags = #{strategy => simple_one_for_one, + intensity => 0, + period => 3600 + }, + ChildSpecs = [#{id => undefined, % As simple_one_for_one is used. + start => {ssh_connection_handler, start_link, []}, + restart => temporary, + shutdown => 4000, + type => worker, + modules => [ssh_connection_handler] + } + ], + {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index c5ab422265..cf409ade6b 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -54,11 +54,12 @@ channel_supervisor(SupPid) -> %%% Supervisor callback %%%========================================================================= init([Role, Address, Port, Profile, Options]) -> - RestartStrategy = one_for_all, - MaxR = 0, - MaxT = 3600, - Children = child_specs(Role, Address, Port, Profile, Options), - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. + SupFlags = #{strategy => one_for_all, + intensity => 0, + period => 3600 + }, + ChildSpecs = child_specs(Role, Address, Port, Profile, Options), + {ok, {SupFlags,ChildSpecs}}. %%%========================================================================= %%% Internal functions @@ -70,22 +71,22 @@ child_specs(server, Address, Port, Profile, Options) -> ssh_connection_child_spec(server, Address, Port, Profile, Options)]. ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> - Name = id(Role, ssh_connection_sup, Address, Port), - StartFunc = {ssh_connection_sup, start_link, [Options]}, - Restart = temporary, - Shutdown = 5000, - Modules = [ssh_connection_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + #{id => id(Role, ssh_connection_sup, Address, Port), + start => {ssh_connection_sup, start_link, [Options]}, + restart => temporary, + shutdown => 5000, + type => supervisor, + modules => [ssh_connection_sup] + }. ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> - Name = id(Role, ssh_channel_sup, Address, Port), - StartFunc = {ssh_channel_sup, start_link, [Options]}, - Restart = temporary, - Shutdown = infinity, - Modules = [ssh_channel_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + #{id => id(Role, ssh_channel_sup, Address, Port), + start => {ssh_channel_sup, start_link, [Options]}, + restart => temporary, + shutdown => infinity, + type => supervisor, + modules => [ssh_channel_sup] + }. id(Role, Sup, Address, Port) -> {Role, Sup, Address, Port}. diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 5463401dcd..6be809b1bd 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -32,19 +32,19 @@ %%% Supervisor callback %%%========================================================================= init(_) -> - SupFlags = {one_for_one, 10, 3600}, - Children = [child_spec(sshd_sup), child_spec(sshc_sup)], %%children(), - {ok, {SupFlags, Children}}. - -%%%========================================================================= -%%% Internal functions -%%%========================================================================= -child_spec(Name) -> - StartFunc = {Name, start_link, []}, - Restart = permanent, - Shutdown = infinity, - Modules = [Name], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - + SupFlags = #{strategy => one_for_one, + intensity => 10, + period => 3600 + }, + ChildSpecs = [#{id => Module, + start => {Module, start_link, []}, + restart => permanent, + shutdown => brutal_kill, + type => supervisor, + modules => [Module] + } + || Module <- [sshd_sup, + sshc_sup] + ], + {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index a923b5ef71..84b4cd3241 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -21,7 +21,7 @@ %% %%---------------------------------------------------------------------- %% Purpose: The ssh server instance supervisor, an instans of this supervisor -%% exists for every ip-address and port combination, hangs under +%% exists for every ip-address and port combination, hangs under %% sshd_sup. %%---------------------------------------------------------------------- @@ -34,58 +34,100 @@ -export([start_link/4, stop_listener/1, stop_listener/3, stop_system/1, stop_system/3, system_supervisor/3, - subsystem_supervisor/1, channel_supervisor/1, - connection_supervisor/1, - acceptor_supervisor/1, start_subsystem/6, restart_subsystem/3, - restart_acceptor/3, stop_subsystem/2]). + subsystem_supervisor/1, channel_supervisor/1, + connection_supervisor/1, + acceptor_supervisor/1, start_subsystem/6, + stop_subsystem/2]). %% Supervisor callback -export([init/1]). %%%========================================================================= -%%% Internal API +%%% API %%%========================================================================= start_link(Address, Port, Profile, Options) -> Name = make_name(Address, Port, Profile), supervisor:start_link({local, Name}, ?MODULE, [Address, Port, Profile, Options]). -stop_listener(SysSup) -> - stop_acceptor(SysSup). +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init([Address, Port, Profile, Options]) -> + SupFlags = #{strategy => one_for_one, + intensity => 0, + period => 3600 + }, + ChildSpecs = + case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of + undefined -> + [#{id => id(ssh_acceptor_sup, Address, Port, Profile), + start => {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}, + restart => transient, + shutdown => infinity, + type => supervisor, + modules => [ssh_acceptor_sup] + }]; + _ -> + [] + end, + {ok, {SupFlags,ChildSpecs}}. + +%%%========================================================================= +%%% Service API +%%%========================================================================= +stop_listener(SystemSup) -> + {Name, AcceptorSup, _, _} = lookup(ssh_acceptor_sup, SystemSup), + case supervisor:terminate_child(AcceptorSup, Name) of + ok -> + supervisor:delete_child(AcceptorSup, Name); + Error -> + Error + end. stop_listener(Address, Port, Profile) -> - Name = make_name(Address, Port, Profile), - stop_acceptor(whereis(Name)). - + stop_listener( + system_supervisor(Address, Port, Profile)). + + stop_system(SysSup) -> - Name = sshd_sup:system_name(SysSup), - spawn(fun() -> sshd_sup:stop_child(Name) end), + spawn(fun() -> sshd_sup:stop_child(SysSup) end), ok. -stop_system(Address, Port, Profile) -> +stop_system(Address, Port, Profile) -> spawn(fun() -> sshd_sup:stop_child(Address, Port, Profile) end), ok. + system_supervisor(Address, Port, Profile) -> Name = make_name(Address, Port, Profile), whereis(Name). subsystem_supervisor(SystemSup) -> - ssh_subsystem_sup(supervisor:which_children(SystemSup)). + {_, Child, _, _} = lookup(ssh_subsystem_sup, SystemSup), + Child. channel_supervisor(SystemSup) -> - SubSysSup = ssh_subsystem_sup(supervisor:which_children(SystemSup)), - ssh_subsystem_sup:channel_supervisor(SubSysSup). + ssh_subsystem_sup:channel_supervisor( + subsystem_supervisor(SystemSup)). connection_supervisor(SystemSup) -> - SubSysSup = ssh_subsystem_sup(supervisor:which_children(SystemSup)), - ssh_subsystem_sup:connection_supervisor(SubSysSup). + ssh_subsystem_sup:connection_supervisor( + subsystem_supervisor(SystemSup)). acceptor_supervisor(SystemSup) -> - ssh_acceptor_sup(supervisor:which_children(SystemSup)). + {_, Child, _, _} = lookup(ssh_acceptor_sup, SystemSup), + Child. + start_subsystem(SystemSup, Role, Address, Port, Profile, Options) -> - Spec = ssh_subsystem_child_spec(Role, Address, Port, Profile, Options), - supervisor:start_child(SystemSup, Spec). + SubsystemSpec = + #{id => make_ref(), + start => {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]}, + restart => temporary, + shutdown => infinity, + type => supervisor, + modules => [ssh_subsystem_sup]}, + supervisor:start_child(SystemSup, SubsystemSpec). stop_subsystem(SystemSup, SubSys) -> case catch lists:keyfind(SubSys, 2, supervisor:which_children(SystemSup)) of @@ -103,60 +145,9 @@ stop_subsystem(SystemSup, SubSys) -> ok end. - -restart_subsystem(Address, Port, Profile) -> - SysSupName = make_name(Address, Port, Profile), - SubSysName = id(ssh_subsystem_sup, Address, Port, Profile), - case supervisor:terminate_child(SysSupName, SubSysName) of - ok -> - supervisor:restart_child(SysSupName, SubSysName); - Error -> - Error - end. - -restart_acceptor(Address, Port, Profile) -> - SysSupName = make_name(Address, Port, Profile), - AcceptorName = id(ssh_acceptor_sup, Address, Port, Profile), - supervisor:restart_child(SysSupName, AcceptorName). - -%%%========================================================================= -%%% Supervisor callback -%%%========================================================================= -init([Address, Port, Profile, Options]) -> - RestartStrategy = one_for_one, - MaxR = 0, - MaxT = 3600, - Children = case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of - undefined -> child_specs(Address, Port, Profile, Options); - _ -> [] - end, - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. - %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(Address, Port, Profile, Options) -> - [ssh_acceptor_child_spec(Address, Port, Profile, Options)]. - -ssh_acceptor_child_spec(Address, Port, Profile, Options) -> - Name = id(ssh_acceptor_sup, Address, Port, Profile), - StartFunc = {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}, - Restart = transient, - Shutdown = infinity, - Modules = [ssh_acceptor_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -ssh_subsystem_child_spec(Role, Address, Port, Profile, Options) -> - Name = make_ref(), - StartFunc = {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]}, - Restart = temporary, - Shutdown = infinity, - Modules = [ssh_subsystem_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - - id(Sup, Address, Port, Profile) -> {Sup, Address, Port, Profile}. @@ -168,23 +159,7 @@ fmt_host(A) when is_atom(A) -> A; fmt_host(S) when is_list(S) -> S. -ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) -> - Child; -ssh_subsystem_sup([_ | Rest]) -> - ssh_subsystem_sup(Rest). - -ssh_acceptor_sup([{_, Child, _, [ssh_acceptor_sup]} | _]) -> - Child; -ssh_acceptor_sup([_ | Rest]) -> - ssh_acceptor_sup(Rest). +lookup(SupModule, SystemSup) -> + lists:keyfind([SupModule], 4, + supervisor:which_children(SystemSup)). -stop_acceptor(Sup) -> - [{Name, AcceptorSup}] = - [{SupName, ASup} || {SupName, ASup, _, [ssh_acceptor_sup]} <- - supervisor:which_children(Sup)], - case supervisor:terminate_child(AcceptorSup, Name) of - ok -> - supervisor:delete_child(AcceptorSup, Name); - Error -> - Error - end. diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 9aab9d57e9..c71b81dc6d 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -32,18 +32,20 @@ %% Supervisor callback -export([init/1]). +-define(SSHC_SUP, ?MODULE). + %%%========================================================================= %%% API %%%========================================================================= start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). + supervisor:start_link({local,?SSHC_SUP}, ?MODULE, []). start_child(Args) -> supervisor:start_child(?MODULE, Args). stop_child(Client) -> spawn(fun() -> - ClientSup = whereis(?MODULE), + ClientSup = whereis(?SSHC_SUP), supervisor:terminate_child(ClientSup, Client) end), ok. @@ -52,19 +54,16 @@ stop_child(Client) -> %%% Supervisor callback %%%========================================================================= init(_) -> - RestartStrategy = simple_one_for_one, - MaxR = 0, - MaxT = 3600, - {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec()]}}. - -%%%========================================================================= -%%% Internal functions -%%%========================================================================= -child_spec() -> - Name = undefined, % As simple_one_for_one is used. - StartFunc = {ssh_connection_handler, start_link, []}, - Restart = temporary, - Shutdown = 4000, - Modules = [ssh_connection_handler], - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + SupFlags = #{strategy => simple_one_for_one, + intensity => 0, + period => 3600 + }, + ChildSpecs = [#{id => undefined, % As simple_one_for_one is used. + start => {ssh_connection_handler, start_link, []}, + restart => temporary, + shutdown => 4000, + type => worker, + modules => [ssh_connection_handler] + } + ], + {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index d4805e9465..449ba20d02 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -19,7 +19,7 @@ %% %% %%---------------------------------------------------------------------- -%% Purpose: The top supervisor for ssh servers hangs under +%% Purpose: The top supervisor for ssh servers hangs under %% ssh_sup. %%---------------------------------------------------------------------- @@ -29,72 +29,79 @@ -include("ssh.hrl"). --export([start_link/0, +-export([start_link/0, start_child/4, stop_child/1, - stop_child/3, - system_name/1]). + stop_child/3 +]). %% Supervisor callback -export([init/1]). +-define(SSHD_SUP, ?MODULE). + %%%========================================================================= %%% API %%%========================================================================= start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). + %% No children are start now. We wait until the user calls ssh:daemon + %% and uses start_child/4 to create the children + supervisor:start_link({local,?SSHD_SUP}, ?MODULE, []). start_child(Address, Port, Profile, Options) -> -io:format("~p:~p ~p:~p~n",[?MODULE,?LINE,Address, Port]), case ssh_system_sup:system_supervisor(Address, Port, Profile) of undefined -> -io:format("~p:~p undefined~n",[?MODULE,?LINE]), + %% Here we start listening on a new Host/Port/Profile Spec = child_spec(Address, Port, Profile, Options), - Reply = supervisor:start_child(?MODULE, Spec), -io:format("~p:~p Reply=~p~n",[?MODULE,?LINE,Reply]), - Reply; + supervisor:start_child(?SSHD_SUP, Spec); Pid -> -io:format("~p:~p Pid=~p~n",[?MODULE,?LINE,Pid]), + %% Here we resume listening on a new Host/Port/Profile after + %% haveing stopped listening to he same with ssh:stop_listen(Pid) AccPid = ssh_system_sup:acceptor_supervisor(Pid), ssh_acceptor_sup:start_child(AccPid, Address, Port, Profile, Options), {ok,Pid} end. -stop_child(Name) -> - supervisor:terminate_child(?MODULE, Name). +stop_child(ChildId) when is_tuple(ChildId) -> + supervisor:terminate_child(?SSHD_SUP, ChildId); +stop_child(ChildPid) when is_pid(ChildPid)-> + stop_child(system_name(ChildPid)). -stop_child(Address, Port, Profile) -> - Name = id(Address, Port, Profile), - stop_child(Name). -system_name(SysSup) -> - Children = supervisor:which_children(sshd_sup), - system_name(SysSup, Children). +stop_child(Address, Port, Profile) -> + Id = id(Address, Port, Profile), + stop_child(Id). %%%========================================================================= %%% Supervisor callback %%%========================================================================= init(_) -> - {ok, {{one_for_one, 10, 3600}, []}}. + SupFlags = #{strategy => one_for_one, + intensity => 10, + period => 3600 + }, + ChildSpecs = [ + ], + {ok, {SupFlags,ChildSpecs}}. %%%========================================================================= %%% Internal functions %%%========================================================================= child_spec(Address, Port, Profile, Options) -> - Name = id(Address, Port,Profile), - StartFunc = {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, - Restart = temporary, - Shutdown = infinity, - Modules = [ssh_system_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + #{id => id(Address, Port, Profile), + start => {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, + restart => temporary, + shutdown => infinity, + type => supervisor, + modules => [ssh_system_sup] + }. id(Address, Port, Profile) -> {server, ssh_system_sup, Address, Port, Profile}. -system_name([], _ ) -> - undefined; -system_name(SysSup, [{Name, SysSup, _, _} | _]) -> - Name; -system_name(SysSup, [_ | Rest]) -> - system_name(SysSup, Rest). +system_name(SysSup) -> + case lists:keyfind(SysSup, 2, supervisor:which_children(?SSHD_SUP)) of + {Name, SysSup, _, _} -> Name; + false -> undefind + end. + -- cgit v1.2.3 From 92bdf870ec6509eb958535780b8655206478f7db Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 28 Mar 2017 17:20:46 +0200 Subject: ssh: change 'brutal_kill' to timeout' --- lib/ssh/src/ssh_acceptor_sup.erl | 2 +- lib/ssh/src/ssh_sup.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 3ad842f98c..26defcfdbd 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -87,7 +87,7 @@ child_spec(Address, Port, Profile, Options) -> #{id => id(Address, Port, Profile), start => {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, restart => transient, - shutdown => brutal_kill, + shutdown => 5500, %brutal_kill, type => worker, modules => [ssh_acceptor] }. diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 6be809b1bd..26574763e4 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -39,7 +39,7 @@ init(_) -> ChildSpecs = [#{id => Module, start => {Module, start_link, []}, restart => permanent, - shutdown => brutal_kill, + shutdown => 4000, %brutal_kill, type => supervisor, modules => [Module] } -- cgit v1.2.3 From 3bed79615b9702f8335dbe75295c6610b097175e Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 29 Mar 2017 13:13:43 +0200 Subject: ssh: remove dead code and add comments --- lib/ssh/src/ssh.erl | 55 ++++++++--------------------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 680047dffd..aff143dc26 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -416,66 +416,27 @@ handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) -> IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility end; -handle_daemon_args(Address, Opts) when is_list(Address) ; is_atom(Address) -> +handle_daemon_args(Address, Opts) when is_list(Address) ; % IP address in string or a domain + is_atom(Address) % domains could be atoms in inet + -> IP = proplists:get_value(ip, Opts), case inet:parse_strict_address(Address) of + %% check if Address is an IP-address {ok, IP} -> {IP, Opts}; {ok, OtherIP} -> {OtherIP, [{ip,OtherIP}|Opts--[{ip,IP}]]}; _ -> + %% Not an IP-address. Check if it is a host name: case inet:getaddr(Address, family(Opts)) of {ok, IP} -> {Address, Opts}; {ok, OtherIP} -> {Address, [{ip,OtherIP}|Opts--[{ip,IP}]]}; - _ -> {Address, Opts} - end - end. - - --ifdef(hulahopp). -%% Check the Address parameter and set an ip-option in some cases. The -%% Address parameter is left unchanged because ssh:stop_listener and -%% ssh:stop_daemon needs to find the system supervisor by name - -handle_daemon_args(any, Opts) -> - %% Listen to 0.0.0.0. The caller may have set an ip-option. Trust - %% that one in such a case. - {any, Opts}; - -handle_daemon_args(loopback, Opts) -> - %% Listen to a loopback address. Let the underlying layers decide - %% in case the caller hasn't set the ip-option. - {loopback, ensure_ip_option(loopback,Opts)}; - -handle_daemon_args(IP, Opts) when is_tuple(IP) -> - %% An IP address in Erlang tuple format: - {IP, ensure_ip_option(IP,Opts)}; - -handle_daemon_args(Address, Opts) when is_list(Address) ; is_atom(Address) -> - %% This might be a host name, an FQDN, an IP address in string format ("127.1.1.1") - %% etc. It might be a string or an atom since inet:hostname() is defined in that way - case inet:parse_strict_address(Address) of - {ok, IP} -> - {Address, ensure_ip_option(IP,Opts)}; - _ -> - %% Try to lookup as a hostname: - case inet:getaddr(Address, family(Opts)) of - {ok, IP} -> - {Address, ensure_ip_option(IP,Opts)}; _ -> - %% Give up and let the underlying system handle this + %% Not a Host name and not an IP address, let + %% inet and the OS later figure out what it + %% could be {Address, Opts} end end. - -%% Add an ip-option if not already present. -ensure_ip_option(Address, Opts) -> - case proplists:get_value(ip, Opts) of - undefined -> [{ip,Address}|Opts]; - _ -> Opts - end. --endif. - - %% Has the caller indicated the address family? family(Opts) -> family(Opts, inet). -- cgit v1.2.3 From a9fc169d5d0ca63a3062800429e3a16169901ab3 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 29 Mar 2017 15:08:48 +0200 Subject: ssh: Change handling of IP addresses, 'any' and names in sup structure --- lib/ssh/src/ssh.erl | 100 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 30 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index aff143dc26..8c802d46eb 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -26,6 +26,7 @@ -include("ssh_connect.hrl"). -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). +-include_lib("kernel/include/inet.hrl"). -export([start/0, start/1, stop/0, connect/2, connect/3, connect/4, @@ -120,7 +121,7 @@ connect(Host, Port, UserOptions) when is_integer(Port), is_list(UserOptions) -> connect(Host, Port, UserOptions, infinity). -connect(Host, Port, UserOptions, Timeout) when is_integer(Port), +connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), Port>0, is_list(UserOptions) -> case ssh_options:handle_options(client, UserOptions) of @@ -130,6 +131,7 @@ connect(Host, Port, UserOptions, Timeout) when is_integer(Port), {_, Transport, _} = TransportOpts = ?GET_OPT(transport, Options), ConnectionTimeout = ?GET_OPT(connect_timeout, Options), SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)], + Host = mangle_connect_address(Host0, SocketOpts), try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), @@ -227,7 +229,7 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> try {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), #{} = Options0 = ssh_options:handle_options(server, UserOptions), - + {{Host,Port}, ListenSocket} = open_listen_socket(Host1, Port0, Options0), @@ -266,27 +268,23 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> - [{Name,Port,Profile}] = - [{Nam,Prt,Prf} + [{IP,Port,Profile}] = + [{IP,Prt,Prf} || {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid), - Nam <- [case inet:parse_strict_address(Hst) of - {ok,IP} -> IP; - _ when Hst=="any" -> any; - _ when Hst=="loopback" -> loopback; - _ -> Hst - end] + IP <- [case inet:parse_strict_address(Hst) of + {ok,IP} -> IP; + _ -> Hst + end] ], {ok, [{port,Port}, - {name,Name}, + {ip,IP}, {profile,Profile} ]}; _ -> {error,bad_daemon_ref} end. - - %%-------------------------------------------------------------------- -spec stop_listener(daemon_ref()) -> ok. -spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. @@ -298,8 +296,14 @@ stop_listener(SysSup) -> ssh_system_sup:stop_listener(SysSup). stop_listener(Address, Port) -> stop_listener(Address, Port, ?DEFAULT_PROFILE). +stop_listener(any, Port, Profile) -> + map_ip(fun(IP) -> + ssh_system_sup:stop_listener(IP, Port, Profile) + end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]); stop_listener(Address, Port, Profile) -> - ssh_system_sup:stop_listener(Address, Port, Profile). + map_ip(fun(IP) -> + ssh_system_sup:stop_listener(IP, Port, Profile) + end, {address,Address}). %%-------------------------------------------------------------------- -spec stop_daemon(daemon_ref()) -> ok. @@ -312,9 +316,15 @@ stop_listener(Address, Port, Profile) -> stop_daemon(SysSup) -> ssh_system_sup:stop_system(SysSup). stop_daemon(Address, Port) -> - ssh_system_sup:stop_system(Address, Port, ?DEFAULT_PROFILE). + stop_daemon(Address, Port, ?DEFAULT_PROFILE). +stop_daemon(any, Port, Profile) -> + map_ip(fun(IP) -> + ssh_system_sup:stop_system(IP, Port, Profile) + end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]); stop_daemon(Address, Port, Profile) -> - ssh_system_sup:stop_system(Address, Port, Profile). + map_ip(fun(IP) -> + ssh_system_sup:stop_system(IP, Port, Profile) + end, {address,Address}). %%-------------------------------------------------------------------- -spec shell(inet:socket() | string()) -> _. @@ -397,6 +407,9 @@ default_algorithms() -> %% consideration %% +%% The handle_daemon_args/2 function basically only sets the ip-option in Opts +%% so that it is correctly set when opening the listening socket. + handle_daemon_args(any, Opts) -> case proplists:get_value(ip, Opts) of undefined -> {any, Opts}; @@ -477,20 +490,17 @@ is_tcp_socket(Socket) -> end. %%%---------------------------------------------------------------- -open_listen_socket(Host0, Port0, Options0) -> - case ?GET_SOCKET_OPT(fd, Options0) of - undefined -> - {ok,LSock} = ssh_acceptor:listen(Port0, Options0), - {ok,{_LHost,LPort}} = inet:sockname(LSock), - {{_LHost,LPort}, LSock}; -%% {{Host0,LPort}, LSock}; - - Fd when is_integer(Fd) -> - %% Do gen_tcp:listen with the option {fd,Fd}: - {ok,LSock} = ssh_acceptor:listen(0, Options0), - {ok,{LHost,LPort}} = inet:sockname(LSock), - {{LHost,LPort}, LSock} - end. +open_listen_socket(_Host0, Port0, Options0) -> + {ok,LSock} = + case ?GET_SOCKET_OPT(fd, Options0) of + undefined -> + ssh_acceptor:listen(Port0, Options0); + Fd when is_integer(Fd) -> + %% Do gen_tcp:listen with the option {fd,Fd}: + ssh_acceptor:listen(0, Options0) + end, + {ok,{LHost,LPort}} = inet:sockname(LSock), + {{LHost,LPort}, LSock}. %%%---------------------------------------------------------------- finalize_start(Host, Port, Profile, Options0, F) -> @@ -509,3 +519,33 @@ finalize_start(Host, Port, Profile, Options0, F) -> end. %%%---------------------------------------------------------------- +map_ip(Fun, {address,IP}) when is_tuple(IP) -> + Fun(IP); +map_ip(Fun, {address,Address}) -> + IPs = try {ok,#hostent{h_addr_list=IP0s}} = inet:gethostbyname(Address), + IP0s + catch + _:_ -> [] + end, + map_ip(Fun, IPs); +map_ip(Fun, IPs) -> + lists:map(Fun, IPs). + +%%%---------------------------------------------------------------- +mangle_connect_address(A, SockOpts) -> + mangle_connect_address1(A, proplists:get_value(inet6,SockOpts,false)). + +loopback(true) -> {0,0,0,0,0,0,0,1}; +loopback(false) -> {127,0,0,1}. + +mangle_connect_address1( loopback, V6flg) -> loopback(V6flg); +mangle_connect_address1( any, V6flg) -> loopback(V6flg); +mangle_connect_address1({0,0,0,0}, _) -> loopback(false); +mangle_connect_address1({0,0,0,0,0,0,0,0}, _) -> loopback(true); +mangle_connect_address1( IP, _) when is_tuple(IP) -> IP; +mangle_connect_address1(A, _) -> + case catch inet:parse_address(A) of + {ok, {0,0,0,0}} -> loopback(false); + {ok, {0,0,0,0,0,0,0,0}} -> loopback(true); + _ -> A + end. -- cgit v1.2.3 From 7ad21ca66f5a46be231fffe884ac2c3b5d97c7ae Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 4 Apr 2017 19:53:05 +0200 Subject: ssh: document what happens when ssh:daemon sets both HostAddr and ip option The idea is that the HostAddress argument takes precedence over an ip-option. However, an ip-option overrides the 'any' HostAddr. This fixes the case of dameon(Port, [{ip,IP}..] in a non-surprising way. --- lib/ssh/src/ssh.erl | 82 ++++++----------------------------------------------- 1 file changed, 8 insertions(+), 74 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 8c802d46eb..3e80a04b70 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -225,7 +225,8 @@ daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 -> daemon(any, Port, UserOptions). -daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> +daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535, + Host0 == any ; Host0 == loopback ; is_tuple(Host0) -> try {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), #{} = Options0 = ssh_options:handle_options(server, UserOptions), @@ -259,7 +260,11 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> {error,Error}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} - end. + end; + +daemon(_, _, _) -> + {error, badarg}. + %%-------------------------------------------------------------------- @@ -378,35 +383,6 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- - -%% - if Address is 'any' and no ip-option is present, the name is -%% 'any' and the socket will listen to all addresses -%% -%% - if Address is 'any' and an ip-option is present, the name is -%% set to the value of the ip-option and the socket will listen -%% to that address -%% -%% - if Address is 'loopback' and no ip-option is present, the name -%% is 'loopback' and an loopback address will be choosen by the -%% underlying layers -%% -%% - if Address is 'loopback' and an ip-option is present, the name -%% is set to the value of the ip-option kept and the socket will -%% listen to that address -%% -%% - if Address is an ip-address, that ip-address is the name and -%% the listening address. An ip-option will be discarded. -%% -%% - if Address is a HostName, and that resolves to an ip-address, -%% that ip-address is the name and the listening address. An -%% ip-option will be discarded. -%% -%% - if Address is a string or an atom other than thoose defined -%% above, that Address will be the name and the listening address -%% will be choosen by the lower layers taking an ip-option in -%% consideration -%% - %% The handle_daemon_args/2 function basically only sets the ip-option in Opts %% so that it is correctly set when opening the listening socket. @@ -416,53 +392,11 @@ handle_daemon_args(any, Opts) -> IP -> {IP, Opts} end; -handle_daemon_args(loopback, Opts) -> - case proplists:get_value(ip, Opts) of - undefined -> {loopback, [{ip,loopback}|Opts]}; - IP -> {IP, Opts} - end; - -handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) -> +handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) ; IPaddr == loopback -> case proplists:get_value(ip, Opts) of undefined -> {IPaddr, [{ip,IPaddr}|Opts]}; IPaddr -> {IPaddr, Opts}; IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility - end; - -handle_daemon_args(Address, Opts) when is_list(Address) ; % IP address in string or a domain - is_atom(Address) % domains could be atoms in inet - -> - IP = proplists:get_value(ip, Opts), - case inet:parse_strict_address(Address) of - %% check if Address is an IP-address - {ok, IP} -> {IP, Opts}; - {ok, OtherIP} -> {OtherIP, [{ip,OtherIP}|Opts--[{ip,IP}]]}; - _ -> - %% Not an IP-address. Check if it is a host name: - case inet:getaddr(Address, family(Opts)) of - {ok, IP} -> {Address, Opts}; - {ok, OtherIP} -> {Address, [{ip,OtherIP}|Opts--[{ip,IP}]]}; - _ -> - %% Not a Host name and not an IP address, let - %% inet and the OS later figure out what it - %% could be - {Address, Opts} - end - end. - -%% Has the caller indicated the address family? -family(Opts) -> - family(Opts, inet). - -family(Opts, Default) -> - case proplists:get_value(inet,Opts) of - true -> inet; - inet -> inet; - inet6 -> inet6; - _ -> case proplists:get_value(inet6,Opts) of - true -> inet6; - _ -> Default - end end. %%%---------------------------------------------------------------- -- cgit v1.2.3 From 43dfbf7533ff9d176051231e52d308613a8d4bd1 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 31 Jan 2017 13:59:19 +0100 Subject: ssh: added message_queue_data,off_heap to spawn Seems to solve some test case problems when heavily loaded --- lib/ssh/src/ssh_connection_handler.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ff94e5dfb6..84adf952e6 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -80,7 +80,11 @@ ) -> {ok, pid()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . start_link(Role, Socket, Options) -> - {ok, proc_lib:spawn_link(?MODULE, init_connection_handler, [Role, Socket, Options])}. + {ok, proc_lib:spawn_opt(?MODULE, + init_connection_handler, + [Role, Socket, Options], + [link, {message_queue_data,off_heap}] + )}. %%-------------------------------------------------------------------- -- cgit v1.2.3 From a0ab002c9f23865b96595a4a95750d85801f93d1 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 12 Apr 2017 19:07:27 +0200 Subject: ssh: change next_event to postpone --- lib/ssh/src/ssh_connection_handler.erl | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 84adf952e6..ca9790ba0d 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -813,7 +813,7 @@ handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_inter send_bytes(Reply, D), {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}}; not_ok -> - {next_state, {userauth,client}, D, [{next_event, internal, Msg}]} + {next_state, {userauth,client}, D, [postpone]} end; handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) -> @@ -842,14 +842,14 @@ handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_inte {next_state, {connected,server}, D#data{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}}}; -handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, +handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, #data{ssh_params = Ssh0} = D0) -> Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Ssh0#ssh.userauth_preference, Method =/= "keyboard-interactive"], D = D0#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}, - {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; + {next_state, {userauth,client}, D, [postpone]}; -handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, +handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, #data{ssh_params = Ssh0} = D0) -> Opts = Ssh0#ssh.opts, D = case ?GET_OPT(password, Opts) of @@ -859,23 +859,23 @@ handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_ D0#data{ssh_params = Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} % FIXME:intermodule dependency end, - {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; + {next_state, {userauth,client}, D, [postpone]}; -handle_event(_, Msg=#ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> - {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; +handle_event(_, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> + {next_state, {userauth,client}, D, [postpone]}; -handle_event(_, Msg=#ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) -> - {next_state, {userauth_keyboard_interactive,client}, D, [{next_event, internal, Msg}]}; +handle_event(_, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) -> + {next_state, {userauth_keyboard_interactive,client}, D, [postpone]}; %%% ######## {connected, client|server} #### -handle_event(_, {#ssh_msg_kexinit{},_} = Event, {connected,Role}, D0) -> +handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params), D = D0#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}, send_bytes(SshPacket, D), - {next_state, {kexinit,Role,renegotiate}, D, [{next_event, internal, Event}]}; + {next_state, {kexinit,Role,renegotiate}, D, [postpone]}; handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) -> {disconnect, _, {{replies,Replies}, _}} = -- cgit v1.2.3 From 24cce98e38f1c8d36abb67bc7aca0668cf64c1ad Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 12 Apr 2017 19:56:12 +0200 Subject: ssh: replace deprecated crypto:rand_uniform --- lib/ssh/src/ssh_transport.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 54ea80c727..6b47868d5c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -181,7 +181,7 @@ ssh_vsn() -> end. random_id(Nlo, Nup) -> - [crypto:rand_uniform($a,$z+1) || _<- lists:duplicate(crypto:rand_uniform(Nlo,Nup+1),x) ]. + [$a + rand:uniform($z-$a+1) - 1 || _<- lists:duplicate(Nlo + rand:uniform(Nup-Nlo+1) - 1, x)]. hello_version_msg(Data) -> [Data,"\r\n"]. @@ -1041,7 +1041,7 @@ padding_length(Size, #ssh{encrypt_block_size = BlockSize, end, PadBlockSize = max(BlockSize,4), MaxExtraBlocks = (max(RandomLengthPadding,MinPaddingLen) - MinPaddingLen) div PadBlockSize, - ExtraPaddingLen = try crypto:rand_uniform(0,MaxExtraBlocks)*PadBlockSize + ExtraPaddingLen = try (rand:uniform(MaxExtraBlocks+1) - 1) * PadBlockSize catch _:_ -> 0 end, MinPaddingLen + ExtraPaddingLen. -- cgit v1.2.3 From 5e2f2fb80636e858877fa4d4ff2d9834bc1cd616 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 13 Apr 2017 14:38:50 +0200 Subject: ssh: re-write to use callback init/1 --- lib/ssh/src/ssh_connection_handler.erl | 144 +++++++++++++++------------------ 1 file changed, 67 insertions(+), 77 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 84adf952e6..11d182849c 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -60,7 +60,7 @@ ]). %%% Behaviour callbacks --export([callback_mode/0, handle_event/4, terminate/3, +-export([init/1, callback_mode/0, handle_event/4, terminate/3, format_status/2, code_change/4]). %%% Exports not intended to be used :). They are used for spawning and tests @@ -362,71 +362,79 @@ renegotiate_data(ConnectionHandler) -> ) -> no_return(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . init_connection_handler(Role, Socket, Opts) -> - process_flag(trap_exit, true), - S0 = init_process_state(Role, Socket, Opts), - try - {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), - S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag - } - of - S -> - gen_statem:enter_loop(?MODULE, - [], %%[{debug,[trace,log,statistics,debug]} || Role==server], - {hello,Role}, - S) - catch - _:Error -> - gen_statem:enter_loop(?MODULE, - [], - {init_error,Error}, - S0) - end. - - -init_process_state(Role, Socket, Opts) -> - D = #data{connection_state = - C = #connection{channel_cache = ssh_channel:cache_create(), - channel_id_seed = 0, - port_bindings = [], - requests = [], - options = Opts}, - starter = ?GET_INTERNAL_OPT(user_pid, Opts), - socket = Socket, - opts = Opts - }, - case Role of - client -> - %% Start the renegotiation timers - timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), - timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), - cache_init_idle_timer(D); - server -> - cache_init_idle_timer( - D#data{connection_state = init_connection(Role, C, Opts)} - ) + case init([Role, Socket, Opts]) of + {ok, StartState, D} -> + process_flag(trap_exit, true), + gen_statem:enter_loop(?MODULE, + [], %%[{debug,[trace,log,statistics,debug]} || Role==server], + StartState, + D); + + {stop, {error,enotconn}} -> + %% Handles the abnormal sequence: + %% SYN-> + %% <-SYNACK + %% ACK-> + %% RST-> + exit({shutdown, "TCP connection to server was prematurely closed by the client"}); + + {stop, OtherError} -> + exit({shutdown, {init,OtherError}}) end. -init_connection(server, C = #connection{}, Opts) -> - Sups = ?GET_INTERNAL_OPT(supervisors, Opts), - SystemSup = proplists:get_value(system_sup, Sups), - SubSystemSup = proplists:get_value(subsystem_sup, Sups), - ConnectionSup = proplists:get_value(connection_sup, Sups), +init([Role,Socket,Opts]) -> + case inet:peername(Socket) of + {ok, PeerAddr} -> + {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), + C = #connection{channel_cache = ssh_channel:cache_create(), + channel_id_seed = 0, + port_bindings = [], + requests = [], + options = Opts}, + D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), + socket = Socket, + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag, + ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts), + opts = Opts + }, + D = case Role of + client -> + %% Start the renegotiation timers + timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), + timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), + cache_init_idle_timer( + D0#data{connection_state = C} + ); + server -> + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + cache_init_idle_timer( + D0#data{connection_state = + C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), + exec = ?GET_OPT(exec, Opts), + system_supervisor = proplists:get_value(system_sup, Sups), + sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), + connection_supervisor = proplists:get_value(connection_sup, Sups) + }}) + end, + {ok, {hello,Role}, D}; + + {error,Error} -> + {stop, Error} + end. - C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), - exec = ?GET_OPT(exec, Opts), - system_supervisor = SystemSup, - sub_system_supervisor = SubSystemSup, - connection_supervisor = ConnectionSup - }. init_ssh_record(Role, Socket, Opts) -> - {ok, PeerAddr} = inet:peername(Socket), + %% Export of this internal function is + %% intended for low-level protocol test suites + {ok,PeerAddr} = inet:peername(Socket), + init_ssh_record(Role, Socket, PeerAddr, Opts). + +init_ssh_record(Role, _Socket, PeerAddr, Opts) -> KeyCb = ?GET_OPT(key_cb, Opts), AuthMethods = case Role of @@ -481,8 +489,7 @@ init_ssh_record(Role, Socket, Opts) -> -type renegotiate_flag() :: init | renegotiate. -type state_name() :: - {init_error,any()} - | {hello, role()} + {hello, role()} | {kexinit, role(), renegotiate_flag()} | {key_exchange, role(), renegotiate_flag()} | {key_exchange_dh_gex_init, server, renegotiate_flag()} @@ -504,26 +511,9 @@ init_ssh_record(Role, Socket, Opts) -> %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -%%% ######## Error in the initialisation #### - callback_mode() -> handle_event_function. -handle_event(_, _Event, {init_error,Error}, _) -> - case Error of - {badmatch,{error,enotconn}} -> - %% Handles the abnormal sequence: - %% SYN-> - %% <-SYNACK - %% ACK-> - %% RST-> - {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}}; - - OtherError -> - {stop, {shutdown,{init,OtherError}}} - end; - - %%% ######## {hello, client|server} #### %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> -- cgit v1.2.3 From 0d91185b9093de3a254f0a869e7dadfcfa79295d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 18 Apr 2017 12:15:58 +0200 Subject: ssh: fix dialyzer errors --- lib/ssh/src/ssh_connection_handler.erl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 1a8e022da8..84bb7dc23f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -337,8 +337,7 @@ renegotiate_data(ConnectionHandler) -> transport_protocol :: atom(), % ex: tcp transport_cb :: atom(), % ex: gen_tcp transport_close_tag :: atom(), % ex: tcp_closed - ssh_params :: #ssh{} - | undefined, + ssh_params :: #ssh{}, socket :: inet:socket(), decrypted_data_buffer = <<>> :: binary(), encrypted_data_buffer = <<>> :: binary(), @@ -370,7 +369,7 @@ init_connection_handler(Role, Socket, Opts) -> StartState, D); - {stop, {error,enotconn}} -> + {stop, enotconn} -> %% Handles the abnormal sequence: %% SYN-> %% <-SYNACK @@ -394,21 +393,20 @@ init([Role,Socket,Opts]) -> requests = [], options = Opts}, D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), + connection_state = C, socket = Socket, transport_protocol = Protocol, transport_cb = Callback, transport_close_tag = CloseTag, ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts), - opts = Opts + opts = Opts }, D = case Role of client -> %% Start the renegotiation timers timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), - cache_init_idle_timer( - D0#data{connection_state = C} - ); + cache_init_idle_timer(D0); server -> Sups = ?GET_INTERNAL_OPT(supervisors, Opts), cache_init_idle_timer( -- cgit v1.2.3 From 192379acc9e112f393ad18e20f4951d1e318a7a0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 26 Apr 2017 11:49:43 +0200 Subject: ssh: Correction of misspelled type --- lib/ssh/src/ssh.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 315310f700..c7ed11895c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -134,7 +134,7 @@ role :: client | role(), peer :: undefined | {inet:hostname(), - {inet:ip_adress(),inet:port_number()}}, %% string version of peer address + {inet:ip_address(),inet:port_number()}}, %% string version of peer address c_vsn, %% client version {Major,Minor} s_vsn, %% server version {Major,Minor} -- cgit v1.2.3 From c0d2e134f90ddd3fd2f5b0f9a94a5b0d55c93416 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 10 Apr 2017 13:19:37 +0200 Subject: ssh: clearify public key option handling Change the handling of option pref_public_key_algs so that the same checks are not performed twice. --- lib/ssh/src/ssh_auth.erl | 47 ++++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 88c8144063..51df54341f 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -175,6 +175,7 @@ service_request_msg(Ssh) -> %%%---------------------------------------------------------------- init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> + %% Client side case ?GET_OPT(user, Opts) of undefined -> ErrStr = "Could not determine the users name", @@ -183,25 +184,17 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> description = ErrStr}); User -> - Msg = #ssh_msg_userauth_request{user = User, - service = "ssh-connection", - method = "none", - data = <<>>}, - Algs0 = ?GET_OPT(pref_public_key_algs, Opts), - %% The following line is not strictly correct. The call returns the - %% supported HOST key types while we are interested in USER keys. However, - %% they "happens" to be the same (for now). This could change.... - %% There is no danger as long as the set of user keys is a subset of the set - %% of host keys. - CryptoSupported = ssh_transport:supported_algorithms(public_key), - Algs = [A || A <- Algs0, - lists:member(A, CryptoSupported)], - - Prefs = method_preference(Algs), - ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, - userauth_preference = Prefs, - userauth_methods = none, - service = "ssh-connection"}) + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = "none", + data = <<>>}, + Ssh#ssh{user = User, + userauth_preference = + method_preference(?GET_OPT(pref_public_key_algs, Opts)), + userauth_methods = none, + service = "ssh-connection"} + ) end. %%%---------------------------------------------------------------- @@ -453,14 +446,14 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -method_preference(Algs) -> - lists:foldr(fun(A, Acc) -> - [{"publickey", ?MODULE, publickey_msg, [A]} | Acc] - end, - [{"password", ?MODULE, password_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} - ], - Algs). +method_preference(PubKeyAlgs) -> + %% PubKeyAlgs: List of user (client) public key algorithms to try to use. + %% All of the acceptable algorithms is the default values. + PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- PubKeyAlgs], + NonPKmethods = [{"password", ?MODULE, password_msg, []}, + {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} + ], + PubKeyDefs ++ NonPKmethods. check_password(User, Password, Opts, Ssh) -> case ?GET_OPT(pwdfun, Opts) of -- cgit v1.2.3 From 29d7533c715f972ee996382c2c45cc0c055e10d2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 10 Apr 2017 16:25:06 +0200 Subject: ssh: Implement ext-info extension. draft-ietf-curdle-ssh-ext-info This is only a draft extension, but it is quite stable and already supported by some implementations. OpenSSH has had it for some year now. --- lib/ssh/src/ssh.hrl | 7 +- lib/ssh/src/ssh_connection_handler.erl | 142 ++++++++++++++++----- lib/ssh/src/ssh_dbg.erl | 5 +- lib/ssh/src/ssh_message.erl | 33 +++++ lib/ssh/src/ssh_options.erl | 22 +++- lib/ssh/src/ssh_transport.erl | 221 ++++++++++++++++++++++----------- lib/ssh/src/ssh_transport.hrl | 15 +++ 7 files changed, 334 insertions(+), 111 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index c7ed11895c..1a95bb27e7 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -145,6 +145,9 @@ c_keyinit, %% binary payload of kexinit packet s_keyinit, %% binary payload of kexinit packet + send_ext_info, %% May send ext-info to peer + recv_ext_info, %% Expect ext-info from peer + algorithms, %% #alg{} kex, %% key exchange algorithm @@ -216,7 +219,9 @@ compress, decompress, c_lng, - s_lng + s_lng, + send_ext_info, + recv_ext_info }). -record(ssh_key, diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 84bb7dc23f..0ff7c9b3a1 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -453,7 +453,9 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> PeerName = case ?GET_INTERNAL_OPT(host, Opts) of PeerIP when is_tuple(PeerIP) -> inet_parse:ntoa(PeerIP); - PeerName0 -> + PeerName0 when is_atom(PeerName0) -> + atom_to_list(PeerName0); + PeerName0 when is_list(PeerName0) -> PeerName0 end, S0#ssh{c_vsn = Vsn, @@ -493,6 +495,7 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> | {key_exchange_dh_gex_init, server, renegotiate_flag()} | {key_exchange_dh_gex_reply, client, renegotiate_flag()} | {new_keys, role()} + | {ext_info, role(), renegotiate_flag()} | {service_request, role()} | {userauth, role()} | {userauth_keyboard_interactive, role()} @@ -589,13 +592,17 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), send_bytes(KexdhReply, D), - {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), + {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), + send_bytes(ExtInfo, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> - {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), + {ok, NewKeys, Ssh1} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + send_bytes(ExtInfo, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- diffie-hellman group exchange @@ -620,13 +627,17 @@ handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), send_bytes(KexEcdhReply, D), - {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), + {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), + send_bytes(ExtInfo, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> - {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), + {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + send_bytes(ExtInfo, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; @@ -635,8 +646,10 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), send_bytes(KexGexReply, D), - {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), + {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), + send_bytes(ExtInfo, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; @@ -645,30 +658,60 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), send_bytes(NewKeys, D), - {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}}; + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + send_bytes(ExtInfo, D), + {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%% ######## {new_keys, client|server} #### %% First key exchange round: -handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D) -> {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), - Ssh = case Role of - client -> - {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1), - send_bytes(MsgReq, D), - Ssh2; - server -> - Ssh1 - end, - {next_state, {service_request,Role}, D#data{ssh_params=Ssh}}; + %% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1), + %% send_bytes(ExtInfo, D), + {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1), + send_bytes(MsgReq, D), + {next_state, {ext_info,client,init}, D#data{ssh_params=Ssh}}; + +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, D) -> + {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), + %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + %% send_bytes(ExtInfo, D), + {next_state, {ext_info,server,init}, D#data{ssh_params=Ssh}}; %% Subsequent key exchange rounds (renegotiation): handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) -> {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), - {next_state, {connected,Role}, D#data{ssh_params=Ssh}}; + %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + %% send_bytes(ExtInfo, D), + {next_state, {ext_info,Role,renegotiate}, D#data{ssh_params=Ssh}}; + + +%%% ######## {ext_info, client|server, init|renegotiate} #### + +handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,init}, D0) -> + D = handle_ssh_msg_ext_info(Msg, D0), + {next_state, {service_request,Role}, D}; + +handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,renegotiate}, D0) -> + D = handle_ssh_msg_ext_info(Msg, D0), + {next_state, {connected,Role}, D}; + +handle_event(_, #ssh_msg_newkeys{}=Msg, {ext_info,_Role,renegotiate}, D) -> + {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), + {keep_state, D#data{ssh_params = Ssh}}; + + +handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) -> + %% If something else arrives, goto next state and handle the event in that one + {next_state, {service_request,Role}, D, [postpone]}; -%%% ######## {service_request, client|server} +handle_event(internal, Msg, {ext_info,Role,renegotiate}, D) when is_tuple(Msg) -> + %% If something else arrives, goto next state and handle the event in that one + {next_state, {connected,Role}, D, [postpone]}; + +%%% ######## {service_request, client|server} #### handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D) -> case ServiceName of @@ -747,6 +790,11 @@ handle_event(_, end; %%---- userauth success to client +handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) -> + %% FIXME: need new state to receive this msg! + D = handle_ssh_msg_ext_info(Msg, D0), + {keep_state, D}; + handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) -> D#data.starter ! ssh_connected, {next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}}; @@ -849,6 +897,11 @@ handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info end, {next_state, {userauth,client}, D, [postpone]}; +handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth_keyboard_interactive_info_response, client}, D0) -> + %% FIXME: need new state to receive this msg! + D = handle_ssh_msg_ext_info(Msg, D0), + {keep_state, D}; + handle_event(_, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> {next_state, {userauth,client}, D, [postpone]}; @@ -1080,26 +1133,34 @@ handle_event({call,_}, _, StateName, _) when StateName /= {connected,server}, StateName /= {connected,client} -> {keep_state_and_data, [postpone]}; -handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, {connected,_}, D0) -> +handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0), %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; -handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, {connected,_}, D0) -> +handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> D = handle_request(ChannelId, Type, Data, true, From, D0), %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; -handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, {connected,_}, D0) -> +handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> {{replies, Replies}, Connection} = ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), {Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}), start_channel_request_timer(ChannelId, From, Timeout), % FIXME: No message exchange so why? {keep_state, D, Repls}; -handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) -> +handle_event({call,From}, {eof, ChannelId}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id, sent_close = false} -> D = send_msg(ssh_connection:channel_eof_msg(Id), D0), @@ -1110,8 +1171,9 @@ handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) -> handle_event({call,From}, {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, - {connected,_}, - D0) -> + StateName, + D0) when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> erlang:monitor(process, ChannelPid), {ChannelId, D1} = new_channel_id(D0), D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId, @@ -1131,7 +1193,9 @@ handle_event({call,From}, start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_cancel_idle_timer(D)}; -handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) -> +handle_event({call,From}, {send_window, ChannelId}, StateName, D) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{send_window_size = WinSize, send_packet_size = Packsize} -> @@ -1141,7 +1205,9 @@ handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) -> end, {keep_state_and_data, [{reply,From,Reply}]}; -handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) -> +handle_event({call,From}, {recv_window, ChannelId}, StateName, D) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_packet_size = Packsize} -> @@ -1151,7 +1217,9 @@ handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) -> end, {keep_state_and_data, [{reply,From,Reply}]}; -handle_event({call,From}, {close, ChannelId}, {connected,_}, D0) -> +handle_event({call,From}, {close, ChannelId}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id} = Channel -> D1 = send_msg(ssh_connection:channel_close_msg(Id), D0), @@ -1323,7 +1391,8 @@ handle_event(Type, Ev, StateName, D) -> Descr = case catch atom_to_list(element(1,Ev)) of "ssh_msg_" ++_ when Type==internal -> - "Message in wrong state"; +%% "Message in wrong state"; +lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); _ -> "Internal error" end, @@ -1516,6 +1585,8 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> send_bytes(Bytes, State), State#data{ssh_params=Ssh}. +send_bytes("", _D) -> + ok; send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> _ = Transport:send(Socket, Bytes), ok. @@ -1621,6 +1692,19 @@ kex(_) -> undefined. cache(#data{connection_state=C}) -> C#connection.channel_cache. +%%%---------------------------------------------------------------- +handle_ssh_msg_ext_info(#ssh_msg_ext_info{}, D=#data{ssh_params = #ssh{recv_ext_info=false}} ) -> + % The peer sent this although we didn't allow it! + D; +handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> + lists:foldl(fun ext_info/2, D0, Data). + +%% ext_info({ExtName,ExtValue}, D0) -> +%% D0; +ext_info(_, D0) -> + %% Not implemented + D0. + %%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 0345bbdea7..9431bf1817 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -56,7 +56,7 @@ messages(Write, MangleArg) when is_function(Write,2), dbg_ssh_messages() -> dbg:tp(ssh_message,encode,1, x), dbg:tp(ssh_message,decode,1, x), - dbg:tpl(ssh_transport,select_algorithm,3, x), + dbg:tpl(ssh_transport,select_algorithm,4, x), dbg:tp(ssh_transport,hello_version_msg,1, x), dbg:tp(ssh_transport,handle_hello_version,1, x). @@ -77,7 +77,7 @@ msg_formater({trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> msg_formater({trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) -> D; -msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg},TS}, D) -> +msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) -> fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D); msg_formater({trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> @@ -160,6 +160,7 @@ shrink_bin(X) -> X. ?wr_record(ssh_msg_kexdh_init); ?wr_record(ssh_msg_kexdh_reply); ?wr_record(ssh_msg_newkeys); +?wr_record(ssh_msg_ext_info); ?wr_record(ssh_msg_kex_dh_gex_request); ?wr_record(ssh_msg_kex_dh_gex_request_old); ?wr_record(ssh_msg_kex_dh_gex_group); diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 562f040477..56f678876c 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -215,6 +215,16 @@ encode(#ssh_msg_service_accept{ }) -> <>; +encode(#ssh_msg_ext_info{ + nr_extensions = N, + data = Data + }) -> + lists:foldl(fun({ExtName,ExtVal}, Acc) -> + <> + end, + <>, + Data); + encode(#ssh_msg_newkeys{}) -> <>; @@ -435,6 +445,18 @@ decode(<>) -> num_responses = Num, data = Data}; +decode(<>) -> + Data = bin_foldr( + fun(Bin,Acc) when length(Acc) == N -> + {Bin,Acc}; + (<>, Acc) -> + {Rest,[{binary_to_list(V0),binary_to_list(V1)}|Acc]} + end, [], BinData), + #ssh_msg_ext_info{ + nr_extensions = N, + data = Data + }; + %%% Keyexchange messages decode(<>) -> decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10); @@ -537,17 +559,28 @@ decode(< + lists:reverse(bin_foldl(Fun, Acc, Bin)). + +bin_foldl(_, Acc, <<>>) -> Acc; +bin_foldl(Fun, Acc0, Bin0) -> + {Bin,Acc} = Fun(Bin0,Acc0), + bin_foldl(Fun, Acc, Bin). + +%%%---------------------------------------------------------------- decode_keyboard_interactive_prompts(<<>>, Acc) -> lists:reverse(Acc); decode_keyboard_interactive_prompts(<>, Acc) -> decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]). +%%%---------------------------------------------------------------- erl_boolean(0) -> false; erl_boolean(1) -> true. +%%%---------------------------------------------------------------- decode_kex_init(<>, Acc, 0) -> list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc])); decode_kex_init(<>, Acc, 0) -> diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index ee3cdbb8a0..6e898b4fde 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -614,11 +614,23 @@ default(common) -> }, {max_random_length_padding, def} => - #{default => ?MAX_RND_PADDING_LEN, - chk => fun check_non_neg_integer/1, - class => user_options - } - }. + #{default => ?MAX_RND_PADDING_LEN, + chk => fun check_non_neg_integer/1, + class => user_options + }, + + {send_ext_info, def} => + #{default => true, + chk => fun erlang:is_boolean/1, + class => user_options + }, + + {recv_ext_info, def} => + #{default => true, + chk => fun erlang:is_boolean/1, + class => user_options + } + }. %%%================================================================ diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 6b47868d5c..d623d24529 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -38,6 +38,7 @@ handle_hello_version/1, key_exchange_init_msg/1, key_init/3, new_keys_message/1, + ext_info_message/1, handle_kexinit_msg/3, handle_kexdh_init/2, handle_kex_dh_gex_group/2, handle_kex_dh_gex_init/2, handle_kex_dh_gex_reply/2, handle_new_keys/2, handle_kex_dh_gex_request/2, @@ -230,7 +231,7 @@ key_exchange_init_msg(Ssh0) -> kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) -> Random = ssh_bits:random(16), PrefAlgs = ?GET_OPT(preferred_algorithms, Opts), - kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs). + kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs, Opts). key_init(client, Ssh, Value) -> Ssh#ssh{c_keyinit = Value}; @@ -238,10 +239,11 @@ key_init(server, Ssh, Value) -> Ssh#ssh{s_keyinit = Value}. -kexinit_message(_Role, Random, Algs, HostKeyAlgs) -> +kexinit_message(Role, Random, Algs, HostKeyAlgs, Opts) -> #ssh_msg_kexinit{ cookie = Random, - kex_algorithms = to_strings( get_algs(kex,Algs) ), + kex_algorithms = to_strings( get_algs(kex,Algs) ) + ++ kex_ext_info(Role,Opts), server_host_key_algorithms = HostKeyAlgs, encryption_algorithms_client_to_server = c2s(cipher,Algs), encryption_algorithms_server_to_client = s2c(cipher,Algs), @@ -263,39 +265,42 @@ get_algs(Key, Algs) -> proplists:get_value(Key, Algs, default_algorithms(Key)). to_strings(L) -> lists:map(fun erlang:atom_to_list/1, L). new_keys_message(Ssh0) -> - {SshPacket, Ssh} = - ssh_packet(#ssh_msg_newkeys{}, Ssh0), + {SshPacket, Ssh1} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), + Ssh = install_alg(snd, Ssh1), {ok, SshPacket, Ssh}. handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, - #ssh{role = client} = Ssh0) -> - {ok, Algoritms} = select_algorithm(client, Own, CounterPart), - case verify_algorithm(Algoritms) of - true -> - key_exchange_first_msg(Algoritms#alg.kex, - Ssh0#ssh{algorithms = Algoritms}); - {false,Alg} -> - %% TODO: Correct code? - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed: " - ++ Alg - }) + #ssh{role = client} = Ssh) -> + try + {ok, Algorithms} = select_algorithm(client, Own, CounterPart, Ssh#ssh.opts), + true = verify_algorithm(Algorithms), + Algorithms + of + Algos -> + key_exchange_first_msg(Algos#alg.kex, + Ssh#ssh{algorithms = Algos}) + catch + _:_ -> + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Selection of key exchange algorithm failed"}) end; handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, - #ssh{role = server} = Ssh) -> - {ok, Algoritms} = select_algorithm(server, CounterPart, Own), - case verify_algorithm(Algoritms) of - true -> - {ok, Ssh#ssh{algorithms = Algoritms}}; - {false,Alg} -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed: " - ++ Alg - }) + #ssh{role = server} = Ssh) -> + try + {ok, Algorithms} = select_algorithm(server, CounterPart, Own, Ssh#ssh.opts), + true = verify_algorithm(Algorithms), + Algorithms + of + Algos -> + {ok, Ssh#ssh{algorithms = Algos}} + catch + _:_ -> + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Selection of key exchange algorithm failed"}) end. @@ -308,6 +313,8 @@ verify_algorithm(#alg{decrypt = undefined}) -> {false, "decrypt"}; verify_algorithm(#alg{compress = undefined}) -> {false, "compress"}; verify_algorithm(#alg{decompress = undefined}) -> {false, "decompress"}; verify_algorithm(#alg{kex = Kex}) -> + %% This also catches the error if 'ext-info-s' or 'ext-info-c' is selected. + %% (draft-ietf-curdle-ssh-ext-info-04 2.2) case lists:member(Kex, supported_algorithms(kex)) of true -> true; false -> {false, "kex"} @@ -414,9 +421,9 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), - exchanged_hash = H, - session_id = sid(Ssh, H)}}; + {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K), + exchanged_hash = H, + session_id = sid(Ssh, H)})}; Error -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{ @@ -584,9 +591,9 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), - exchanged_hash = H, - session_id = sid(Ssh, H)}}; + {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K), + exchanged_hash = H, + session_id = sid(Ssh, H)})}; _Error -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{ @@ -660,9 +667,9 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), - exchanged_hash = H, - session_id = sid(Ssh, H)}}; + {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K), + exchanged_hash = H, + session_id = sid(Ssh, H)})}; Error -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{ @@ -682,7 +689,7 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, %%%---------------------------------------------------------------- handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> - try install_alg(Ssh0) of + try install_alg(rcv, Ssh0) of #ssh{} = Ssh -> {ok, Ssh} catch @@ -693,6 +700,34 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> }) end. + +%%%---------------------------------------------------------------- +kex_ext_info(Role, Opts) -> + case ?GET_OPT(recv_ext_info,Opts) of + true when Role==client -> ["ext-info-c"]; + true when Role==server -> ["ext-info-s"]; + false -> [] + end. + +ext_info_message(#ssh{role=client, + algorithms=#alg{send_ext_info=true}} = Ssh0) -> + %% FIXME: no extensions implemented for clients + {ok, "", Ssh0}; + +ext_info_message(#ssh{role=server, + algorithms=#alg{send_ext_info=true}} = Ssh0) -> + AlgsList = lists:map(fun erlang:atom_to_list/1, + ssh_transport:default_algorithms(public_key)), + Msg = #ssh_msg_ext_info{nr_extensions = 1, + data = [{"server-sig-algs", string:join(AlgsList,",")}] + }, + {SshPacket, Ssh} = ssh_packet(Msg, Ssh0), + {ok, SshPacket, Ssh}; + +ext_info_message(Ssh0) -> + {ok, "", Ssh0}. % "" means: 'do not send' + +%%%---------------------------------------------------------------- %% select session id sid(#ssh{session_id = undefined}, H) -> H; @@ -812,7 +847,7 @@ known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_} %% %% The first algorithm in each list MUST be the preferred (guessed) %% algorithm. Each string MUST contain at least one algorithm name. -select_algorithm(Role, Client, Server) -> +select_algorithm(Role, Client, Server, Opts) -> {Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server), {SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server), @@ -837,17 +872,34 @@ select_algorithm(Role, Client, Server) -> Kex = select(Client#ssh_msg_kexinit.kex_algorithms, Server#ssh_msg_kexinit.kex_algorithms), - Alg = #alg{kex = Kex, - hkey = HK, - encrypt = Encrypt, - decrypt = Decrypt, - send_mac = SendMac, - recv_mac = RecvMac, - compress = Compression, - decompress = Decompression, - c_lng = C_Lng, - s_lng = S_Lng}, - {ok, Alg}. + SendExtInfo = + %% To send we must have that option enabled and ... + ?GET_OPT(send_ext_info,Opts) andalso + %% ... the peer must have told us to send: + case Role of + server -> lists:member("ext-info-c", Client#ssh_msg_kexinit.kex_algorithms); + client -> lists:member("ext-info-s", Server#ssh_msg_kexinit.kex_algorithms) + end, + + RecvExtInfo = + %% The peer should not send unless told so by us (which is + %% guided by an option). + %% (However a malicious peer could send anyway, so we must be prepared) + ?GET_OPT(recv_ext_info,Opts), + + {ok, #alg{kex = Kex, + hkey = HK, + encrypt = Encrypt, + decrypt = Decrypt, + send_mac = SendMac, + recv_mac = RecvMac, + compress = Compression, + decompress = Decompression, + c_lng = C_Lng, + s_lng = S_Lng, + send_ext_info = SendExtInfo, + recv_ext_info = RecvExtInfo + }}. %%% It is an agreed problem with RFC 5674 that if the selection is @@ -928,45 +980,66 @@ select_compression_decompression(server, Client, Server) -> Server#ssh_msg_kexinit.compression_algorithms_server_to_client), {Compression, Decompression}. -install_alg(SSH) -> - SSH1 = alg_final(SSH), - SSH2 = alg_setup(SSH1), - alg_init(SSH2). +%% DIr = rcv | snd +install_alg(Dir, SSH) -> + SSH1 = alg_final(Dir, SSH), + SSH2 = alg_setup(Dir, SSH1), + alg_init(Dir, SSH2). -alg_setup(SSH) -> +alg_setup(snd, SSH) -> ALG = SSH#ssh.algorithms, SSH#ssh{kex = ALG#alg.kex, hkey = ALG#alg.hkey, encrypt = ALG#alg.encrypt, - decrypt = ALG#alg.decrypt, send_mac = ALG#alg.send_mac, send_mac_size = mac_digest_size(ALG#alg.send_mac), + compress = ALG#alg.compress, + c_lng = ALG#alg.c_lng, + s_lng = ALG#alg.s_lng, + send_ext_info = ALG#alg.send_ext_info, + recv_ext_info = ALG#alg.recv_ext_info + }; + +alg_setup(rcv, SSH) -> + ALG = SSH#ssh.algorithms, + SSH#ssh{kex = ALG#alg.kex, + hkey = ALG#alg.hkey, + decrypt = ALG#alg.decrypt, recv_mac = ALG#alg.recv_mac, recv_mac_size = mac_digest_size(ALG#alg.recv_mac), - compress = ALG#alg.compress, decompress = ALG#alg.decompress, c_lng = ALG#alg.c_lng, s_lng = ALG#alg.s_lng, - algorithms = undefined + send_ext_info = ALG#alg.send_ext_info, + recv_ext_info = ALG#alg.recv_ext_info }. -alg_init(SSH0) -> + +alg_init(snd, SSH0) -> {ok,SSH1} = send_mac_init(SSH0), - {ok,SSH2} = recv_mac_init(SSH1), - {ok,SSH3} = encrypt_init(SSH2), - {ok,SSH4} = decrypt_init(SSH3), - {ok,SSH5} = compress_init(SSH4), - {ok,SSH6} = decompress_init(SSH5), - SSH6. - -alg_final(SSH0) -> + {ok,SSH2} = encrypt_init(SSH1), + {ok,SSH3} = compress_init(SSH2), + SSH3; + +alg_init(rcv, SSH0) -> + {ok,SSH1} = recv_mac_init(SSH0), + {ok,SSH2} = decrypt_init(SSH1), + {ok,SSH3} = decompress_init(SSH2), + SSH3. + + +alg_final(snd, SSH0) -> {ok,SSH1} = send_mac_final(SSH0), - {ok,SSH2} = recv_mac_final(SSH1), - {ok,SSH3} = encrypt_final(SSH2), - {ok,SSH4} = decrypt_final(SSH3), - {ok,SSH5} = compress_final(SSH4), - {ok,SSH6} = decompress_final(SSH5), - SSH6. + {ok,SSH2} = encrypt_final(SSH1), + {ok,SSH3} = compress_final(SSH2), + SSH3; + +alg_final(rcv, SSH0) -> + {ok,SSH1} = recv_mac_final(SSH0), + {ok,SSH2} = decrypt_final(SSH1), + {ok,SSH3} = decompress_final(SSH2), + SSH3. + select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS -> A = CL -- SL, %% algortihms only used by client diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index 19b3f5c437..faae6008f2 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -48,6 +48,7 @@ -define(SSH_MSG_DEBUG, 4). -define(SSH_MSG_SERVICE_REQUEST, 5). -define(SSH_MSG_SERVICE_ACCEPT, 6). +-define(SSH_MSG_EXT_INFO, 7). -define(SSH_MSG_KEXINIT, 20). -define(SSH_MSG_NEWKEYS, 21). @@ -88,6 +89,20 @@ name %% string }). +-record(ssh_msg_ext_info, + { + nr_extensions, %% uint32 + + %% repeat the following 2 fields "nr-extensions" times: + %% string extension-name + %% string extension-value + + data %% [{extension-name, %% string + %% extension-value}, %% string + %% ... + %% ] + }). + -record(ssh_msg_kexinit, { cookie, %% random(16) -- cgit v1.2.3 From b7cba805e37e591d8fa7d7df06f9563a9f926e23 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 26 Apr 2017 12:08:01 +0200 Subject: ssh: state machine fixes for calls during re-negotiation --- lib/ssh/src/ssh_connection_handler.erl | 47 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 27 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 0ff7c9b3a1..128a9175f5 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -365,7 +365,7 @@ init_connection_handler(Role, Socket, Opts) -> {ok, StartState, D} -> process_flag(trap_exit, true), gen_statem:enter_loop(?MODULE, - [], %%[{debug,[trace,log,statistics,debug]} || Role==server], + [], %%[{debug,[trace,log,statistics,debug]} ], %% [] StartState, D); @@ -504,6 +504,10 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> -type handle_event_result() :: gen_statem:handle_event_result(). +-define(CONNECTED(StateName), + (element(1,StateName) == connected orelse + element(1,StateName) == ext_info ) ). + -spec handle_event(gen_statem:event_type(), event_content(), state_name(), @@ -1020,12 +1024,10 @@ handle_event(cast, data_size, _, _) -> -handle_event(cast, _, StateName, _) when StateName /= {connected,server}, - StateName /= {connected,client} -> +handle_event(cast, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; - -handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) -> +handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTED(StateName) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_window_pending = Pending, @@ -1051,7 +1053,7 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) -> keep_state_and_data end; -handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) -> +handle_event(cast, {reply_request,success,ChannelId}, StateName, D) when ?CONNECTED(StateName) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = RemoteId} -> Msg = ssh_connection:channel_success_msg(RemoteId), @@ -1062,13 +1064,13 @@ handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) -> keep_state_and_data end; -handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, {connected,_}, D) -> +handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, StateName, D) when ?CONNECTED(StateName) -> {keep_state, handle_request(ChannelPid, ChannelId, Type, Data, false, none, D)}; -handle_event(cast, {request,ChannelId,Type,Data}, {connected,_}, D) -> +handle_event(cast, {request,ChannelId,Type,Data}, StateName, D) when ?CONNECTED(StateName) -> {keep_state, handle_request(ChannelId, Type, Data, false, none, D)}; -handle_event(cast, {unknown,Data}, {connected,_}, D) -> +handle_event(cast, {unknown,Data}, StateName, D) when ?CONNECTED(StateName) -> Msg = #ssh_msg_unimplemented{sequence = Data}, {keep_state, send_msg(Msg,D)}; @@ -1129,29 +1131,25 @@ handle_event({call,From}, stop, StateName, D0) -> {Repls,D} = send_replies(Replies, D0), {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}}; -handle_event({call,_}, _, StateName, _) when StateName /= {connected,server}, - StateName /= {connected,client} -> +handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0), %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> D = handle_request(ChannelId, Type, Data, true, From, D0), %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> {{replies, Replies}, Connection} = ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), {Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}), @@ -1159,8 +1157,7 @@ handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) {keep_state, D, Repls}; handle_event({call,From}, {eof, ChannelId}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id, sent_close = false} -> D = send_msg(ssh_connection:channel_eof_msg(Id), D0), @@ -1172,8 +1169,7 @@ handle_event({call,From}, {eof, ChannelId}, StateName, D0) handle_event({call,From}, {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, StateName, - D0) when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + D0) when ?CONNECTED(StateName) -> erlang:monitor(process, ChannelPid), {ChannelId, D1} = new_channel_id(D0), D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId, @@ -1194,8 +1190,7 @@ handle_event({call,From}, {keep_state, cache_cancel_idle_timer(D)}; handle_event({call,From}, {send_window, ChannelId}, StateName, D) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{send_window_size = WinSize, send_packet_size = Packsize} -> @@ -1206,8 +1201,7 @@ handle_event({call,From}, {send_window, ChannelId}, StateName, D) {keep_state_and_data, [{reply,From,Reply}]}; handle_event({call,From}, {recv_window, ChannelId}, StateName, D) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_packet_size = Packsize} -> @@ -1218,8 +1212,7 @@ handle_event({call,From}, {recv_window, ChannelId}, StateName, D) {keep_state_and_data, [{reply,From,Reply}]}; handle_event({call,From}, {close, ChannelId}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id} = Channel -> D1 = send_msg(ssh_connection:channel_close_msg(Id), D0), -- cgit v1.2.3 From 519f89016e7ce755775a88730814fa34af21676c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 19 Apr 2017 12:01:26 +0200 Subject: ssh: server-sig-algs, client side --- lib/ssh/src/ssh.hrl | 2 +- lib/ssh/src/ssh_auth.erl | 85 +++++++++++++++------------------- lib/ssh/src/ssh_connection_handler.erl | 23 +++++++-- lib/ssh/src/ssh_options.erl | 20 ++------ 4 files changed, 61 insertions(+), 69 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 1a95bb27e7..cf2a359e6c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -38,7 +38,6 @@ -define(MAX_RND_PADDING_LEN, 15). -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). --define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']). -define(FALSE, 0). -define(TRUE, 1). @@ -201,6 +200,7 @@ userauth_quiet_mode, % boolean() userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"] userauth_supported_methods, % string() eg "keyboard-interactive,password" + userauth_pubkeys, kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive" userauth_preference, available_host_keys, diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 51df54341f..aadd1ad6dc 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -136,34 +136,40 @@ keyboard_interactive_msg([#ssh{user = User, Ssh) end. -publickey_msg([Alg, #ssh{user = User, +publickey_msg([SigAlg, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> - Hash = ssh_transport:sha(Alg), + Hash = ssh_transport:sha(SigAlg), + KeyAlg = key_alg(SigAlg), {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:user_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of {ok, PrivKey} -> - StrAlgo = atom_to_list(Alg), - case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of - not_ok -> - {not_ok, Ssh}; + SigAlgStr = atom_to_list(SigAlg), + try + Key = ssh_transport:extract_public_key(PrivKey), + public_key:ssh_encode(Key, ssh2_pubkey) + of PubKeyBlob -> - SigData = build_sig_data(SessionId, - User, Service, PubKeyBlob, StrAlgo), + SigData = build_sig_data(SessionId, User, Service, + PubKeyBlob, SigAlgStr), Sig = ssh_transport:sign(SigData, Hash, PrivKey), - SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]), + SigBlob = list_to_binary([?string(SigAlgStr), + ?binary(Sig)]), ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, service = Service, method = "publickey", data = [?TRUE, - ?string(StrAlgo), + ?string(SigAlgStr), ?binary(PubKeyBlob), ?binary(SigBlob)]}, Ssh) - end; + catch + _:_ -> + {not_ok, Ssh} + end; _Error -> {not_ok, Ssh} end. @@ -190,8 +196,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> method = "none", data = <<>>}, Ssh#ssh{user = User, - userauth_preference = - method_preference(?GET_OPT(pref_public_key_algs, Opts)), + userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys), userauth_methods = none, service = "ssh-connection"} ) @@ -265,8 +270,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, #ssh{opts = Opts, userauth_supported_methods = Methods} = Ssh) -> - case pre_verify_sig(User, binary_to_list(BAlg), - KeyBlob, Opts) of + case pre_verify_sig(User, KeyBlob, Opts) of true -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet( @@ -446,10 +450,10 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -method_preference(PubKeyAlgs) -> +method_preference(SigKeyAlgs) -> %% PubKeyAlgs: List of user (client) public key algorithms to try to use. %% All of the acceptable algorithms is the default values. - PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- PubKeyAlgs], + PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- SigKeyAlgs], NonPKmethods = [{"password", ?MODULE, password_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} ], @@ -492,9 +496,9 @@ get_password_option(Opts, User) -> false -> ?GET_OPT(password, Opts) end. -pre_verify_sig(User, Alg, KeyBlob, Opts) -> +pre_verify_sig(User, KeyBlob, Opts) -> try - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) @@ -505,21 +509,19 @@ pre_verify_sig(User, Alg, KeyBlob, Opts) -> verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> try - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) of - true -> - PlainText = build_sig_data(SessionId, User, - Service, KeyBlob, Alg), - <> = SigWLen, - <> = AlgSig, - ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key); - false -> - false - end + Key0 = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception + true = KeyCb:is_auth_key(Key0, User, [{key_cb_private,KeyCbOpts}|UserOpts]), + Key0 + of + Key -> + PlainText = build_sig_data(SessionId, User, Service, + KeyBlob, Alg), + <> = SigWLen, + <> = AlgSig, + ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key) catch _:_ -> false @@ -591,18 +593,7 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> language = "en"}}) end. -decode_public_key_v2(Bin, _Type) -> - try - public_key:ssh_decode(Bin, ssh2_pubkey) - of - Key -> {ok, Key} - catch - _:_ -> {error, bad_format} - end. -encode_public_key(_Alg, Key) -> - try - public_key:ssh_encode(Key, ssh2_pubkey) - catch - _:_ -> not_ok - end. +key_alg('rsa-sha2-256') -> 'ssh-rsa'; +key_alg('rsa-sha2-512') -> 'ssh-rsa'; +key_alg(Alg) -> Alg. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 128a9175f5..ac1b792f32 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -464,6 +464,7 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> true -> ssh_io; false -> ssh_no_io end, + userauth_pubkeys = ?GET_OPT(pref_public_key_algs, Opts), userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), peer = {PeerName, PeerAddr} }; @@ -711,7 +712,7 @@ handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) -> %% If something else arrives, goto next state and handle the event in that one {next_state, {service_request,Role}, D, [postpone]}; -handle_event(internal, Msg, {ext_info,Role,renegotiate}, D) when is_tuple(Msg) -> +handle_event(internal, Msg, {ext_info,Role,_ReNegFlag}, D) when is_tuple(Msg) -> %% If something else arrives, goto next state and handle the event in that one {next_state, {connected,Role}, D, [postpone]}; @@ -1131,6 +1132,7 @@ handle_event({call,From}, stop, StateName, D0) -> {Repls,D} = send_replies(Replies, D0), {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}}; + handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; @@ -1380,12 +1382,16 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> handle_event(internal, {disconnect,Msg,_Reason}, StateName, D) -> disconnect(Msg, StateName, D); +handle_event(_Type, _Msg, {ext_info,Role,_ReNegFlag}, D) -> + %% If something else arrives, goto next state and handle the event in that one + {next_state, {connected,Role}, D, [postpone]}; + handle_event(Type, Ev, StateName, D) -> Descr = case catch atom_to_list(element(1,Ev)) of "ssh_msg_" ++_ when Type==internal -> %% "Message in wrong state"; -lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); + lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); _ -> "Internal error" end, @@ -1689,11 +1695,20 @@ cache(#data{connection_state=C}) -> C#connection.channel_cache. handle_ssh_msg_ext_info(#ssh_msg_ext_info{}, D=#data{ssh_params = #ssh{recv_ext_info=false}} ) -> % The peer sent this although we didn't allow it! D; + handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> lists:foldl(fun ext_info/2, D0, Data). -%% ext_info({ExtName,ExtValue}, D0) -> -%% D0; + +ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client}=Ssh0}) -> + %% Make strings to eliminate risk of beeing bombed with odd strings that fills the atom table: + SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), + Ssh = Ssh0#ssh{userauth_pubkeys = + [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), + lists:member(SigAlg, SupportedAlgs) + ]}, + D0#data{ssh_params = Ssh}; + ext_info(_, D0) -> %% Not implemented D0. diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6e898b4fde..0886d5b34d 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -437,9 +437,7 @@ default(client) -> {pref_public_key_algs, def} => #{default => - %% Get dynamically supported keys in the order of the ?SUPPORTED_USER_KEYS - [A || A <- ?SUPPORTED_USER_KEYS, - lists:member(A, ssh_transport:supported_algorithms(public_key))], + ssh_transport:supported_algorithms(public_key), chk => fun check_pref_public_key_algs/1, class => @@ -670,20 +668,8 @@ check_pref_public_key_algs(V) -> PKs = ssh_transport:supported_algorithms(public_key), CHK = fun(A, Ack) -> case lists:member(A, PKs) of - true -> - [A|Ack]; - false -> - %% Check with the documented options, that is, - %% the one we can handle - case lists:member(A,?SUPPORTED_USER_KEYS) of - false -> - %% An algorithm ssh never can handle - error_in_check(A, "Not supported public key"); - true -> - %% An algorithm ssh can handle, but not in - %% this very call - Ack - end + true -> [A|Ack]; + false -> error_in_check(A, "Not supported public key") end end, case lists:foldr( -- cgit v1.2.3 From a053401a7a7142d4d2a068b2945ef91cb7957f89 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 19 Apr 2017 14:04:05 +0200 Subject: ssh: server-sig-algs, server side --- lib/ssh/src/ssh_transport.erl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index d623d24529..3c2c345261 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -710,12 +710,12 @@ kex_ext_info(Role, Opts) -> end. ext_info_message(#ssh{role=client, - algorithms=#alg{send_ext_info=true}} = Ssh0) -> - %% FIXME: no extensions implemented for clients + send_ext_info=true} = Ssh0) -> + %% FIXME: no extensions implemented {ok, "", Ssh0}; ext_info_message(#ssh{role=server, - algorithms=#alg{send_ext_info=true}} = Ssh0) -> + send_ext_info=true} = Ssh0) -> AlgsList = lists:map(fun erlang:atom_to_list/1, ssh_transport:default_algorithms(public_key)), Msg = #ssh_msg_ext_info{nr_extensions = 1, @@ -729,10 +729,8 @@ ext_info_message(Ssh0) -> %%%---------------------------------------------------------------- %% select session id -sid(#ssh{session_id = undefined}, H) -> - H; -sid(#ssh{session_id = Id}, _) -> - Id. +sid(#ssh{session_id = undefined}, H) -> H; +sid(#ssh{session_id = Id}, _) -> Id. %% %% The host key should be read from storage -- cgit v1.2.3 From 2e55f44545504aa1ba109e072e6833f5c045b58f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 19 Apr 2017 14:10:29 +0200 Subject: ssh: Implement signature algorithms rsa-sha2-*. draft-ietf-curdle-rsa-sha2 --- lib/ssh/src/ssh_connection_handler.erl | 53 +++++---- lib/ssh/src/ssh_file.erl | 24 ++-- lib/ssh/src/ssh_message.erl | 19 ++- lib/ssh/src/ssh_transport.erl | 205 ++++++++++++++++++--------------- 4 files changed, 167 insertions(+), 134 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ac1b792f32..220b05e6b0 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -490,20 +490,32 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> -type renegotiate_flag() :: init | renegotiate. -type state_name() :: - {hello, role()} - | {kexinit, role(), renegotiate_flag()} - | {key_exchange, role(), renegotiate_flag()} - | {key_exchange_dh_gex_init, server, renegotiate_flag()} + {hello, role() } + | {kexinit, role(), renegotiate_flag()} + | {key_exchange, role(), renegotiate_flag()} + | {key_exchange_dh_gex_init, server, renegotiate_flag()} | {key_exchange_dh_gex_reply, client, renegotiate_flag()} - | {new_keys, role()} - | {ext_info, role(), renegotiate_flag()} - | {service_request, role()} - | {userauth, role()} - | {userauth_keyboard_interactive, role()} - | {connected, role()} + | {new_keys, role(), renegotiate_flag()} + | {ext_info, role(), renegotiate_flag()} + | {service_request, role() } + | {userauth, role() } + | {userauth_keyboard_interactive, role() } + | {userauth_keyboard_interactive_extra, server } + | {userauth_keyboard_interactive_info_response, client } + | {connected, role() } . --type handle_event_result() :: gen_statem:handle_event_result(). +%% The state names must fulfill some rules regarding +%% where the role() and the renegotiate_flag() is placed: + +-spec role(state_name()) -> role(). +role({_,Role}) -> Role; +role({_,Role,_}) -> Role. + +-spec renegotiation(state_name()) -> boolean(). +renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; +renegotiation(_) -> false. + -define(CONNECTED(StateName), (element(1,StateName) == connected orelse @@ -513,7 +525,7 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> event_content(), state_name(), #data{} - ) -> handle_event_result(). + ) -> gen_statem:event_handler_result(state_name()) . %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1530,16 +1542,6 @@ finalize_termination(_StateName, #data{transport_cb = Transport, peer_role(client) -> server; peer_role(server) -> client. -%%-------------------------------------------------------------------- -%% StateName to Role -role({_,Role}) -> Role; -role({_,Role,_}) -> Role. - -%%-------------------------------------------------------------------- -%% Check the StateName to see if we are in the renegotiation phase -renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; -renegotiation(_) -> false. - %%-------------------------------------------------------------------- supported_host_keys(client, _, Options) -> try @@ -1576,8 +1578,11 @@ find_sup_hkeys(Options) -> %% Alg :: atom() available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) -> UserOpts = ?GET_OPT(user_options, Opts), - element(1, - catch KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts])) == ok. + case KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + {ok,_} -> true; + _ -> false + end. + send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 88f4d10792..4498c70d34 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -75,17 +75,12 @@ host_key(Algorithm, Opts) -> Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), case decode(File, Password) of {ok,Key} -> - case {Key,Algorithm} of - {#'RSAPrivateKey'{}, 'ssh-rsa'} -> {ok,Key}; - {#'DSAPrivateKey'{}, 'ssh-dss'} -> {ok,Key}; - {#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}, 'ecdsa-sha2-nistp256'} -> {ok,Key}; - {#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}, 'ecdsa-sha2-nistp384'} -> {ok,Key}; - {#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}, 'ecdsa-sha2-nistp521'} -> {ok,Key}; - _ -> - {error,bad_keytype_in_file} + case ssh_transport:valid_key_sha_alg(Key,Algorithm) of + true -> {ok,Key}; + false -> {error,bad_keytype_in_file} end; - Other -> - Other + {error,DecodeError} -> + {error,DecodeError} end. is_auth_key(Key, User,Opts) -> @@ -115,6 +110,9 @@ user_key(Algorithm, Opts) -> %% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key"; +file_base_name('rsa-sha2-256' ) -> "ssh_host_rsa_key"; +file_base_name('rsa-sha2-384' ) -> "ssh_host_rsa_key"; +file_base_name('rsa-sha2-512' ) -> "ssh_host_rsa_key"; file_base_name('ssh-dss' ) -> "ssh_host_dsa_key"; file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key"; file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key"; @@ -253,12 +251,18 @@ do_lookup_host_key(KeyToMatch, Host, Alg, Opts) -> identity_key_filename('ssh-dss' ) -> "id_dsa"; identity_key_filename('ssh-rsa' ) -> "id_rsa"; +identity_key_filename('rsa-sha2-256' ) -> "id_rsa"; +identity_key_filename('rsa-sha2-384' ) -> "id_rsa"; +identity_key_filename('rsa-sha2-512' ) -> "id_rsa"; identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa"; identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa"; identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa". identity_pass_phrase("ssh-dss" ) -> dsa_pass_phrase; identity_pass_phrase("ssh-rsa" ) -> rsa_pass_phrase; +identity_pass_phrase("rsa-sha2-256" ) -> rsa_pass_phrase; +identity_pass_phrase("rsa-sha2-384" ) -> rsa_pass_phrase; +identity_pass_phrase("rsa-sha2-512" ) -> rsa_pass_phrase; identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase; identity_pass_phrase(P) when is_atom(P) -> identity_pass_phrase(atom_to_list(P)). diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 56f678876c..21c0eabcd3 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -602,11 +602,22 @@ decode_signature(<>) -> Signature. -encode_signature(#'RSAPublicKey'{}, Signature) -> - <>), ?Ebinary(Signature)>>; -encode_signature({_, #'Dss-Parms'{}}, Signature) -> +encode_signature({#'RSAPublicKey'{},Sign}, Signature) -> + SignName = list_to_binary(atom_to_list(Sign)), + <>; +encode_signature({{_, #'Dss-Parms'{}},_}, Signature) -> <>), ?Ebinary(Signature)>>; -encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) -> +encode_signature({{#'ECPoint'{}, {namedCurve,OID}},_}, Signature) -> CurveName = public_key:oid2ssh_curvename(OID), <>), ?Ebinary(Signature)>>. +%% encode_signature(#'RSAPublicKey'{}, Signature) -> +%% SignName = <<"ssh-rsa">>, +%% <>; +%% encode_signature({_, #'Dss-Parms'{}}, Signature) -> +%% <>), ?Ebinary(Signature)>>; +%% encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) -> +%% CurveName = public_key:oid2ssh_curvename(OID), +%% <>), ?Ebinary(Signature)>>. + + diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 3c2c345261..3cf1e60634 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -48,6 +48,7 @@ parallell_gen_key/1, extract_public_key/1, ssh_packet/2, pack/2, + valid_key_sha_alg/2, sha/1, sign/3, verify/4]). %%% For test suites @@ -117,6 +118,9 @@ supported_algorithms(public_key) -> {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]}, + {'rsa-sha2-384', [{public_keys,rsa}, {hashs,sha384} ]}, + {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1 ]); @@ -377,7 +381,8 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% diffie-hellman-group18-sha512 %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, - Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) -> + Ssh0 = #ssh{algorithms = #alg{kex=Kex, + hkey=SignAlg} = Algs}) -> %% server {G, P} = dh_group(Kex), if @@ -385,12 +390,12 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Sz = dh_bits(Algs), {Public, Private} = generate_key(dh, [P,G,2*Sz]), K = compute_key(dh, E, Private, [P,G]), - MyPrivHostKey = get_host_key(Ssh0), + MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_h(Ssh0, MyPubHostKey, E, Public, K), - H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), + H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {E,Public,K}), + H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh1} = - ssh_packet(#ssh_msg_kexdh_reply{public_host_key = MyPubHostKey, + ssh_packet(#ssh_msg_kexdh_reply{public_host_key = {MyPubHostKey,SignAlg}, f = Public, h_sig = H_SIG }, Ssh0), @@ -411,13 +416,14 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, f = F, h_sig = H_SIG}, - #ssh{keyex_key = {{Private, Public}, {G, P}}} = Ssh0) -> + #ssh{keyex_key = {{Private, Public}, {G, P}}, + algorithms = #alg{kex=Kex, + hkey=SignAlg}} = Ssh0) -> %% client if 1= K = compute_key(dh, F, Private, [P,G]), - H = kex_h(Ssh0, PeerPubHostKey, Public, F, K), - + H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Public,F,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -493,7 +499,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, Ssh#ssh{keyex_key = {x, {G, P}}, - keyex_info = {-1, -1, NBits} % flag for kex_h hash calc + keyex_info = {-1, -1, NBits} % flag for kex_hash calc }}; {error,_} -> ssh_connection_handler:disconnect( @@ -539,20 +545,21 @@ handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) -> handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, #ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits}} = - Ssh0) -> + keyex_info = {Min, Max, NBits}, + algorithms = #alg{kex=Kex, + hkey=SignAlg}} = Ssh0) -> %% server if 1= K = compute_key(dh, E, Private, [P,G]), if 1 - MyPrivHostKey = get_host_key(Ssh0), + MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_h(Ssh0, MyPubHostKey, Min, NBits, Max, P, G, E, Public, K), - H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), + H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,E,Public,K}), + H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh} = - ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey, + ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = {MyPubHostKey,SignAlg}, f = Public, h_sig = H_SIG}, Ssh0), {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), @@ -578,7 +585,9 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK f = F, h_sig = H_SIG}, #ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits}} = + keyex_info = {Min, Max, NBits}, + algorithms = #alg{kex=Kex, + hkey=SignAlg}} = Ssh0) -> %% client if @@ -586,8 +595,7 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK K = compute_key(dh, F, Private, [P,G]), if 1 - H = kex_h(Ssh0, PeerPubHostKey, Min, NBits, Max, P, G, Public, F, K), - + H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,Public,F,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -623,7 +631,8 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK %%% diffie-hellman-ecdh-sha2-* %%% handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, - Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) -> + Ssh0 = #ssh{algorithms = #alg{kex=Kex, + hkey=SignAlg}}) -> %% at server Curve = ecdh_curve(Kex), {MyPublic, MyPrivate} = generate_key(ecdh, Curve), @@ -631,12 +640,12 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, compute_key(ecdh, PeerPublic, MyPrivate, Curve) of K -> - MyPrivHostKey = get_host_key(Ssh0), + MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_h(Ssh0, Curve, MyPubHostKey, PeerPublic, MyPublic, K), - H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), + H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Curve), {PeerPublic, MyPublic, K}), + H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh1} = - ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = MyPubHostKey, + ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = {MyPubHostKey,SignAlg}, q_s = MyPublic, h_sig = H_SIG}, Ssh0), @@ -656,14 +665,15 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, q_s = PeerPublic, h_sig = H_SIG}, - #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}} = Ssh0 + #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}, + algorithms = #alg{hkey=SignAlg}} = Ssh0 ) -> %% at client try compute_key(ecdh, PeerPublic, MyPrivate, Curve) of K -> - H = kex_h(Ssh0, Curve, PeerPubHostKey, MyPublic, PeerPublic, K), + H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Curve), {MyPublic,PeerPublic,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -735,25 +745,14 @@ sid(#ssh{session_id = Id}, _) -> Id. %% %% The host key should be read from storage %% -get_host_key(SSH) -> - #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts, algorithms = ALG} = SSH, +get_host_key(SSH, SignAlg) -> + #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts} = SSH, UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:host_key(ALG#alg.hkey, [{key_cb_private,KeyCbOpts}|UserOpts]) of - {ok, #'RSAPrivateKey'{} = Key} -> Key; - {ok, #'DSAPrivateKey'{} = Key} -> Key; - {ok, #'ECPrivateKey'{} = Key} -> Key; - Result -> - exit({error, {Result, unsupported_key_type}}) + case KeyCb:host_key(SignAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + {ok, PrivHostKey} -> PrivHostKey; + Result -> exit({error, {Result, unsupported_key_type}}) end. -sign_host_key(_Ssh, PrivateKey, H) -> - sign(H, sign_host_key_sha(PrivateKey), PrivateKey). - -sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve,OID}}) -> sha(OID); -sign_host_key_sha(#'RSAPrivateKey'{}) -> sha; -sign_host_key_sha(#'DSAPrivateKey'{}) -> sha. - - extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> #'RSAPublicKey'{modulus = N, publicExponent = E}; extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> @@ -763,8 +762,8 @@ extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID}, {#'ECPoint'{point=Q}, {namedCurve,OID}}. -verify_host_key(SSH, PublicKey, Digest, Signature) -> - case verify(Digest, host_key_sha(PublicKey), Signature, PublicKey) of +verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, Signature) -> + case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of false -> {error, bad_signature}; true -> @@ -772,16 +771,6 @@ verify_host_key(SSH, PublicKey, Digest, Signature) -> end. -host_key_sha(#'RSAPublicKey'{}) -> sha; -host_key_sha({_, #'Dss-Parms'{}}) -> sha; -host_key_sha({#'ECPoint'{},{namedCurve,OID}}) -> sha(OID). - -public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; -public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; -public_algo({#'ECPoint'{},{namedCurve,OID}}) -> - Curve = public_key:oid2ssh_curvename(OID), - list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). - accepted_host(Ssh, PeerName, Public, Opts) -> case ?GET_OPT(silently_accept_hosts, Opts) of @@ -1201,29 +1190,29 @@ payload(<>) -> <> = PayloadAndPadding, Payload. -sign(SigData, Hash, #'DSAPrivateKey'{} = Key) -> - DerSignature = public_key:sign(SigData, Hash, Key), +sign(SigData, HashAlg, #'DSAPrivateKey'{} = Key) -> + DerSignature = public_key:sign(SigData, HashAlg, Key), #'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature), <>; -sign(SigData, Hash, Key = #'ECPrivateKey'{}) -> - DerEncodedSign = public_key:sign(SigData, Hash, Key), +sign(SigData, HashAlg, Key = #'ECPrivateKey'{}) -> + DerEncodedSign = public_key:sign(SigData, HashAlg, Key), #'ECDSA-Sig-Value'{r=R, s=S} = public_key:der_decode('ECDSA-Sig-Value', DerEncodedSign), <>; -sign(SigData, Hash, Key) -> - public_key:sign(SigData, Hash, Key). +sign(SigData, HashAlg, Key) -> + public_key:sign(SigData, HashAlg, Key). -verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) -> +verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) -> <> = Sig, Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), - public_key:verify(PlainText, Hash, Signature, Key); -verify(PlainText, Hash, Sig, {#'ECPoint'{},_} = Key) -> + public_key:verify(PlainText, HashAlg, Signature, Key); +verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key) -> <> = Sig, Sval = #'ECDSA-Sig-Value'{r=R, s=S}, DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), - public_key:verify(PlainText, Hash, DerEncodedSig, Key); -verify(PlainText, Hash, Sig, Key) -> - public_key:verify(PlainText, Hash, Sig, Key). + public_key:verify(PlainText, HashAlg, DerEncodedSig, Key); +verify(PlainText, HashAlg, Sig, Key) -> + public_key:verify(PlainText, HashAlg, Sig, Key). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1730,39 +1719,63 @@ hash(K, H, Ki, N, HashAlg) -> hash(K, H, <>, N-128, HashAlg). %%%---------------------------------------------------------------- -kex_h(SSH, Key, E, F, K) -> - KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), - L = <>, - crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). - -kex_h(SSH, Curve, Key, Q_c, Q_s, K) -> - KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), - L = <>, - crypto:hash(sha(Curve), L). - -kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) -> - KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), - L = if Min==-1; Max==-1 -> - %% flag from 'ssh_msg_kex_dh_gex_request_old' - %% It was like this before that message was supported, - %% why? - <>; - true -> - <> - end, - crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). - +kex_hash(SSH, Key, SignAlg, HashAlg, Args) -> + crypto:hash(HashAlg, kex_plaintext(SSH,Key,SignAlg,Args)). + +kex_plaintext(SSH, Key, SignAlg, Args) -> + EncodedKey = public_key:ssh_encode({Key,SignAlg}, ssh2_pubkey), + <>. + +kex_alg_dependent({E, F, K}) -> + %% diffie-hellman and ec diffie-hellman (with E = Q_c, F = Q_s) + <>; + +kex_alg_dependent({-1, _, -1, _, _, E, F, K}) -> + %% ssh_msg_kex_dh_gex_request_old + <>; + +kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) -> + %% diffie-hellman group exchange + <>. + +%%%---------------------------------------------------------------- + +valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-512') -> true; +valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-384') -> true; +valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-256') -> true; +valid_key_sha_alg(#'RSAPublicKey'{}, 'ssh-rsa' ) -> true; + +valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-512') -> true; +valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-384') -> true; +valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-256') -> true; +valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true; + +valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true; +valid_key_sha_alg(#'DSAPrivateKey'{}, 'ssh-dss') -> true; + +valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg); +valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg); +valid_key_sha_alg(_, _) -> false. + + + +public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-curdle-rsa-sha2 +public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; +public_algo({#'ECPoint'{},{namedCurve,OID}}) -> + Curve = public_key:oid2ssh_curvename(OID), + list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). + + + sha('ssh-rsa') -> sha; +sha('rsa-sha2-256') -> sha256; +sha('rsa-sha2-384') -> sha384; +sha('rsa-sha2-512') -> sha512; sha('ssh-dss') -> sha; sha('ecdsa-sha2-nistp256') -> sha(secp256r1); sha('ecdsa-sha2-nistp384') -> sha(secp384r1); -- cgit v1.2.3 From 9bcd621df2abf35394cd9f68b42c446d3ab83f11 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 26 Apr 2017 16:52:05 +0200 Subject: ssh: Codenomicon/Defensics fixes --- lib/ssh/src/ssh_auth.erl | 24 ++++++++++-------------- lib/ssh/src/ssh_connection_handler.erl | 13 ++++++++----- lib/ssh/src/ssh_transport.erl | 27 ++++++++++++++++++--------- 3 files changed, 36 insertions(+), 28 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index aadd1ad6dc..9eb11a53dc 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -296,8 +296,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, userauth_supported_methods = Methods} = Ssh) -> case verify_sig(SessionId, User, "ssh-connection", - binary_to_list(BAlg), - KeyBlob, SigWLen, Opts) of + BAlg, KeyBlob, SigWLen, Opts) of true -> {authorized, User, ssh_transport:ssh_packet( @@ -507,21 +506,18 @@ pre_verify_sig(User, KeyBlob, Opts) -> false end. -verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> +verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, Opts) -> try + Alg = binary_to_list(AlgBin), {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), - Key0 = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception - true = KeyCb:is_auth_key(Key0, User, [{key_cb_private,KeyCbOpts}|UserOpts]), - Key0 - of - Key -> - PlainText = build_sig_data(SessionId, User, Service, - KeyBlob, Alg), - <> = SigWLen, - <> = AlgSig, - ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key) + Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception + true = KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]), + PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg), + <> = SigWLen, + <> = AlgSig, + ssh_transport:verify(PlainText, ssh_transport:sha(Alg), Sig, Key) catch _:_ -> false diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 220b05e6b0..74e14a233f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1710,6 +1710,7 @@ ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client}=Ss SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), Ssh = Ssh0#ssh{userauth_pubkeys = [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), + %% length of SigAlg is implicitly checked by member: lists:member(SigAlg, SupportedAlgs) ]}, D0#data{ssh_params = Ssh}; @@ -2008,12 +2009,14 @@ handshake(Pid, Ref, Timeout) -> end. update_inet_buffers(Socket) -> - {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]), - MinVal = 655360, - case - [{Tag,MinVal} || {Tag,Val} <- BufSzs0, - Val < MinVal] + try + {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]), + MinVal = 655360, + [{Tag,MinVal} || {Tag,Val} <- BufSzs0, + Val < MinVal] of [] -> ok; NewOpts -> inet:setopts(Socket, NewOpts) + catch + _:_ -> ok end. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 3cf1e60634..09b5d1ac81 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1202,15 +1202,23 @@ sign(SigData, HashAlg, Key) -> public_key:sign(SigData, HashAlg, Key). verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) -> - <> = Sig, - Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), - public_key:verify(PlainText, HashAlg, Signature, Key); + case Sig of + <> -> + Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), + public_key:verify(PlainText, HashAlg, Signature, Key); + _ -> + false + end; verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key) -> - <> = Sig, - Sval = #'ECDSA-Sig-Value'{r=R, s=S}, - DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), - public_key:verify(PlainText, HashAlg, DerEncodedSig, Key); + case Sig of + <> -> + Sval = #'ECDSA-Sig-Value'{r=R, s=S}, + DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), + public_key:verify(PlainText, HashAlg, DerEncodedSig, Key); + _ -> + false + end; verify(PlainText, HashAlg, Sig, Key) -> public_key:verify(PlainText, HashAlg, Sig, Key). @@ -1795,7 +1803,8 @@ sha(?'secp384r1') -> sha(secp384r1); sha(?'secp521r1') -> sha(secp521r1); sha('ecdh-sha2-nistp256') -> sha(secp256r1); sha('ecdh-sha2-nistp384') -> sha(secp384r1); -sha('ecdh-sha2-nistp521') -> sha(secp521r1). +sha('ecdh-sha2-nistp521') -> sha(secp521r1); +sha(Str) when is_list(Str), length(Str)<50 -> sha(list_to_atom(Str)). mac_key_bytes('hmac-sha1') -> 20; -- cgit v1.2.3 From 6f26467274a77d0838596775f3e7e6a33aad7273 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 27 Apr 2017 14:58:47 +0200 Subject: ssh: Don't expose new rsa-sha2-* as default --- lib/ssh/src/ssh_transport.erl | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 09b5d1ac81..7c7dda7a1e 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -92,6 +92,10 @@ default_algorithms(cipher) -> default_algorithms(mac) -> supported_algorithms(mac, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); +default_algorithms(public_key) -> + supported_algorithms(public_key, ['rsa-sha2-256', + 'rsa-sha2-384', + 'rsa-sha2-512']); default_algorithms(Alg) -> supported_algorithms(Alg, []). -- cgit v1.2.3 From 83e20c62057ebc1d8064bf57b01be560cd244e1d Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Thu, 4 May 2017 15:42:21 +0200 Subject: Update copyright year --- lib/ssh/src/Makefile | 2 +- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh.hrl | 2 +- lib/ssh/src/ssh_acceptor.erl | 2 +- lib/ssh/src/ssh_acceptor_sup.erl | 2 +- lib/ssh/src/ssh_auth.erl | 2 +- lib/ssh/src/ssh_cli.erl | 2 +- lib/ssh/src/ssh_connect.hrl | 2 +- lib/ssh/src/ssh_connection.erl | 2 +- lib/ssh/src/ssh_connection_handler.erl | 2 +- lib/ssh/src/ssh_connection_sup.erl | 2 +- lib/ssh/src/ssh_dbg.erl | 2 +- lib/ssh/src/ssh_file.erl | 2 +- lib/ssh/src/ssh_io.erl | 2 +- lib/ssh/src/ssh_message.erl | 2 +- lib/ssh/src/ssh_sftp.erl | 2 +- lib/ssh/src/ssh_sftpd.erl | 2 +- lib/ssh/src/ssh_sftpd_file_api.erl | 2 +- lib/ssh/src/ssh_subsystem_sup.erl | 2 +- lib/ssh/src/ssh_sup.erl | 2 +- lib/ssh/src/ssh_system_sup.erl | 2 +- lib/ssh/src/ssh_transport.erl | 2 +- lib/ssh/src/ssh_transport.hrl | 2 +- lib/ssh/src/sshc_sup.erl | 2 +- lib/ssh/src/sshd_sup.erl | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index f826fdfd9b..9e8d80c71f 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2004-2016. All Rights Reserved. +# Copyright Ericsson AB 2004-2017. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 3e80a04b70..5ebab43c30 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -1,7 +1,7 @@ % %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index cf2a359e6c..d6d412db43 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index f7fbd7ccad..d66a34c58a 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 26defcfdbd..a24664793b 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 9eb11a53dc..6cf659f830 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 4c4f61e036..62854346b0 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index c91c56435e..a8de5f9a2f 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 930ccecb4c..7e9ee78fd2 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 74e14a233f..342583306b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl index fad796f196..60ee8b7c73 100644 --- a/lib/ssh/src/ssh_connection_sup.erl +++ b/lib/ssh/src/ssh_connection_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 9431bf1817..7dfbfc3b4b 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 4498c70d34..6692432fcf 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 6828fd4760..8ba759ad60 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 21c0eabcd3..609040826f 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index f1f7b57e8d..c1558a19b1 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index b879116393..427edf01ab 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl index e444e52ac0..81f181f1fc 100644 --- a/lib/ssh/src/ssh_sftpd_file_api.erl +++ b/lib/ssh/src/ssh_sftpd_file_api.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index cf409ade6b..8db051095c 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 26574763e4..eaec7a54e4 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 84b4cd3241..e70abf59c2 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7c7dda7a1e..25c64a4f25 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index faae6008f2..87c3719514 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index c71b81dc6d..133b2c6450 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 449ba20d02..c23e65d955 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From efbae4afb84ef03364a1de349d98413211946ad4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 9 May 2017 18:38:35 +0200 Subject: ssh: Option 'auth_methods' available not only in server but also in client --- lib/ssh/src/ssh_connection_handler.erl | 6 +----- lib/ssh/src/ssh_options.erl | 12 ++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 342583306b..39bd54869f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -434,11 +434,7 @@ init_ssh_record(Role, Socket, Opts) -> init_ssh_record(Role, _Socket, PeerAddr, Opts) -> KeyCb = ?GET_OPT(key_cb, Opts), - AuthMethods = - case Role of - server -> ?GET_OPT(auth_methods, Opts); - client -> undefined - end, + AuthMethods = ?GET_OPT(auth_methods, Opts), S0 = #ssh{role = Role, key_cb = KeyCb, opts = Opts, diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 0886d5b34d..6bd6ab74c3 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -293,12 +293,6 @@ default(server) -> class => user_options }, - {auth_methods, def} => - #{default => ?SUPPORTED_AUTH_METHODS, - chk => fun check_string/1, - class => user_options - }, - {auth_method_kb_interactive_data, def} => #{default => undefined, % Default value can be constructed when User is known chk => fun({S1,S2,S3,B}) -> @@ -583,6 +577,12 @@ default(common) -> }, %%%%% Undocumented + {auth_methods, def} => + #{default => ?SUPPORTED_AUTH_METHODS, + chk => fun check_string/1, + class => user_options + }, + {transport, def} => #{default => ?DEFAULT_TRANSPORT, chk => fun({A,B,C}) -> -- cgit v1.2.3 From 6e9f9cbfc1f69735788651369bf6e288e23fbced Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 10 May 2017 12:39:02 +0200 Subject: ssh: Doc option 'auth_methods' for client --- lib/ssh/src/ssh_options.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6bd6ab74c3..f98422c324 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -576,13 +576,13 @@ default(common) -> class => user_options }, -%%%%% Undocumented {auth_methods, def} => #{default => ?SUPPORTED_AUTH_METHODS, chk => fun check_string/1, class => user_options }, +%%%%% Undocumented {transport, def} => #{default => ?DEFAULT_TRANSPORT, chk => fun({A,B,C}) -> -- cgit v1.2.3 From 242dddbc918c87571013e7e0acd29b4abbe12911 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 10 May 2017 11:42:09 +0200 Subject: ssh: Better error checking for option 'auth_methods' --- lib/ssh/src/ssh_options.erl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index f98422c324..78f68dbcb1 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -578,7 +578,16 @@ default(common) -> {auth_methods, def} => #{default => ?SUPPORTED_AUTH_METHODS, - chk => fun check_string/1, + chk => fun(As) -> + try + Sup = string:tokens(?SUPPORTED_AUTH_METHODS, ","), + New = string:tokens(As, ","), + [] == [X || X <- New, + not lists:member(X,Sup)] + catch + _:_ -> false + end + end, class => user_options }, -- cgit v1.2.3 From de3c2e70b3bf3387877b6624b6772395664039d6 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 12 May 2017 16:11:13 +0200 Subject: ssh: Tests for ext-info extension (ext-info-c) --- lib/ssh/src/ssh_transport.erl | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 25c64a4f25..bd1cb4bd22 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -724,9 +724,21 @@ kex_ext_info(Role, Opts) -> end. ext_info_message(#ssh{role=client, - send_ext_info=true} = Ssh0) -> - %% FIXME: no extensions implemented - {ok, "", Ssh0}; + send_ext_info=true, + opts=Opts} = Ssh0) -> + %% Since no extension sent by the client is implemented, we add a fake one + %% to be able to test the framework. + %% Remove this when there is one and update ssh_protocol_SUITE whare it is used. + case proplists:get_value(ext_info_client, ?GET_OPT(tstflg,Opts)) of + true -> + Msg = #ssh_msg_ext_info{nr_extensions = 1, + data = [{"test@erlang.org", "Testing,PleaseIgnore"}] + }, + {SshPacket, Ssh} = ssh_packet(Msg, Ssh0), + {ok, SshPacket, Ssh}; + _ -> + {ok, "", Ssh0} + end; ext_info_message(#ssh{role=server, send_ext_info=true} = Ssh0) -> -- cgit v1.2.3 From 77371ab686d408e13dc8549085c0fdb9a5b30733 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 15 May 2017 13:58:27 +0200 Subject: ssh: ssh_file:user_key/2 checks ec keytype --- lib/ssh/src/ssh_file.erl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 6692432fcf..33792da38f 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -75,10 +75,7 @@ host_key(Algorithm, Opts) -> Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), case decode(File, Password) of {ok,Key} -> - case ssh_transport:valid_key_sha_alg(Key,Algorithm) of - true -> {ok,Key}; - false -> {error,bad_keytype_in_file} - end; + check_key_type(Key, Algorithm); {error,DecodeError} -> {error,DecodeError} end. @@ -104,10 +101,20 @@ is_host_key(Key, PeerName, Algorithm, Opts) -> user_key(Algorithm, Opts) -> File = file_name(user, identity_key_filename(Algorithm), Opts), Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), - decode(File, Password). + case decode(File, Password) of + {ok, Key} -> + check_key_type(Key, Algorithm); + Error -> + Error + end. %% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +check_key_type(Key, Algorithm) -> + case ssh_transport:valid_key_sha_alg(Key,Algorithm) of + true -> {ok,Key}; + false -> {error,bad_keytype_in_file} + end. file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key"; file_base_name('rsa-sha2-256' ) -> "ssh_host_rsa_key"; -- cgit v1.2.3 From df8ec436495f62cff4f433aaf9129505ee41e189 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 15 May 2017 19:30:14 +0200 Subject: ssh: Enable rsa-sha2-* Conflicts: lib/ssh/src/ssh_transport.erl --- lib/ssh/src/ssh_transport.erl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index bd1cb4bd22..aaec733f3c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -92,10 +92,7 @@ default_algorithms(cipher) -> default_algorithms(mac) -> supported_algorithms(mac, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); -default_algorithms(public_key) -> - supported_algorithms(public_key, ['rsa-sha2-256', - 'rsa-sha2-384', - 'rsa-sha2-512']); + default_algorithms(Alg) -> supported_algorithms(Alg, []). @@ -122,10 +119,9 @@ supported_algorithms(public_key) -> {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]}, - {'rsa-sha2-384', [{public_keys,rsa}, {hashs,sha384} ]}, {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]}, - {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1 ]); -- cgit v1.2.3 From f4cf6605e8ddf4accb553c155a77878031850128 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 5 May 2017 16:18:00 +0200 Subject: ssh: fix broken preferred_algorithms and pref_public_key_algs options --- lib/ssh/src/ssh_options.erl | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 78f68dbcb1..aebb5a7062 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -430,12 +430,9 @@ default(client) -> }, {pref_public_key_algs, def} => - #{default => - ssh_transport:supported_algorithms(public_key), - chk => - fun check_pref_public_key_algs/1, - class => - ssh + #{default => ssh_transport:default_algorithms(public_key), + chk => fun check_pref_public_key_algs/1, + class => user_options }, {dh_gex_limits, def} => @@ -817,16 +814,23 @@ valid_hash(X, _) -> error_in_check(X, "Expect atom or list in fingerprint spec" %%%---------------------------------------------------------------- check_preferred_algorithms(Algs) -> + [error_in_check(K,"Bad preferred_algorithms key") + || {K,_} <- Algs, + not lists:keymember(K,1,ssh:default_algorithms())], + try alg_duplicates(Algs, [], []) of [] -> {true, - [try ssh_transport:supported_algorithms(Key) - of - DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs) - catch - _:_ -> error_in_check(Key,"Bad preferred_algorithms key") - end || {Key,Vals} <- Algs] + [case proplists:get_value(Key, Algs) of + undefined -> + {Key,DefAlgs}; + Vals -> + handle_pref_alg(Key,Vals,SupAlgs) + end + || {{Key,DefAlgs}, {Key,SupAlgs}} <- lists:zip(ssh:default_algorithms(), + ssh_transport:supported_algorithms()) + ] }; Dups -> -- cgit v1.2.3 From ebd2baf9c433d489aff66f14505b5c221ba04165 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 8 May 2017 14:57:11 +0200 Subject: ssh: Use 'server-sig-algs' for client's selection of algs --- lib/ssh/src/ssh_connection_handler.erl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 39bd54869f..6a6b9896cb 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1701,15 +1701,18 @@ handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> lists:foldl(fun ext_info/2, D0, Data). -ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client}=Ssh0}) -> +ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client, + userauth_pubkeys=ClientSigAlgs}=Ssh0}) -> %% Make strings to eliminate risk of beeing bombed with odd strings that fills the atom table: SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), - Ssh = Ssh0#ssh{userauth_pubkeys = - [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), - %% length of SigAlg is implicitly checked by member: - lists:member(SigAlg, SupportedAlgs) - ]}, - D0#data{ssh_params = Ssh}; + ServerSigAlgs = [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), + %% length of SigAlg is implicitly checked by the comparison + %% in member/2: + lists:member(SigAlg, SupportedAlgs) + ], + CommonAlgs = [Alg || Alg <- ServerSigAlgs, + lists:member(Alg, ClientSigAlgs)], + D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = CommonAlgs} }; ext_info(_, D0) -> %% Not implemented -- cgit v1.2.3 From 90de09b680d33bf4e048771381134ac8d7e0fa70 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 11 May 2017 15:19:18 +0200 Subject: ssh: select server-sig-algs from configured algos (preferred_algorithms) --- lib/ssh/src/ssh_transport.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index bd1cb4bd22..cebbec7792 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -741,9 +741,11 @@ ext_info_message(#ssh{role=client, end; ext_info_message(#ssh{role=server, - send_ext_info=true} = Ssh0) -> + send_ext_info=true, + opts = Opts} = Ssh0) -> AlgsList = lists:map(fun erlang:atom_to_list/1, - ssh_transport:default_algorithms(public_key)), + proplists:get_value(public_key, + ?GET_OPT(preferred_algorithms, Opts))), Msg = #ssh_msg_ext_info{nr_extensions = 1, data = [{"server-sig-algs", string:join(AlgsList,",")}] }, -- cgit v1.2.3 From 4d7ff0a8169141d18335638cf7c6e48d4c18cdf2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 16 May 2017 11:52:45 +0200 Subject: ssh: disable rsa-sha2-* for clients because there is a bug in the client verification code for those algorithms --- lib/ssh/src/ssh_options.erl | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index aebb5a7062..12c0190082 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -392,6 +392,12 @@ default(server) -> class => user_options }, + {preferred_algorithms, def} => + #{default => ssh:default_algorithms(), + chk => fun check_preferred_algorithms/1, + class => user_options + }, + %%%%% Undocumented {infofun, def} => #{default => fun(_,_,_) -> void end, @@ -430,11 +436,26 @@ default(client) -> }, {pref_public_key_algs, def} => - #{default => ssh_transport:default_algorithms(public_key), + #{default => ssh_transport:default_algorithms(public_key) -- ['rsa-sha2-256', + 'rsa-sha2-512'], chk => fun check_pref_public_key_algs/1, class => user_options }, + {preferred_algorithms, def} => + #{default => [{K,Vs} || {K,Vs0} <- ssh:default_algorithms(), + Vs <- [case K of + public_key -> + Vs0 -- ['rsa-sha2-256', + 'rsa-sha2-512']; + _ -> + Vs0 + end] + ], + chk => fun check_preferred_algorithms/1, + class => user_options + }, + {dh_gex_limits, def} => #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? chk => fun({Min,I,Max}) -> @@ -500,12 +521,6 @@ default(common) -> class => user_options }, - {preferred_algorithms, def} => - #{default => ssh:default_algorithms(), - chk => fun check_preferred_algorithms/1, - class => user_options - }, - {id_string, def} => #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 chk => fun(random) -> -- cgit v1.2.3 From 594d84311dd22658df695f238ac562fdcba9f060 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 May 2017 20:52:53 +0200 Subject: Revert "ssh: disable rsa-sha2-* for clients" This reverts commit 4d7ff0a8169141d18335638cf7c6e48d4c18cdf2. --- lib/ssh/src/ssh_options.erl | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 12c0190082..aebb5a7062 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -392,12 +392,6 @@ default(server) -> class => user_options }, - {preferred_algorithms, def} => - #{default => ssh:default_algorithms(), - chk => fun check_preferred_algorithms/1, - class => user_options - }, - %%%%% Undocumented {infofun, def} => #{default => fun(_,_,_) -> void end, @@ -436,26 +430,11 @@ default(client) -> }, {pref_public_key_algs, def} => - #{default => ssh_transport:default_algorithms(public_key) -- ['rsa-sha2-256', - 'rsa-sha2-512'], + #{default => ssh_transport:default_algorithms(public_key), chk => fun check_pref_public_key_algs/1, class => user_options }, - {preferred_algorithms, def} => - #{default => [{K,Vs} || {K,Vs0} <- ssh:default_algorithms(), - Vs <- [case K of - public_key -> - Vs0 -- ['rsa-sha2-256', - 'rsa-sha2-512']; - _ -> - Vs0 - end] - ], - chk => fun check_preferred_algorithms/1, - class => user_options - }, - {dh_gex_limits, def} => #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? chk => fun({Min,I,Max}) -> @@ -521,6 +500,12 @@ default(common) -> class => user_options }, + {preferred_algorithms, def} => + #{default => ssh:default_algorithms(), + chk => fun check_preferred_algorithms/1, + class => user_options + }, + {id_string, def} => #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 chk => fun(random) -> -- cgit v1.2.3 From c99b6f0aa70457453b37533adf6d3872f7009fac Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 May 2017 10:03:34 +0200 Subject: ssh: Handle if server-sig-algs and client has empty intersection In case server-sig-algs names only algorithms unknown to the client, the client will try with the ones it knows --- lib/ssh/src/ssh_connection_handler.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 6a6b9896cb..a77cfe51b5 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1712,7 +1712,12 @@ ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client, ], CommonAlgs = [Alg || Alg <- ServerSigAlgs, lists:member(Alg, ClientSigAlgs)], - D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = CommonAlgs} }; + SelectedAlgs = + case CommonAlgs of + [] -> ClientSigAlgs; % server-sig-algs value is just an advice + _ -> CommonAlgs + end, + D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = SelectedAlgs} }; ext_info(_, D0) -> %% Not implemented -- cgit v1.2.3 From 3507ea008839ad68dc16060a2696e3efde551684 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 May 2017 20:33:14 +0200 Subject: ssh: fix the rsa-sha2-* hostkey verify error --- lib/ssh/src/ssh_message.erl | 4 ++-- lib/ssh/src/ssh_transport.erl | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 609040826f..4f2eeca026 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -598,8 +598,8 @@ decode_kex_init(<>, Acc, N) -> %%% Signature decode/encode %%% -decode_signature(<>) -> - Signature. +decode_signature(<>) -> + {binary_to_list(Alg), Signature}. encode_signature({#'RSAPublicKey'{},Sign}, Signature) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 1a15798080..412f5de9de 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -776,16 +776,20 @@ extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID}, {#'ECPoint'{point=Q}, {namedCurve,OID}}. -verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, Signature) -> - case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of - false -> - {error, bad_signature}; - true -> - known_host_key(SSH, PublicKey, public_algo(PublicKey)) +verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature}) -> + case atom_to_list(Alg#alg.hkey) of + AlgStr -> + case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of + false -> + {error, bad_signature}; + true -> + known_host_key(SSH, PublicKey, public_algo(PublicKey)) + end; + _ -> + {error, bad_signature_name} end. - accepted_host(Ssh, PeerName, Public, Opts) -> case ?GET_OPT(silently_accept_hosts, Opts) of -- cgit v1.2.3 From 8343f1a9c8b6b9e506a298d286eb5e480f516fd3 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 May 2017 17:53:27 +0200 Subject: ssh: remove extra options copy from internal state --- lib/ssh/src/ssh_connection_handler.erl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 6a6b9896cb..5ca040f7ec 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -346,7 +346,7 @@ renegotiate_data(ConnectionHandler) -> | undefined, last_size_rekey = 0 :: non_neg_integer(), event_queue = [] :: list(), - opts :: ssh_options:options(), +% opts :: ssh_options:options(), inet_initial_recbuf_size :: pos_integer() | undefined }). @@ -398,8 +398,7 @@ init([Role,Socket,Opts]) -> transport_protocol = Protocol, transport_cb = Callback, transport_close_tag = CloseTag, - ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts), - opts = Opts + ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts) }, D = case Role of client -> @@ -1012,7 +1011,7 @@ handle_event(cast, renegotiate, _, _) -> handle_event(cast, data_size, {connected,Role}, D) -> {ok, [{send_oct,Sent0}]} = inet:getstat(D#data.socket, [send_oct]), Sent = Sent0 - D#data.last_size_rekey, - MaxSent = ?GET_OPT(rekey_limit, D#data.opts), + MaxSent = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), case Sent >= MaxSent of true -> @@ -1862,7 +1861,7 @@ get_repl(X, Acc) -> exit({get_repl,X,Acc}). %%%---------------------------------------------------------------- --define(CALL_FUN(Key,D), catch (?GET_OPT(Key, D#data.opts)) ). +-define(CALL_FUN(Key,D), catch (?GET_OPT(Key, (D#data.ssh_params)#ssh.opts)) ). disconnect_fun({disconnect,Msg}, D) -> ?CALL_FUN(disconnectfun,D)(Msg); disconnect_fun(Reason, D) -> ?CALL_FUN(disconnectfun,D)(Reason). @@ -1912,7 +1911,7 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts, %%% channels open for a while. cache_init_idle_timer(D) -> - case ?GET_OPT(idle_time, D#data.opts) of + case ?GET_OPT(idle_time, (D#data.ssh_params)#ssh.opts) of infinity -> D#data{idle_timer_value = infinity, idle_timer_ref = infinity % A flag used later... -- cgit v1.2.3 From 580dc012238b4fd2839730c0c44edaef55dc9b4b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 22 May 2017 15:03:09 +0200 Subject: ssh: fix ssh_property_test --- lib/ssh/src/ssh_message.erl | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 4f2eeca026..b1fc05ae33 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -611,13 +611,3 @@ encode_signature({{#'ECPoint'{}, {namedCurve,OID}},_}, Signature) -> CurveName = public_key:oid2ssh_curvename(OID), <>), ?Ebinary(Signature)>>. -%% encode_signature(#'RSAPublicKey'{}, Signature) -> -%% SignName = <<"ssh-rsa">>, -%% <>; -%% encode_signature({_, #'Dss-Parms'{}}, Signature) -> -%% <>), ?Ebinary(Signature)>>; -%% encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) -> -%% CurveName = public_key:oid2ssh_curvename(OID), -%% <>), ?Ebinary(Signature)>>. - - -- cgit v1.2.3 From 917712f10dd5e8dea17d12f7c9835680ad32ba9f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 19 May 2017 16:48:08 +0200 Subject: ssh: ssh_dbg print some server-sig-algs info --- lib/ssh/src/ssh_dbg.erl | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 7dfbfc3b4b..820d7ec61b 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -58,7 +58,8 @@ dbg_ssh_messages() -> dbg:tp(ssh_message,decode,1, x), dbg:tpl(ssh_transport,select_algorithm,4, x), dbg:tp(ssh_transport,hello_version_msg,1, x), - dbg:tp(ssh_transport,handle_hello_version,1, x). + dbg:tp(ssh_transport,handle_hello_version,1, x), + dbg:tpl(ssh_connection_handler,ext_info,2, x). %%%---------------------------------------------------------------- stop() -> @@ -90,6 +91,28 @@ msg_formater({trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS} msg_formater({trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> D; +msg_formater({trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",_SigAlgs},State]},TS}, D) -> + try lists:keyfind(ssh, 1, tuple_to_list(State)) of + false -> + D; + #ssh{userauth_pubkeys = PKs} -> + fmt("~n~s ~p Client suggests ~p~n", [ts(TS),Pid,PKs], D) + catch + _:_ -> + D + end; + +msg_formater({trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},State,TS}, D) -> + try lists:keyfind(ssh, 1, tuple_to_list(State)) of + false -> + D; + #ssh{userauth_pubkeys = PKs} -> + fmt("~n~s ~p Client will try public keys ~p~n", [ts(TS),Pid,PKs], D) + catch + _:_ -> + D + end; + msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D); -- cgit v1.2.3 From d1a31c78d75c16a360ee1de973660b9ec1caeb58 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 22 May 2017 16:49:37 +0200 Subject: ssh: Undocumented ssh_dbg extended with auth/0 auth/1 auth/2 ct_auth/0 ct_messages/0 --- lib/ssh/src/ssh_dbg.erl | 134 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 102 insertions(+), 32 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 820d7ec61b..003b3856e6 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -22,9 +22,10 @@ -module(ssh_dbg). --export([messages/0, - messages/1, - messages/2, +-export([messages/0, messages/1, messages/2, + ct_messages/0, + auth/0, auth/1, auth/2, + ct_auth/0, stop/0 ]). @@ -43,15 +44,33 @@ messages() -> messages(fun(String,_D) -> io:format(String) end). +ct_messages() -> + messages(fun(String,_D) -> ct:log(String,[]) end). + messages(Write) when is_function(Write,2) -> messages(Write, fun(X) -> X end). messages(Write, MangleArg) when is_function(Write,2), is_function(MangleArg,1) -> - catch dbg:start(), - setup_tracer(Write, MangleArg), - dbg:p(new,[c,timestamp]), - dbg_ssh_messages(). + cond_start(msg, Write, MangleArg), + dbg_ssh_messages(), + dbg_ssh_auth(). + + +auth() -> + auth(fun(String,_D) -> io:format(String) end). + +ct_auth() -> + auth(fun(String,_D) -> ct:log(String,[]) end). + +auth(Write) when is_function(Write,2) -> + auth(Write, fun(X) -> X end). + +auth(Write, MangleArg) when is_function(Write,2), + is_function(MangleArg,1) -> + cond_start(auth, Write, MangleArg), + dbg_ssh_auth(). + dbg_ssh_messages() -> dbg:tp(ssh_message,encode,1, x), @@ -61,80 +80,131 @@ dbg_ssh_messages() -> dbg:tp(ssh_transport,handle_hello_version,1, x), dbg:tpl(ssh_connection_handler,ext_info,2, x). +dbg_ssh_auth() -> + dbg:tp(ssh_transport,hello_version_msg,1, x), + dbg:tp(ssh_transport,handle_hello_version,1, x), + dbg:tp(ssh_message,encode,1, x), + dbg:tpl(ssh_transport,select_algorithm,4, x), + dbg:tpl(ssh_connection_handler,ext_info,2, x), + lists:foreach(fun(F) -> dbg:tp(ssh_auth, F, x) end, + [publickey_msg, password_msg, keyboard_interactive_msg]). + %%%---------------------------------------------------------------- stop() -> dbg:stop(). %%%================================================================ -msg_formater({trace_ts,Pid,call,{ssh_message,encode,[Msg]},TS}, D) -> +cond_start(Type, Write, MangleArg) -> + try + dbg:start(), + setup_tracer(Type, Write, MangleArg), + dbg:p(new,[c,timestamp]) + catch + _:_ -> ok + end. + + +msg_formater(msg, {trace_ts,Pid,call,{ssh_message,encode,[Msg]},TS}, D) -> fmt("~n~s SEND ~p ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); -msg_formater({trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) -> +msg_formater(msg, {trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) -> D; -msg_formater({trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> +msg_formater(msg, {trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> D; -msg_formater({trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> +msg_formater(msg, {trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> fmt("~n~s ~p RECV ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); + +msg_formater(auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_failure{authentications=As},TS}, D) -> + fmt("~n~s ~p Client login FAILURE. Try ~s~n", [ts(TS),Pid,As], D); -msg_formater({trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) -> +msg_formater(auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_success{},TS}, D) -> + fmt("~n~s ~p Client login SUCCESS~n", [ts(TS),Pid], D); + + +msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) -> D; -msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) -> +msg_formater(_, {trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) -> fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D); -msg_formater({trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> +msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> D; -msg_formater({trace_ts,Pid,return_from,{ssh_transport,hello_version_msg,1},Hello,TS}, D) -> +msg_formater(_, {trace_ts,Pid,return_from,{ssh_transport,hello_version_msg,1},Hello,TS}, D) -> fmt("~n~s ~p TCP SEND HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); -msg_formater({trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS}, D) -> +msg_formater(_, {trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS}, D) -> fmt("~n~s ~p RECV HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); -msg_formater({trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> +msg_formater(_, {trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> D; -msg_formater({trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",_SigAlgs},State]},TS}, D) -> +msg_formater(_, {trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",_SigAlgs},State]},TS}, D) -> try lists:keyfind(ssh, 1, tuple_to_list(State)) of false -> D; #ssh{userauth_pubkeys = PKs} -> - fmt("~n~s ~p Client suggests ~p~n", [ts(TS),Pid,PKs], D) + fmt("~n~s ~p Client got suggestion to use user public key sig-algs~n ~p~n", [ts(TS),Pid,PKs], D) catch _:_ -> D end; -msg_formater({trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},State,TS}, D) -> +msg_formater(_, {trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},State,TS}, D) -> try lists:keyfind(ssh, 1, tuple_to_list(State)) of false -> D; #ssh{userauth_pubkeys = PKs} -> - fmt("~n~s ~p Client will try public keys ~p~n", [ts(TS),Pid,PKs], D) + fmt("~n~s ~p Client will try user public key sig-algs~n ~p~n", [ts(TS),Pid,PKs], D) catch _:_ -> D end; -msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> +msg_formater(_, {trace_ts,Pid,call,{ssh_auth,publickey_msg,[[SigAlg,#ssh{user=User}]]},TS}, D) -> + fmt("~n~s ~p Client will try to login user ~p with public key algorithm ~p~n", [ts(TS),Pid,User,SigAlg], D); +msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> + fmt("~s ~p User ~p can't login with that kind of public key~n", [ts(TS),Pid,User], D); + +msg_formater(_, {trace_ts,Pid,call,{ssh_auth,password_msg,[[#ssh{user=User}]]},TS}, D) -> + fmt("~n~s ~p Client will try to login user ~p with password~n", [ts(TS),Pid,User], D); +msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,password_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> + fmt("~s ~p User ~p can't login with password~n", [ts(TS),Pid,User], D); + +msg_formater(_, {trace_ts,Pid,call,{ssh_auth,keyboard_interactive_msg,[[#ssh{user=User}]]},TS}, D) -> + fmt("~n~s ~p Client will try to login user ~p with password~n", [ts(TS),Pid,User], D); +msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,keyboard_interactive_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> + fmt("~s ~p User ~p can't login with keyboard_interactive password~n", [ts(TS),Pid,User], D); + +msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D); -msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Dest,TS}, D) -> +msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Dest,TS}, D) -> fmt("~n~s ~p TCP SEND from ~p TO ~p~n ~p~n", [ts(TS),Pid,Sock,Dest, shrink_bin(Bytes)], D); -msg_formater({trace_ts,Pid,send,ErlangMsg,Dest,TS}, D) -> +msg_formater(msg, {trace_ts,Pid,send,ErlangMsg,Dest,TS}, D) -> fmt("~n~s ~p ERL MSG SEND TO ~p~n ~p~n", [ts(TS),Pid,Dest, shrink_bin(ErlangMsg)], D); -msg_formater({trace_ts,Pid,'receive',{tcp,Sock,Bytes},TS}, D) -> +msg_formater(msg, {trace_ts,Pid,'receive',{tcp,Sock,Bytes},TS}, D) -> fmt("~n~s ~p TCP RECEIVE on ~p~n ~p~n", [ts(TS),Pid,Sock,shrink_bin(Bytes)], D); -msg_formater({trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> +msg_formater(msg, {trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> fmt("~n~s ~p ERL MSG RECEIVE~n ~p~n", [ts(TS),Pid,shrink_bin(ErlangMsg)], D); -msg_formater(M, D) -> - fmt("~nDBG ~n~p~n", [shrink_bin(M)], D). +%% msg_formater(_, {trace_ts,_Pid,return_from,MFA,_Ret,_TS}=M, D) -> +%% case lists:member(MFA, [{ssh_auth,keyboard_interactive_msg,1}, +%% {ssh_auth,password_msg,1}, +%% {ssh_auth,publickey_msg,1}]) of +%% true -> +%% D; +%% false -> +%% fmt("~nDBG ~n~p~n", [shrink_bin(M)], D) +%% end; + +%% msg_formater(_, M, D) -> +%% fmt("~nDBG ~n~p~n", [shrink_bin(M)], D). -%% msg_formater(_, D) -> -%% D. +msg_formater(_, _, D) -> + D. fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) -> @@ -146,9 +216,9 @@ ts({_,_,Usec}=Now) -> ts(_) -> "-". %%%---------------------------------------------------------------- -setup_tracer(Write, MangleArg) -> +setup_tracer(Type, Write, MangleArg) -> Handler = fun(Arg, D) -> - msg_formater(MangleArg(Arg), D) + msg_formater(Type, MangleArg(Arg), D) end, InitialData = #data{writer = Write}, {ok,_} = dbg:tracer(process, {Handler, InitialData}), -- cgit v1.2.3 From 8611454d37da15627a79507ca62bf25843e62493 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 23 May 2017 13:46:43 +0200 Subject: ssh: Improve 'server-sig-algs' handling in client --- lib/ssh/src/ssh_auth.erl | 59 +++++++++++++---------- lib/ssh/src/ssh_connection_handler.erl | 88 +++++++++++++++++++++++----------- 2 files changed, 95 insertions(+), 52 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 6cf659f830..ac64a7bf14 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -28,7 +28,8 @@ -include("ssh_auth.hrl"). -include("ssh_transport.hrl"). --export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, +-export([get_public_key/2, + publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, service_request_msg/1, init_userauth_request_msg/1, userauth_request_msg/1, handle_userauth_request/3, handle_userauth_info_request/2, handle_userauth_info_response/2 @@ -136,41 +137,49 @@ keyboard_interactive_msg([#ssh{user = User, Ssh) end. -publickey_msg([SigAlg, #ssh{user = User, - session_id = SessionId, - service = Service, - opts = Opts} = Ssh]) -> - Hash = ssh_transport:sha(SigAlg), + +get_public_key(SigAlg, #ssh{opts = Opts}) -> KeyAlg = key_alg(SigAlg), {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of - {ok, PrivKey} -> - SigAlgStr = atom_to_list(SigAlg), + {ok, PrivKey} -> try Key = ssh_transport:extract_public_key(PrivKey), public_key:ssh_encode(Key, ssh2_pubkey) of - PubKeyBlob -> - SigData = build_sig_data(SessionId, User, Service, - PubKeyBlob, SigAlgStr), - Sig = ssh_transport:sign(SigData, Hash, PrivKey), - SigBlob = list_to_binary([?string(SigAlgStr), - ?binary(Sig)]), - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "publickey", - data = [?TRUE, - ?string(SigAlgStr), - ?binary(PubKeyBlob), - ?binary(SigBlob)]}, - Ssh) + PubKeyBlob -> {ok,{PrivKey,PubKeyBlob}} catch _:_ -> - {not_ok, Ssh} + not_ok end; - _Error -> + _Error -> + not_ok + end. + + +publickey_msg([SigAlg, #ssh{user = User, + session_id = SessionId, + service = Service} = Ssh]) -> + case get_public_key(SigAlg, Ssh) of + {ok, {PrivKey,PubKeyBlob}} -> + SigAlgStr = atom_to_list(SigAlg), + SigData = build_sig_data(SessionId, User, Service, + PubKeyBlob, SigAlgStr), + Hash = ssh_transport:sha(SigAlg), + Sig = ssh_transport:sign(SigData, Hash, PrivKey), + SigBlob = list_to_binary([?string(SigAlgStr), + ?binary(Sig)]), + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "publickey", + data = [?TRUE, + ?string(SigAlgStr), + ?binary(PubKeyBlob), + ?binary(SigBlob)]}, + Ssh); + _ -> {not_ok, Ssh} end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f1ce337947..4c6aff5c24 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -453,16 +453,20 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> PeerName0 when is_list(PeerName0) -> PeerName0 end, - S0#ssh{c_vsn = Vsn, - c_version = Version, - io_cb = case ?GET_OPT(user_interaction, Opts) of - true -> ssh_io; - false -> ssh_no_io - end, - userauth_pubkeys = ?GET_OPT(pref_public_key_algs, Opts), - userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), - peer = {PeerName, PeerAddr} - }; + S1 = + S0#ssh{c_vsn = Vsn, + c_version = Version, + io_cb = case ?GET_OPT(user_interaction, Opts) of + true -> ssh_io; + false -> ssh_no_io + end, + userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), + peer = {PeerName, PeerAddr} + }, + S1#ssh{userauth_pubkeys = [K || K <- ?GET_OPT(pref_public_key_algs, Opts), + is_usable_user_pubkey(K, S1) + ] + }; server -> S0#ssh{s_vsn = Vsn, @@ -1700,28 +1704,58 @@ handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> lists:foldl(fun ext_info/2, D0, Data). -ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client, - userauth_pubkeys=ClientSigAlgs}=Ssh0}) -> - %% Make strings to eliminate risk of beeing bombed with odd strings that fills the atom table: - SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), - ServerSigAlgs = [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), - %% length of SigAlg is implicitly checked by the comparison - %% in member/2: - lists:member(SigAlg, SupportedAlgs) - ], - CommonAlgs = [Alg || Alg <- ServerSigAlgs, - lists:member(Alg, ClientSigAlgs)], - SelectedAlgs = - case CommonAlgs of - [] -> ClientSigAlgs; % server-sig-algs value is just an advice - _ -> CommonAlgs - end, - D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = SelectedAlgs} }; +ext_info({"server-sig-algs",SigAlgsStr}, + D0 = #data{ssh_params=#ssh{role=client, + userauth_pubkeys=ClientSigAlgs}=Ssh0}) -> + %% ClientSigAlgs are the pub_key algortithms that: + %% 1) is usable, that is, the user has such a public key and + %% 2) is either the default list or set by the caller + %% with the client option 'pref_public_key_algs' + %% + %% The list is already checked for duplicates. + + SigAlgs = [A || Astr <- string:tokens(SigAlgsStr, ","), + A <- try [list_to_existing_atom(Astr)] + %% list_to_existing_atom will fail for unknown algorithms + catch _:_ -> [] + end], + + CommonAlgs = [A || A <- SigAlgs, + lists:member(A, ClientSigAlgs)], + + %% Re-arrange the client supported public-key algorithms so that the server + %% preferred ones are tried first. + %% Trying algorithms not mentioned by the server is ok, since the server can't know + %% if the client supports 'server-sig-algs' or not. + + D0#data{ + ssh_params = + Ssh0#ssh{ + userauth_pubkeys = + CommonAlgs ++ (ClientSigAlgs -- CommonAlgs) + }}; + + %% If there are algorithms common to the client and the server, use them. + %% Otherwise try with ones that the client supports. The server-sig-alg + %% list is a suggestion, not an order. + %% case CommonAlgs of + %% [_|_] -> + %% D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = CommonAlgs}}; + %% [] -> + %% D0 + %% end; ext_info(_, D0) -> %% Not implemented D0. +%%%---------------------------------------------------------------- +is_usable_user_pubkey(A, Ssh) -> + case ssh_auth:get_public_key(A, Ssh) of + {ok,_} -> true; + _ -> false + end. + %%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of -- cgit v1.2.3 From 9c4d91f4726ff84df8877fc6c73edcd116775a52 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 24 May 2017 15:04:43 +0200 Subject: ssh: ssh_options checks 'pref_public_key_algs' for dubblets --- lib/ssh/src/ssh_options.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index aebb5a7062..7eeed70739 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -674,7 +674,11 @@ check_pref_public_key_algs(V) -> PKs = ssh_transport:supported_algorithms(public_key), CHK = fun(A, Ack) -> case lists:member(A, PKs) of - true -> [A|Ack]; + true -> + case lists:member(A,Ack) of + false -> [A|Ack]; + true -> Ack % Remove duplicates + end; false -> error_in_check(A, "Not supported public key") end end, -- cgit v1.2.3 From b4327e257147a64fc088d1448132f5794bad879f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 24 May 2017 15:11:03 +0200 Subject: ssh: Change printouts for ssh_dbg:auth() This reverts commit 4ee80fd8738393bf581e0393416befda1ca621b6. --- lib/ssh/src/ssh_dbg.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 003b3856e6..d5d4ab04c3 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -136,12 +136,13 @@ msg_formater(_, {trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]}, msg_formater(_, {trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> D; -msg_formater(_, {trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",_SigAlgs},State]},TS}, D) -> +msg_formater(_, {trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",SigAlgs},State]},TS}, D) -> try lists:keyfind(ssh, 1, tuple_to_list(State)) of false -> D; #ssh{userauth_pubkeys = PKs} -> - fmt("~n~s ~p Client got suggestion to use user public key sig-algs~n ~p~n", [ts(TS),Pid,PKs], D) + fmt("~n~s ~p Client got suggestion to use user public key sig-algs~n ~p~n and can use~n ~p~n", + [ts(TS),Pid,string:tokens(SigAlgs,","),PKs], D) catch _:_ -> D -- cgit v1.2.3 From a17b94cc89dd14cf1027e05a0b3def68f2c1e96c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Jun 2017 13:36:24 +0200 Subject: ssh: Removed out-commented code --- lib/ssh/src/ssh_connection_handler.erl | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4c6aff5c24..8d3ddb09a4 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1735,16 +1735,6 @@ ext_info({"server-sig-algs",SigAlgsStr}, CommonAlgs ++ (ClientSigAlgs -- CommonAlgs) }}; - %% If there are algorithms common to the client and the server, use them. - %% Otherwise try with ones that the client supports. The server-sig-alg - %% list is a suggestion, not an order. - %% case CommonAlgs of - %% [_|_] -> - %% D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = CommonAlgs}}; - %% [] -> - %% D0 - %% end; - ext_info(_, D0) -> %% Not implemented D0. -- cgit v1.2.3 From 8ce5d8239bc49ca72df11ca0a614dfa01fbf931c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Jun 2017 16:02:12 +0200 Subject: ssh: Restructure internal tool ssh_dbg The need for more trace patterns requires a somewhat different structure. It was previoiusly a bit difficult to use in e.g. test suites. Now it is easier. --- lib/ssh/src/ssh_dbg.erl | 88 ++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index d5d4ab04c3..3f742ad9b6 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -22,10 +22,8 @@ -module(ssh_dbg). --export([messages/0, messages/1, messages/2, - ct_messages/0, - auth/0, auth/1, auth/2, - ct_auth/0, +-export([messages/0, messages/1, messages/2, messages/3, + auth/0, auth/1, auth/2, auth/3, stop/0 ]). @@ -37,50 +35,52 @@ -include("ssh_connect.hrl"). -include("ssh_auth.hrl"). --record(data, { - writer, - acc = []}). %%%================================================================ -messages() -> - messages(fun(String,_D) -> io:format(String) end). +messages() -> start(msg). +messages(F) -> start(msg,F). +messages(F,X) -> start(msg,F,X). +messages(F,M,I) -> start(msg,F,M,I). -ct_messages() -> - messages(fun(String,_D) -> ct:log(String,[]) end). +auth() -> start(auth). +auth(F) -> start(auth,F). +auth(F,X) -> start(auth,F,X). +auth(F,M,I) -> start(auth,F,M,I). -messages(Write) when is_function(Write,2) -> - messages(Write, fun(X) -> X end). - -messages(Write, MangleArg) when is_function(Write,2), - is_function(MangleArg,1) -> - cond_start(msg, Write, MangleArg), - dbg_ssh_messages(), - dbg_ssh_auth(). +stop() -> dbg:stop(). +%%%---------------------------------------------------------------- +start(Type) -> start(Type, fun io:format/2). -auth() -> - auth(fun(String,_D) -> io:format(String) end). +start(Type, F) when is_function(F,2) -> start(Type, fmt_fun(F)); +start(Type, F) when is_function(F,3) -> start(Type, F, id_fun()). -ct_auth() -> - auth(fun(String,_D) -> ct:log(String,[]) end). +start(Type, WriteFun, MangleArgFun) when is_function(WriteFun, 3), + is_function(MangleArgFun, 1) -> + start(Type, WriteFun, MangleArgFun, []); +start(Type, WriteFun, InitValue) -> + start(Type, WriteFun, id_fun(), InitValue). -auth(Write) when is_function(Write,2) -> - auth(Write, fun(X) -> X end). +start(Type, WriteFun, MangleArgFun, InitValue) when is_function(WriteFun, 3), + is_function(MangleArgFun, 1) -> + cond_start(Type, WriteFun, MangleArgFun, InitValue), + dbg_ssh(Type). -auth(Write, MangleArg) when is_function(Write,2), - is_function(MangleArg,1) -> - cond_start(auth, Write, MangleArg), - dbg_ssh_auth(). +%%%---------------------------------------------------------------- +fmt_fun(F) -> fun(Fmt,Args,Data) -> F(Fmt,Args), Data end. +id_fun() -> fun(X) -> X end. -dbg_ssh_messages() -> +%%%---------------------------------------------------------------- +dbg_ssh(msg) -> + dbg_ssh(auth), dbg:tp(ssh_message,encode,1, x), dbg:tp(ssh_message,decode,1, x), dbg:tpl(ssh_transport,select_algorithm,4, x), dbg:tp(ssh_transport,hello_version_msg,1, x), dbg:tp(ssh_transport,handle_hello_version,1, x), - dbg:tpl(ssh_connection_handler,ext_info,2, x). + dbg:tpl(ssh_connection_handler,ext_info,2, x); -dbg_ssh_auth() -> +dbg_ssh(auth) -> dbg:tp(ssh_transport,hello_version_msg,1, x), dbg:tp(ssh_transport,handle_hello_version,1, x), dbg:tp(ssh_message,encode,1, x), @@ -89,15 +89,11 @@ dbg_ssh_auth() -> lists:foreach(fun(F) -> dbg:tp(ssh_auth, F, x) end, [publickey_msg, password_msg, keyboard_interactive_msg]). -%%%---------------------------------------------------------------- -stop() -> - dbg:stop(). - %%%================================================================ -cond_start(Type, Write, MangleArg) -> +cond_start(Type, WriteFun, MangleArgFun, Init) -> try dbg:start(), - setup_tracer(Type, Write, MangleArg), + setup_tracer(Type, WriteFun, MangleArgFun, Init), dbg:p(new,[c,timestamp]) catch _:_ -> ok @@ -207,21 +203,25 @@ msg_formater(msg, {trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> msg_formater(_, _, D) -> D. +%%%---------------------------------------------------------------- +-record(data, {writer, + acc}). -fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) -> - D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}. +fmt(Fmt, Args, D=#data{writer=Write, acc=Acc}) -> + D#data{acc = Write(Fmt,Args,Acc)}. ts({_,_,Usec}=Now) -> {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now), io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); ts(_) -> "-". -%%%---------------------------------------------------------------- -setup_tracer(Type, Write, MangleArg) -> + +setup_tracer(Type, WriteFun, MangleArgFun, Init) -> Handler = fun(Arg, D) -> - msg_formater(Type, MangleArg(Arg), D) + msg_formater(Type, MangleArgFun(Arg), D) end, - InitialData = #data{writer = Write}, + InitialData = #data{writer = WriteFun, + acc = Init}, {ok,_} = dbg:tracer(process, {Handler, InitialData}), ok. -- cgit v1.2.3 From 6be0ed124af615a3ea17d4f913649b50aef68821 Mon Sep 17 00:00:00 2001 From: Daniel Pettersson Date: Wed, 5 Jul 2017 23:35:58 +0200 Subject: Add support for binary input in ssh_io --- lib/ssh/src/ssh_io.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 8ba759ad60..a7cd1daeec 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -31,8 +31,8 @@ read_line(Prompt, Opts) -> format("~s", [listify(Prompt)]), ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question}, receive - Answer when is_list(Answer) -> - Answer + Answer when is_list(Answer) or is_binary(Answer) -> + unicode:characters_to_list(Answer) end. yes_no(Prompt, Opts) -> @@ -44,7 +44,7 @@ yes_no(Prompt, Opts) -> y -> yes; n -> no; - Answer when is_list(Answer) -> + Answer when is_list(Answer) or is_binary(Answer) -> case trim(Answer) of "y" -> yes; "n" -> no; @@ -60,7 +60,7 @@ read_password(Prompt, Opts) -> format("~s", [listify(Prompt)]), ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), user_password}, receive - Answer when is_list(Answer) -> + Answer when is_list(Answer) or is_binary(Answer) -> case trim(Answer) of "" -> read_password(Prompt, Opts); -- cgit v1.2.3 From fac5b0637447a2f8b098dc92ff3a0b6b3cd4e962 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 17 Aug 2017 12:07:59 +0200 Subject: Accept non-binary options as socket-options For example the option {raw,6,6,<<2,0,0,0>>} --- lib/ssh/src/ssh_options.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 7eeed70739..b41ad8b33b 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -236,7 +236,10 @@ save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> %% by the check fun will give an error exception: error:{check,{BadValue,Extra}} -> error({eoptions, {Key,BadValue}, Extra}) - end. + end; +save(Opt, _Defs, OptMap) when is_map(OptMap) -> + OptMap#{socket_options := [Opt | maps:get(socket_options,OptMap)]}. + %%%================================================================ %%% -- cgit v1.2.3 From 5265f855ed1878158b2dc546fa3037b86743229c Mon Sep 17 00:00:00 2001 From: Hamidreza Soleimani Date: Mon, 14 Aug 2017 22:49:58 +0200 Subject: Fix file owner access permission in ssh_sftp module Previously, a hard-coded atom (read_write) has been used as file owner access permission in response to ssh_sftp:read_file_info/2 function. With this fix, the actual value of file owner access permission is added to the returning record. That value is calculated from file mode value. --- lib/ssh/src/ssh_sftp.erl | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index c1558a19b1..9e1229dc85 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1050,7 +1050,7 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) -> #file_info{ size = A#ssh_xfer_attr.size, type = A#ssh_xfer_attr.type, - access = read_write, %% FIXME: read/write/read_write/none + access = file_mode_to_owner_access(A#ssh_xfer_attr.permissions), atime = unix_to_datetime(A#ssh_xfer_attr.atime), mtime = unix_to_datetime(A#ssh_xfer_attr.mtime), ctime = unix_to_datetime(A#ssh_xfer_attr.createtime), @@ -1062,6 +1062,28 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) -> uid = A#ssh_xfer_attr.owner, gid = A#ssh_xfer_attr.group}. +file_mode_to_owner_access(FileMode) + when is_integer(FileMode) -> + %% The file mode contains the access permissions. + %% The read and write access permission of file owner + %% are located in 8th and 7th bit of file mode respectively. + + ReadPermission = ((FileMode bsr 8) band 1), + WritePermission = ((FileMode bsr 7) band 1), + case {ReadPermission, WritePermission} of + {1, 1} -> + read_write; + {1, 0} -> + read; + {0, 1} -> + write; + {0, 0} -> + none; + _ -> + undefined + end; +file_mode_to_owner_access(_) -> + undefined. unix_to_datetime(undefined) -> undefined; -- cgit v1.2.3 From 4bdb8f52303ceecb5e7c00592b53aa8dcaa49ab9 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 28 Aug 2017 11:49:46 +0200 Subject: ssh: Added option 'modify_algorithms' with prepend, append and rm If a user wants to enable one non-default algorithm or remove a default one, the option preferred_algorithms is too complicated since it replaces the whole set of algorithms. --- lib/ssh/src/ssh_options.erl | 270 ++++++++++++++++++++++++++++++------------ lib/ssh/src/ssh_transport.erl | 19 +++ 2 files changed, 214 insertions(+), 75 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index b41ad8b33b..d8931da774 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -170,9 +170,10 @@ handle_options(Role, PropList0, Opts0) when is_map(Opts0), OptionDefinitions), %% Enter the user's values into the map; unknown keys are %% treated as socket options - lists:foldl(fun(KV, Vals) -> - save(KV, OptionDefinitions, Vals) - end, InitialMap, PropList1) + final_preferred_algorithms( + lists:foldl(fun(KV, Vals) -> + save(KV, OptionDefinitions, Vals) + end, InitialMap, PropList1)) catch error:{eoptions, KV, undefined} -> {error, {eoptions,KV}}; @@ -509,6 +510,15 @@ default(common) -> class => user_options }, + %% NOTE: This option is supposed to be used only in this very module (?MODULE). There is + %% a final stage in handle_options that "merges" the preferred_algorithms option and this one. + %% The preferred_algorithms is the one to use in the rest of the ssh application! + {modify_algorithms, def} => + #{default => undefined, % signals error if unsupported algo in preferred_algorithms :( + chk => fun check_modify_algorithms/1, + class => user_options + }, + {id_string, def} => #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 chk => fun(random) -> @@ -820,83 +830,193 @@ valid_hash(L, Ss) when is_list(L) -> lists:all(fun(S) -> valid_hash(S,Ss) end, L valid_hash(X, _) -> error_in_check(X, "Expect atom or list in fingerprint spec"). %%%---------------------------------------------------------------- -check_preferred_algorithms(Algs) -> - [error_in_check(K,"Bad preferred_algorithms key") - || {K,_} <- Algs, - not lists:keymember(K,1,ssh:default_algorithms())], +check_modify_algorithms(M) when is_list(M) -> + [error_in_check(Op_KVs, "Bad modify_algorithms") + || Op_KVs <- M, + not is_tuple(Op_KVs) + orelse (size(Op_KVs) =/= 2) + orelse (not lists:member(element(1,Op_KVs), [append,prepend,rm]))], + {true, [{Op,normalize_mod_algs(KVs,false)} || {Op,KVs} <- M]}; +check_modify_algorithms(_) -> + error_in_check(modify_algorithms, "Bad option value. List expected."). + + + + +normalize_mod_algs(KVs, UseDefaultAlgs) -> + normalize_mod_algs(ssh_transport:algo_classes(), KVs, [], UseDefaultAlgs). + +normalize_mod_algs([K|Ks], KVs0, Acc, UseDefaultAlgs) -> + %% Pick the expected keys in order and check if they are in the user's list + {Vs1, KVs} = + case lists:keytake(K, 1, KVs0) of + {value, {K,Vs0}, KVs1} -> + {Vs0, KVs1}; + false -> + {[], KVs0} + end, + Vs = normalize_mod_alg_list(K, Vs1, UseDefaultAlgs), + normalize_mod_algs(Ks, KVs, [{K,Vs} | Acc], UseDefaultAlgs); +normalize_mod_algs([], [], Acc, _) -> + %% No values left in the key-value list after removing the expected entries + %% (thats good) + lists:reverse(Acc); +normalize_mod_algs([], [{K,_}|_], _, _) -> + %% Some values left in the key-value list after removing the expected entries + %% (thats bad) + case ssh_transport:algo_class(K) of + true -> error_in_check(K, "Duplicate key"); + false -> error_in_check(K, "Unknown key") + end; +normalize_mod_algs([], [X|_], _, _) -> + error_in_check(X, "Bad list element"). + + + +%%% Handle the algorithms list +normalize_mod_alg_list(K, Vs, UseDefaultAlgs) -> + normalize_mod_alg_list(K, + ssh_transport:algo_two_spec_class(K), + Vs, + def_alg(K,UseDefaultAlgs)). + + +normalize_mod_alg_list(_K, _, [], Default) -> + Default; + +normalize_mod_alg_list(K, true, [{client2server,L1}], [_,{server2client,L2}]) -> + [nml1(K,{client2server,L1}), + {server2client,L2}]; + +normalize_mod_alg_list(K, true, [{server2client,L2}], [{client2server,L1},_]) -> + [{client2server,L1}, + nml1(K,{server2client,L2})]; + +normalize_mod_alg_list(K, true, [{server2client,L2},{client2server,L1}], _) -> + [nml1(K,{client2server,L1}), + nml1(K,{server2client,L2})]; + +normalize_mod_alg_list(K, true, [{client2server,L1},{server2client,L2}], _) -> + [nml1(K,{client2server,L1}), + nml1(K,{server2client,L2})]; + +normalize_mod_alg_list(K, true, L0, _) -> + L = nml(K,L0), % Throws errors + [{client2server,L}, + {server2client,L}]; + +normalize_mod_alg_list(K, false, L, _) -> + nml(K,L). + + +nml1(K, {T,V}) when T==client2server ; T==server2client -> + {T, nml({K,T}, V)}. + +nml(K, L) -> + [error_in_check(K, "Bad value for this key") % This is a throw + || V <- L, + not is_atom(V) + ], + case L -- lists:usort(L) of + [] -> ok; + Dups -> error_in_check({K,Dups}, "Duplicates") % This is a throw + end, + L. - try alg_duplicates(Algs, [], []) - of - [] -> - {true, - [case proplists:get_value(Key, Algs) of - undefined -> - {Key,DefAlgs}; - Vals -> - handle_pref_alg(Key,Vals,SupAlgs) - end - || {{Key,DefAlgs}, {Key,SupAlgs}} <- lists:zip(ssh:default_algorithms(), - ssh_transport:supported_algorithms()) - ] - }; - - Dups -> - error_in_check(Dups, "Duplicates") - catch - _:_ -> - false - end. -alg_duplicates([{K,V}|KVs], Ks, Dups0) -> - Dups = - case lists:member(K,Ks) of - true -> [K|Dups0]; - false -> Dups0 - end, - case V--lists:usort(V) of - [] -> alg_duplicates(KVs, [K|Ks], Dups); - Ds -> alg_duplicates(KVs, [K|Ks], Dups++Ds) +def_alg(K, false) -> + case ssh_transport:algo_two_spec_class(K) of + false -> []; + true -> [{client2server,[]}, {server2client,[]}] end; -alg_duplicates([], _Ks, Dups) -> - Dups. - -handle_pref_alg(Key, - Vs=[{client2server,C2Ss=[_|_]},{server2client,S2Cs=[_|_]}], - [{client2server,Sup_C2Ss},{server2client,Sup_S2Cs}] - ) -> - chk_alg_vs(Key, C2Ss, Sup_C2Ss), - chk_alg_vs(Key, S2Cs, Sup_S2Cs), - {Key, Vs}; - -handle_pref_alg(Key, - Vs=[{server2client,[_|_]},{client2server,[_|_]}], - Sup=[{client2server,_},{server2client,_}] - ) -> - handle_pref_alg(Key, lists:reverse(Vs), Sup); - -handle_pref_alg(Key, - Vs=[V|_], - Sup=[{client2server,_},{server2client,_}] - ) when is_atom(V) -> - handle_pref_alg(Key, [{client2server,Vs},{server2client,Vs}], Sup); - -handle_pref_alg(Key, - Vs=[V|_], - Sup=[S|_] - ) when is_atom(V), is_atom(S) -> - chk_alg_vs(Key, Vs, Sup), - {Key, Vs}; - -handle_pref_alg(Key, Vs, _) -> - error_in_check({Key,Vs}, "Badly formed list"). - -chk_alg_vs(OptKey, Values, SupportedValues) -> - case (Values -- SupportedValues) of - [] -> Values; - [none] -> [none]; % for testing only - Bad -> error_in_check({OptKey,Bad}, "Unsupported value(s) found") +def_alg(K, true) -> + ssh_transport:default_algorithms(K). + + + +check_preferred_algorithms(Algs) when is_list(Algs) -> + check_input_ok(Algs), + {true, normalize_mod_algs(Algs, true)}; + +check_preferred_algorithms(Algs) when is_list(Algs) -> + check_preferred_algorithms({false,Algs}); + +check_preferred_algorithms(_) -> + error_in_check(modify_algorithms, "Bad option value. List expected."). + + +check_input_ok(Algs) -> + [error_in_check(KVs, "Bad preferred_algorithms") + || KVs <- Algs, + not is_tuple(KVs) + orelse (size(KVs) =/= 2)]. + +%%%---------------------------------------------------------------- +final_preferred_algorithms(Options) -> + Result = + case ?GET_OPT(modify_algorithms, Options) of + undefined -> + rm_non_supported(true, + ?GET_OPT(preferred_algorithms, Options)); + ModAlgs -> + rm_non_supported(false, + eval_ops(?GET_OPT(preferred_algorithms, Options), + ModAlgs)) + end, + error_if_empty(Result), % Throws errors if any value list is empty + ?PUT_OPT({preferred_algorithms,Result}, Options). + +eval_ops(PrefAlgs, ModAlgs) -> + lists:foldl(fun eval_op/2, PrefAlgs, ModAlgs). + +eval_op({Op,AlgKVs}, PrefAlgs) -> + eval_op(Op, AlgKVs, PrefAlgs, []). + +eval_op(Op, [{C,L1}|T1], [{C,L2}|T2], Acc) -> + eval_op(Op, T1, T2, [{C,eval_op(Op,L1,L2,[])} | Acc]); + +eval_op(_, [], [], Acc) -> lists:reverse(Acc); +eval_op(rm, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> Pref -- Opt; +eval_op(append, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> (Pref--Opt) ++ Opt; +eval_op(prepend, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> Opt ++ (Pref--Opt). + + +rm_non_supported(UnsupIsErrorFlg, KVs) -> + [{K,rmns(K,Vs, UnsupIsErrorFlg)} || {K,Vs} <- KVs]. + +rmns(K, Vs, UnsupIsErrorFlg) -> + case ssh_transport:algo_two_spec_class(K) of + false -> + rm_unsup(Vs, ssh_transport:supported_algorithms(K), UnsupIsErrorFlg, K); + true -> + [{C, rm_unsup(Vsx, Sup, UnsupIsErrorFlg, {K,C})} + || {{C,Vsx},{C,Sup}} <- lists:zip(Vs,ssh_transport:supported_algorithms(K)) + ] end. +rm_unsup(A, B, Flg, ErrInf) -> + case A--B of + Unsup=[_|_] when Flg==true -> error({eoptions, + {preferred_algorithms,{ErrInf,Unsup}}, + "Unsupported value(s) found" + }); + Unsup -> A -- Unsup + end. + + +error_if_empty([{K,[]}|_]) -> + error({eoptions, K, "Empty resulting algorithm list"}); +error_if_empty([{K,[{client2server,[]}, {server2client,[]}]}]) -> + error({eoptions, K, "Empty resulting algorithm list"}); +error_if_empty([{K,[{client2server,[]}|_]} | _]) -> + error({eoptions, {K,client2server}, "Empty resulting algorithm list"}); +error_if_empty([{K,[_,{server2client,[]}|_]} | _]) -> + error({eoptions, {K,server2client}, "Empty resulting algorithm list"}); +error_if_empty([_|T]) -> + error_if_empty(T); +error_if_empty([]) -> + ok. + %%%---------------------------------------------------------------- forbidden_option(K,V) -> Txt = io_lib:format("The option '~s' is used internally. The " diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 412f5de9de..c48c0800e4 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -34,6 +34,8 @@ -export([next_seqnum/1, supported_algorithms/0, supported_algorithms/1, default_algorithms/0, default_algorithms/1, + algo_classes/0, algo_class/1, + algo_two_spec_classes/0, algo_two_spec_class/1, handle_packet_part/4, handle_hello_version/1, key_exchange_init_msg/1, @@ -81,6 +83,23 @@ default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()]. algo_classes() -> [kex, public_key, cipher, mac, compression]. +algo_class(kex) -> true; +algo_class(public_key) -> true; +algo_class(cipher) -> true; +algo_class(mac) -> true; +algo_class(compression) -> true; +algo_class(_) -> false. + + +algo_two_spec_classes() -> [cipher, mac, compression]. + +algo_two_spec_class(cipher) -> true; +algo_two_spec_class(mac) -> true; +algo_two_spec_class(compression) -> true; +algo_two_spec_class(_) -> false. + + + default_algorithms(kex) -> supported_algorithms(kex, [ 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 -- cgit v1.2.3 From fdb4455125776076b72dc1e31f2909c5ea819651 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 31 Aug 2017 17:53:45 +0200 Subject: ssh: ssh:chk_algos_opts/1 This function is intended primarily for users who want to experiment with the options preferred_algorithms and modify_algorithms. --- lib/ssh/src/ssh.erl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 5ebab43c30..1a5d48baca 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -35,6 +35,7 @@ daemon/1, daemon/2, daemon/3, daemon_info/1, default_algorithms/0, + chk_algos_opts/1, stop_listener/1, stop_listener/2, stop_listener/3, stop_daemon/1, stop_daemon/2, stop_daemon/3, shell/1, shell/2, shell/3 @@ -380,6 +381,27 @@ start_shell(Error) -> default_algorithms() -> ssh_transport:default_algorithms(). +%%-------------------------------------------------------------------- +-spec chk_algos_opts(list(any())) -> algs_list() . +%%-------------------------------------------------------------------- +chk_algos_opts(Opts) -> + case lists:foldl( + fun({preferred_algorithms,_}, Acc) -> Acc; + ({modify_algorithms,_}, Acc) -> Acc; + (KV, Acc) -> [KV|Acc] + end, [], Opts) + of + [] -> + case ssh_options:handle_options(client, Opts) of + M when is_map(M) -> + maps:get(preferred_algorithms, M); + Others -> + Others + end; + OtherOps -> + {error, {non_algo_opts_found,OtherOps}} + end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -- cgit v1.2.3 From 8182596a52b95edb78542bf854aa7b8ff93cc245 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 5 Sep 2017 16:10:36 +0200 Subject: ssh: fix dialyzer error --- lib/ssh/src/ssh_options.erl | 3 --- 1 file changed, 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index d8931da774..6939094401 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -938,9 +938,6 @@ check_preferred_algorithms(Algs) when is_list(Algs) -> check_input_ok(Algs), {true, normalize_mod_algs(Algs, true)}; -check_preferred_algorithms(Algs) when is_list(Algs) -> - check_preferred_algorithms({false,Algs}); - check_preferred_algorithms(_) -> error_in_check(modify_algorithms, "Bad option value. List expected."). -- cgit v1.2.3 From b8d031c4fbb91454f72a0882afe5289fe0fce933 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 28 Sep 2017 18:14:07 +0200 Subject: ssh: exclude aes_gcm if peer is OpenSSH 6.2 (known bug) --- lib/ssh/src/ssh_transport.erl | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index c48c0800e4..0052347912 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -251,9 +251,9 @@ key_exchange_init_msg(Ssh0) -> {SshPacket, Ssh} = ssh_packet(Msg, Ssh0), {Msg, SshPacket, Ssh}. -kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) -> +kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs} = Ssh) -> Random = ssh_bits:random(16), - PrefAlgs = ?GET_OPT(preferred_algorithms, Opts), + PrefAlgs = adjust_algs_for_peer_version(Role, ?GET_OPT(preferred_algorithms, Opts), Ssh), kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs, Opts). key_init(client, Ssh, Value) -> @@ -261,7 +261,22 @@ key_init(client, Ssh, Value) -> key_init(server, Ssh, Value) -> Ssh#ssh{s_keyinit = Value}. - +adjust_algs_for_peer_version(client, PrefAlgs, #ssh{s_version=V}) -> + adjust_algs_for_peer_version(V, PrefAlgs); +adjust_algs_for_peer_version(server, PrefAlgs, #ssh{c_version=V}) -> + adjust_algs_for_peer_version(V, PrefAlgs). +%% +adjust_algs_for_peer_version("SSH-2.0-OpenSSH_6.2"++_, PrefAlgs) -> + C0 = proplists:get_value(cipher, PrefAlgs, same([])), + C = [{D,L} || D <- [client2server, server2client], + L <- [[K || K <- proplists:get_value(D, C0, []), + K =/= 'aes256-gcm@openssh.com', + K =/= 'aes128-gcm@openssh.com']] + ], + lists:keyreplace(cipher, 1, PrefAlgs, {cipher,C}); +adjust_algs_for_peer_version(_, PrefAlgs) -> + PrefAlgs. + kexinit_message(Role, Random, Algs, HostKeyAlgs, Opts) -> #ssh_msg_kexinit{ cookie = Random, -- cgit v1.2.3 From d30fa857278e011f95a0707944a566a26ac6d09c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 29 Sep 2017 13:25:29 +0200 Subject: ssh: fix broken printouts --- lib/ssh/src/ssh_connection_handler.erl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8d3ddb09a4..4158a52a27 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1357,6 +1357,7 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> report -> Msg = lists:flatten( io_lib:format( + "*** SSH: " "Unexpected message '~p' received in state '~p'\n" "Role: ~p\n" "Peer: ~p\n" @@ -1365,7 +1366,7 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> StateName, Ssh#ssh.role, Ssh#ssh.peer, - ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)])), + ?GET_INTERNAL_OPT(address, Ssh#ssh.opts, undefined)])), error_logger:info_report(Msg), keep_state_and_data; @@ -1374,7 +1375,8 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> Other -> Msg = lists:flatten( - io_lib:format("Call to fun in 'unexpectedfun' failed:~n" + io_lib:format("*** SSH: " + "Call to fun in 'unexpectedfun' failed:~n" "Return: ~p\n" "Message: ~p\n" "Role: ~p\n" @@ -1383,8 +1385,8 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> [Other, UnexpectedMessage, Ssh#ssh.role, - element(2,Ssh#ssh.peer), - ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)] + Ssh#ssh.peer, + ?GET_INTERNAL_OPT(address, Ssh#ssh.opts, undefined)] )), error_logger:error_report(Msg), keep_state_and_data -- cgit v1.2.3 From 8d37b35a3d68a5acf4889d5df20b51790ec19097 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 7 Sep 2017 18:24:05 +0200 Subject: ssh: add ecdsa_pass_phrase option in analogy with rsa_pass_phrase --- lib/ssh/src/ssh_options.erl | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6939094401..68c99743ee 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -421,6 +421,12 @@ default(client) -> class => user_options }, + {ecdsa_pass_phrase, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + {silently_accept_hosts, def} => #{default => false, chk => fun check_silently_accept_hosts/1, -- cgit v1.2.3 From 04961b439c6e863baa474fb3d7a0e3f6f4043f06 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 8 Sep 2017 15:49:29 +0200 Subject: ssh: Make host key checking more debug friendly --- lib/ssh/src/ssh_transport.erl | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 0052347912..46154cf536 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -824,6 +824,7 @@ verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature}) end. +%%% -> boolean() | {error,_} accepted_host(Ssh, PeerName, Public, Opts) -> case ?GET_OPT(silently_accept_hosts, Opts) of @@ -845,11 +846,16 @@ accepted_host(Ssh, PeerName, Public, Opts) -> %% Call-back alternatives: A user provided fun is called for the decision: F when is_function(F,2) -> - true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public))); + case catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public)) of + true -> true; + _ -> {error, fingerprint_check_failed} + end; {DigestAlg,F} when is_function(F,2) -> - true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))) - + case catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public)) of + true -> true; + _ -> {error, {fingerprint_check_failed,DigestAlg}} + end end. @@ -867,18 +873,27 @@ fmt_hostkey(X) -> X. known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}} = Ssh, Public, Alg) -> UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:is_host_key(Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of - true -> + case is_host_key(KeyCb, Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + {_,true} -> ok; - false -> + {_,false} -> case accepted_host(Ssh, PeerName, Public, Opts) of true -> - KeyCb:add_host_key(PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]); + {_,R} = add_host_key(KeyCb, PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]), + R; false -> - {error, rejected} + {error, rejected_by_user}; + {error,E} -> + {error,E} end end. +is_host_key(KeyCb, Public, PeerName, Alg, Data) -> + {KeyCb, KeyCb:is_host_key(Public, PeerName, Alg, Data)}. + +add_host_key(KeyCb, PeerName, Public, Data) -> + {KeyCb, KeyCb:add_host_key(PeerName, Public, Data)}. + %% Each of the algorithm strings MUST be a comma-separated list of %% algorithm names (see ''Algorithm Naming'' in [SSH-ARCH]). Each -- cgit v1.2.3 From fd4b67f6b54e3cbd5deea66afe2e456d7904b73a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 8 Sep 2017 15:58:06 +0200 Subject: ssh: enable host key debugging in ssh_dbg --- lib/ssh/src/ssh_dbg.erl | 111 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 32 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 3f742ad9b6..e4a9c66a43 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -24,6 +24,7 @@ -export([messages/0, messages/1, messages/2, messages/3, auth/0, auth/1, auth/2, auth/3, + hostkey/0, hostkey/1, hostkey/2, hostkey/3, stop/0 ]). @@ -46,6 +47,11 @@ auth(F) -> start(auth,F). auth(F,X) -> start(auth,F,X). auth(F,M,I) -> start(auth,F,M,I). +hostkey() -> start(hostkey). +hostkey(F) -> start(hostkey,F). +hostkey(F,X) -> start(hostkey,F,X). +hostkey(F,M,I) -> start(hostkey,F,M,I). + stop() -> dbg:stop(). %%%---------------------------------------------------------------- @@ -71,23 +77,36 @@ fmt_fun(F) -> fun(Fmt,Args,Data) -> F(Fmt,Args), Data end. id_fun() -> fun(X) -> X end. %%%---------------------------------------------------------------- +dbg_ssh(auth) -> + [dbg:tp(ssh_transport,hello_version_msg,1, x), + dbg:tp(ssh_transport,handle_hello_version,1, x), + dbg:tp(ssh_message,encode,1, x), + dbg:tpl(ssh_transport,select_algorithm,4, x), + dbg:tpl(ssh_connection_handler,ext_info,2, x), + lists:map(fun(F) -> dbg:tp(ssh_auth, F, x) end, + [publickey_msg, password_msg, keyboard_interactive_msg]) + ]; + +dbg_ssh(hostkey) -> + [dbg:tpl(ssh_transport, verify_host_key, 4, x), + dbg:tp(ssh_transport, verify, 4, x), + dbg:tpl(ssh_transport, known_host_key, 3, x), +%% dbg:tpl(ssh_transport, accepted_host, 4, x), + dbg:tpl(ssh_transport, add_host_key, 4, x), + dbg:tpl(ssh_transport, is_host_key, 5, x) + ]; + dbg_ssh(msg) -> - dbg_ssh(auth), - dbg:tp(ssh_message,encode,1, x), - dbg:tp(ssh_message,decode,1, x), - dbg:tpl(ssh_transport,select_algorithm,4, x), - dbg:tp(ssh_transport,hello_version_msg,1, x), - dbg:tp(ssh_transport,handle_hello_version,1, x), - dbg:tpl(ssh_connection_handler,ext_info,2, x); + [dbg_ssh(hostkey), + dbg_ssh(auth), + dbg:tp(ssh_message,encode,1, x), + dbg:tp(ssh_message,decode,1, x), + dbg:tpl(ssh_transport,select_algorithm,4, x), + dbg:tp(ssh_transport,hello_version_msg,1, x), + dbg:tp(ssh_transport,handle_hello_version,1, x), + dbg:tpl(ssh_connection_handler,ext_info,2, x) + ]. -dbg_ssh(auth) -> - dbg:tp(ssh_transport,hello_version_msg,1, x), - dbg:tp(ssh_transport,handle_hello_version,1, x), - dbg:tp(ssh_message,encode,1, x), - dbg:tpl(ssh_transport,select_algorithm,4, x), - dbg:tpl(ssh_connection_handler,ext_info,2, x), - lists:foreach(fun(F) -> dbg:tp(ssh_auth, F, x) end, - [publickey_msg, password_msg, keyboard_interactive_msg]). %%%================================================================ cond_start(Type, WriteFun, MangleArgFun, Init) -> @@ -110,10 +129,10 @@ msg_formater(msg, {trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> msg_formater(msg, {trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> fmt("~n~s ~p RECV ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); -msg_formater(auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_failure{authentications=As},TS}, D) -> +msg_formater(_auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_failure{authentications=As},TS}, D) -> fmt("~n~s ~p Client login FAILURE. Try ~s~n", [ts(TS),Pid,As], D); -msg_formater(auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_success{},TS}, D) -> +msg_formater(_auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_success{},TS}, D) -> fmt("~n~s ~p Client login SUCCESS~n", [ts(TS),Pid], D); @@ -155,10 +174,50 @@ msg_formater(_, {trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},St D end; +msg_formater(_, {trace_ts,Pid,call, {ssh_transport,verify_host_key,[_Ssh,_PK,_Dgst,{AlgStr,_Sign}]},TS}, D) -> + fmt("~n~s ~p Client got a ~s hostkey. Will try to verify it~n", [ts(TS),Pid,AlgStr], D); +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,verify_host_key,4}, Result, TS}, D) -> + case Result of + ok -> fmt("~n~s ~p Hostkey verified.~n", [ts(TS),Pid], D); + {error,E} -> + fmt("~n~s ~p ***** Hostkey NOT verified: ~p ******!~n", [ts(TS),Pid,E], D); + _ -> fmt("~n~s ~p ***** Hostkey is NOT verified: ~p ******!~n", [ts(TS),Pid,Result], D) + end; + +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,verify,4}, Result, TS}, D) -> + case Result of + true -> D; + _ -> fmt("~n~s ~p Couldn't verify the signature!~n", [ts(TS),Pid], D) + end; + +msg_formater(_, {trace_ts,_Pid,call, {ssh_transport,is_host_key,_}, _TS}, D) -> D; +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,is_host_key,5}, {CbMod,Result}, TS}, D) -> + case Result of + true -> fmt("~n~s ~p Hostkey found by ~p.~n", [ts(TS),Pid,CbMod], D); + _ -> fmt("~n~s ~p Hostkey NOT found by ~p.~n", [ts(TS),Pid,CbMod], D) + end; + +msg_formater(_, {trace_ts,_Pid,call, {ssh_transport,add_host_key,_}, _TS}, D) -> D; +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,add_host_key,4}, {CbMod,Result}, TS}, D) -> + case Result of + ok -> fmt("~n~s ~p New hostkey added by ~p.~n", [ts(TS),Pid,CbMod], D); + _ -> D + end; + +msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,known_host_key,_},_TS}, D) -> D; +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,known_host_key,3}, Result, TS}, D) -> + case Result of + ok -> D; + {error,E} -> fmt("~n~s ~p Hostkey addition failed: ~p~n", [ts(TS),Pid,E], D); + _ -> fmt("~n~s ~p Hostkey addition: ~p~n", [ts(TS),Pid,Result], D) + end; + msg_formater(_, {trace_ts,Pid,call,{ssh_auth,publickey_msg,[[SigAlg,#ssh{user=User}]]},TS}, D) -> fmt("~n~s ~p Client will try to login user ~p with public key algorithm ~p~n", [ts(TS),Pid,User,SigAlg], D); msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> fmt("~s ~p User ~p can't login with that kind of public key~n", [ts(TS),Pid,User], D); +msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{_,#ssh{user=User}},TS}, D) -> + fmt("~s ~p User ~p logged in~n", [ts(TS),Pid,User], D); msg_formater(_, {trace_ts,Pid,call,{ssh_auth,password_msg,[[#ssh{user=User}]]},TS}, D) -> fmt("~n~s ~p Client will try to login user ~p with password~n", [ts(TS),Pid,User], D); @@ -187,21 +246,9 @@ msg_formater(msg, {trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> fmt("~n~s ~p ERL MSG RECEIVE~n ~p~n", [ts(TS),Pid,shrink_bin(ErlangMsg)], D); -%% msg_formater(_, {trace_ts,_Pid,return_from,MFA,_Ret,_TS}=M, D) -> -%% case lists:member(MFA, [{ssh_auth,keyboard_interactive_msg,1}, -%% {ssh_auth,password_msg,1}, -%% {ssh_auth,publickey_msg,1}]) of -%% true -> -%% D; -%% false -> -%% fmt("~nDBG ~n~p~n", [shrink_bin(M)], D) -%% end; - -%% msg_formater(_, M, D) -> -%% fmt("~nDBG ~n~p~n", [shrink_bin(M)], D). - -msg_formater(_, _, D) -> - D. +msg_formater(_, _M, D) -> + fmt("~nDBG other ~n~p~n", [shrink_bin(_M)], D), + D. %%%---------------------------------------------------------------- -record(data, {writer, -- cgit v1.2.3 From b8e813ee550ef91748ea1e434a9a55b79e8a356b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 12 Sep 2017 13:10:03 +0200 Subject: ssh: ssh_dbg reports cryptolib info --- lib/ssh/src/ssh_dbg.erl | 61 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index e4a9c66a43..906640b490 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -77,7 +77,15 @@ fmt_fun(F) -> fun(Fmt,Args,Data) -> F(Fmt,Args), Data end. id_fun() -> fun(X) -> X end. %%%---------------------------------------------------------------- -dbg_ssh(auth) -> +dbg_ssh(What) -> + case [E || E <- lists:flatten(dbg_ssh0(What)), + element(1,E) =/= ok] of + [] -> ok; + Other -> Other + end. + + +dbg_ssh0(auth) -> [dbg:tp(ssh_transport,hello_version_msg,1, x), dbg:tp(ssh_transport,handle_hello_version,1, x), dbg:tp(ssh_message,encode,1, x), @@ -87,7 +95,7 @@ dbg_ssh(auth) -> [publickey_msg, password_msg, keyboard_interactive_msg]) ]; -dbg_ssh(hostkey) -> +dbg_ssh0(hostkey) -> [dbg:tpl(ssh_transport, verify_host_key, 4, x), dbg:tp(ssh_transport, verify, 4, x), dbg:tpl(ssh_transport, known_host_key, 3, x), @@ -96,9 +104,9 @@ dbg_ssh(hostkey) -> dbg:tpl(ssh_transport, is_host_key, 5, x) ]; -dbg_ssh(msg) -> - [dbg_ssh(hostkey), - dbg_ssh(auth), +dbg_ssh0(msg) -> + [dbg_ssh0(hostkey), + dbg_ssh0(auth), dbg:tp(ssh_message,encode,1, x), dbg:tp(ssh_message,decode,1, x), dbg:tpl(ssh_transport,select_algorithm,4, x), @@ -252,8 +260,14 @@ msg_formater(_, _M, D) -> %%%---------------------------------------------------------------- -record(data, {writer, + initialized, acc}). +fmt(Fmt, Args, D=#data{initialized=false}) -> + fmt(Fmt, Args, + D#data{acc = (D#data.writer)("~s~n", [initial_info()], D#data.acc), + initialized = true} + ); fmt(Fmt, Args, D=#data{writer=Write, acc=Acc}) -> D#data{acc = Write(Fmt,Args,Acc)}. @@ -268,10 +282,47 @@ setup_tracer(Type, WriteFun, MangleArgFun, Init) -> msg_formater(Type, MangleArgFun(Arg), D) end, InitialData = #data{writer = WriteFun, + initialized = false, acc = Init}, {ok,_} = dbg:tracer(process, {Handler, InitialData}), ok. + +initial_info() -> + Lines = + [ts(erlang:timestamp()), + "", + "SSH:"] + ++ as_list_of_lines(case application:get_key(ssh,vsn) of + {ok,Vsn} -> Vsn; + _ -> "(ssh not started)" + end) + ++ ["", + "Cryptolib:"] + ++ as_list_of_lines(crypto:info_lib()) + ++ ["", + "Crypto app:"] + ++ as_list_of_lines(crypto:supports()), + W = max_len(Lines), + append_lines([line_of($*, W+4)] + ++ prepend_lines("* ", Lines) + ++ [line_of($-, W+4)], + io_lib:nl() + ). + + +as_list_of_lines(Term) -> + prepend_lines(" ", + string:tokens(lists:flatten(io_lib:format("~p",[Term])), + io_lib:nl() % Get line endings in current OS + ) + ). + +line_of(Char,W) -> lists:duplicate(W,Char). +max_len(L) -> lists:max([length(S) || S<-L]). +append_lines(L, X) -> [S++X || S<-L]. +prepend_lines(X, L) -> [X++S || S<-L]. + %%%---------------------------------------------------------------- shrink_bin(B) when is_binary(B), size(B)>256 -> {'*** SHRINKED BIN', size(B), -- cgit v1.2.3 From c38f7ea8a4ac9906f94ec69e011296d3763392b7 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 6 Oct 2017 11:24:03 +0200 Subject: ssh: ssh_dbg:algs function --- lib/ssh/src/ssh_dbg.erl | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 906640b490..af9ad52d68 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -24,6 +24,7 @@ -export([messages/0, messages/1, messages/2, messages/3, auth/0, auth/1, auth/2, auth/3, + algs/0, algs/1, algs/2, algs/3, hostkey/0, hostkey/1, hostkey/2, hostkey/3, stop/0 ]). @@ -47,6 +48,11 @@ auth(F) -> start(auth,F). auth(F,X) -> start(auth,F,X). auth(F,M,I) -> start(auth,F,M,I). +algs() -> start(algs). +algs(F) -> start(algs,F). +algs(F,X) -> start(algs,F,X). +algs(F,M,I) -> start(algs,F,M,I). + hostkey() -> start(hostkey). hostkey(F) -> start(hostkey,F). hostkey(F,X) -> start(hostkey,F,X). @@ -95,6 +101,11 @@ dbg_ssh0(auth) -> [publickey_msg, password_msg, keyboard_interactive_msg]) ]; +dbg_ssh0(algs) -> + [dbg:tpl(ssh_transport,select_algorithm,4, x), + dbg:tpl(ssh_connection_handler,ext_info,2, x) + ]; + dbg_ssh0(hostkey) -> [dbg:tpl(ssh_transport, verify_host_key, 4, x), dbg:tp(ssh_transport, verify, 4, x), -- cgit v1.2.3 From 667d1e91f10832ea5c90ab049cdab6e8dd77b60b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 12 Oct 2017 13:36:27 +0200 Subject: ssh: Adapt to simplified public_key and simplify ssh --- lib/ssh/src/ssh_message.erl | 20 ++++++++++---------- lib/ssh/src/ssh_transport.erl | 30 ++++++++++++++---------------- 2 files changed, 24 insertions(+), 26 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index b1fc05ae33..eb06f05a4a 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -252,12 +252,12 @@ encode(#ssh_msg_kexdh_init{e = E}) -> <>; encode(#ssh_msg_kexdh_reply{ - public_host_key = Key, + public_host_key = {Key,SigAlg}, f = F, h_sig = Signature }) -> EncKey = public_key:ssh_encode(Key, ssh2_pubkey), - EncSign = encode_signature(Key, Signature), + EncSign = encode_signature(Key, SigAlg, Signature), <>; encode(#ssh_msg_kex_dh_gex_request{ @@ -278,20 +278,20 @@ encode(#ssh_msg_kex_dh_gex_init{e = Public}) -> encode(#ssh_msg_kex_dh_gex_reply{ %% Will be private key encode_host_key extracts only the public part! - public_host_key = Key, + public_host_key = {Key,SigAlg}, f = F, h_sig = Signature }) -> EncKey = public_key:ssh_encode(Key, ssh2_pubkey), - EncSign = encode_signature(Key, Signature), + EncSign = encode_signature(Key, SigAlg, Signature), <>; encode(#ssh_msg_kex_ecdh_init{q_c = Q_c}) -> <>; -encode(#ssh_msg_kex_ecdh_reply{public_host_key = Key, q_s = Q_s, h_sig = Sign}) -> +encode(#ssh_msg_kex_ecdh_reply{public_host_key = {Key,SigAlg}, q_s = Q_s, h_sig = Sign}) -> EncKey = public_key:ssh_encode(Key, ssh2_pubkey), - EncSign = encode_signature(Key, Sign), + EncSign = encode_signature(Key, SigAlg, Sign), <>; encode(#ssh_msg_ignore{data = Data}) -> @@ -602,12 +602,12 @@ decode_signature(<>) -> {binary_to_list(Alg), Signature}. -encode_signature({#'RSAPublicKey'{},Sign}, Signature) -> - SignName = list_to_binary(atom_to_list(Sign)), +encode_signature(#'RSAPublicKey'{}, SigAlg, Signature) -> + SignName = list_to_binary(atom_to_list(SigAlg)), <>; -encode_signature({{_, #'Dss-Parms'{}},_}, Signature) -> +encode_signature({_, #'Dss-Parms'{}}, _SigAlg, Signature) -> <>), ?Ebinary(Signature)>>; -encode_signature({{#'ECPoint'{}, {namedCurve,OID}},_}, Signature) -> +encode_signature({#'ECPoint'{}, {namedCurve,OID}}, _SigAlg, Signature) -> CurveName = public_key:oid2ssh_curvename(OID), <>), ?Ebinary(Signature)>>. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 46154cf536..e92c727559 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -426,7 +426,7 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, K = compute_key(dh, E, Private, [P,G]), MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {E,Public,K}), + H = kex_hash(Ssh0, MyPubHostKey, sha(Kex), {E,Public,K}), H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_reply{public_host_key = {MyPubHostKey,SignAlg}, @@ -451,13 +451,12 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, f = F, h_sig = H_SIG}, #ssh{keyex_key = {{Private, Public}, {G, P}}, - algorithms = #alg{kex=Kex, - hkey=SignAlg}} = Ssh0) -> + algorithms = #alg{kex=Kex}} = Ssh0) -> %% client if 1= K = compute_key(dh, F, Private, [P,G]), - H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Public,F,K}), + H = kex_hash(Ssh0, PeerPubHostKey, sha(Kex), {Public,F,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -590,7 +589,7 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, 1 MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,E,Public,K}), + H = kex_hash(Ssh0, MyPubHostKey, sha(Kex), {Min,NBits,Max,P,G,E,Public,K}), H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = {MyPubHostKey,SignAlg}, @@ -620,8 +619,7 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK h_sig = H_SIG}, #ssh{keyex_key = {{Private, Public}, {G, P}}, keyex_info = {Min, Max, NBits}, - algorithms = #alg{kex=Kex, - hkey=SignAlg}} = + algorithms = #alg{kex=Kex}} = Ssh0) -> %% client if @@ -629,7 +627,7 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK K = compute_key(dh, F, Private, [P,G]), if 1 - H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,Public,F,K}), + H = kex_hash(Ssh0, PeerPubHostKey, sha(Kex), {Min,NBits,Max,P,G,Public,F,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -676,7 +674,7 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, K -> MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Curve), {PeerPublic, MyPublic, K}), + H = kex_hash(Ssh0, MyPubHostKey, sha(Curve), {PeerPublic, MyPublic, K}), H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = {MyPubHostKey,SignAlg}, @@ -699,15 +697,15 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, q_s = PeerPublic, h_sig = H_SIG}, - #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}, - algorithms = #alg{hkey=SignAlg}} = Ssh0 + #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve} + } = Ssh0 ) -> %% at client try compute_key(ecdh, PeerPublic, MyPrivate, Curve) of K -> - H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Curve), {MyPublic,PeerPublic,K}), + H = kex_hash(Ssh0, PeerPubHostKey, sha(Curve), {MyPublic,PeerPublic,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -1794,11 +1792,11 @@ hash(K, H, Ki, N, HashAlg) -> hash(K, H, <>, N-128, HashAlg). %%%---------------------------------------------------------------- -kex_hash(SSH, Key, SignAlg, HashAlg, Args) -> - crypto:hash(HashAlg, kex_plaintext(SSH,Key,SignAlg,Args)). +kex_hash(SSH, Key, HashAlg, Args) -> + crypto:hash(HashAlg, kex_plaintext(SSH,Key,Args)). -kex_plaintext(SSH, Key, SignAlg, Args) -> - EncodedKey = public_key:ssh_encode({Key,SignAlg}, ssh2_pubkey), +kex_plaintext(SSH, Key, Args) -> + EncodedKey = public_key:ssh_encode(Key, ssh2_pubkey), < Date: Tue, 10 Oct 2017 19:59:01 +0200 Subject: ssh: Sharpen the PubKey validity check --- lib/ssh/src/ssh_transport.erl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index e92c727559..2c5a8ad26e 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1830,11 +1830,14 @@ valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true; valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true; valid_key_sha_alg(#'DSAPrivateKey'{}, 'ssh-dss') -> true; -valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg); -valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg); +valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg); +valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg); valid_key_sha_alg(_, _) -> false. - +valid_key_sha_alg_ec(OID, Alg) -> + Curve = public_key:oid2ssh_curvename(OID), + Alg == list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). + public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-curdle-rsa-sha2 public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; -- cgit v1.2.3 From 9fc2073320b27f003764c2d78541a41e306a7f2a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 10 Oct 2017 21:58:46 +0200 Subject: ssh: Server checks host key files at start and at accept --- lib/ssh/src/ssh.erl | 6 ++++ lib/ssh/src/ssh_connection_handler.erl | 58 ++++++++++++++++------------------ lib/ssh/src/ssh_transport.erl | 10 ++++-- 3 files changed, 42 insertions(+), 32 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 1a5d48baca..032d87bdad 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -188,6 +188,7 @@ daemon(Port) -> daemon(Socket, UserOptions) when is_port(Socket) -> try #{} = Options = ssh_options:handle_options(server, UserOptions), + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> {ok, {IP,Port}} = inet:sockname(Socket), @@ -461,6 +462,9 @@ open_listen_socket(_Host0, Port0, Options0) -> %%%---------------------------------------------------------------- finalize_start(Host, Port, Profile, Options0, F) -> try + %% throws error:Error if no usable hostkey is found + ssh_connection_handler:available_hkey_algorithms(server, Options0), + sshd_sup:start_child(Host, Port, Profile, Options0) of {error, {already_started, _}} -> @@ -470,6 +474,8 @@ finalize_start(Host, Port, Profile, Options0, F) -> Result = {ok,_} -> F(Options0, Result) catch + error:{shutdown,Err} -> + {error,Err}; exit:{noproc, _} -> {error, ssh_not_started} end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4158a52a27..802bf62570 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -46,6 +46,7 @@ %%% Internal application API -export([start_connection/4, + available_hkey_algorithms/2, open_channel/6, request/6, request/7, reply_request/3, @@ -432,13 +433,12 @@ init_ssh_record(Role, Socket, Opts) -> init_ssh_record(Role, Socket, PeerAddr, Opts). init_ssh_record(Role, _Socket, PeerAddr, Opts) -> - KeyCb = ?GET_OPT(key_cb, Opts), AuthMethods = ?GET_OPT(auth_methods, Opts), S0 = #ssh{role = Role, - key_cb = KeyCb, + key_cb = ?GET_OPT(key_cb, Opts), opts = Opts, userauth_supported_methods = AuthMethods, - available_host_keys = supported_host_keys(Role, KeyCb, Opts), + available_host_keys = available_hkey_algorithms(Role, Opts), random_length_padding = ?GET_OPT(max_random_length_padding, Opts) }, @@ -1544,44 +1544,42 @@ peer_role(client) -> server; peer_role(server) -> client. %%-------------------------------------------------------------------- -supported_host_keys(client, _, Options) -> - try - find_sup_hkeys(Options) - of - [] -> +available_hkey_algorithms(Role, Options) -> + KeyCb = ?GET_OPT(key_cb, Options), + case [A || A <- available_hkey_algos(Options), + (Role==client) orelse available_host_key(KeyCb, A, Options) + ] of + + [] when Role==client -> error({shutdown, "No public key algs"}); - Algs -> - [atom_to_list(A) || A<-Algs] - catch - exit:Reason -> - error({shutdown, Reason}) - end; -supported_host_keys(server, KeyCb, Options) -> - [atom_to_list(A) || A <- find_sup_hkeys(Options), - available_host_key(KeyCb, A, Options) - ]. + [] when Role==server -> + error({shutdown, "No host key available"}); -find_sup_hkeys(Options) -> - case proplists:get_value(public_key, - ?GET_OPT(preferred_algorithms,Options) - ) - of - undefined -> - ssh_transport:default_algorithms(public_key); - L -> - NonSupported = L--ssh_transport:supported_algorithms(public_key), - L -- NonSupported + Algs -> + [atom_to_list(A) || A<-Algs] end. +available_hkey_algos(Options) -> + SupAlgos = ssh_transport:supported_algorithms(public_key), + HKeys = proplists:get_value(public_key, + ?GET_OPT(preferred_algorithms,Options) + ), + NonSupported = HKeys -- SupAlgos, + AvailableAndSupported = HKeys -- NonSupported, + AvailableAndSupported. + %% Alg :: atom() available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) -> UserOpts = ?GET_OPT(user_options, Opts), case KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of - {ok,_} -> true; - _ -> false + {ok,Key} -> + %% Check the key - the KeyCb may be a buggy plugin + ssh_transport:valid_key_sha_alg(Key, Alg); + _ -> + false end. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 2c5a8ad26e..d8f7a96c15 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -795,8 +795,14 @@ get_host_key(SSH, SignAlg) -> #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts} = SSH, UserOpts = ?GET_OPT(user_options, Opts), case KeyCb:host_key(SignAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of - {ok, PrivHostKey} -> PrivHostKey; - Result -> exit({error, {Result, unsupported_key_type}}) + {ok, PrivHostKey} -> + %% Check the key - the KeyCb may be a buggy plugin + case valid_key_sha_alg(PrivHostKey, SignAlg) of + true -> PrivHostKey; + false -> exit({error, bad_hostkey}) + end; + Result -> + exit({error, {Result, unsupported_key_type}}) end. extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> -- cgit v1.2.3 From c34bbd1fa8606f47ddf31e3135b8d716f71a804d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 11 Oct 2017 14:55:34 +0200 Subject: ssh: Client checks user's public key --- lib/ssh/src/ssh_auth.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index ac64a7bf14..894877f8bf 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -145,14 +145,17 @@ get_public_key(SigAlg, #ssh{opts = Opts}) -> case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of {ok, PrivKey} -> try + %% Check the key - the KeyCb may be a buggy plugin + true = ssh_transport:valid_key_sha_alg(PrivKey, KeyAlg), Key = ssh_transport:extract_public_key(PrivKey), public_key:ssh_encode(Key, ssh2_pubkey) of PubKeyBlob -> {ok,{PrivKey,PubKeyBlob}} catch _:_ -> - not_ok + not_ok end; + _Error -> not_ok end. -- cgit v1.2.3 From 638f0faf282218089cdbf636bf487a7cad95a621 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 9 Nov 2017 12:35:12 +0100 Subject: ssh: Don't remove trailing WS in Hello msg --- lib/ssh/src/ssh_transport.erl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 5391df723c..b2d655955f 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1825,12 +1825,6 @@ same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% trim_tail(Str) -> - lists:reverse(trim_head(lists:reverse(Str))). - -trim_head([$\s|Cs]) -> trim_head(Cs); -trim_head([$\t|Cs]) -> trim_head(Cs); -trim_head([$\n|Cs]) -> trim_head(Cs); -trim_head([$\r|Cs]) -> trim_head(Cs); -trim_head(Cs) -> Cs. - - + lists:takewhile(fun(C) -> + C=/=$\r andalso C=/=$\n + end, Str). -- cgit v1.2.3 From becf2f12731bf3bedc7ec0c2e90caa3a0c6b58ee Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 9 Nov 2017 12:35:12 +0100 Subject: ssh: Don't remove trailing WS in Hello msg --- lib/ssh/src/ssh_transport.erl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 9bebaf2d9b..61a1a57063 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1827,12 +1827,6 @@ same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% trim_tail(Str) -> - lists:reverse(trim_head(lists:reverse(Str))). - -trim_head([$\s|Cs]) -> trim_head(Cs); -trim_head([$\t|Cs]) -> trim_head(Cs); -trim_head([$\n|Cs]) -> trim_head(Cs); -trim_head([$\r|Cs]) -> trim_head(Cs); -trim_head(Cs) -> Cs. - - + lists:takewhile(fun(C) -> + C=/=$\r andalso C=/=$\n + end, Str). -- cgit v1.2.3 From aa8b4b31e2b31766f8aba4e1bca19d3ed006e272 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 31 Oct 2017 22:53:25 +0100 Subject: ssh: Update SSH for engine-stored host keys --- lib/ssh/src/ssh_transport.erl | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index d8f7a96c15..892db6b64f 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -811,7 +811,15 @@ extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> {Y, #'Dss-Parms'{p=P, q=Q, g=G}}; extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID}, publicKey = Q}) -> - {#'ECPoint'{point=Q}, {namedCurve,OID}}. + {#'ECPoint'{point=Q}, {namedCurve,OID}}; +extract_public_key(#{engine:=_, key_id:=_, algorithm:=Alg} = M) -> + case {Alg, crypto:privkey_to_pubkey(Alg, M)} of + {rsa, [E,N]} -> + #'RSAPublicKey'{modulus = N, publicExponent = E}; + {dss, [P,Q,G,Y]} -> + {Y, #'Dss-Parms'{p=P, q=Q, g=G}} + end. + verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature}) -> @@ -1261,10 +1269,12 @@ payload(<>) -> <> = PayloadAndPadding, Payload. +sign(SigData, HashAlg, #{algorithm:=dss} = Key) -> + mk_dss_sig(crypto:sign(dss, HashAlg, SigData, Key)); +sign(SigData, HashAlg, #{algorithm:=SigAlg} = Key) -> + crypto:sign(SigAlg, HashAlg, SigData, Key); sign(SigData, HashAlg, #'DSAPrivateKey'{} = Key) -> - DerSignature = public_key:sign(SigData, HashAlg, Key), - #'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature), - <>; + mk_dss_sig(public_key:sign(SigData, HashAlg, Key)); sign(SigData, HashAlg, Key = #'ECPrivateKey'{}) -> DerEncodedSign = public_key:sign(SigData, HashAlg, Key), #'ECDSA-Sig-Value'{r=R, s=S} = public_key:der_decode('ECDSA-Sig-Value', DerEncodedSign), @@ -1272,6 +1282,12 @@ sign(SigData, HashAlg, Key = #'ECPrivateKey'{}) -> sign(SigData, HashAlg, Key) -> public_key:sign(SigData, HashAlg, Key). + +mk_dss_sig(DerSignature) -> + #'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature), + <>. + + verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) -> case Sig of <> -> @@ -1823,6 +1839,8 @@ kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) -> %%%---------------------------------------------------------------- +valid_key_sha_alg(#{engine:=_, key_id:=_}, _Alg) -> true; % Engine key + valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-512') -> true; valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-384') -> true; valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-256') -> true; -- cgit v1.2.3 From 5968beba4043823e25e975dd3c30ff0f37546c14 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 14 Nov 2017 19:33:34 +0100 Subject: ssh: Fix broken error handling during session setup --- lib/ssh/src/ssh_connection_handler.erl | 36 ++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4158a52a27..de4f26fea7 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -369,16 +369,17 @@ init_connection_handler(Role, Socket, Opts) -> StartState, D); - {stop, enotconn} -> - %% Handles the abnormal sequence: - %% SYN-> - %% <-SYNACK - %% ACK-> - %% RST-> - exit({shutdown, "TCP connection to server was prematurely closed by the client"}); - - {stop, OtherError} -> - exit({shutdown, {init,OtherError}}) + {stop, Error} -> + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + C = #connection{system_supervisor = proplists:get_value(system_sup, Sups), + sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), + connection_supervisor = proplists:get_value(connection_sup, Sups) + }, + gen_statem:enter_loop(?MODULE, + [], + {init_error,Error}, + #data{connection_state=C, + socket=Socket}) end. @@ -531,6 +532,21 @@ renegotiation(_) -> false. callback_mode() -> handle_event_function. + +handle_event(_, _Event, {init_error,Error}, _) -> + case Error of + enotconn -> + %% Handles the abnormal sequence: + %% SYN-> + %% <-SYNACK + %% ACK-> + %% RST-> + {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}}; + + OtherError -> + {stop, {shutdown,{init,OtherError}}} + end; + %%% ######## {hello, client|server} #### %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> -- cgit v1.2.3 From 6642d2c27f29b4d7cb1d31c3397a8aec3860c98b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 14 Nov 2017 20:40:12 +0100 Subject: ssh: dialyzer fixes --- lib/ssh/src/ssh_connection_handler.erl | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index de4f26fea7..54fce6bd99 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -324,23 +324,32 @@ renegotiate_data(ConnectionHandler) -> %% Internal process state %%==================================================================== -record(data, { - starter :: pid(), + starter :: pid() + | undefined, auth_user :: string() | undefined, connection_state :: #connection{}, - latest_channel_id = 0 :: non_neg_integer(), + latest_channel_id = 0 :: non_neg_integer() + | undefined, idle_timer_ref :: undefined | infinity | reference(), idle_timer_value = infinity :: infinity | pos_integer(), - transport_protocol :: atom(), % ex: tcp - transport_cb :: atom(), % ex: gen_tcp - transport_close_tag :: atom(), % ex: tcp_closed - ssh_params :: #ssh{}, - socket :: inet:socket(), - decrypted_data_buffer = <<>> :: binary(), - encrypted_data_buffer = <<>> :: binary(), + transport_protocol :: atom() + | undefined, % ex: tcp + transport_cb :: atom() + | undefined, % ex: gen_tcp + transport_close_tag :: atom() + | undefined, % ex: tcp_closed + ssh_params :: #ssh{} + | undefined, + socket :: inet:socket() + | undefined, + decrypted_data_buffer = <<>> :: binary() + | undefined, + encrypted_data_buffer = <<>> :: binary() + | undefined, undecrypted_packet_length :: undefined | non_neg_integer(), key_exchange_init_msg :: #ssh_msg_kexinit{} | undefined, -- cgit v1.2.3 From a36785f388f5545524832811b711748ca37b5209 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Nov 2017 14:29:47 +0100 Subject: ssh: Unicode encoding fix Conflicts: lib/ssh/src/ssh_connection_handler.erl --- lib/ssh/src/ssh.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index f88098819d..b27e91213d 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -60,8 +60,8 @@ -define(uint16(X), << ?UINT16(X) >> ). -define(uint32(X), << ?UINT32(X) >> ). -define(uint64(X), << ?UINT64(X) >> ). --define(string(X), << ?STRING(list_to_binary(X)) >> ). -define(string_utf8(X), << ?STRING(unicode:characters_to_binary(X)) >> ). +-define(string(X), ?string_utf8(X)). -define(binary(X), << ?STRING(X) >>). -define(SSH_CIPHER_NONE, 0). -- cgit v1.2.3 From 70773b1498cb845db16ed83f3f4c3b1b52e08ab2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 9 Nov 2017 12:35:12 +0100 Subject: ssh: Don't remove trailing WS in Hello msg --- lib/ssh/src/ssh_transport.erl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 0052347912..6d158537d4 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1987,12 +1987,6 @@ same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% trim_tail(Str) -> - lists:reverse(trim_head(lists:reverse(Str))). - -trim_head([$\s|Cs]) -> trim_head(Cs); -trim_head([$\t|Cs]) -> trim_head(Cs); -trim_head([$\n|Cs]) -> trim_head(Cs); -trim_head([$\r|Cs]) -> trim_head(Cs); -trim_head(Cs) -> Cs. - - + lists:takewhile(fun(C) -> + C=/=$\r andalso C=/=$\n + end, Str). -- cgit v1.2.3 From 14ab113180b16227638a1d2f3130e5e66d43e182 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 8 Dec 2017 10:28:26 +0100 Subject: ssh: Update runtime dependencies of ssh --- lib/ssh/src/ssh.app.src | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 974292fde1..4a22322333 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -42,10 +42,10 @@ {env, []}, {mod, {ssh_app, []}}, {runtime_dependencies, [ - "crypto-3.7.3", + "crypto-4.2", "erts-6.0", "kernel-3.0", - "public_key-1.4", + "public_key-1.5.2", "stdlib-3.3" ]}]}. -- cgit v1.2.3 From 8aeec7d6748dab5924713b58e3011dc62d331fa5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 14 Dec 2017 12:54:16 +0100 Subject: ssh: Special treatment of OpenSSH clients >= 7.2 rsa-sha2-* public keys Thoose clients signs with sha instead of sha2-*. Try first to verify with the correct one, and if that fails, retry with sha1. --- lib/ssh/src/ssh_auth.erl | 9 ++++----- lib/ssh/src/ssh_transport.erl | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 894877f8bf..03d264745b 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -304,11 +304,10 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, SigWLen/binary>> }, SessionId, - #ssh{opts = Opts, - userauth_supported_methods = Methods} = Ssh) -> + #ssh{userauth_supported_methods = Methods} = Ssh) -> case verify_sig(SessionId, User, "ssh-connection", - BAlg, KeyBlob, SigWLen, Opts) of + BAlg, KeyBlob, SigWLen, Ssh) of true -> {authorized, User, ssh_transport:ssh_packet( @@ -518,7 +517,7 @@ pre_verify_sig(User, KeyBlob, Opts) -> false end. -verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, Opts) -> +verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts = Opts} = Ssh) -> try Alg = binary_to_list(AlgBin), {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), @@ -529,7 +528,7 @@ verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, Opts) -> <> = SigWLen, <> = AlgSig, - ssh_transport:verify(PlainText, ssh_transport:sha(Alg), Sig, Key) + ssh_transport:verify(PlainText, ssh_transport:sha(Alg), Sig, Key, Ssh) catch _:_ -> false diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 90a94a7e86..154894cda8 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -51,7 +51,7 @@ extract_public_key/1, ssh_packet/2, pack/2, valid_key_sha_alg/2, - sha/1, sign/3, verify/4]). + sha/1, sign/3, verify/5]). %%% For test suites -export([pack/3]). @@ -825,7 +825,7 @@ extract_public_key(#{engine:=_, key_id:=_, algorithm:=Alg} = M) -> verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature}) -> case atom_to_list(Alg#alg.hkey) of AlgStr -> - case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of + case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey, SSH) of false -> {error, bad_signature}; true -> @@ -1288,7 +1288,7 @@ mk_dss_sig(DerSignature) -> <>. -verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) -> +verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key, _) -> case Sig of <> -> Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), @@ -1296,7 +1296,7 @@ verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) -> _ -> false end; -verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key) -> +verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key, _) -> case Sig of <> -> @@ -1306,7 +1306,15 @@ verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key) -> _ -> false end; -verify(PlainText, HashAlg, Sig, Key) -> + +verify(PlainText, HashAlg, Sig, #'RSAPublicKey'{}=Key, #ssh{role = server, + c_version = "SSH-2.0-OpenSSH_7."++_}) + when HashAlg == sha256; HashAlg == sha512 -> + %% Public key signing bug in in OpenSSH >= 7.2 + public_key:verify(PlainText, HashAlg, Sig, Key) + orelse public_key:verify(PlainText, sha, Sig, Key); + +verify(PlainText, HashAlg, Sig, Key, _) -> public_key:verify(PlainText, HashAlg, Sig, Key). -- cgit v1.2.3 From fef827bf46a238eaa0cf96b7d75491fc27f0abce Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 18 Dec 2017 17:45:14 +0100 Subject: ssh: ssh_dbg updates --- lib/ssh/src/ssh_dbg.erl | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index af9ad52d68..eb2c2848f3 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -146,7 +146,26 @@ msg_formater(msg, {trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D msg_formater(msg, {trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> D; msg_formater(msg, {trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> - fmt("~n~s ~p RECV ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); + Extra = + case Msg of + #ssh_msg_userauth_info_request{data = D0} -> + try ssh_message:decode_keyboard_interactive_prompts(D0, []) + of + Acc -> + io_lib:format(" -- decoded data:~n", []) ++ + element(1, + lists:mapfoldl( + fun({Prompt,Echo}, N) -> + {io_lib:format(" prompt[~p]: \"~s\" (echo=~p)~n",[N,Prompt,Echo]), N+1} + end, 1, Acc)) + catch + _:_ -> + "" + end; + _ -> + "" + end, + fmt("~n~s ~p RECV ~s~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg)),Extra], D); msg_formater(_auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_failure{authentications=As},TS}, D) -> fmt("~n~s ~p Client login FAILURE. Try ~s~n", [ts(TS),Pid,As], D); @@ -232,21 +251,22 @@ msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,known_host_key,3}, Res end; msg_formater(_, {trace_ts,Pid,call,{ssh_auth,publickey_msg,[[SigAlg,#ssh{user=User}]]},TS}, D) -> - fmt("~n~s ~p Client will try to login user ~p with public key algorithm ~p~n", [ts(TS),Pid,User,SigAlg], D); + fmt("~n~s ~p Client will try to login user ~p with method: public key algorithm ~p~n", [ts(TS),Pid,User,SigAlg], D); msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> - fmt("~s ~p User ~p can't login with that kind of public key~n", [ts(TS),Pid,User], D); -msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{_,#ssh{user=User}},TS}, D) -> - fmt("~s ~p User ~p logged in~n", [ts(TS),Pid,User], D); + fmt("~s ~p User ~p can't use that kind of public key~n", [ts(TS),Pid,User], D); +msg_formater(_, {trace_ts,_Pid,return_from,{ssh_auth,publickey_msg,1},_,_TS}, D) -> D; msg_formater(_, {trace_ts,Pid,call,{ssh_auth,password_msg,[[#ssh{user=User}]]},TS}, D) -> - fmt("~n~s ~p Client will try to login user ~p with password~n", [ts(TS),Pid,User], D); + fmt("~n~s ~p Client will try to login user ~p with method: password~n", [ts(TS),Pid,User], D); msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,password_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> - fmt("~s ~p User ~p can't login with password~n", [ts(TS),Pid,User], D); + fmt("~s ~p User ~p can't use method password as login method~n", [ts(TS),Pid,User], D); +msg_formater(_, {trace_ts,_Pid,return_from,{ssh_auth,password_msg,1},_Result,_TS}, D) -> D; msg_formater(_, {trace_ts,Pid,call,{ssh_auth,keyboard_interactive_msg,[[#ssh{user=User}]]},TS}, D) -> - fmt("~n~s ~p Client will try to login user ~p with password~n", [ts(TS),Pid,User], D); + fmt("~n~s ~p Client will try to login user ~p with method: keyboard-interactive~n", [ts(TS),Pid,User], D); msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,keyboard_interactive_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> - fmt("~s ~p User ~p can't login with keyboard_interactive password~n", [ts(TS),Pid,User], D); + fmt("~s ~p User ~p can't use method keyboard-interactive as login method~n", [ts(TS),Pid,User], D); +msg_formater(_, {trace_ts,_Pid,return_from,{ssh_auth,keyboard_interactive_msg,1},_Result,_TS}, D) -> D; msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D); -- cgit v1.2.3 From 404424917140bd50896dd9aa5cc7288ea2d7abce Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 8 Dec 2017 18:51:56 +0100 Subject: ssh: A compatibility testing suite using dockers This suite tests compatibility with different combinations of OpenSSH and OpenSSL. The peer SSH is running in a docker container. --- lib/ssh/src/ssh_transport.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 154894cda8..ad9efc4755 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -54,7 +54,7 @@ sha/1, sign/3, verify/5]). %%% For test suites --export([pack/3]). +-export([pack/3, adjust_algs_for_peer_version/2]). -export([decompress/2, decrypt_blocks/3, is_valid_mac/3 ]). % FIXME: remove -define(Estring(X), ?STRING((if is_binary(X) -> X; -- cgit v1.2.3 From 7d44b20a3346132d3f2800a567c2130820732f32 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 19 Jan 2018 17:13:34 +0100 Subject: ssh: Fix cli Conflicts: lib/ssh/src/ssh.hrl lib/ssh/src/ssh_cli.erl --- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh.hrl | 6 ++++++ lib/ssh/src/ssh_cli.erl | 21 +++++++++++++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 54f94acbdc..96a51bcefc 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -134,7 +134,7 @@ daemon(Port, Options) -> daemon(HostAddr, Port, Options0) -> Options1 = case proplists:get_value(shell, Options0) of undefined -> - [{shell, {shell, start, []}} | Options0]; + [{shell, ?DEFAULT_SHELL} | Options0]; _ -> Options0 end, diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index b27e91213d..7ac861096e 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -33,6 +33,12 @@ -define(REKEY_DATA_TIMOUT, 60000). -define(DEFAULT_PROFILE, default). +-define(DEFAULT_TRANSPORT, {tcp, gen_tcp, tcp_closed} ). + +-define(DEFAULT_SHELL, {shell, start, []} ). + +-define(MAX_RND_PADDING_LEN, 15). + -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). -define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 71f62a960e..6bb2f17e88 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -112,7 +112,8 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, cm = ConnectionHandler}}; handle_ssh_msg({ssh_cm, ConnectionHandler, - {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined} = State) -> + {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined, + shell=?DEFAULT_SHELL} = State) -> {Reply, Status} = exec(Cmd), write_chars(ConnectionHandler, ChannelId, io_lib:format("~p\n", [Reply])), @@ -121,6 +122,15 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), ssh_connection:send_eof(ConnectionHandler, ChannelId), {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; + +handle_ssh_msg({ssh_cm, ConnectionHandler, + {exec, ChannelId, WantReply, _Cmd}}, #state{exec = undefined} = State) -> + write_chars(ConnectionHandler, ChannelId, 1, "Prohibited.\n"), + ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId), + ssh_connection:exit_status(ConnectionHandler, ChannelId, 255), + ssh_connection:send_eof(ConnectionHandler, ChannelId), + {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; + handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}}, State) -> NewState = start_shell(ConnectionHandler, Cmd, State), @@ -431,12 +441,15 @@ move_cursor(From, To, #ssh_pty{width=Width, term=Type}) -> %% %%% make sure that there is data to send %% %%% before calling ssh_connection:send write_chars(ConnectionHandler, ChannelId, Chars) -> + write_chars(ConnectionHandler, ChannelId, ?SSH_EXTENDED_DATA_DEFAULT, Chars). + +write_chars(ConnectionHandler, ChannelId, Type, Chars) -> case erlang:iolist_size(Chars) of - 0 -> + 0 -> ok; - _ -> + _ -> ssh_connection:send(ConnectionHandler, ChannelId, - ?SSH_EXTENDED_DATA_DEFAULT, Chars) + Type, Chars) end. %%% tail, works with empty lists -- cgit v1.2.3 From ca0582834661c54018d82e1f5b1abe6824053963 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 19 Jan 2018 17:13:34 +0100 Subject: ssh: Fix cli Conflicts: lib/ssh/src/ssh.hrl lib/ssh/src/ssh_cli.erl --- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh.hrl | 6 ++++++ lib/ssh/src/ssh_cli.erl | 17 +++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 290525cec0..ecdb184db3 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -289,7 +289,7 @@ is_tcp_socket(Socket) -> daemon_shell_opt(Options) -> case proplists:get_value(shell, Options) of undefined -> - [{shell, {shell, start, []}} | Options]; + [{shell, ?DEFAULT_SHELL} | Options]; _ -> Options end. diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 4cd91177f6..9d119f4f42 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -33,6 +33,12 @@ -define(REKEY_DATA_TIMOUT, 60000). -define(DEFAULT_PROFILE, default). +-define(DEFAULT_TRANSPORT, {tcp, gen_tcp, tcp_closed} ). + +-define(DEFAULT_SHELL, {shell, start, []} ). + +-define(MAX_RND_PADDING_LEN, 15). + -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). -define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 7c7b9e7922..94d1275e7b 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -127,7 +127,8 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, cm = ConnectionHandler}}; handle_ssh_msg({ssh_cm, ConnectionHandler, - {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined} = State) -> + {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined, + shell=?DEFAULT_SHELL} = State) -> {Reply, Status} = exec(Cmd), write_chars(ConnectionHandler, ChannelId, io_lib:format("~p\n", [Reply])), @@ -136,6 +137,15 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), ssh_connection:send_eof(ConnectionHandler, ChannelId), {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; + +handle_ssh_msg({ssh_cm, ConnectionHandler, + {exec, ChannelId, WantReply, _Cmd}}, #state{exec = undefined} = State) -> + write_chars(ConnectionHandler, ChannelId, 1, "Prohibited.\n"), + ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId), + ssh_connection:exit_status(ConnectionHandler, ChannelId, 255), + ssh_connection:send_eof(ConnectionHandler, ChannelId), + {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; + handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}}, State) -> NewState = start_shell(ConnectionHandler, Cmd, State), @@ -453,11 +463,14 @@ move_cursor(From, To, #ssh_pty{width=Width, term=Type}) -> %% %%% make sure that there is data to send %% %%% before calling ssh_connection:send write_chars(ConnectionHandler, ChannelId, Chars) -> + write_chars(ConnectionHandler, ChannelId, ?SSH_EXTENDED_DATA_DEFAULT, Chars). + +write_chars(ConnectionHandler, ChannelId, Type, Chars) -> case has_chars(Chars) of false -> ok; true -> ssh_connection:send(ConnectionHandler, ChannelId, - ?SSH_EXTENDED_DATA_DEFAULT, + Type, Chars) end. -- cgit v1.2.3 From 19265ed2beb0da5d02ed76456beda694ad98399a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 13 Feb 2018 15:12:17 +0100 Subject: ssh: Move starting of channel child to ssh_channel_sup --- lib/ssh/src/ssh_channel_sup.erl | 12 ++++++++++-- lib/ssh/src/ssh_connection.erl | 28 +++++++++------------------- 2 files changed, 19 insertions(+), 21 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl index 6b01dc334d..deb0116958 100644 --- a/lib/ssh/src/ssh_channel_sup.erl +++ b/lib/ssh/src/ssh_channel_sup.erl @@ -26,7 +26,7 @@ -behaviour(supervisor). --export([start_link/1, start_child/2]). +-export([start_link/1, start_child/5]). %% Supervisor callback -export([init/1]). @@ -37,7 +37,15 @@ start_link(Args) -> supervisor:start_link(?MODULE, [Args]). -start_child(Sup, ChildSpec) -> +start_child(Sup, Callback, Id, Args, Exec) -> + ChildSpec = + #{id => make_ref(), + start => {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]}, + restart => temporary, + shutdown => 3600, + type => worker, + modules => [ssh_channel] + }, supervisor:start_child(Sup, ChildSpec). %%%========================================================================= diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 7e9ee78fd2..946ae2967b 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -812,22 +812,20 @@ start_channel(Cb, Id, Args, SubSysSup, Opts) -> start_channel(Cb, Id, Args, SubSysSup, undefined, Opts). start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> - ChildSpec = child_spec(Cb, Id, Args, Exec), ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), - assert_limit_num_channels_not_exceeded(ChannelSup, Opts), - ssh_channel_sup:start_child(ChannelSup, ChildSpec). + case max_num_channels_not_exceeded(ChannelSup, Opts) of + true -> + ssh_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); + false -> + throw(max_num_channels_exceeded) + end. -assert_limit_num_channels_not_exceeded(ChannelSup, Opts) -> +max_num_channels_not_exceeded(ChannelSup, Opts) -> MaxNumChannels = ?GET_OPT(max_channels, Opts), NumChannels = length([x || {_,_,worker,[ssh_channel]} <- supervisor:which_children(ChannelSup)]), - if - %% Note that NumChannels is BEFORE starting a new one - NumChannels < MaxNumChannels -> - ok; - true -> - throw(max_num_channels_exceeded) - end. + %% Note that NumChannels is BEFORE starting a new one + NumChannels < MaxNumChannels. %%-------------------------------------------------------------------- %%% Internal functions @@ -874,14 +872,6 @@ check_subsystem(SsName, Options) -> Value end. -child_spec(Callback, Id, Args, Exec) -> - Name = make_ref(), - StartFunc = {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]}, - Restart = temporary, - Shutdown = 3600, - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, [ssh_channel]}. - start_cli(#connection{cli_spec = no_cli}, _) -> {error, cli_disabled}; start_cli(#connection{options = Options, -- cgit v1.2.3 From 83cd7724b244a4d5dd3efbdbb66811e781136ac9 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 8 Feb 2018 17:06:44 +0100 Subject: ssh: Dont repeat supervisor defaults in map fields --- lib/ssh/src/ssh_acceptor_sup.erl | 5 +---- lib/ssh/src/ssh_channel_sup.erl | 1 - lib/ssh/src/ssh_connection_sup.erl | 5 +---- lib/ssh/src/ssh_subsystem_sup.erl | 8 ++------ lib/ssh/src/ssh_sup.erl | 15 +++++++-------- lib/ssh/src/ssh_system_sup.erl | 9 +++------ lib/ssh/src/sshc_sup.erl | 5 +---- lib/ssh/src/sshd_sup.erl | 6 ++---- 8 files changed, 17 insertions(+), 37 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index a24664793b..fc564a359b 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -86,10 +86,7 @@ child_spec(Address, Port, Profile, Options) -> Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT), #{id => id(Address, Port, Profile), start => {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, - restart => transient, - shutdown => 5500, %brutal_kill, - type => worker, - modules => [ssh_acceptor] + restart => transient % because a crashed listener could be replaced by a new one }. id(Address, Port, Profile) -> diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl index deb0116958..8444533fd1 100644 --- a/lib/ssh/src/ssh_channel_sup.erl +++ b/lib/ssh/src/ssh_channel_sup.erl @@ -42,7 +42,6 @@ start_child(Sup, Callback, Id, Args, Exec) -> #{id => make_ref(), start => {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]}, restart => temporary, - shutdown => 3600, type => worker, modules => [ssh_channel] }, diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl index 60ee8b7c73..2e8450090a 100644 --- a/lib/ssh/src/ssh_connection_sup.erl +++ b/lib/ssh/src/ssh_connection_sup.erl @@ -52,10 +52,7 @@ init(_) -> }, ChildSpecs = [#{id => undefined, % As simple_one_for_one is used. start => {ssh_connection_handler, start_link, []}, - restart => temporary, - shutdown => 4000, - type => worker, - modules => [ssh_connection_handler] + restart => temporary % because there is no way to restart a crashed connection } ], {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index 8db051095c..77da240a66 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -74,18 +74,14 @@ ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> #{id => id(Role, ssh_connection_sup, Address, Port), start => {ssh_connection_sup, start_link, [Options]}, restart => temporary, - shutdown => 5000, - type => supervisor, - modules => [ssh_connection_sup] + type => supervisor }. ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> #{id => id(Role, ssh_channel_sup, Address, Port), start => {ssh_channel_sup, start_link, [Options]}, restart => temporary, - shutdown => infinity, - type => supervisor, - modules => [ssh_channel_sup] + type => supervisor }. id(Role, Sup, Address, Port) -> diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index eaec7a54e4..8183016ba5 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -36,15 +36,14 @@ init(_) -> intensity => 10, period => 3600 }, - ChildSpecs = [#{id => Module, - start => {Module, start_link, []}, - restart => permanent, - shutdown => 4000, %brutal_kill, - type => supervisor, - modules => [Module] + ChildSpecs = [#{id => sshd_sup, + start => {sshd_sup, start_link, []}, + type => supervisor + }, + #{id => sshc_sup, + start => {sshc_sup, start_link, []}, + type => supervisor } - || Module <- [sshd_sup, - sshc_sup] ], {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index e70abf59c2..17f990c5d8 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -63,9 +63,7 @@ init([Address, Port, Profile, Options]) -> [#{id => id(ssh_acceptor_sup, Address, Port, Profile), start => {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}, restart => transient, - shutdown => infinity, - type => supervisor, - modules => [ssh_acceptor_sup] + type => supervisor }]; _ -> [] @@ -124,9 +122,8 @@ start_subsystem(SystemSup, Role, Address, Port, Profile, Options) -> #{id => make_ref(), start => {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]}, restart => temporary, - shutdown => infinity, - type => supervisor, - modules => [ssh_subsystem_sup]}, + type => supervisor + }, supervisor:start_child(SystemSup, SubsystemSpec). stop_subsystem(SystemSup, SubSys) -> diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 133b2c6450..fd4d8a3c07 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -60,10 +60,7 @@ init(_) -> }, ChildSpecs = [#{id => undefined, % As simple_one_for_one is used. start => {ssh_connection_handler, start_link, []}, - restart => temporary, - shutdown => 4000, - type => worker, - modules => [ssh_connection_handler] + restart => temporary % because there is no way to restart a crashed connection } ], {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index c23e65d955..779a861a54 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -90,10 +90,8 @@ init(_) -> child_spec(Address, Port, Profile, Options) -> #{id => id(Address, Port, Profile), start => {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, - restart => temporary, - shutdown => infinity, - type => supervisor, - modules => [ssh_system_sup] + restart => temporary, + type => supervisor }. id(Address, Port, Profile) -> -- cgit v1.2.3 From e2ed1a9701cb83f4568ab17b8f0659803c57e988 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 20 Feb 2018 13:02:28 +0100 Subject: ssh: No error message for signal kill --- lib/ssh/src/ssh_connection_handler.erl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 0ca960ef96..c8ac3a9c04 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1460,13 +1460,12 @@ terminate(shutdown, StateName, State0) -> State0), finalize_termination(StateName, State); -%% terminate({shutdown,Msg}, StateName, State0) when is_record(Msg,ssh_msg_disconnect)-> -%% State = send_msg(Msg, State0), -%% finalize_termination(StateName, Msg, State); - terminate({shutdown,_R}, StateName, State) -> finalize_termination(StateName, State); +terminate(kill, StateName, State) -> + finalize_termination(StateName, State); + terminate(Reason, StateName, State0) -> %% Others, e.g undef, {badmatch,_} log_error(Reason), -- cgit v1.2.3 From ed01a9d3f2614933d05e7d85b4bb3568c4e04cdb Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 21 Feb 2018 14:48:33 +0100 Subject: ssh: Add option save_accepted_host --- lib/ssh/src/ssh_options.erl | 6 ++++++ lib/ssh/src/ssh_transport.erl | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 68c99743ee..cf1534bd78 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -439,6 +439,12 @@ default(client) -> class => user_options }, + {save_accepted_host, def} => + #{default => true, + chk => fun erlang:is_boolean/1, + class => user_options + }, + {pref_public_key_algs, def} => #{default => ssh_transport:default_algorithms(public_key), chk => fun check_pref_public_key_algs/1, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 154894cda8..a7f4854b4b 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -889,10 +889,13 @@ known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_} {_,true} -> ok; {_,false} -> + DoAdd = ?GET_OPT(save_accepted_host, Opts), case accepted_host(Ssh, PeerName, Public, Opts) of - true -> + true when DoAdd == true -> {_,R} = add_host_key(KeyCb, PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]), R; + true when DoAdd == false -> + ok; false -> {error, rejected_by_user}; {error,E} -> -- cgit v1.2.3 From 2c525a02f087fff609cc6c7736c5c582f91e00dc Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 17 Jan 2018 12:22:02 +0100 Subject: ssh: Disable exec for non-default shell --- lib/ssh/src/ssh.hrl | 2 ++ lib/ssh/src/ssh_cli.erl | 17 +++++++++++++++-- lib/ssh/src/ssh_options.erl | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 3dee1c5521..4711f54fb5 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -35,6 +35,8 @@ -define(DEFAULT_TRANSPORT, {tcp, gen_tcp, tcp_closed} ). +-define(DEFAULT_SHELL, {shell, start, []} ). + -define(MAX_RND_PADDING_LEN, 15). -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 62854346b0..958c342f5f 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -127,7 +127,8 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, cm = ConnectionHandler}}; handle_ssh_msg({ssh_cm, ConnectionHandler, - {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined} = State) -> + {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined, + shell=?DEFAULT_SHELL} = State) -> {Reply, Status} = exec(Cmd), write_chars(ConnectionHandler, ChannelId, io_lib:format("~p\n", [Reply])), @@ -136,6 +137,15 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), ssh_connection:send_eof(ConnectionHandler, ChannelId), {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; + +handle_ssh_msg({ssh_cm, ConnectionHandler, + {exec, ChannelId, WantReply, _Cmd}}, #state{exec = undefined} = State) -> + write_chars(ConnectionHandler, ChannelId, 1, "Prohibited.\n"), + ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId), + ssh_connection:exit_status(ConnectionHandler, ChannelId, 255), + ssh_connection:send_eof(ConnectionHandler, ChannelId), + {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; + handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}}, State) -> NewState = start_shell(ConnectionHandler, Cmd, State), @@ -453,11 +463,14 @@ move_cursor(From, To, #ssh_pty{width=Width, term=Type}) -> %% %%% make sure that there is data to send %% %%% before calling ssh_connection:send write_chars(ConnectionHandler, ChannelId, Chars) -> + write_chars(ConnectionHandler, ChannelId, ?SSH_EXTENDED_DATA_DEFAULT, Chars). + +write_chars(ConnectionHandler, ChannelId, Type, Chars) -> case has_chars(Chars) of false -> ok; true -> ssh_connection:send(ConnectionHandler, ChannelId, - ?SSH_EXTENDED_DATA_DEFAULT, + Type, Chars) end. diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index cf1534bd78..1e10f72956 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -268,7 +268,7 @@ default(server) -> }, {shell, def} => - #{default => {shell, start, []}, + #{default => ?DEFAULT_SHELL, chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A); (V) -> check_function1(V) orelse check_function2(V) end, -- cgit v1.2.3 From bef30c216f24297b6d8812187db2bd0cfcaa3658 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 28 Feb 2018 18:30:24 +0100 Subject: ssh: Fix the bug --- lib/ssh/src/ssh_connection_handler.erl | 46 +++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 14 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index c8ac3a9c04..e11d3adee4 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1174,17 +1174,25 @@ handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0) when ?CONNECTED(StateName) -> - D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0), - %% Note reply to channel will happen later when reply is recived from peer on the socket - start_channel_request_timer(ChannelId, From, Timeout), - {keep_state, cache_request_idle_timer_check(D)}; + case handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0) of + {error,Error} -> + {keep_state, D0, {reply,From,{error,Error}}}; + D -> + %% Note reply to channel will happen later when reply is recived from peer on the socket + start_channel_request_timer(ChannelId, From, Timeout), + {keep_state, cache_request_idle_timer_check(D)} + end; handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0) when ?CONNECTED(StateName) -> - D = handle_request(ChannelId, Type, Data, true, From, D0), - %% Note reply to channel will happen later when reply is recived from peer on the socket - start_channel_request_timer(ChannelId, From, Timeout), - {keep_state, cache_request_idle_timer_check(D)}; + case handle_request(ChannelId, Type, Data, true, From, D0) of + {error,Error} -> + {keep_state, D0, {reply,From,{error,Error}}}; + D -> + %% Note reply to channel will happen later when reply is recived from peer on the socket + start_channel_request_timer(ChannelId, From, Timeout), + {keep_state, cache_request_idle_timer_check(D)} + end; handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) when ?CONNECTED(StateName) -> @@ -1773,21 +1781,31 @@ is_usable_user_pubkey(A, Ssh) -> %%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of - #channel{remote_id = Id} = Channel -> + #channel{remote_id = Id, + sent_close = false} = Channel -> update_sys(cache(D), Channel, Type, ChannelPid), send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data), add_request(WantReply, ChannelId, From, D)); - undefined -> - D + + _ when WantReply==true -> + {error,closed}; + + _ -> + D end. handle_request(ChannelId, Type, Data, WantReply, From, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of - #channel{remote_id = Id} -> + #channel{remote_id = Id, + sent_close = false} -> send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data), add_request(WantReply, ChannelId, From, D)); - undefined -> - D + + _ when WantReply==true -> + {error,closed}; + + _ -> + D end. %%%---------------------------------------------------------------- -- cgit v1.2.3 From 7a86736c1e2bc0468acd18b1f84c7cbf055befc8 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 6 Mar 2018 11:42:28 +0100 Subject: ssh: Retry and exponentially backoff listener restart in case of eaddrinuse. This could happen if the acceptor process dies and is restarted immediatly. --- lib/ssh/src/ssh_acceptor.erl | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index d66a34c58a..27d4242dd4 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -86,7 +86,8 @@ acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) -> acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout); {error,_} -> % Not open, a restart - {ok,NewLSock} = listen(Port, Opts), + %% Allow gen_tcp:listen to fail 4 times if eaddrinuse: + {ok,NewLSock} = try_listen(Port, Opts, 4), proc_lib:init_ack(Parent, {ok, self()}), Opts1 = ?DELETE_INTERNAL_OPT(lsocket, Opts), {_, Callback, _} = ?GET_OPT(transport, Opts1), @@ -98,6 +99,19 @@ acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) -> end. +try_listen(Port, Opts, NtriesLeft) -> + try_listen(Port, Opts, 1, NtriesLeft). + +try_listen(Port, Opts, N, Nmax) -> + case listen(Port, Opts) of + {error,eaddrinuse} when N + timer:sleep(10*N), % Sleep 10, 20, 30,... ms + try_listen(Port, Opts, N+1, Nmax); + Other -> + Other + end. + + request_ownership(LSock, SockOwner) -> SockOwner ! {request_control,LSock,self()}, receive -- cgit v1.2.3 From 95d5f3022d6fa5aa74205c214b31f9b747530265 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 26 Feb 2018 12:39:13 +0100 Subject: ssh: Removed unused sshc_sup:stop_child/1 --- lib/ssh/src/sshc_sup.erl | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index fd4d8a3c07..f4b39dbbdc 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -27,7 +27,7 @@ -behaviour(supervisor). --export([start_link/0, start_child/1, stop_child/1]). +-export([start_link/0, start_child/1]). %% Supervisor callback -export([init/1]). @@ -43,13 +43,6 @@ start_link() -> start_child(Args) -> supervisor:start_child(?MODULE, Args). -stop_child(Client) -> - spawn(fun() -> - ClientSup = whereis(?SSHC_SUP), - supervisor:terminate_child(ClientSup, Client) - end), - ok. - %%%========================================================================= %%% Supervisor callback %%%========================================================================= -- cgit v1.2.3 From 1926844b8a34cf955d39eae986906d8c30522f41 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 26 Feb 2018 16:53:57 +0100 Subject: ssh: Remove spawn in ssh_system_sup:stop_system/1 --- lib/ssh/src/ssh_system_sup.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 17f990c5d8..469f9560e9 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -88,11 +88,11 @@ stop_listener(Address, Port, Profile) -> stop_system(SysSup) -> - spawn(fun() -> sshd_sup:stop_child(SysSup) end), + catch sshd_sup:stop_child(SysSup), ok. stop_system(Address, Port, Profile) -> - spawn(fun() -> sshd_sup:stop_child(Address, Port, Profile) end), + catch sshd_sup:stop_child(Address, Port, Profile), ok. -- cgit v1.2.3 From 4d5218efec5049c243a5faeac70fd473c028d87f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 15 Mar 2018 17:02:13 +0100 Subject: ssh: Remove deadlock in supervisor tree --- lib/ssh/src/ssh_connection_handler.erl | 57 ++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 24 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e11d3adee4..852e70d9e2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1168,7 +1168,6 @@ handle_event({call,From}, stop, StateName, D0) -> {Repls,D} = send_replies(Replies, D0), {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}}; - handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; @@ -1450,37 +1449,43 @@ handle_event(Type, Ev, StateName, D) -> -spec terminate(any(), state_name(), #data{} - ) -> finalize_termination_result() . + ) -> term(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . terminate(normal, StateName, State) -> - finalize_termination(StateName, State); + stop_subsystem(State), + close_transport(State); terminate({shutdown,{init,Reason}}, StateName, State) -> error_logger:info_report(io_lib:format("Erlang ssh in connection handler init: ~p~n",[Reason])), - finalize_termination(StateName, State); + stop_subsystem(State), + close_transport(State); terminate(shutdown, StateName, State0) -> %% Terminated by supervisor State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Application shutdown"}, - State0), - finalize_termination(StateName, State); + description = "Application shutdown"}, + State0), + close_transport(State); terminate({shutdown,_R}, StateName, State) -> - finalize_termination(StateName, State); + %% Internal termination + stop_subsystem(State), + close_transport(State); terminate(kill, StateName, State) -> - finalize_termination(StateName, State); + stop_subsystem(State), + close_transport(State); terminate(Reason, StateName, State0) -> %% Others, e.g undef, {badmatch,_} log_error(Reason), State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Internal error"}, + description = "Internal error"}, State0), - finalize_termination(StateName, State). + stop_subsystem(State), + close_transport(State). %%-------------------------------------------------------------------- @@ -1555,21 +1560,25 @@ start_the_connection_child(UserPid, Role, Socket, Options0) -> %%-------------------------------------------------------------------- %% Stopping --type finalize_termination_result() :: ok . - -finalize_termination(_StateName, #data{transport_cb = Transport, - connection_state = Connection, - socket = Socket}) -> - case Connection of - #connection{system_supervisor = SysSup, - sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) -> - ssh_system_sup:stop_subsystem(SysSup, SubSysSup); - _ -> - do_nothing - end, - (catch Transport:close(Socket)), + +stop_subsystem(#data{connection_state = + #connection{system_supervisor = SysSup, + sub_system_supervisor = SubSysSup}}) when is_pid(SubSysSup) -> + ssh_system_sup:stop_subsystem(SysSup, SubSysSup); +stop_subsystem(_) -> ok. + +close_transport(#data{transport_cb = Transport, + socket = Socket}) -> + try + Transport:close(Socket) + of + _ -> ok + catch + _:_ -> ok + end. + %%-------------------------------------------------------------------- %% "Invert" the Role peer_role(client) -> server; -- cgit v1.2.3 From 1a93d0f54e4064b22923cc5cb426c82150ad4431 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 20 Mar 2018 12:08:36 +0100 Subject: ssh: Fix bad spec for double_algs() in ssh.hrl --- lib/ssh/src/ssh.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 4711f54fb5..8d950eea3c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -114,7 +114,7 @@ | {mac, double_algs()} | {compression, double_algs()} . -type simple_algs() :: list( atom() ) . --type double_algs() :: list( {client2serverlist,simple_algs()} | {server2client,simple_algs()} ) +-type double_algs() :: list( {client2server,simple_algs()} | {server2client,simple_algs()} ) | simple_algs() . -type options() :: #{socket_options := socket_options(), -- cgit v1.2.3 From 4eb60b4d23befd64250b8aca456f082e5d212878 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 2 Mar 2018 18:02:48 +0100 Subject: ssh: Simplification of using fun:s as exec subsystems --- lib/ssh/src/ssh_cli.erl | 270 +++++++++++++++++++++++++++----------------- lib/ssh/src/ssh_options.erl | 8 +- 2 files changed, 170 insertions(+), 108 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 958c342f5f..783f2f80c0 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -118,42 +118,53 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, write_chars(ConnectionHandler, ChannelId, Chars), {ok, State#state{pty = Pty, buf = NewBuf}}; -handle_ssh_msg({ssh_cm, ConnectionHandler, - {shell, ChannelId, WantReply}}, State) -> +handle_ssh_msg({ssh_cm, ConnectionHandler, {shell, ChannelId, WantReply}}, State) -> NewState = start_shell(ConnectionHandler, State), - ssh_connection:reply_request(ConnectionHandler, WantReply, - success, ChannelId), - {ok, NewState#state{channel = ChannelId, - cm = ConnectionHandler}}; - -handle_ssh_msg({ssh_cm, ConnectionHandler, - {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined, - shell=?DEFAULT_SHELL} = State) -> - {Reply, Status} = exec(Cmd), - write_chars(ConnectionHandler, - ChannelId, io_lib:format("~p\n", [Reply])), - ssh_connection:reply_request(ConnectionHandler, WantReply, - success, ChannelId), - ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), - ssh_connection:send_eof(ConnectionHandler, ChannelId), - {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; - -handle_ssh_msg({ssh_cm, ConnectionHandler, - {exec, ChannelId, WantReply, _Cmd}}, #state{exec = undefined} = State) -> - write_chars(ConnectionHandler, ChannelId, 1, "Prohibited.\n"), ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId), - ssh_connection:exit_status(ConnectionHandler, ChannelId, 255), - ssh_connection:send_eof(ConnectionHandler, ChannelId), - {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; - -handle_ssh_msg({ssh_cm, ConnectionHandler, - {exec, ChannelId, WantReply, Cmd}}, State) -> - NewState = start_shell(ConnectionHandler, Cmd, State), - ssh_connection:reply_request(ConnectionHandler, WantReply, - success, ChannelId), {ok, NewState#state{channel = ChannelId, cm = ConnectionHandler}}; +handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}}, S0) -> + case + case S0#state.exec of + {direct,F} -> + %% Exec called and a Fun or MFA is defined to use. The F returns the + %% value to return. + exec_direct(ConnectionHandler, F, Cmd); + + undefined when S0#state.shell == ?DEFAULT_SHELL -> + %% Exec called and the shell is the default shell (= Erlang shell). + %% To be exact, eval the term as an Erlang term (but not using the + %% ?DEFAULT_SHELL directly). This disables banner, prompts and such. + exec_in_erlang_default_shell(Cmd); + + undefined -> + %% Exec called, but the a shell other than the default shell is defined. + %% No new exec shell is defined, so don't execute! + %% We don't know if it is intended to use the new shell or not. + {"Prohibited.", 255, 1}; + + _ -> + %% Exec called and a Fun or MFA is defined to use. The F communicates via + %% standard io:write/read. + %% Kept for compatibility. + S1 = start_exec_shell(ConnectionHandler, Cmd, S0), + ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId), + {ok, S1} + end + of + {Reply, Status, Type} -> + write_chars(ConnectionHandler, ChannelId, Type, Reply), + ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId), + ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), + ssh_connection:send_eof(ConnectionHandler, ChannelId), + {stop, ChannelId, S0#state{channel = ChannelId, cm = ConnectionHandler}}; + + {ok, S} -> + {ok, S#state{channel = ChannelId, + cm = ConnectionHandler}} + end; + handle_ssh_msg({ssh_cm, _ConnectionHandler, {eof, _ChannelId}}, State) -> {ok, State}; @@ -259,35 +270,7 @@ to_group(Data, Group) -> end, to_group(Tail, Group). -exec(Cmd) -> - case eval(parse(scan(Cmd))) of - {error, _} -> - {Cmd, 0}; %% This should be an external call - Term -> - Term - end. - -scan(Cmd) -> - erl_scan:string(Cmd). - -parse({ok, Tokens, _}) -> - erl_parse:parse_exprs(Tokens); -parse(Error) -> - Error. - -eval({ok, Expr_list}) -> - case (catch erl_eval:exprs(Expr_list, - erl_eval:new_bindings())) of - {value, Value, _NewBindings} -> - {Value, 0}; - {'EXIT', {Error, _}} -> - {Error, -1}; - Error -> - {Error, -1} - end; -eval(Error) -> - {Error, -1}. - +%%-------------------------------------------------------------------- %%% io_request, handle io requests from the user process, %%% Note, this is not the real I/O-protocol, but the mockup version %%% used between edlin and a user_driver. The protocol tags are @@ -506,53 +489,130 @@ bin_to_list(L) when is_list(L) -> bin_to_list(I) when is_integer(I) -> I. + +%%-------------------------------------------------------------------- start_shell(ConnectionHandler, State) -> - Shell = State#state.shell, - ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler, - [peer, user]), - ShellFun = case is_function(Shell) of - true -> - User = proplists:get_value(user, ConnectionInfo), - case erlang:fun_info(Shell, arity) of - {arity, 1} -> - fun() -> Shell(User) end; - {arity, 2} -> - {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), - fun() -> Shell(User, PeerAddr) end; - _ -> - Shell - end; - _ -> - Shell - end, - Echo = get_echo(State#state.pty), - Group = group:start(self(), ShellFun, [{echo, Echo}]), - State#state{group = Group, buf = empty_buf()}. - -start_shell(_ConnectionHandler, Cmd, #state{exec={M, F, A}} = State) -> - Group = group:start(self(), {M, F, A++[Cmd]}, [{echo, false}]), - State#state{group = Group, buf = empty_buf()}; -start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function(Shell) -> - - ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler, - [peer, user]), - User = proplists:get_value(user, ConnectionInfo), - ShellFun = - case erlang:fun_info(Shell, arity) of - {arity, 1} -> - fun() -> Shell(Cmd) end; - {arity, 2} -> - fun() -> Shell(Cmd, User) end; - {arity, 3} -> - {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), - fun() -> Shell(Cmd, User, PeerAddr) end; - _ -> - Shell - end, - Echo = get_echo(State#state.pty), - Group = group:start(self(), ShellFun, [{echo,Echo}]), - State#state{group = Group, buf = empty_buf()}. + ShellSpawner = + case State#state.shell of + Shell when is_function(Shell, 1) -> + [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]), + fun() -> Shell(User) end; + Shell when is_function(Shell, 2) -> + ConnectionInfo = + ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]), + User = proplists:get_value(user, ConnectionInfo), + {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), + fun() -> Shell(User, PeerAddr) end; + {_,_,_} = Shell -> + Shell + end, + State#state{group = group:start(self(), ShellSpawner, [{echo, get_echo(State#state.pty)}]), + buf = empty_buf()}. + +%%-------------------------------------------------------------------- +start_exec_shell(ConnectionHandler, Cmd, State) -> + ExecShellSpawner = + case State#state.exec of + ExecShell when is_function(ExecShell, 1) -> + fun() -> ExecShell(Cmd) end; + ExecShell when is_function(ExecShell, 2) -> + [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]), + fun() -> ExecShell(Cmd, User) end; + ExecShell when is_function(ExecShell, 3) -> + ConnectionInfo = + ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]), + User = proplists:get_value(user, ConnectionInfo), + {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), + fun() -> ExecShell(Cmd, User, PeerAddr) end; + {M,F,A} -> + {M, F, A++[Cmd]} + end, + State#state{group = group:start(self(), ExecShellSpawner, [{echo,false}]), + buf = empty_buf()}. + +%%-------------------------------------------------------------------- +exec_in_erlang_default_shell(Cmd) -> + case eval(parse(scan(Cmd))) of + {ok, Term} -> + {io_lib:format("~p\n", [Term]), 0, 0}; + {error, Error} when is_atom(Error) -> + {io_lib:format("Error in ~p: ~p\n", [Cmd,Error]), -1, 1}; + _ -> + {io_lib:format("Error: ~p\n", [Cmd]), -1, 1} + end. + +scan(Cmd) -> + erl_scan:string(Cmd). + +parse({ok, Tokens, _}) -> + erl_parse:parse_exprs(Tokens); +parse(Error) -> + Error. +eval({ok, Expr_list}) -> + case (catch erl_eval:exprs(Expr_list, + erl_eval:new_bindings())) of + {value, Value, _NewBindings} -> + {ok, Value}; + {'EXIT', {Error, _}} -> + {error, Error}; + {error, Error} -> + {error, Error}; + Error -> + {error, Error} + end; +eval({error,Error}) -> + {error, Error}; +eval(Error) -> + {error, Error}. + +%%-------------------------------------------------------------------- +exec_direct(ConnectionHandler, ExecSpec, Cmd) -> + try + case ExecSpec of + _ when is_function(ExecSpec, 1) -> + ExecSpec(Cmd); + _ when is_function(ExecSpec, 2) -> + [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]), + ExecSpec(Cmd, User); + _ when is_function(ExecSpec, 3) -> + ConnectionInfo = + ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]), + User = proplists:get_value(user, ConnectionInfo), + {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), + ExecSpec(Cmd, User, PeerAddr) + end + of + Reply -> + return_direct_exec_reply(Reply, Cmd) + catch + C:Error -> + {io_lib:format("Error in \"~s\": ~p ~p~n", [Cmd,C,Error]), -1, 1} + end. + + + +return_direct_exec_reply(Reply, Cmd) -> + case fmt_exec_repl(Reply) of + {ok,S} -> + {S, 0, 0}; + {error,S} -> + {io_lib:format("Error in \"~s\": ~s~n", [Cmd,S]), -1, 1} + end. + +fmt_exec_repl({T,A}) when T==ok ; T==error -> + try + {T, io_lib:format("~s",[A])} + catch + error:badarg -> + {T, io_lib:format("~p", [A])}; + C:Err -> + {error, io_lib:format("~p:~p~n",[C,Err])} + end; +fmt_exec_repl(Other) -> + {error, io_lib:format("Bad exec-plugin return: ~p",[Other])}. + +%%-------------------------------------------------------------------- % Pty can be undefined if the client never sets any pty options before % starting the shell. get_echo(undefined) -> diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 1e10f72956..c05293d1ae 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -275,10 +275,12 @@ default(server) -> class => user_options }, - {exec, def} => % FIXME: need some archeology.... + {exec, def} => #{default => undefined, - chk => fun({M,F,_}) -> is_atom(M) andalso is_atom(F); - (V) -> is_function(V) + chk => fun({direct, V}) -> check_function1(V) orelse check_function2(V) orelse check_function3(V); + %% Compatibility (undocumented): + ({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A); + (V) -> check_function1(V) orelse check_function2(V) orelse check_function3(V) end, class => user_options }, -- cgit v1.2.3 From 18d9abb63605628e4564cb925f359c73e1f24c4f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 30 Jan 2018 19:40:58 +0100 Subject: ssh: Simplify ssh_connection and ssh_connection_handler and their internal interfaces --- lib/ssh/src/ssh_connection.erl | 639 +++++++++++++++++---------------- lib/ssh/src/ssh_connection_handler.erl | 98 +++-- 2 files changed, 377 insertions(+), 360 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 946ae2967b..03336d6b25 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -40,16 +40,29 @@ -export([window_change/4, window_change/6, signal/3, exit_status/3]). -%% Internal application API --export([channel_data/5, handle_msg/3, channel_eof_msg/1, - channel_close_msg/1, channel_success_msg/1, channel_failure_msg/1, +%% Internal SSH application API +-export([channel_data/5, + handle_msg/3, + handle_stop/1, + + channel_adjust_window_msg/2, + channel_close_msg/1, + channel_open_failure_msg/4, + channel_open_msg/5, channel_status_msg/1, - channel_adjust_window_msg/2, channel_data_msg/3, - channel_open_msg/5, channel_open_confirmation_msg/4, - channel_open_failure_msg/4, channel_request_msg/4, + channel_data_msg/3, + channel_eof_msg/1, + channel_failure_msg/1, + channel_open_confirmation_msg/4, + channel_request_msg/4, + channel_success_msg/1, + request_failure_msg/0, - request_success_msg/1, bind/4, unbind/3, unbind_channel/2, - bound_channel/3, encode_ip/1]). + request_success_msg/1, + + bind/4, unbind/3, unbind_channel/2, + bound_channel/3, encode_ip/1 + ]). %%-------------------------------------------------------------------- %%% API @@ -232,27 +245,15 @@ exit_status(ConnectionHandler, Channel, Status) -> "exit-status", false, [?uint32(Status)], 0). %%-------------------------------------------------------------------- -%%% Internal API +%%% Internal, that is, ssh application internal API %%-------------------------------------------------------------------- -l2b(L) when is_integer(hd(L)) -> - try list_to_binary(L) - of - B -> B - catch - _:_ -> - unicode:characters_to_binary(L) - end; -l2b([H|T]) -> - << (l2b(H))/binary, (l2b(T))/binary >>; -l2b(B) when is_binary(B) -> - B; -l2b([]) -> - <<>>. - +%%%---------------------------------------------------------------- +%%% Send data on a channel/connection as result of for example +%%% ssh_connection:send (executed in the ssh_connection_state machine) +%%% -channel_data(ChannelId, DataType, Data, Connection, From) - when is_list(Data)-> +channel_data(ChannelId, DataType, Data, Connection, From) when is_list(Data)-> channel_data(ChannelId, DataType, l2b(Data), Connection, From); channel_data(ChannelId, DataType, Data, @@ -271,11 +272,18 @@ channel_data(ChannelId, DataType, Data, SendData)} end, SendList), FlowCtrlMsgs = flow_control(Replies, Channel, Cache), - {{replies, Replies ++ FlowCtrlMsgs}, Connection}; + {Replies ++ FlowCtrlMsgs, Connection}; _ -> - {{replies,[{channel_request_reply,From,{error,closed}}]}, Connection} + {[{channel_request_reply,From,{error,closed}}], Connection} end. +%%%---------------------------------------------------------------- +%%% Handle the channel messages on behalf of the ssh_connection_handler +%%% state machine. +%%% +%%% Replies {Reply, UpdatedConnection} +%%% + handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, sender_channel = RemoteId, initial_window_size = WindowSz, @@ -292,8 +300,7 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, ), send_window_size = WindowSz, send_packet_size = PacketSz}), - {Reply, Connection} = reply_msg(Channel, Connection0, {open, ChannelId}), - {{replies, [Reply]}, Connection}; + reply_msg(Channel, Connection0, {open, ChannelId}); handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId, reason = Reason, @@ -302,36 +309,16 @@ handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId, #connection{channel_cache = Cache} = Connection0, _) -> Channel = ssh_channel:cache_lookup(Cache, ChannelId), ssh_channel:cache_delete(Cache, ChannelId), - {Reply, Connection} = - reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang}), - {{replies, [Reply]}, Connection}; + reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang}); -handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, - #connection{channel_cache = Cache} = Connection0, _) -> - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - case reply_msg(Channel, Connection0, success) of - {[], Connection} -> - {noreply, Connection}; - {Reply, Connection} -> - {{replies, [Reply]}, Connection} - end; - -handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, - #connection{channel_cache = Cache} = Connection0, _) -> - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - case reply_msg(Channel, Connection0, failure) of - {[], Connection} -> - {noreply, Connection}; - {Reply, Connection} -> - {{replies, [Reply]}, Connection} - end; +handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _) -> + reply_msg(ChannelId, Connection, success); +handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, Connection, _) -> + reply_msg(ChannelId, Connection, failure); -handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, - #connection{channel_cache = Cache} = Connection0, _) -> - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - {Reply, Connection} = reply_msg(Channel, Connection0, {eof, ChannelId}), - {{replies, [Reply]}, Connection}; +handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _) -> + reply_msg(ChannelId, Connection, {eof, ChannelId}); handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, #connection{channel_cache = Cache} = Connection0, _) -> @@ -358,42 +345,23 @@ handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, [{flow_control, From, {error, closed}}] end, - Replies = ConnReplyMsgs ++ [CloseMsg] ++ SendReplyMsgs, - {{replies, Replies}, Connection}; + Replies = ConnReplyMsgs ++ CloseMsg ++ SendReplyMsgs, + {Replies, Connection}; undefined -> - {{replies, []}, Connection0} + {[], Connection0} end; handle_msg(#ssh_msg_channel_data{recipient_channel = ChannelId, data = Data}, - #connection{channel_cache = Cache} = Connection0, _) -> - - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{recv_window_size = Size} = Channel -> - WantedSize = Size - size(Data), - ssh_channel:cache_update(Cache, Channel#channel{ - recv_window_size = WantedSize}), - {Replies, Connection} = - channel_data_reply(Cache, Channel, Connection0, 0, Data), - {{replies, Replies}, Connection}; - undefined -> - {noreply, Connection0} - end; + Connection, _) -> + channel_data_reply_msg(ChannelId, Connection, 0, Data); handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId, data_type_code = DataType, data = Data}, - #connection{channel_cache = Cache} = Connection0, _) -> - - #channel{recv_window_size = Size} = Channel = - ssh_channel:cache_lookup(Cache, ChannelId), - WantedSize = Size - size(Data), - ssh_channel:cache_update(Cache, Channel#channel{ - recv_window_size = WantedSize}), - {Replies, Connection} = - channel_data_reply(Cache, Channel, Connection0, DataType, Data), - {{replies, Replies}, Connection}; + Connection, _) -> + channel_data_reply_msg(ChannelId, Connection, DataType, Data); handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, bytes_to_add = Add}, @@ -409,7 +377,7 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, {connection_reply, channel_data_msg(RemoteId, Type, Data)} end, SendList), FlowCtrlMsgs = flow_control(Channel, Cache), - {{replies, Replies ++ FlowCtrlMsgs}, Connection}; + {Replies ++ FlowCtrlMsgs, Connection}; handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, sender_channel = RemoteId, @@ -430,8 +398,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, "Connection refused", "en"), - {{replies, [{connection_reply, FailMsg}]}, - Connection0} + {[{connection_reply, FailMsg}], Connection0} end; MinAcceptedPackSz > PacketSz -> @@ -439,7 +406,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, lists:concat(["Maximum packet size below ",MinAcceptedPackSz, " not supported"]), "en"), - {{replies, [{connection_reply, FailMsg}]}, Connection0} + {[{connection_reply, FailMsg}], Connection0} end; handle_msg(#ssh_msg_channel_open{channel_type = "session", @@ -452,34 +419,30 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session", FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, "Connection refused", "en"), - {{replies, [{connection_reply, FailMsg}]}, - Connection}; + {[{connection_reply, FailMsg}], Connection}; handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _) -> FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "Not allowed", "en"), - {{replies, [{connection_reply, FailMsg}]}, Connection}; + {[{connection_reply, FailMsg}], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exit-status", data = Data}, - #connection{channel_cache = Cache} = Connection, _) -> + Connection, _) -> <> = Data, - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - {Reply, Connection} = - reply_msg(Channel, Connection, {exit_status, ChannelId, Status}), - {{replies, [Reply]}, Connection}; + reply_msg(ChannelId, Connection, {exit_status, ChannelId, Status}); handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exit-signal", want_reply = false, data = Data}, - #connection{channel_cache = Cache} = Connection0, _) -> - <> = Data, + #connection{channel_cache = Cache} = Connection0, _) -> + <> = Data, Channel = ssh_channel:cache_lookup(Cache, ChannelId), RemoteId = Channel#channel.remote_id, {Reply, Connection} = reply_msg(Channel, Connection0, @@ -488,52 +451,41 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, binary_to_list(Err), binary_to_list(Lang)}), CloseMsg = channel_close_msg(RemoteId), - {{replies, [{connection_reply, CloseMsg}, Reply]}, - Connection}; + {[{connection_reply, CloseMsg}|Reply], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "xon-xoff", want_reply = false, data = Data}, - #connection{channel_cache = Cache} = Connection, _) -> + Connection, _) -> <> = Data, - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - {Reply, Connection} = - reply_msg(Channel, Connection, {xon_xoff, ChannelId, CDo=/= 0}), - {{replies, [Reply]}, Connection}; + reply_msg(ChannelId, Connection, {xon_xoff, ChannelId, CDo=/= 0}); handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "window-change", want_reply = false, data = Data}, - #connection{channel_cache = Cache} = Connection0, _) -> + Connection0, _) -> <> = Data, - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - {Reply, Connection} = - reply_msg(Channel, Connection0, {window_change, ChannelId, - Width, Height, - PixWidth, PixHeight}), - {{replies, [Reply]}, Connection}; + ?UINT32(PixWidth), ?UINT32(PixHeight)>> = Data, + reply_msg(ChannelId, Connection0, {window_change, ChannelId, + Width, Height, + PixWidth, PixHeight}); handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "signal", data = Data}, - #connection{channel_cache = Cache} = Connection0, _) -> - <> = Data, - - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - {Reply, Connection} = - reply_msg(Channel, Connection0, {signal, ChannelId, - binary_to_list(SigName)}), - {{replies, [Reply]}, Connection}; + Connection0, _) -> + <> = Data, + reply_msg(ChannelId, Connection0, {signal, ChannelId, + binary_to_list(SigName)}); handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "subsystem", want_reply = WantReply, data = Data}, #connection{channel_cache = Cache} = Connection, server) -> - <> = Data, + <> = Data, #channel{remote_id = RemoteId} = Channel0 = ssh_channel:cache_lookup(Cache, ChannelId), @@ -547,92 +499,77 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, ssh_channel:cache_update(Cache, Channel), Reply = {connection_reply, channel_success_msg(RemoteId)}, - {{replies, [Reply]}, Connection} + {[Reply], Connection} catch _:_ -> - ErrorReply = {connection_reply, - channel_failure_msg(RemoteId)}, - {{replies, [ErrorReply]}, Connection} + ErrorReply = {connection_reply, channel_failure_msg(RemoteId)}, + {[ErrorReply], Connection} end; handle_msg(#ssh_msg_channel_request{request_type = "subsystem"}, Connection, client) -> %% The client SHOULD ignore subsystem requests. See RFC 4254 6.5. - {{replies, []}, Connection}; + {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "pty-req", want_reply = WantReply, data = Data}, - #connection{channel_cache = Cache} = Connection, server) -> - <> = Data, + Connection, server) -> + <> = Data, TermName = binary_to_list(BTermName), - PtyRequest = {TermName, Width, Height, PixWidth, PixHeight, decode_pty_opts(Modes)}, - - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - handle_cli_msg(Connection, Channel, + handle_cli_msg(Connection, ChannelId, {pty, ChannelId, WantReply, PtyRequest}); handle_msg(#ssh_msg_channel_request{request_type = "pty-req"}, Connection, client) -> %% The client SHOULD ignore pty requests. See RFC 4254 6.2. - {{replies, []}, Connection}; + {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "shell", want_reply = WantReply}, - #connection{channel_cache = Cache} = Connection, server) -> - - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - - handle_cli_msg(Connection, Channel, + Connection, server) -> + handle_cli_msg(Connection, ChannelId, {shell, ChannelId, WantReply}); handle_msg(#ssh_msg_channel_request{request_type = "shell"}, Connection, client) -> %% The client SHOULD ignore shell requests. See RFC 4254 6.5. - {{replies, []}, Connection}; + {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exec", want_reply = WantReply, data = Data}, - #connection{channel_cache = Cache} = Connection, server) -> - <> = Data, - - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - - handle_cli_msg(Connection, Channel, + Connection, server) -> + <> = Data, + handle_cli_msg(Connection, ChannelId, {exec, ChannelId, WantReply, binary_to_list(Command)}); handle_msg(#ssh_msg_channel_request{request_type = "exec"}, Connection, client) -> %% The client SHOULD ignore exec requests. See RFC 4254 6.5. - {{replies, []}, Connection}; + {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "env", want_reply = WantReply, data = Data}, - #connection{channel_cache = Cache} = Connection, server) -> - - <> = Data, - - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - - handle_cli_msg(Connection, Channel, + Connection, server) -> + <> = Data, + handle_cli_msg(Connection, ChannelId, {env, ChannelId, WantReply, Var, Value}); handle_msg(#ssh_msg_channel_request{request_type = "env"}, Connection, client) -> %% The client SHOULD ignore env requests. - {{replies, []}, Connection}; + {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = _Other, @@ -642,13 +579,12 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = RemoteId} -> FailMsg = channel_failure_msg(RemoteId), - {{replies, [{connection_reply, FailMsg}]}, - Connection}; + {[{connection_reply, FailMsg}], Connection}; undefined -> %% Chanel has been closed - {noreply, Connection} + {[], Connection} end; true -> - {noreply, Connection} + {[], Connection} end; handle_msg(#ssh_msg_global_request{name = _Type, @@ -656,79 +592,53 @@ handle_msg(#ssh_msg_global_request{name = _Type, data = _Data}, Connection, _) -> if WantReply == true -> FailMsg = request_failure_msg(), - {{replies, [{connection_reply, FailMsg}]}, - Connection}; + {[{connection_reply, FailMsg}], Connection}; true -> - {noreply, Connection} + {[], Connection} end; handle_msg(#ssh_msg_request_failure{}, #connection{requests = [{_, From} | Rest]} = Connection, _) -> - {{replies, [{channel_request_reply, From, {failure, <<>>}}]}, + {[{channel_request_reply, From, {failure, <<>>}}], Connection#connection{requests = Rest}}; handle_msg(#ssh_msg_request_success{data = Data}, #connection{requests = [{_, From} | Rest]} = Connection, _) -> - {{replies, [{channel_request_reply, From, {success, Data}}]}, + {[{channel_request_reply, From, {success, Data}}], Connection#connection{requests = Rest}}; handle_msg(#ssh_msg_disconnect{code = Code, - description = Description, - language = _Lang }, - #connection{channel_cache = Cache} = Connection0, _) -> - {Connection, Replies} = - ssh_channel:cache_foldl(fun(Channel, {Connection1, Acc}) -> - {Reply, Connection2} = - reply_msg(Channel, - Connection1, - {closed, Channel#channel.local_id}), - {Connection2, [Reply | Acc]} - end, {Connection0, []}, Cache), - - ssh_channel:cache_delete(Cache), - {disconnect, {Code, Description}, {{replies, Replies}, Connection}}. - -handle_cli_msg(#connection{channel_cache = Cache} = Connection, - #channel{user = undefined, - remote_id = RemoteId, - local_id = ChannelId} = Channel0, Reply0) -> - case (catch start_cli(Connection, ChannelId)) of - {ok, Pid} -> - erlang:monitor(process, Pid), - Channel = Channel0#channel{user = Pid}, - ssh_channel:cache_update(Cache, Channel), - {Reply, Connection1} = reply_msg(Channel, Connection, Reply0), - {{replies, [Reply]}, Connection1}; - _Other -> - Reply = {connection_reply, - channel_failure_msg(RemoteId)}, - {{replies, [Reply]}, Connection} - end; + description = Description}, + Connection, _) -> + {disconnect, {Code, Description}, handle_stop(Connection)}. -handle_cli_msg(Connection0, Channel, Reply0) -> - {Reply, Connection} = reply_msg(Channel, Connection0, Reply0), - {{replies, [Reply]}, Connection}. -channel_eof_msg(ChannelId) -> - #ssh_msg_channel_eof{recipient_channel = ChannelId}. - -channel_close_msg(ChannelId) -> - #ssh_msg_channel_close {recipient_channel = ChannelId}. - -channel_status_msg({success, ChannelId}) -> - channel_success_msg(ChannelId); -channel_status_msg({failure, ChannelId}) -> - channel_failure_msg(ChannelId). - -channel_success_msg(ChannelId) -> - #ssh_msg_channel_success{recipient_channel = ChannelId}. - -channel_failure_msg(ChannelId) -> - #ssh_msg_channel_failure{recipient_channel = ChannelId}. +%%%---------------------------------------------------------------- +%%% Returns pending responses to be delivered to the peer when a +%%% Channel/Connection closes +%%% +handle_stop(#connection{channel_cache = Cache} = Connection0) -> + {Connection, Replies} = + ssh_channel:cache_foldl( + fun(Channel, {Connection1, Acc}) -> + {Reply, Connection2} = + reply_msg(Channel, Connection1, + {closed, Channel#channel.local_id}), + {Connection2, Reply ++ Acc} + end, {Connection0, []}, Cache), + ssh_channel:cache_delete(Cache), + {Replies, Connection}. +%%%---------------------------------------------------------------- +%%% channel_*_msg(...) +%%% Returns a #ssh_msg_....{} for channel operations. +%%% channel_adjust_window_msg(ChannelId, Bytes) -> #ssh_msg_channel_window_adjust{recipient_channel = ChannelId, bytes_to_add = Bytes}. +channel_close_msg(ChannelId) -> + #ssh_msg_channel_close {recipient_channel = ChannelId}. + channel_data_msg(ChannelId, 0, Data) -> #ssh_msg_channel_data{recipient_channel = ChannelId, data = Data}; @@ -737,6 +647,12 @@ channel_data_msg(ChannelId, Type, Data) -> data_type_code = Type, data = Data}. +channel_eof_msg(ChannelId) -> + #ssh_msg_channel_eof{recipient_channel = ChannelId}. + +channel_failure_msg(ChannelId) -> + #ssh_msg_channel_failure{recipient_channel = ChannelId}. + channel_open_msg(Type, ChannelId, WindowSize, MaxPacketSize, Data) -> #ssh_msg_channel_open{channel_type = Type, sender_channel = ChannelId, @@ -757,18 +673,34 @@ channel_open_failure_msg(RemoteId, Reason, Description, Lang) -> description = Description, lang = Lang}. +channel_status_msg({success, ChannelId}) -> + channel_success_msg(ChannelId); + +channel_status_msg({failure, ChannelId}) -> + channel_failure_msg(ChannelId). + channel_request_msg(ChannelId, Type, WantReply, Data) -> #ssh_msg_channel_request{recipient_channel = ChannelId, request_type = Type, want_reply = WantReply, data = Data}. +channel_success_msg(ChannelId) -> + #ssh_msg_channel_success{recipient_channel = ChannelId}. + +%%%---------------------------------------------------------------- +%%% request_*_msg(...) +%%% Returns a #ssh_msg_....{} for request responses. +%%% request_failure_msg() -> #ssh_msg_request_failure{}. request_success_msg(Data) -> #ssh_msg_request_success{data = Data}. +%%%---------------------------------------------------------------- +%%% +%%% bind(IP, Port, ChannelPid, Connection) -> Binds = [{{IP, Port}, ChannelPid} | lists:keydelete({IP, Port}, 1, @@ -808,6 +740,68 @@ encode_ip(Addr) when is_list(Addr) -> end end. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% +%%% Internal functions +%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%---------------------------------------------------------------- +%%% Create the channel data when an ssh_msg_open_channel message +%%% of "session" typ is handled +%%% +setup_session(#connection{channel_cache = Cache, + channel_id_seed = NewChannelID + } = C, + RemoteId, Type, WindowSize, PacketSize) -> + NextChannelID = NewChannelID + 1, + Channel = + #channel{type = Type, + sys = "ssh", + local_id = NewChannelID, + recv_window_size = ?DEFAULT_WINDOW_SIZE, + recv_packet_size = ?DEFAULT_PACKET_SIZE, + send_window_size = WindowSize, + send_packet_size = PacketSize, + send_buf = queue:new(), + remote_id = RemoteId + }, + ssh_channel:cache_update(Cache, Channel), + OpenConfMsg = channel_open_confirmation_msg(RemoteId, NewChannelID, + ?DEFAULT_WINDOW_SIZE, + ?DEFAULT_PACKET_SIZE), + Reply = {connection_reply, OpenConfMsg}, + {[Reply], C#connection{channel_id_seed = NextChannelID}}. + + +%%%---------------------------------------------------------------- +%%% Start a cli or subsystem +%%% +start_cli(#connection{options = Options, + cli_spec = CliSpec, + exec = Exec, + sub_system_supervisor = SubSysSup}, ChannelId) -> + case CliSpec of + no_cli -> + {error, cli_disabled}; + {CbModule, Args} -> + start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options) + end. + + +start_subsystem(BinName, #connection{options = Options, + sub_system_supervisor = SubSysSup}, + #channel{local_id = ChannelId}, _ReplyMsg) -> + Name = binary_to_list(BinName), + case check_subsystem(Name, Options) of + {Callback, Opts} when is_atom(Callback), Callback =/= none -> + start_channel(Callback, ChannelId, Opts, SubSysSup, Options); + {Other, _} when Other =/= none -> + {error, legacy_option_not_supported} + end. + + +%%% Helpers for starting cli/subsystems start_channel(Cb, Id, Args, SubSysSup, Opts) -> start_channel(Cb, Id, Args, SubSysSup, undefined, Opts). @@ -827,33 +821,6 @@ max_num_channels_not_exceeded(ChannelSup, Opts) -> %% Note that NumChannels is BEFORE starting a new one NumChannels < MaxNumChannels. -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -setup_session(#connection{channel_cache = Cache - } = Connection0, - RemoteId, - Type, WindowSize, PacketSize) -> - {ChannelId, Connection} = new_channel_id(Connection0), - - Channel = #channel{type = Type, - sys = "ssh", - local_id = ChannelId, - recv_window_size = ?DEFAULT_WINDOW_SIZE, - recv_packet_size = ?DEFAULT_PACKET_SIZE, - send_window_size = WindowSize, - send_packet_size = PacketSize, - send_buf = queue:new(), - remote_id = RemoteId - }, - ssh_channel:cache_update(Cache, Channel), - OpenConfMsg = channel_open_confirmation_msg(RemoteId, ChannelId, - ?DEFAULT_WINDOW_SIZE, - ?DEFAULT_PACKET_SIZE), - - {{replies, [{connection_reply, OpenConfMsg}]}, Connection}. - - check_subsystem("sftp"= SsName, Options) -> case ?GET_OPT(subsystems, Options) of no_subsys -> % FIXME: Can 'no_subsys' ever be matched? @@ -872,64 +839,10 @@ check_subsystem(SsName, Options) -> Value end. -start_cli(#connection{cli_spec = no_cli}, _) -> - {error, cli_disabled}; -start_cli(#connection{options = Options, - cli_spec = {CbModule, Args}, - exec = Exec, - sub_system_supervisor = SubSysSup}, ChannelId) -> - start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options). - -start_subsystem(BinName, #connection{options = Options, - sub_system_supervisor = SubSysSup}, - #channel{local_id = ChannelId}, _ReplyMsg) -> - Name = binary_to_list(BinName), - case check_subsystem(Name, Options) of - {Callback, Opts} when is_atom(Callback), Callback =/= none -> - start_channel(Callback, ChannelId, Opts, SubSysSup, Options); - {Other, _} when Other =/= none -> - {error, legacy_option_not_supported} - end. - -channel_data_reply(_, #channel{local_id = ChannelId} = Channel, - Connection0, DataType, Data) -> - {Reply, Connection} = - reply_msg(Channel, Connection0, {data, ChannelId, DataType, Data}), - {[Reply], Connection}. - -new_channel_id(Connection) -> - ID = Connection#connection.channel_id_seed, - {ID, Connection#connection{channel_id_seed = ID + 1}}. - -reply_msg(Channel, Connection, {open, _} = Reply) -> - request_reply_or_data(Channel, Connection, Reply); -reply_msg(Channel, Connection, {open_error, _, _, _} = Reply) -> - request_reply_or_data(Channel, Connection, Reply); -reply_msg(Channel, Connection, success = Reply) -> - request_reply_or_data(Channel, Connection, Reply); -reply_msg(Channel, Connection, failure = Reply) -> - request_reply_or_data(Channel, Connection, Reply); -reply_msg(Channel, Connection, {closed, _} = Reply) -> - request_reply_or_data(Channel, Connection, Reply); -reply_msg(undefined, Connection, _Reply) -> - {noreply, Connection}; -reply_msg(#channel{user = ChannelPid}, Connection, Reply) -> - {{channel_data, ChannelPid, Reply}, Connection}. - - -request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid}, - #connection{requests = Requests} = - Connection, Reply) -> - case lists:keysearch(ChannelId, 1, Requests) of - {value, {ChannelId, From}} -> - {{channel_request_reply, From, Reply}, - Connection#connection{requests = - lists:keydelete(ChannelId, 1, Requests)}}; - false when (Reply == success) or (Reply == failure) -> - {[], Connection}; - false -> - {{channel_data, ChannelPid, Reply}, Connection} - end. +%%%---------------------------------------------------------------- +%%% +%%% Send-window handling +%%% update_send_window(Channel, _, undefined, #connection{channel_cache = Cache}) -> @@ -984,6 +897,11 @@ handle_send_window({Type, Data}, _, PacketSize, WindowSize, Acc) -> <> = Data, {WindowSize - PacketSize, [{Type, Msg1} | Acc], {Type, Msg2}}. +%%%---------------------------------------------------------------- +%%% +%%% Flow control +%%% + flow_control(Channel, Cache) -> flow_control([window_adjusted], Channel, Cache). @@ -1002,6 +920,11 @@ flow_control([_|_], #channel{flow_control = From, flow_control(_,_,_) -> []. +%%%---------------------------------------------------------------- +%%% +%%% Pseudo terminal stuff +%%% + pty_req(ConnectionHandler, Channel, Term, Width, Height, PixWidth, PixHeight, PtyOpts, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, @@ -1027,8 +950,7 @@ pty_default_dimensions(Dimension, TermData) -> encode_pty_opts(Opts) -> Bin = list_to_binary(encode_pty_opts2(Opts)), - Len = size(Bin), - <>. + <>. encode_pty_opts2([]) -> [?TTY_OP_END]; @@ -1147,7 +1069,7 @@ decode_pty_opts(<<>>) -> []; decode_pty_opts(<<0, 0, 0, 0>>) -> []; -decode_pty_opts(<>) -> +decode_pty_opts(<>) -> decode_pty_opts2(Modes); decode_pty_opts(Binary) -> decode_pty_opts2(Binary). @@ -1224,3 +1146,104 @@ backwards_compatible([{pixel_hight, Value} | Rest], Acc) -> backwards_compatible(Rest, [{height, Value} | Acc]); backwards_compatible([Value| Rest], Acc) -> backwards_compatible(Rest, [ Value | Acc]). + + +%%%---------------------------------------------------------------- +%%% +%%% Common part of handling channel messages meant for a cli (like "env", "exec" etc) +%%% Called at the finnish of handle_msg(#ssh_msg_channel_request,...) +%%% + +handle_cli_msg(C0, ChId, Reply0) -> + Cache = C0#connection.channel_cache, + Ch0 = ssh_channel:cache_lookup(Cache, ChId), + case Ch0#channel.user of + undefined -> + case (catch start_cli(C0, ChId)) of + {ok, Pid} -> + erlang:monitor(process, Pid), + Ch = Ch0#channel{user = Pid}, + ssh_channel:cache_update(Cache, Ch), + reply_msg(Ch, C0, Reply0); + _Other -> + Reply = {connection_reply, channel_failure_msg(Ch0#channel.remote_id)}, + {[Reply], C0} + end; + + _ -> + reply_msg(Ch0, C0, Reply0) + end. + +%%%---------------------------------------------------------------- +%%% +%%% Request response handling on return to the calling ssh_connection_handler +%%% state machine. +%%% + +channel_data_reply_msg(ChannelId, Connection, DataType, Data) -> + case ssh_channel:cache_lookup(Connection#connection.channel_cache, ChannelId) of + #channel{recv_window_size = Size} = Channel -> + WantedSize = Size - size(Data), + ssh_channel:cache_update(Connection#connection.channel_cache, + Channel#channel{recv_window_size = WantedSize}), + reply_msg(Channel, Connection, {data, ChannelId, DataType, Data}); + undefined -> + {[], Connection} + end. + + +reply_msg(ChId, C, Reply) when is_integer(ChId) -> + reply_msg(ssh_channel:cache_lookup(C#connection.channel_cache, ChId), C, Reply); + +reply_msg(Channel, Connection, {open, _} = Reply) -> + request_reply_or_data(Channel, Connection, Reply); +reply_msg(Channel, Connection, {open_error, _, _, _} = Reply) -> + request_reply_or_data(Channel, Connection, Reply); +reply_msg(Channel, Connection, success = Reply) -> + request_reply_or_data(Channel, Connection, Reply); +reply_msg(Channel, Connection, failure = Reply) -> + request_reply_or_data(Channel, Connection, Reply); +reply_msg(Channel, Connection, {closed, _} = Reply) -> + request_reply_or_data(Channel, Connection, Reply); +reply_msg(undefined, Connection, _Reply) -> + {[], Connection}; +reply_msg(#channel{user = ChannelPid}, Connection, Reply) -> + {[{channel_data, ChannelPid, Reply}], Connection}. + + +request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid}, + #connection{requests = Requests} = + Connection, Reply) -> + case lists:keysearch(ChannelId, 1, Requests) of + {value, {ChannelId, From}} -> + {[{channel_request_reply, From, Reply}], + Connection#connection{requests = + lists:keydelete(ChannelId, 1, Requests)}}; + false when (Reply == success) or (Reply == failure) -> + {[], Connection}; + false -> + {[{channel_data, ChannelPid, Reply}], Connection} + end. + + + +%%%---------------------------------------------------------------- +%%% l(ist)2b(inary) +%%% +l2b(L) when is_integer(hd(L)) -> + try list_to_binary(L) + of + B -> B + catch + _:_ -> + unicode:characters_to_binary(L) + end; +l2b([H|T]) -> + << (l2b(H))/binary, (l2b(T))/binary >>; +l2b(B) when is_binary(B) -> + B; +l2b([]) -> + <<>>. + + + diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 852e70d9e2..f47ab41624 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -960,9 +960,9 @@ handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) -> {next_state, {kexinit,Role,renegotiate}, D, [postpone]}; handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) -> - {disconnect, _, {{replies,Replies}, _}} = + {disconnect, _, RepliesCon} = ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName)), - {Actions,D} = send_replies(Replies, D0), + {Actions,D} = send_replies(RepliesCon, D0), disconnect_fun(Desc, D), {stop_and_reply, {shutdown,Desc}, Actions, D}; @@ -1159,14 +1159,9 @@ handle_event({call,From}, {info, ChannelPid}, _, D) -> end, [], cache(D)), {keep_state_and_data, [{reply, From, {ok,Result}}]}; -handle_event({call,From}, stop, StateName, D0) -> - {disconnect, _Reason, {{replies, Replies}, Connection}} = - ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "User closed down connection"}, - D0#data.connection_state, - role(StateName)), - {Repls,D} = send_replies(Replies, D0), - {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}}; +handle_event({call,From}, stop, _StateName, D0) -> + {Repls,D} = send_replies(ssh_connection:handle_stop(D0#data.connection_state), D0), + {stop_and_reply, normal, [{reply,From,ok}|Repls], D}; handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; @@ -1195,9 +1190,8 @@ handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) when ?CONNECTED(StateName) -> - {{replies, Replies}, Connection} = - ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), - {Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}), + {Repls,D} = send_replies(ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), + D0), start_channel_request_timer(ChannelId, From, Timeout), % FIXME: No message exchange so why? {keep_state, D, Repls}; @@ -1373,9 +1367,7 @@ handle_event(info, {timeout, {_, From} = Request}, _, %%% Handle that ssh channels user process goes down handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D0) -> - {{replies, Replies}, D1} = handle_channel_down(ChannelPid, D0), - {Repls, D} = send_replies(Replies, D1), - {keep_state, D, Repls}; + {keep_state, handle_channel_down(ChannelPid, D0)}; %%% So that terminate will be run when supervisor is shutdown handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> @@ -1677,7 +1669,20 @@ handle_connection_msg(Msg, StateName, D0 = #data{starter = User, Renegotiation = renegotiation(StateName), Role = role(StateName), try ssh_connection:handle_msg(Msg, Connection0, Role) of - {{replies, Replies}, Connection} -> + {disconnect, Reason0, RepliesConn} -> + {Repls, D} = send_replies(RepliesConn, D0), + case {Reason0,Role} of + {{_, Reason}, client} when ((StateName =/= {connected,client}) and (not Renegotiation)) -> + User ! {self(), not_connected, Reason}; + _ -> + ok + end, + {stop_and_reply, {shutdown,normal}, Repls, D}; + + {[], Connection} -> + {keep_state, D0#data{connection_state = Connection}}; + + {Replies, Connection} when is_list(Replies) -> {Repls, D} = case StateName of {connected,_} -> @@ -1686,30 +1691,15 @@ handle_connection_msg(Msg, StateName, D0 = #data{starter = User, {ConnReplies, NonConnReplies} = lists:splitwith(fun not_connected_filter/1, Replies), send_replies(NonConnReplies, D0#data{event_queue = Qev0 ++ ConnReplies}) end, - {keep_state, D, Repls}; - - {noreply, Connection} -> - {keep_state, D0#data{connection_state = Connection}}; - - {disconnect, Reason0, {{replies, Replies}, Connection}} -> - {Repls, D} = send_replies(Replies, D0#data{connection_state = Connection}), - case {Reason0,Role} of - {{_, Reason}, client} when ((StateName =/= {connected,client}) and (not Renegotiation)) -> - User ! {self(), not_connected, Reason}; - _ -> - ok - end, - {stop_and_reply, {shutdown,normal}, Repls, D#data{connection_state = Connection}} + {keep_state, D, Repls} catch - _:Error -> - {disconnect, _Reason, {{replies, Replies}, Connection}} = - ssh_connection:handle_msg( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Internal error"}, - Connection0, Role), - {Repls, D} = send_replies(Replies, D0#data{connection_state = Connection}), - {stop_and_reply, {shutdown,Error}, Repls, D#data{connection_state = Connection}} + Class:Error -> + {Repls, D1} = send_replies(ssh_connection:handle_stop(Connection0), D0), + {Shutdown, D} = ?send_disconnect(?SSH_DISCONNECT_BY_APPLICATION, + io_lib:format("Internal error: ~p:~p",[Class,Error]), + StateName, D1), + {stop_and_reply, Shutdown, Repls, D} end. @@ -1819,15 +1809,16 @@ handle_request(ChannelId, Type, Data, WantReply, From, D) -> %%%---------------------------------------------------------------- handle_channel_down(ChannelPid, D) -> + Cache = cache(D), ssh_channel:cache_foldl( - fun(Channel, Acc) when Channel#channel.user == ChannelPid -> - ssh_channel:cache_delete(cache(D), - Channel#channel.local_id), - Acc; - (_,Acc) -> - Acc - end, [], cache(D)), - {{replies, []}, cache_check_set_idle_timer(D)}. + fun(#channel{user=U, + local_id=Id}, Acc) when U == ChannelPid -> + ssh_channel:cache_delete(Cache, Id), + Acc; + (_,Acc) -> + Acc + end, [], Cache), + cache_check_set_idle_timer(D). update_sys(Cache, Channel, Type, ChannelPid) -> @@ -1911,10 +1902,11 @@ not_connected_filter({connection_reply, _Data}) -> true; not_connected_filter(_) -> false. %%%---------------------------------------------------------------- + +send_replies({Repls,C = #connection{}}, D) when is_list(Repls) -> + send_replies(Repls, D#data{connection_state=C}); send_replies(Repls, State) -> - lists:foldl(fun get_repl/2, - {[],State}, - Repls). + lists:foldl(fun get_repl/2, {[],State}, Repls). get_repl({connection_reply,Msg}, {CallRepls,S}) -> if is_record(Msg, ssh_msg_channel_success) -> @@ -1935,8 +1927,10 @@ get_repl({flow_control,Cache,Channel,From,Msg}, {CallRepls,S}) -> {[{reply,From,Msg}|CallRepls], S}; get_repl({flow_control,From,Msg}, {CallRepls,S}) -> {[{reply,From,Msg}|CallRepls], S}; -get_repl(noreply, Acc) -> - Acc; +%% get_repl(noreply, Acc) -> +%% Acc; +%% get_repl([], Acc) -> +%% Acc; get_repl(X, Acc) -> exit({get_repl,X,Acc}). -- cgit v1.2.3 From 759ffa6660a8c8d14cc7010d4678160006c9c8e5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 19 Feb 2018 13:20:20 +0100 Subject: ssh: Unused fields #ssh.hkey and #ssh.kex removed --- lib/ssh/src/ssh.hrl | 2 -- lib/ssh/src/ssh_transport.erl | 10 +++------- 2 files changed, 3 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 8d950eea3c..1a2f003190 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -151,8 +151,6 @@ algorithms, %% #alg{} - kex, %% key exchange algorithm - hkey, %% host key algorithm key_cb, %% Private/Public key callback module io_cb, %% Interaction callback module diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 975053d301..c221cf5905 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1057,9 +1057,7 @@ install_alg(Dir, SSH) -> alg_setup(snd, SSH) -> ALG = SSH#ssh.algorithms, - SSH#ssh{kex = ALG#alg.kex, - hkey = ALG#alg.hkey, - encrypt = ALG#alg.encrypt, + SSH#ssh{encrypt = ALG#alg.encrypt, send_mac = ALG#alg.send_mac, send_mac_size = mac_digest_size(ALG#alg.send_mac), compress = ALG#alg.compress, @@ -1071,9 +1069,7 @@ alg_setup(snd, SSH) -> alg_setup(rcv, SSH) -> ALG = SSH#ssh.algorithms, - SSH#ssh{kex = ALG#alg.kex, - hkey = ALG#alg.hkey, - decrypt = ALG#alg.decrypt, + SSH#ssh{decrypt = ALG#alg.decrypt, recv_mac = ALG#alg.recv_mac, recv_mac_size = mac_digest_size(ALG#alg.recv_mac), decompress = ALG#alg.decompress, @@ -1810,7 +1806,7 @@ mac('hmac-sha2-512', Key, SeqNum, Data) -> hash(_SSH, _Char, 0) -> <<>>; hash(SSH, Char, N) -> - HashAlg = sha(SSH#ssh.kex), + HashAlg = sha(SSH#ssh.algorithms#alg.kex), K = SSH#ssh.shared_secret, H = SSH#ssh.exchanged_hash, K1 = crypto:hash(HashAlg, [K, H, Char, SSH#ssh.session_id]), -- cgit v1.2.3 From 221dc123b58da0952fb77ff13243ecc8b53f1ae4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 19 Feb 2018 13:25:51 +0100 Subject: ssh: Add ssh_connection_handler:alg/1 for test purposes --- lib/ssh/src/ssh_connection_handler.erl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f47ab41624..907ecef860 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -68,7 +68,7 @@ -export([init_connection_handler/3, % proc_lib:spawn needs this init_ssh_record/3, % Export of this internal function % intended for low-level protocol test suites - renegotiate/1, renegotiate_data/1 % Export intended for test cases + renegotiate/1, renegotiate_data/1, alg/1 % Export intended for test cases ]). %%==================================================================== @@ -320,6 +320,9 @@ renegotiate(ConnectionHandler) -> renegotiate_data(ConnectionHandler) -> cast(ConnectionHandler, data_size). +%%-------------------------------------------------------------------- +alg(ConnectionHandler) -> + call(ConnectionHandler, get_alg). %%==================================================================== %% Internal process state @@ -1030,6 +1033,10 @@ handle_event(cast, renegotiate, {connected,Role}, D) -> {next_state, {kexinit,Role,renegotiate}, D#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; +handle_event({call,From}, get_alg, _, D) -> + #ssh{algorithms=Algs} = D#data.ssh_params, + {keep_state_and_data, [{reply,From,Algs}]}; + handle_event(cast, renegotiate, _, _) -> %% Already in key-exchange so safe to ignore timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), % FIXME: not here in original -- cgit v1.2.3 From 3352eaf189c55e60778fc76b94530fac0c314fba Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 15 Feb 2018 15:46:27 +0100 Subject: ssh: New ssh_dbg module --- lib/ssh/src/Makefile | 2 +- lib/ssh/src/ssh.hrl | 9 + lib/ssh/src/ssh_dbg.erl | 624 ++++++++++++++++++++---------------------------- lib/ssh/src/ssh_dbg.hrl | 27 --- 4 files changed, 272 insertions(+), 390 deletions(-) delete mode 100644 lib/ssh/src/ssh_dbg.hrl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 9e8d80c71f..bcd13213b3 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -97,7 +97,7 @@ APP_TARGET= $(EBIN)/$(APP_FILE) APPUP_SRC= $(APPUP_FILE).src APPUP_TARGET= $(EBIN)/$(APPUP_FILE) -INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl ssh_dbg.hrl +INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl # ---------------------------------------------------- # FLAGS diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 1a2f003190..48af72210c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -246,4 +246,13 @@ _ -> exit(Reason) end). + +%% dbg help macros +-define(wr_record(N,BlackList), + wr_record(R=#N{}) -> ssh_dbg:wr_record(R, record_info(fields,N), BlackList) + ). + +-define(wr_record(N), ?wr_record(N, [])). + + -endif. % SSH_HRL defined diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index eb2c2848f3..2ee4237e05 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -20,339 +20,110 @@ %% +%%% Purpose: +%%% This module implements support for using the Erlang trace in a simple way for ssh +%%% debugging. +%%% +%%% Begin the session with ssh_dbg:start(). This will do a dbg:start() if needed and +%%% then dbg:p/2 to set some flags. +%%% +%%% Next select trace points to activate: for example plain text printouts of messages +%%% sent or received. This is switched on and off with ssh_dbg:on(TracePoint(s)) and +%%% ssh_dbg:off(TracePoint(s)). For example: +%%% +%%% ssh_dbg:on(messages) -- switch on printing plain text messages +%%% ssh_dbg:on([alg,terminate]) -- switch on printing info about algorithm negotiation +%%% ssh_dbg:on() -- switch on all ssh debugging +%%% +%%% To switch, use the off/0 or off/1 function in the same way, for example: +%%% +%%% ssh_dbg:off(alg) -- switch off algorithm negotiation tracing, but keep all other +%%% ssh_dbg:off() -- switch off all ssh debugging +%%% +%%% Present the trace result with some other method than the default io:format/2: +%%% ssh_dbg:start(fun(Format,Args) -> +%%% my_special( io_lib:format(Format,Args) ) +%%% end) +%%% + -module(ssh_dbg). --export([messages/0, messages/1, messages/2, messages/3, - auth/0, auth/1, auth/2, auth/3, - algs/0, algs/1, algs/2, algs/3, - hostkey/0, hostkey/1, hostkey/2, hostkey/3, - stop/0 +-export([start/0, start/1, + stop/0, + start_server/0, + start_tracer/0, start_tracer/1, + on/1, on/0, + off/1, off/0, + go_on/0 ]). -export([shrink_bin/1, - wr_record/3]). + reduce_state/1, + wr_record/3]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2]). -include("ssh.hrl"). -include("ssh_transport.hrl"). -include("ssh_connect.hrl"). -include("ssh_auth.hrl"). -%%%================================================================ -messages() -> start(msg). -messages(F) -> start(msg,F). -messages(F,X) -> start(msg,F,X). -messages(F,M,I) -> start(msg,F,M,I). - -auth() -> start(auth). -auth(F) -> start(auth,F). -auth(F,X) -> start(auth,F,X). -auth(F,M,I) -> start(auth,F,M,I). - -algs() -> start(algs). -algs(F) -> start(algs,F). -algs(F,X) -> start(algs,F,X). -algs(F,M,I) -> start(algs,F,M,I). - -hostkey() -> start(hostkey). -hostkey(F) -> start(hostkey,F). -hostkey(F,X) -> start(hostkey,F,X). -hostkey(F,M,I) -> start(hostkey,F,M,I). - -stop() -> dbg:stop(). - -%%%---------------------------------------------------------------- -start(Type) -> start(Type, fun io:format/2). +-behaviour(gen_server). +-define(SERVER, ?MODULE). -start(Type, F) when is_function(F,2) -> start(Type, fmt_fun(F)); -start(Type, F) when is_function(F,3) -> start(Type, F, id_fun()). - -start(Type, WriteFun, MangleArgFun) when is_function(WriteFun, 3), - is_function(MangleArgFun, 1) -> - start(Type, WriteFun, MangleArgFun, []); -start(Type, WriteFun, InitValue) -> - start(Type, WriteFun, id_fun(), InitValue). +%%%================================================================ -start(Type, WriteFun, MangleArgFun, InitValue) when is_function(WriteFun, 3), - is_function(MangleArgFun, 1) -> - cond_start(Type, WriteFun, MangleArgFun, InitValue), - dbg_ssh(Type). +-define(ALL_DBG_TYPES, get_all_dbg_types()). -%%%---------------------------------------------------------------- -fmt_fun(F) -> fun(Fmt,Args,Data) -> F(Fmt,Args), Data end. +start() -> start(fun io:format/2). -id_fun() -> fun(X) -> X end. +start(IoFmtFun) when is_function(IoFmtFun,2) ; is_function(IoFmtFun,3) -> + start_server(), + catch dbg:start(), + start_tracer(IoFmtFun), + dbg:p(all, get_all_trace_flags()), + ?ALL_DBG_TYPES. -%%%---------------------------------------------------------------- -dbg_ssh(What) -> - case [E || E <- lists:flatten(dbg_ssh0(What)), - element(1,E) =/= ok] of - [] -> ok; - Other -> Other - end. - - -dbg_ssh0(auth) -> - [dbg:tp(ssh_transport,hello_version_msg,1, x), - dbg:tp(ssh_transport,handle_hello_version,1, x), - dbg:tp(ssh_message,encode,1, x), - dbg:tpl(ssh_transport,select_algorithm,4, x), - dbg:tpl(ssh_connection_handler,ext_info,2, x), - lists:map(fun(F) -> dbg:tp(ssh_auth, F, x) end, - [publickey_msg, password_msg, keyboard_interactive_msg]) - ]; - -dbg_ssh0(algs) -> - [dbg:tpl(ssh_transport,select_algorithm,4, x), - dbg:tpl(ssh_connection_handler,ext_info,2, x) - ]; - -dbg_ssh0(hostkey) -> - [dbg:tpl(ssh_transport, verify_host_key, 4, x), - dbg:tp(ssh_transport, verify, 4, x), - dbg:tpl(ssh_transport, known_host_key, 3, x), -%% dbg:tpl(ssh_transport, accepted_host, 4, x), - dbg:tpl(ssh_transport, add_host_key, 4, x), - dbg:tpl(ssh_transport, is_host_key, 5, x) - ]; - -dbg_ssh0(msg) -> - [dbg_ssh0(hostkey), - dbg_ssh0(auth), - dbg:tp(ssh_message,encode,1, x), - dbg:tp(ssh_message,decode,1, x), - dbg:tpl(ssh_transport,select_algorithm,4, x), - dbg:tp(ssh_transport,hello_version_msg,1, x), - dbg:tp(ssh_transport,handle_hello_version,1, x), - dbg:tpl(ssh_connection_handler,ext_info,2, x) - ]. - - -%%%================================================================ -cond_start(Type, WriteFun, MangleArgFun, Init) -> +stop() -> try - dbg:start(), - setup_tracer(Type, WriteFun, MangleArgFun, Init), - dbg:p(new,[c,timestamp]) + dbg:stop_clear(), + gen_server:stop(?SERVER) catch _:_ -> ok end. +start_server() -> + gen_server:start({local,?SERVER}, ?MODULE, [], []). + -msg_formater(msg, {trace_ts,Pid,call,{ssh_message,encode,[Msg]},TS}, D) -> - fmt("~n~s SEND ~p ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); -msg_formater(msg, {trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) -> - D; - -msg_formater(msg, {trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> - D; -msg_formater(msg, {trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> - Extra = - case Msg of - #ssh_msg_userauth_info_request{data = D0} -> - try ssh_message:decode_keyboard_interactive_prompts(D0, []) - of - Acc -> - io_lib:format(" -- decoded data:~n", []) ++ - element(1, - lists:mapfoldl( - fun({Prompt,Echo}, N) -> - {io_lib:format(" prompt[~p]: \"~s\" (echo=~p)~n",[N,Prompt,Echo]), N+1} - end, 1, Acc)) - catch - _:_ -> - "" - end; - _ -> - "" +start_tracer() -> start_tracer(fun io:format/2). + +start_tracer(WriteFun) when is_function(WriteFun,2) -> + start_tracer(fun(F,A,S) -> WriteFun(F,A), S end); +start_tracer(WriteFun) when is_function(WriteFun,3) -> + start_tracer(WriteFun, undefined). + + +start_tracer(WriteFun, InitAcc) when is_function(WriteFun, 3) -> + Handler = + fun(Arg, Acc0) -> + try_all_types_in_all_modules(gen_server:call(?SERVER, get_on), + Arg, WriteFun, + Acc0) end, - fmt("~n~s ~p RECV ~s~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg)),Extra], D); - -msg_formater(_auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_failure{authentications=As},TS}, D) -> - fmt("~n~s ~p Client login FAILURE. Try ~s~n", [ts(TS),Pid,As], D); - -msg_formater(_auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_success{},TS}, D) -> - fmt("~n~s ~p Client login SUCCESS~n", [ts(TS),Pid], D); - - -msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) -> - D; -msg_formater(_, {trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) -> - fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D); - -msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> - D; -msg_formater(_, {trace_ts,Pid,return_from,{ssh_transport,hello_version_msg,1},Hello,TS}, D) -> - fmt("~n~s ~p TCP SEND HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); - -msg_formater(_, {trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS}, D) -> - fmt("~n~s ~p RECV HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); -msg_formater(_, {trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> - D; - -msg_formater(_, {trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",SigAlgs},State]},TS}, D) -> - try lists:keyfind(ssh, 1, tuple_to_list(State)) of - false -> - D; - #ssh{userauth_pubkeys = PKs} -> - fmt("~n~s ~p Client got suggestion to use user public key sig-algs~n ~p~n and can use~n ~p~n", - [ts(TS),Pid,string:tokens(SigAlgs,","),PKs], D) - catch - _:_ -> - D - end; - -msg_formater(_, {trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},State,TS}, D) -> - try lists:keyfind(ssh, 1, tuple_to_list(State)) of - false -> - D; - #ssh{userauth_pubkeys = PKs} -> - fmt("~n~s ~p Client will try user public key sig-algs~n ~p~n", [ts(TS),Pid,PKs], D) - catch - _:_ -> - D - end; - -msg_formater(_, {trace_ts,Pid,call, {ssh_transport,verify_host_key,[_Ssh,_PK,_Dgst,{AlgStr,_Sign}]},TS}, D) -> - fmt("~n~s ~p Client got a ~s hostkey. Will try to verify it~n", [ts(TS),Pid,AlgStr], D); -msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,verify_host_key,4}, Result, TS}, D) -> - case Result of - ok -> fmt("~n~s ~p Hostkey verified.~n", [ts(TS),Pid], D); - {error,E} -> - fmt("~n~s ~p ***** Hostkey NOT verified: ~p ******!~n", [ts(TS),Pid,E], D); - _ -> fmt("~n~s ~p ***** Hostkey is NOT verified: ~p ******!~n", [ts(TS),Pid,Result], D) - end; - -msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,verify,4}, Result, TS}, D) -> - case Result of - true -> D; - _ -> fmt("~n~s ~p Couldn't verify the signature!~n", [ts(TS),Pid], D) - end; - -msg_formater(_, {trace_ts,_Pid,call, {ssh_transport,is_host_key,_}, _TS}, D) -> D; -msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,is_host_key,5}, {CbMod,Result}, TS}, D) -> - case Result of - true -> fmt("~n~s ~p Hostkey found by ~p.~n", [ts(TS),Pid,CbMod], D); - _ -> fmt("~n~s ~p Hostkey NOT found by ~p.~n", [ts(TS),Pid,CbMod], D) - end; - -msg_formater(_, {trace_ts,_Pid,call, {ssh_transport,add_host_key,_}, _TS}, D) -> D; -msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,add_host_key,4}, {CbMod,Result}, TS}, D) -> - case Result of - ok -> fmt("~n~s ~p New hostkey added by ~p.~n", [ts(TS),Pid,CbMod], D); - _ -> D - end; - -msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,known_host_key,_},_TS}, D) -> D; -msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,known_host_key,3}, Result, TS}, D) -> - case Result of - ok -> D; - {error,E} -> fmt("~n~s ~p Hostkey addition failed: ~p~n", [ts(TS),Pid,E], D); - _ -> fmt("~n~s ~p Hostkey addition: ~p~n", [ts(TS),Pid,Result], D) - end; - -msg_formater(_, {trace_ts,Pid,call,{ssh_auth,publickey_msg,[[SigAlg,#ssh{user=User}]]},TS}, D) -> - fmt("~n~s ~p Client will try to login user ~p with method: public key algorithm ~p~n", [ts(TS),Pid,User,SigAlg], D); -msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> - fmt("~s ~p User ~p can't use that kind of public key~n", [ts(TS),Pid,User], D); -msg_formater(_, {trace_ts,_Pid,return_from,{ssh_auth,publickey_msg,1},_,_TS}, D) -> D; - -msg_formater(_, {trace_ts,Pid,call,{ssh_auth,password_msg,[[#ssh{user=User}]]},TS}, D) -> - fmt("~n~s ~p Client will try to login user ~p with method: password~n", [ts(TS),Pid,User], D); -msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,password_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> - fmt("~s ~p User ~p can't use method password as login method~n", [ts(TS),Pid,User], D); -msg_formater(_, {trace_ts,_Pid,return_from,{ssh_auth,password_msg,1},_Result,_TS}, D) -> D; - -msg_formater(_, {trace_ts,Pid,call,{ssh_auth,keyboard_interactive_msg,[[#ssh{user=User}]]},TS}, D) -> - fmt("~n~s ~p Client will try to login user ~p with method: keyboard-interactive~n", [ts(TS),Pid,User], D); -msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,keyboard_interactive_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> - fmt("~s ~p User ~p can't use method keyboard-interactive as login method~n", [ts(TS),Pid,User], D); -msg_formater(_, {trace_ts,_Pid,return_from,{ssh_auth,keyboard_interactive_msg,1},_Result,_TS}, D) -> D; - -msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> - fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D); - -msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Dest,TS}, D) -> - fmt("~n~s ~p TCP SEND from ~p TO ~p~n ~p~n", [ts(TS),Pid,Sock,Dest, shrink_bin(Bytes)], D); - -msg_formater(msg, {trace_ts,Pid,send,ErlangMsg,Dest,TS}, D) -> - fmt("~n~s ~p ERL MSG SEND TO ~p~n ~p~n", [ts(TS),Pid,Dest, shrink_bin(ErlangMsg)], D); - - -msg_formater(msg, {trace_ts,Pid,'receive',{tcp,Sock,Bytes},TS}, D) -> - fmt("~n~s ~p TCP RECEIVE on ~p~n ~p~n", [ts(TS),Pid,Sock,shrink_bin(Bytes)], D); - -msg_formater(msg, {trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> - fmt("~n~s ~p ERL MSG RECEIVE~n ~p~n", [ts(TS),Pid,shrink_bin(ErlangMsg)], D); - - -msg_formater(_, _M, D) -> - fmt("~nDBG other ~n~p~n", [shrink_bin(_M)], D), - D. + dbg:tracer(process, {Handler,InitAcc}). %%%---------------------------------------------------------------- --record(data, {writer, - initialized, - acc}). - -fmt(Fmt, Args, D=#data{initialized=false}) -> - fmt(Fmt, Args, - D#data{acc = (D#data.writer)("~s~n", [initial_info()], D#data.acc), - initialized = true} - ); -fmt(Fmt, Args, D=#data{writer=Write, acc=Acc}) -> - D#data{acc = Write(Fmt,Args,Acc)}. - -ts({_,_,Usec}=Now) -> - {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now), - io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); -ts(_) -> - "-". +on() -> on(?ALL_DBG_TYPES). +on(Type) -> switch(on, Type). -setup_tracer(Type, WriteFun, MangleArgFun, Init) -> - Handler = fun(Arg, D) -> - msg_formater(Type, MangleArgFun(Arg), D) - end, - InitialData = #data{writer = WriteFun, - initialized = false, - acc = Init}, - {ok,_} = dbg:tracer(process, {Handler, InitialData}), - ok. - - -initial_info() -> - Lines = - [ts(erlang:timestamp()), - "", - "SSH:"] - ++ as_list_of_lines(case application:get_key(ssh,vsn) of - {ok,Vsn} -> Vsn; - _ -> "(ssh not started)" - end) - ++ ["", - "Cryptolib:"] - ++ as_list_of_lines(crypto:info_lib()) - ++ ["", - "Crypto app:"] - ++ as_list_of_lines(crypto:supports()), - W = max_len(Lines), - append_lines([line_of($*, W+4)] - ++ prepend_lines("* ", Lines) - ++ [line_of($-, W+4)], - io_lib:nl() - ). - + +off() -> off(?ALL_DBG_TYPES). % A bit overkill... +off(Type) -> switch(off, Type). -as_list_of_lines(Term) -> - prepend_lines(" ", - string:tokens(lists:flatten(io_lib:format("~p",[Term])), - io_lib:nl() % Get line endings in current OS - ) - ). - -line_of(Char,W) -> lists:duplicate(W,Char). -max_len(L) -> lists:max([length(S) || S<-L]). -append_lines(L, X) -> [S++X || S<-L]. -prepend_lines(X, L) -> [X++S || S<-L]. +go_on() -> + IsOn = gen_server:call(?SERVER, get_on), + on(IsOn). %%%---------------------------------------------------------------- shrink_bin(B) when is_binary(B), size(B)>256 -> {'*** SHRINKED BIN', @@ -365,69 +136,198 @@ shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L); shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T))); shrink_bin(X) -> X. +%%%---------------------------------------------------------------- +%% Replace last element (the state) with "#{}" +reduce_state(T) -> + try + erlang:setelement(size(T), + T, + lists:concat(['#',element(1,element(size(T),T)),'{}']) + ) + catch + _:_ -> + T + end. + +%%%================================================================ +-record(data, { + types_on = [] + }). + +%%%---------------------------------------------------------------- +init(_) -> + {ok, #data{}}. + +%%%---------------------------------------------------------------- +handle_call({switch,on,Types}, _From, D) -> + NowOn = lists:usort(Types ++ D#data.types_on), + call_modules(on, Types, NowOn), + {reply, {ok,NowOn}, D#data{types_on = NowOn}}; + +handle_call({switch,off,Types}, _From, D) -> + StillOn = D#data.types_on -- Types, + call_modules(off, Types, StillOn), + call_modules(on, StillOn, StillOn), + {reply, {ok,StillOn}, D#data{types_on = StillOn}}; + +handle_call(get_on, _From, D) -> + {reply, D#data.types_on, D}; + +handle_call(C, _From, D) -> + io:format('*** Unknown call: ~p~n',[C]), + {reply, {error,{unknown_call,C}}, D}. + + +handle_cast(C, D) -> + io:format('*** Unknown cast: ~p~n',[C]), + {noreply, D}. + +handle_info(C, D) -> + io:format('*** Unknown info: ~p~n',[C]), + {noreply, D}. + + +%%%================================================================ + +%%%---------------------------------------------------------------- +ssh_modules_with_trace() -> + {ok,AllSshModules} = application:get_key(ssh, modules), + [M || M <- AllSshModules, + lists:member({dbg_trace,3}, M:module_info(exports))]. + %%%---------------------------------------------------------------- --define(wr_record(N,BlackList), wr_record(R=#N{}) -> wr_record(R, record_info(fields,N), BlackList)). - --define(wr_record(N), ?wr_record(N, [])). - - -?wr_record(alg); - -?wr_record(ssh_msg_disconnect); -?wr_record(ssh_msg_ignore); -?wr_record(ssh_msg_unimplemented); -?wr_record(ssh_msg_debug); -?wr_record(ssh_msg_service_request); -?wr_record(ssh_msg_service_accept); -?wr_record(ssh_msg_kexinit); -?wr_record(ssh_msg_kexdh_init); -?wr_record(ssh_msg_kexdh_reply); -?wr_record(ssh_msg_newkeys); -?wr_record(ssh_msg_ext_info); -?wr_record(ssh_msg_kex_dh_gex_request); -?wr_record(ssh_msg_kex_dh_gex_request_old); -?wr_record(ssh_msg_kex_dh_gex_group); -?wr_record(ssh_msg_kex_dh_gex_init); -?wr_record(ssh_msg_kex_dh_gex_reply); -?wr_record(ssh_msg_kex_ecdh_init); -?wr_record(ssh_msg_kex_ecdh_reply); - -?wr_record(ssh_msg_userauth_request); -?wr_record(ssh_msg_userauth_failure); -?wr_record(ssh_msg_userauth_success); -?wr_record(ssh_msg_userauth_banner); -?wr_record(ssh_msg_userauth_passwd_changereq); -?wr_record(ssh_msg_userauth_pk_ok); -?wr_record(ssh_msg_userauth_info_request); -?wr_record(ssh_msg_userauth_info_response); - -?wr_record(ssh_msg_global_request); -?wr_record(ssh_msg_request_success); -?wr_record(ssh_msg_request_failure); -?wr_record(ssh_msg_channel_open); -?wr_record(ssh_msg_channel_open_confirmation); -?wr_record(ssh_msg_channel_open_failure); -?wr_record(ssh_msg_channel_window_adjust); -?wr_record(ssh_msg_channel_data); -?wr_record(ssh_msg_channel_extended_data); -?wr_record(ssh_msg_channel_eof); -?wr_record(ssh_msg_channel_close); -?wr_record(ssh_msg_channel_request); -?wr_record(ssh_msg_channel_success); -?wr_record(ssh_msg_channel_failure); - -wr_record(R) -> io_lib:format('~p~n',[R]). +get_all_trace_flags() -> + get_all_trace_flags(ssh_modules_with_trace()). +get_all_trace_flags(Modules) -> + lists:usort( + lists:flatten( + lists:foldl( + fun(Type, Acc) -> + call_modules(flags, Type, undefined, Acc, Modules) + end, [timestamp], ?ALL_DBG_TYPES))). +%%%---------------------------------------------------------------- +get_all_dbg_types() -> + lists:usort( + lists:flatten( + call_modules(points, undefined) )). + +%%%---------------------------------------------------------------- +call_modules(Cmnd, Type) -> + call_modules(Cmnd, Type, undefined). + +call_modules(Cmnd, Type, Arg) -> + call_modules(Cmnd, Type, Arg, []). + +call_modules(Cmnd, Type, Arg, Acc0) -> + call_modules(Cmnd, Type, Arg, Acc0, ssh_modules_with_trace()). + +call_modules(Cmnd, Types, Arg, Acc0, Modules) when is_list(Types) -> + lists:foldl( + fun(Type, Acc) -> + call_modules(Cmnd, Type, Arg, Acc, Modules) + end, Acc0, Types); + +call_modules(Cmnd, Type, Arg, Acc0, Modules) -> + lists:foldl( + fun(Mod, Acc) -> + try Mod:dbg_trace(Cmnd, Type, Arg) + of + Result -> [Result|Acc] + catch + _:_ -> Acc + end + end, Acc0, Modules). + +%%%---------------------------------------------------------------- +switch(X, Type) when is_atom(Type) -> + switch(X, [Type]); + +switch(X, Types) when is_list(Types) -> + case whereis(?SERVER) of + undefined -> + start(); + _ -> + ok + end, + case lists:usort(Types) -- ?ALL_DBG_TYPES of + [] -> + gen_server:call(?SERVER, {switch,X,Types}); + L -> + {error, {unknown, L}} + end. + +%%%---------------------------------------------------------------- +%%% Format of trace messages are described in reference manual for erlang:trace/4 +%%% {call,MFA} +%%% {return_from,{M,F,N},Result} +%%% {send,Msg,To} +%%% {'receive',Msg} + +trace_pid({trace,Pid,_}) -> Pid; +trace_pid({trace,Pid,_,_}) -> Pid; +trace_pid({trace,Pid,_,_,_}) -> Pid; +trace_pid({trace,Pid,_,_,_,_}) -> Pid; +trace_pid({trace,Pid,_,_,_,_,_}) -> Pid; +trace_pid({trace_ts,Pid,_,_TS}) -> Pid; +trace_pid({trace_ts,Pid,_,_,_TS}) -> Pid; +trace_pid({trace_ts,Pid,_,_,_,_TS}) -> Pid; +trace_pid({trace_ts,Pid,_,_,_,_,_TS}) -> Pid; +trace_pid({trace_ts,Pid,_,_,_,_,_,_TS}) -> Pid. + +trace_ts({trace_ts,_Pid,_,TS}) -> ts(TS); +trace_ts({trace_ts,_Pid,_,_,TS}) -> ts(TS); +trace_ts({trace_ts,_Pid,_,_,_,TS}) -> ts(TS); +trace_ts({trace_ts,_Pid,_,_,_,_,TS}) -> ts(TS); +trace_ts({trace_ts,_Pid,_,_,_,_,_,TS}) -> ts(TS); +trace_ts(_) -> "-". + +trace_info({trace,_Pid,A}) -> A; +trace_info({trace,_Pid,A,B}) -> {A,B}; +trace_info({trace,_Pid,A,B,C}) -> {A,B,C}; +trace_info({trace,_Pid,A,B,C,D}) -> {A,B,C,D}; +trace_info({trace,_Pid,A,B,C,D,E}) -> {A,B,C,D,E}; +trace_info({trace_ts,_Pid,A,_TS}) -> A; +trace_info({trace_ts,_Pid,A,B,_TS}) -> {A,B}; +trace_info({trace_ts,_Pid,A,B,C,_TS}) -> {A,B,C}; +trace_info({trace_ts,_Pid,A,B,C,D,_TS}) -> {A,B,C,D}; +trace_info({trace_ts,_Pid,A,B,C,D,E,_TS}) -> {A,B,C,D,E}. + + +try_all_types_in_all_modules(TypesOn, Arg, WriteFun, Acc0) -> + SshModules = ssh_modules_with_trace(), + TS = trace_ts(Arg), + PID = trace_pid(Arg), + INFO = trace_info(Arg), + lists:foldl( + fun(Type, Acc1) -> + lists:foldl( + fun(SshMod,Acc) -> + try WriteFun("~n~s ~p ~s~n", + [lists:flatten(TS), PID, lists:flatten(SshMod:dbg_trace(format,Type,INFO))], + Acc) + catch + _:_ -> Acc + end + end, Acc1, SshModules) + end, Acc0, TypesOn). + +%%%---------------------------------------------------------------- wr_record(T, Fs, BL) when is_tuple(T) -> wr_record(tuple_to_list(T), Fs, BL); -wr_record([Name|Values], Fields, BlackL) -> +wr_record([_Name|Values], Fields, BlackL) -> W = case Fields of [] -> 0; _ -> lists:max([length(atom_to_list(F)) || F<-Fields]) end, - [io_lib:format("~p:~n",[string:to_upper(atom_to_list(Name))]) - | [io_lib:format(" ~*p: ~p~n",[W,Tag,Value]) || {Tag,Value} <- lists:zip(Fields,Values), - not lists:member(Tag,BlackL) - ] + [io_lib:format(" ~*p: ~p~n",[W,Tag,Value]) || {Tag,Value} <- lists:zip(Fields,Values), + not lists:member(Tag,BlackL) ]. + +%%%---------------------------------------------------------------- +ts({_,_,Usec}=Now) when is_integer(Usec) -> + {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now), + io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); +ts(_) -> + "-". diff --git a/lib/ssh/src/ssh_dbg.hrl b/lib/ssh/src/ssh_dbg.hrl deleted file mode 100644 index e94664737b..0000000000 --- a/lib/ssh/src/ssh_dbg.hrl +++ /dev/null @@ -1,27 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --ifndef(SSH_DBG_HRL). --define(SSH_DBG_HRL, 1). - --define(formatrec(RecName,R), - ssh_dbg:wr_record(R, record_info(fields,RecName), [])). - --endif. % SSH_DBG_HRL defined -- cgit v1.2.3 From 3715deff44f38aa35873106cd1f116c74c65040a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 28 Feb 2018 16:47:20 +0100 Subject: ssh: Use the new ssh_dbg module --- lib/ssh/src/ssh.hrl | 2 + lib/ssh/src/ssh_acceptor.erl | 32 ++ lib/ssh/src/ssh_auth.erl | 37 +- lib/ssh/src/ssh_channel.erl | 93 ++++- lib/ssh/src/ssh_cli.erl | 18 + lib/ssh/src/ssh_connection.erl | 1 + lib/ssh/src/ssh_connection_handler.erl | 614 +++++++++++++++++++++++---------- lib/ssh/src/ssh_message.erl | 85 +++++ lib/ssh/src/ssh_no_io.erl | 27 +- lib/ssh/src/ssh_sftp.erl | 20 ++ lib/ssh/src/ssh_sftpd.erl | 19 + lib/ssh/src/ssh_shell.erl | 20 ++ lib/ssh/src/ssh_transport.erl | 208 +++++------ lib/ssh/src/ssh_transport.hrl | 3 + 14 files changed, 850 insertions(+), 329 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 48af72210c..0e118ac13f 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -137,6 +137,8 @@ {inet:hostname(), {inet:ip_address(),inet:port_number()}}, %% string version of peer address + local, %% Local sockname. Need this AFTER a socket is closed by i.e. a crash + c_vsn, %% client version {Major,Minor} s_vsn, %% server version {Major,Minor} diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 27d4242dd4..516a9febaa 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -33,6 +33,8 @@ %% spawn export -export([acceptor_init/5, acceptor_loop/6]). +-export([dbg_trace/3]). + -define(SLEEP_TIME, 200). %%==================================================================== @@ -195,3 +197,33 @@ handle_error(Reason) -> error_logger:error_report(String), exit({accept_failed, String}). +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [connections]; + +dbg_trace(flags, connections, _) -> [c]; +dbg_trace(on, connections, _) -> dbg:tp(?MODULE, acceptor_init, 5, x), + dbg:tpl(?MODULE, handle_connection, 5, x); +dbg_trace(off, connections, _) -> dbg:ctp(?MODULE, acceptor_init, 5), + dbg:ctp(?MODULE, handle_connection, 5); +dbg_trace(format, connections, {call, {?MODULE,acceptor_init, + [_Parent, Port, Address, _Opts, _AcceptTimeout]}}) -> + [io_lib:format("Starting LISTENER on ~s:~p\n", [ntoa(Address),Port]) + ]; +dbg_trace(format, connections, {return_from, {?MODULE,handle_connection,5}, {error,Error}}) -> + ["Starting connection to server failed:\n", + io_lib:format("Error = ~p", [Error]) + ]. + + + +ntoa(A) -> + try inet:ntoa(A) + catch + _:_ when is_list(A) -> A; + _:_ -> io_lib:format('~p',[A]) + end. + diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 03d264745b..bf3f5a68e4 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -40,15 +40,12 @@ %%-------------------------------------------------------------------- %%%---------------------------------------------------------------- userauth_request_msg(#ssh{userauth_methods = ServerMethods, - userauth_supported_methods = UserPrefMethods, % Note: this is not documented as supported for clients + userauth_supported_methods = UserPrefMethods, userauth_preference = ClientMethods0 } = Ssh0) -> case sort_select_mthds(ClientMethods0, UserPrefMethods, ServerMethods) of [] -> - Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, - description = "Unable to connect using the available authentication methods", - language = "en"}, - {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh0)}; + {send_disconnect, ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, Ssh0}; [{Pref,Module,Function,Args} | Prefs] -> Ssh = case Pref of @@ -196,11 +193,8 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> %% Client side case ?GET_OPT(user, Opts) of undefined -> - ErrStr = "Could not determine the users name", - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, - description = ErrStr}); - + ?DISCONNECT(?SSH_DISCONNECT_ILLEGAL_USER_NAME, + "Could not determine the users name"); User -> ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, @@ -451,11 +445,8 @@ handle_userauth_info_response({extra,#ssh_msg_userauth_info_response{}}, handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "Server does not support keyboard-interactive" - }). - + ?DISCONNECT(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + "Server does not support keyboard-interactive"). %%-------------------------------------------------------------------- %%% Internal functions @@ -492,10 +483,8 @@ check_password(User, Password, Opts, Ssh) -> {false,NewState} -> {false, Ssh#ssh{pwdfun_user_state=NewState}}; disconnect -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "Unable to connect using the available authentication methods" - }) + ?DISCONNECT(?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + "") end end. @@ -591,16 +580,12 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> case KbdInteractFun(Name, Instr, Prompts) of Rs when length(Rs) == NumPrompts -> Rs; - Rs -> - throw({mismatching_number_of_responses, - {got,Rs}, - {expected, NumPrompts}, - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction failed", - language = "en"}}) + _Rs -> + nok end. key_alg('rsa-sha2-256') -> 'ssh-rsa'; key_alg('rsa-sha2-512') -> 'ssh-rsa'; key_alg(Alg) -> Alg. + diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index 85b31f3669..b90e571448 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -22,6 +22,7 @@ -module(ssh_channel). +-include("ssh.hrl"). -include("ssh_connect.hrl"). -callback init(Args :: term()) -> @@ -71,6 +72,8 @@ cache_info/2, cache_find/2, get_print_info/1]). +-export([dbg_trace/3]). + -record(state, { cm, channel_cb, @@ -159,14 +162,7 @@ init([Options]) -> ConnectionManager = proplists:get_value(cm, Options), ChannelId = proplists:get_value(channel_id, Options), process_flag(trap_exit, true), - InitArgs = - case proplists:get_value(exec, Options) of - undefined -> - proplists:get_value(init_args, Options); - Exec -> - proplists:get_value(init_args, Options) ++ [Exec] - end, - try Cb:init(InitArgs) of + try Cb:init(channel_cb_init_args(Options)) of {ok, ChannelState} -> State = #state{cm = ConnectionManager, channel_cb = Cb, @@ -188,6 +184,14 @@ init([Options]) -> {stop, Reason} end. +channel_cb_init_args(Options) -> + case proplists:get_value(exec, Options) of + undefined -> + proplists:get_value(init_args, Options); + Exec -> + proplists:get_value(init_args, Options) ++ [Exec] + end. + %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% {reply, Reply, State, Timeout} | @@ -377,3 +381,76 @@ adjust_window(_) -> ok. +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [terminate, channels, channel_events]; + + +dbg_trace(flags, channels, A) -> [c] ++ dbg_trace(flags, terminate, A); +dbg_trace(on, channels, A) -> dbg:tp(?MODULE, init, 1, x), + dbg_trace(on, terminate, A); +dbg_trace(off, channels, A) -> dbg:ctpg(?MODULE, init, 1), + dbg_trace(off, terminate, A); +dbg_trace(format, channels, {call, {?MODULE,init, [[KVs]]}}) -> + ["Server Channel Starting:\n", + io_lib:format("Connection: ~p, ChannelId: ~p, CallBack: ~p\nCallBack init args = ~p", + [proplists:get_value(K,KVs) || K <- [cm, channel_id, channel_cb]] + ++ [channel_cb_init_args(KVs)]) + ]; +dbg_trace(format, channels, {return_from, {?MODULE,init,1}, {stop,Reason}}) -> + ["Server Channel Start FAILED!\n", + io_lib:format("Reason = ~p", [Reason]) + ]; +dbg_trace(format, channels, F) -> + dbg_trace(format, terminate, F); + + +dbg_trace(flags, terminate, _) -> [c]; +dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); +dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); +dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> + ["Server Channel Terminating:\n", + io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) + ]; + +dbg_trace(flags, channel_events, _) -> [c]; +dbg_trace(on, channel_events, _) -> dbg:tp(?MODULE, handle_call, 3, x), + dbg:tp(?MODULE, handle_cast, 2, x), + dbg:tp(?MODULE, handle_info, 2, x); +dbg_trace(off, channel_events, _) -> dbg:ctpg(?MODULE, handle_call, 3), + dbg:ctpg(?MODULE, handle_cast, 2), + dbg:ctpg(?MODULE, handle_info, 2); +dbg_trace(format, channel_events, {call, {?MODULE,handle_call, [Call,From,State]}}) -> + [hdr("is called", State), + io_lib:format("From: ~p~nCall: ~p~n", [From, Call]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_call,3}, Ret}) -> + ["Server Channel call returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]; +dbg_trace(format, channel_events, {call, {?MODULE,handle_cast, [Cast,State]}}) -> + [hdr("got cast", State), + io_lib:format("Cast: ~p~n", [Cast]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_cast,2}, Ret}) -> + ["Server Channel cast returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]; +dbg_trace(format, channel_events, {call, {?MODULE,handle_info, [Info,State]}}) -> + [hdr("got info", State), + io_lib:format("Info: ~p~n", [Info]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_info,2}, Ret}) -> + ["Server Channel info returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]. + +hdr(Title, S) -> + io_lib:format("Server Channel (Id=~p, CB=~p) ~s:\n", [S#state.channel_id, S#state.channel_cb, Title]). + +?wr_record(state). + + diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 783f2f80c0..26c7cb45aa 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -33,6 +33,8 @@ %% ssh_channel callbacks -export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). +-export([dbg_trace/3]). + %% state -record(state, { cm, @@ -638,3 +640,19 @@ not_zero(0, B) -> not_zero(A, _) -> A. +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [terminate]; + +dbg_trace(flags, terminate, _) -> [c]; +dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); +dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); +dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> + ["Cli Terminating:\n", + io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) + ]. + +?wr_record(state). diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 03336d6b25..2b8780a991 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -601,6 +601,7 @@ handle_msg(#ssh_msg_request_failure{}, #connection{requests = [{_, From} | Rest]} = Connection, _) -> {[{channel_request_reply, From, {failure, <<>>}}], Connection#connection{requests = Rest}}; + handle_msg(#ssh_msg_request_success{data = Data}, #connection{requests = [{_, From} | Rest]} = Connection, _) -> {[{channel_request_reply, From, {success, Data}}], diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 907ecef860..4261e5bf13 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -56,7 +56,7 @@ connection_info/2, channel_info/3, adjust_window/3, close/2, - disconnect/1, disconnect/2, + disconnect/4, get_print_info/1 ]). @@ -71,6 +71,18 @@ renegotiate/1, renegotiate_data/1, alg/1 % Export intended for test cases ]). +-export([dbg_trace/3]). + + +-define(send_disconnect(Code, DetailedText, StateName, State), + send_disconnect(Code, DetailedText, ?MODULE, ?LINE, StateName, State)). + +-define(send_disconnect(Code, Reason, DetailedText, StateName, State), + send_disconnect(Code, Reason, DetailedText, ?MODULE, ?LINE, StateName, State)). + +-define(call_disconnectfun_and_log_cond(LogMsg, DetailedText, StateName, D), + call_disconnectfun_and_log_cond(LogMsg, DetailedText, ?MODULE, ?LINE, StateName, D)). + %%==================================================================== %% Start / stop %%==================================================================== @@ -149,17 +161,16 @@ start_connection(server = Role, Socket, Options, Timeout) -> %%-------------------------------------------------------------------- %%% Some other module has decided to disconnect. --spec disconnect(#ssh_msg_disconnect{}) -> no_return(). --spec disconnect(#ssh_msg_disconnect{}, iodata()) -> no_return(). + +-spec disconnect(Code::integer(), Details::iodata(), + Module::atom(), Line::integer()) -> no_return(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -disconnect(Msg = #ssh_msg_disconnect{}) -> - throw({keep_state_and_data, - [{next_event, internal, {disconnect, Msg, Msg#ssh_msg_disconnect.description}}]}). -disconnect(Msg = #ssh_msg_disconnect{}, ExtraInfo) -> - throw({keep_state_and_data, - [{next_event, internal, {disconnect, Msg, {Msg#ssh_msg_disconnect.description,ExtraInfo}}}]}). +% Preferable called with the macro ?DISCONNECT +disconnect(Code, DetailedText, Module, Line) -> + throw({keep_state_and_data, + [{next_event, internal, {send_disconnect, Code, DetailedText, Module, Line}}]}). %%-------------------------------------------------------------------- -spec open_channel(connection_ref(), @@ -445,7 +456,7 @@ init_ssh_record(Role, Socket, Opts) -> {ok,PeerAddr} = inet:peername(Socket), init_ssh_record(Role, Socket, PeerAddr, Opts). -init_ssh_record(Role, _Socket, PeerAddr, Opts) -> +init_ssh_record(Role, Socket, PeerAddr, Opts) -> AuthMethods = ?GET_OPT(auth_methods, Opts), S0 = #ssh{role = Role, key_cb = ?GET_OPT(key_cb, Opts), @@ -456,6 +467,10 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> }, {Vsn, Version} = ssh_transport:versions(Role, Opts), + LocalName = case inet:sockname(Socket) of + {ok,Local} -> Local; + _ -> undefined + end, case Role of client -> PeerName = case ?GET_INTERNAL_OPT(host, Opts) of @@ -474,7 +489,8 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> false -> ssh_no_io end, userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), - peer = {PeerName, PeerAddr} + peer = {PeerName, PeerAddr}, + local = LocalName }, S1#ssh{userauth_pubkeys = [K || K <- ?GET_OPT(pref_public_key_algs, Opts), is_usable_user_pubkey(K, S1) @@ -487,7 +503,8 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> io_cb = ?GET_INTERNAL_OPT(io_cb, Opts, ssh_io), userauth_methods = string:tokens(AuthMethods, ","), kb_tries_left = 3, - peer = {undefined, PeerAddr} + peer = {undefined, PeerAddr}, + local = LocalName } end. @@ -545,7 +562,7 @@ callback_mode() -> handle_event_function. -handle_event(_, _Event, {init_error,Error}, _) -> +handle_event(_, _Event, {init_error,Error}=StateName, D) -> case Error of enotconn -> %% Handles the abnormal sequence: @@ -553,6 +570,9 @@ handle_event(_, _Event, {init_error,Error}, _) -> %% <-SYNACK %% ACK-> %% RST-> + ?call_disconnectfun_and_log_cond("Protocol Error", + "TCP connenction to server was prematurely closed by the client", + StateName, D), {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}}; OtherError -> @@ -561,7 +581,7 @@ handle_event(_, _Event, {init_error,Error}, _) -> %%% ######## {hello, client|server} #### %% The very first event that is sent when the we are set as controlling process of Socket -handle_event(_, socket_control, {hello,_}, D) -> +handle_event(_, socket_control, {hello,_}=StateName, D) -> VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)), send_bytes(VsnMsg, D), case inet:getopts(Socket=D#data.socket, [recbuf]) of @@ -576,10 +596,13 @@ handle_event(_, socket_control, {hello,_}, D) -> {keep_state, D#data{inet_initial_recbuf_size=Size}}; Other -> + ?call_disconnectfun_and_log_cond("Option return", + io_lib:format("Unexpected getopts return:~n ~p",[Other]), + StateName, D), {stop, {shutdown,{unexpected_getopts_return, Other}}} end; -handle_event(_, {info_line,_Line}, {hello,Role}, D) -> +handle_event(_, {info_line,_Line}, {hello,Role}=StateName, D) -> case Role of client -> %% The server may send info lines to the client before the version_exchange @@ -590,28 +613,33 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) -> %% But the client may NOT send them to the server. Openssh answers with cleartext, %% and so do we send_bytes("Protocol mismatch.", D), + ?call_disconnectfun_and_log_cond("Protocol mismatch.", + "Protocol mismatch in version exchange. Client sent info lines.", + StateName, D), {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}} end; -handle_event(_, {version_exchange,Version}, {hello,Role}, D) -> +handle_event(_, {version_exchange,Version}, {hello,Role}, D0) -> {NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version), - case handle_version(NumVsn, StrVsn, D#data.ssh_params) of + case handle_version(NumVsn, StrVsn, D0#data.ssh_params) of {ok, Ssh1} -> %% Since the hello part is finnished correctly, we set the %% socket to the packet handling mode (including recbuf size): - inet:setopts(D#data.socket, [{packet,0}, + inet:setopts(D0#data.socket, [{packet,0}, {mode,binary}, {active, once}, - {recbuf, D#data.inet_initial_recbuf_size}]), + {recbuf, D0#data.inet_initial_recbuf_size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), - send_bytes(SshPacket, D), - {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh, + send_bytes(SshPacket, D0), + {next_state, {kexinit,Role,init}, D0#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; not_supported -> - disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, - description = ["Protocol version ",StrVsn," not supported"]}, - {next_state, {hello,Role}, D}) + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, + io_lib:format("Offending version is ~p",[string:chomp(Version)]), + {hello,Role}, + D0), + {stop, Shutdown, D} end; @@ -757,18 +785,20 @@ handle_event(internal, Msg, {ext_info,Role,_ReNegFlag}, D) when is_tuple(Msg) -> %%% ######## {service_request, client|server} #### -handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D) -> +handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D0) -> case ServiceName of "ssh-userauth" -> - Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params, + Ssh0 = #ssh{session_id=SessionId} = D0#data.ssh_params, {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - send_bytes(Reply, D), - {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; + send_bytes(Reply, D0), + {next_state, {userauth,server}, D0#data{ssh_params = Ssh}}; _ -> - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "Unknown service"}, - StateName, D) + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + io_lib:format("Unknown service: ~p",[ServiceName]), + StateName, D0), + {stop, Shutdown, D} end; handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, @@ -784,15 +814,15 @@ handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request handle_event(_, Msg = #ssh_msg_userauth_request{service = ServiceName, method = Method}, StateName = {userauth,server}, - D = #data{ssh_params=Ssh0}) -> + D0 = #data{ssh_params=Ssh0}) -> case {ServiceName, Ssh0#ssh.service, Method} of {"ssh-connection", "ssh-connection", "none"} -> %% Probably the very first userauth_request but we deny unauthorized login {not_authorized, _, {Reply,Ssh}} = ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), - send_bytes(Reply, D), - {keep_state, D#data{ssh_params = Ssh}}; + send_bytes(Reply, D0), + {keep_state, D0#data{ssh_params = Ssh}}; {"ssh-connection", "ssh-connection", Method} -> %% Userauth request with a method like "password" or so @@ -801,20 +831,20 @@ handle_event(_, %% Yepp! we support this method case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of {authorized, User, {Reply, Ssh}} -> - send_bytes(Reply, D), - D#data.starter ! ssh_connected, - connected_fun(User, Method, D), + send_bytes(Reply, D0), + D0#data.starter ! ssh_connected, + connected_fun(User, Method, D0), {next_state, {connected,server}, - D#data{auth_user = User, + D0#data{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> - retry_fun(User, Reason, D), - send_bytes(Reply, D), - {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}}; + retry_fun(User, Reason, D0), + send_bytes(Reply, D0), + {next_state, {userauth_keyboard_interactive,server}, D0#data{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> - retry_fun(User, Reason, D), - send_bytes(Reply, D), - {keep_state, D#data{ssh_params = Ssh}} + retry_fun(User, Reason, D0), + send_bytes(Reply, D0), + {keep_state, D0#data{ssh_params = Ssh}} end; false -> %% No we do not support this method (=/= none) @@ -828,9 +858,11 @@ handle_event(_, %% {ServiceName, Expected, Method} when Expected =/= ServiceName -> Do what? {ServiceName, _, _} when ServiceName =/= "ssh-connection" -> - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "Unknown service"}, - StateName, D) + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + io_lib:format("Unknown service: ~p",[ServiceName]), + StateName, D0), + {stop, Shutdown, D} end; %%---- userauth success to client @@ -846,14 +878,14 @@ handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_para %%---- userauth failure response to client handle_event(_, #ssh_msg_userauth_failure{}, {userauth,client}=StateName, - D = #data{ssh_params = #ssh{userauth_methods = []}}) -> - Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, - description = "Unable to connect using the available" - " authentication methods"}, - disconnect(Msg, StateName, D); - + #data{ssh_params = #ssh{userauth_methods = []}} = D0) -> + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + io_lib:format("User auth failed for: ~p",[D0#data.auth_user]), + StateName, D0), + {stop, Shutdown, D}; handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client}, - D = #data{ssh_params = Ssh0}) -> + D0 = #data{ssh_params = Ssh0}) -> %% The prefered authentication method failed try next method Ssh1 = case Ssh0#ssh.userauth_methods of none -> @@ -864,15 +896,18 @@ handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName= Ssh0 end, case ssh_auth:userauth_request_msg(Ssh1) of - {disconnect, DisconnectMsg, {Msg, Ssh}} -> - send_bytes(Msg, D), - disconnect(DisconnectMsg, StateName, D#data{ssh_params = Ssh}); + {send_disconnect, Code, Ssh} -> + {Shutdown, D} = + ?send_disconnect(Code, + io_lib:format("User auth failed for: ~p",[D0#data.auth_user]), + StateName, D0#data{ssh_params = Ssh}), + {stop, Shutdown, D}; {"keyboard-interactive", {Msg, Ssh}} -> - send_bytes(Msg, D), - {next_state, {userauth_keyboard_interactive,client}, D#data{ssh_params = Ssh}}; + send_bytes(Msg, D0), + {next_state, {userauth_keyboard_interactive,client}, D0#data{ssh_params = Ssh}}; {_Method, {Msg, Ssh}} -> - send_bytes(Msg, D), - {keep_state, D#data{ssh_params = Ssh}} + send_bytes(Msg, D0), + {keep_state, D0#data{ssh_params = Ssh}} end; %%---- banner to client @@ -966,7 +1001,7 @@ handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) -> {disconnect, _, RepliesCon} = ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName)), {Actions,D} = send_replies(RepliesCon, D0), - disconnect_fun(Desc, D), + disconnect_fun("Received disconnect: "++Desc, D), {stop_and_reply, {shutdown,Desc}, Actions, D}; handle_event(_, #ssh_msg_ignore{}, _, _) -> @@ -1288,29 +1323,32 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, D0#data.ssh_params) of {packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} -> - D = D0#data{ssh_params = + D1 = D0#data{ssh_params = Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)}, decrypted_data_buffer = <<>>, undecrypted_packet_length = undefined, encrypted_data_buffer = EncryptedDataRest}, try - ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D)) + ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D1)) of Msg = #ssh_msg_kexinit{} -> - {keep_state, D, [{next_event, internal, prepare_next_packet}, + {keep_state, D1, [{next_event, internal, prepare_next_packet}, {next_event, internal, {Msg,DecryptedBytes}} ]}; Msg -> - {keep_state, D, [{next_event, internal, prepare_next_packet}, + {keep_state, D1, [{next_event, internal, prepare_next_packet}, {next_event, internal, Msg} ]} catch - _C:_E -> - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet"}, - StateName, D) + C:E -> + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, + io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p", + [C,E,erlang:get_stacktrace()]), + StateName, D1), + {stop, Shutdown, D} end; - + {get_more, DecryptedBytes, EncryptedDataRest, RemainingSshPacketLen, Ssh1} -> %% Here we know that there are not enough bytes in %% EncryptedDataRest to use. We must wait for more. @@ -1321,19 +1359,26 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, ssh_params = Ssh1}}; {bad_mac, Ssh1} -> - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet"}, - StateName, D0#data{ssh_params=Ssh1}); - - {error, {exceeds_max_size,_PacketLen}} -> - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet"}, - StateName, D0) + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, + "Bad packet: bad mac", + StateName, D0#data{ssh_params=Ssh1}), + {stop, Shutdown, D}; + + {error, {exceeds_max_size,PacketLen}} -> + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, + io_lib:format("Bad packet: Size (~p bytes) exceeds max size", + [PacketLen]), + StateName, D0), + {stop, Shutdown, D} catch - _C:_E -> - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet"}, - StateName, D0) + C:E -> + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, + io_lib:format("Bad packet: Couldn't decrypt~n~p:~p~n~p",[C,E,erlang:get_stacktrace()]), + StateName, D0), + {stop, Shutdown, D} end; @@ -1349,15 +1394,13 @@ handle_event(internal, prepare_next_packet, _, D) -> inet:setopts(D#data.socket, [{active, once}]), keep_state_and_data; -handle_event(info, {CloseTag,Socket}, StateName, - D = #data{socket = Socket, - transport_close_tag = CloseTag}) -> - %% Simulate a disconnect from the peer - handle_event(info, - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Connection closed"}, - StateName, - D); +handle_event(info, {CloseTag,Socket}, _StateName, + D0 = #data{socket = Socket, + transport_close_tag = CloseTag, + connection_state = C0}) -> + {Repls, D} = send_replies(ssh_connection:handle_stop(C0), D0), + disconnect_fun("Received a transport close", D), + {stop_and_reply, {shutdown,"Connection closed"}, Repls, D}; handle_event(info, {timeout, {_, From} = Request}, _, #data{connection_state = #connection{requests = Requests} = C0} = D) -> @@ -1423,25 +1466,26 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> keep_state_and_data end; -handle_event(internal, {disconnect,Msg,_Reason}, StateName, D) -> - disconnect(Msg, StateName, D); +handle_event(internal, {send_disconnect,Code,DetailedText,Module,Line}, StateName, D0) -> + {Shutdown, D} = + send_disconnect(Code, DetailedText, Module, Line, StateName, D0), + {stop, Shutdown, D}; handle_event(_Type, _Msg, {ext_info,Role,_ReNegFlag}, D) -> %% If something else arrives, goto next state and handle the event in that one {next_state, {connected,Role}, D, [postpone]}; -handle_event(Type, Ev, StateName, D) -> - Descr = +handle_event(Type, Ev, StateName, D0) -> + Details = case catch atom_to_list(element(1,Ev)) of "ssh_msg_" ++_ when Type==internal -> -%% "Message in wrong state"; lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); _ -> - "Internal error" + io_lib:format("Unhandled event in state ~p:~n~p", [StateName,Ev]) end, - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = Descr}, - StateName, D). + {Shutdown, D} = + ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D0), + {stop, Shutdown, D}. %%-------------------------------------------------------------------- @@ -1452,39 +1496,49 @@ handle_event(Type, Ev, StateName, D) -> %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -terminate(normal, StateName, State) -> - stop_subsystem(State), - close_transport(State); +terminate(normal, _StateName, D) -> + stop_subsystem(D), + close_transport(D); + +terminate({shutdown,"Connection closed"}, _StateName, D) -> + %% Normal: terminated by a sent by peer + stop_subsystem(D), + close_transport(D); -terminate({shutdown,{init,Reason}}, StateName, State) -> - error_logger:info_report(io_lib:format("Erlang ssh in connection handler init: ~p~n",[Reason])), - stop_subsystem(State), - close_transport(State); +terminate({shutdown,{init,Reason}}, StateName, D) -> + %% Error in initiation. "This error should not occur". + log(error, D, io_lib:format("Shutdown in init (StateName=~p): ~p~n",[StateName,Reason])), + stop_subsystem(D), + close_transport(D); -terminate(shutdown, StateName, State0) -> +terminate({shutdown,_R}, _StateName, D) -> + %% Internal termination, usually already reported via ?send_disconnect resulting in a log entry + stop_subsystem(D), + close_transport(D); + +terminate(shutdown, _StateName, D0) -> %% Terminated by supervisor - State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Application shutdown"}, - State0), - close_transport(State); - -terminate({shutdown,_R}, StateName, State) -> - %% Internal termination - stop_subsystem(State), - close_transport(State); - -terminate(kill, StateName, State) -> - stop_subsystem(State), - close_transport(State); - -terminate(Reason, StateName, State0) -> - %% Others, e.g undef, {badmatch,_} - log_error(Reason), - State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Internal error"}, - State0), - stop_subsystem(State), - close_transport(State). + %% Use send_msg directly instead of ?send_disconnect to avoid filling the log + D = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "Terminated (shutdown) by supervisor"}, + D0), + stop_subsystem(D), + close_transport(D); + +terminate(kill, _StateName, D) -> + %% Got a kill signal + stop_subsystem(D), + close_transport(D); + +terminate(Reason, StateName, D0) -> + %% Others, e.g undef, {badmatch,_}, ... + log(error, D0, Reason), + {_ShutdownReason, D} = ?send_disconnect(?SSH_DISCONNECT_BY_APPLICATION, + "Internal error", + io_lib:format("Reason: ~p",[Reason]), + StateName, D0), + stop_subsystem(D), + close_transport(D). %%-------------------------------------------------------------------- @@ -1493,36 +1547,41 @@ terminate(Reason, StateName, State0) -> format_status(normal, [_, _StateName, D]) -> [{data, [{"State", D}]}]; format_status(terminate, [_, _StateName, D]) -> - DataPropList0 = fmt_stat_rec(record_info(fields, data), D, - [decrypted_data_buffer, - encrypted_data_buffer, - key_exchange_init_msg, - user_passwords, - opts, - inet_initial_recbuf_size]), - SshPropList = fmt_stat_rec(record_info(fields, ssh), D#data.ssh_params, - [c_keyinit, - s_keyinit, - send_mac_key, - send_mac_size, - recv_mac_key, - recv_mac_size, - encrypt_keys, - encrypt_ctx, - decrypt_keys, - decrypt_ctx, - compress_ctx, - decompress_ctx, - shared_secret, - exchanged_hash, - session_id, - keyex_key, - keyex_info, - available_host_keys]), - DataPropList = lists:keyreplace(ssh_params, 1, DataPropList0, - {ssh_params,SshPropList}), - [{data, [{"State", DataPropList}]}]. - + [{data, [{"State", state_data2proplist(D)}]}]. + + +state_data2proplist(D) -> + DataPropList0 = + fmt_stat_rec(record_info(fields, data), D, + [decrypted_data_buffer, + encrypted_data_buffer, + key_exchange_init_msg, + user_passwords, + opts, + inet_initial_recbuf_size]), + SshPropList = + fmt_stat_rec(record_info(fields, ssh), D#data.ssh_params, + [c_keyinit, + s_keyinit, + send_mac_key, + send_mac_size, + recv_mac_key, + recv_mac_size, + encrypt_keys, + encrypt_ctx, + decrypt_keys, + decrypt_ctx, + compress_ctx, + decompress_ctx, + shared_secret, + exchanged_hash, + session_id, + keyex_key, + keyex_info, + available_host_keys]), + lists:keyreplace(ssh_params, 1, DataPropList0, + {ssh_params,SshPropList}). + fmt_stat_rec(FieldNames, Rec, Exclude) -> Values = tl(tuple_to_list(Rec)), @@ -1847,11 +1906,49 @@ new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} = Connection#connection{channel_id_seed = Id + 1}}}. %%%---------------------------------------------------------------- -%% %%% This server/client has decided to disconnect via the state machine: -disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) -> - State = send_msg(Msg, State0), - disconnect_fun(Description, State), - {stop, {shutdown,Description}, State}. +%%% This server/client has decided to disconnect via the state machine: +%%% The unused arguments are for debugging. + +send_disconnect(Code, DetailedText, Module, Line, StateName, D) -> + send_disconnect(Code, default_text(Code), DetailedText, Module, Line, StateName, D). + +send_disconnect(Code, Reason, DetailedText, Module, Line, StateName, D0) -> + Msg = #ssh_msg_disconnect{code = Code, + description = Reason}, + D = send_msg(Msg, D0), + LogMsg = io_lib:format("Disconnects with code = ~p [RFC4253 11.1]: ~s",[Code,Reason]), + call_disconnectfun_and_log_cond(LogMsg, DetailedText, Module, Line, StateName, D), + {{shutdown,Reason}, D}. + +call_disconnectfun_and_log_cond(LogMsg, DetailedText, Module, Line, StateName, D) -> + case disconnect_fun(LogMsg, D) of + void -> + log(info, D, + io_lib:format("~s~n" + "State = ~p~n" + "Module = ~p, Line = ~p.~n" + "Details:~n ~s~n", + [LogMsg, StateName, Module, Line, DetailedText])); + _ -> + ok + end. + + +default_text(?SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT) -> "Host not allowed to connect"; +default_text(?SSH_DISCONNECT_PROTOCOL_ERROR) -> "Protocol error"; +default_text(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED) -> "Key exchange failed"; +default_text(?SSH_DISCONNECT_RESERVED) -> "Reserved"; +default_text(?SSH_DISCONNECT_MAC_ERROR) -> "Mac error"; +default_text(?SSH_DISCONNECT_COMPRESSION_ERROR) -> "Compression error"; +default_text(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE) -> "Service not available"; +default_text(?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED) -> "Protocol version not supported"; +default_text(?SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE) -> "Host key not verifiable"; +default_text(?SSH_DISCONNECT_CONNECTION_LOST) -> "Connection lost"; +default_text(?SSH_DISCONNECT_BY_APPLICATION) -> "By application"; +default_text(?SSH_DISCONNECT_TOO_MANY_CONNECTIONS) -> "Too many connections"; +default_text(?SSH_DISCONNECT_AUTH_CANCELLED_BY_USER) -> "Auth cancelled by user"; +default_text(?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) -> "Unable to connect using the available authentication methods"; +default_text(?SSH_DISCONNECT_ILLEGAL_USER_NAME) -> "Illegal user name". %%%---------------------------------------------------------------- counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) -> @@ -1864,8 +1961,7 @@ conn_info(client_version, #data{ssh_params=S}) -> {S#ssh.c_vsn, S#ssh.c_version} conn_info(server_version, #data{ssh_params=S}) -> {S#ssh.s_vsn, S#ssh.s_version}; conn_info(peer, #data{ssh_params=S}) -> S#ssh.peer; conn_info(user, D) -> D#data.auth_user; -conn_info(sockname, D) -> {ok, SockName} = inet:sockname(D#data.socket), - SockName; +conn_info(sockname, #data{ssh_params=S}) -> S#ssh.local; %% dbg options ( = not documented): conn_info(socket, D) -> D#data.socket; conn_info(chan_ids, D) -> @@ -1896,13 +1992,43 @@ fold_keys(Keys, Fun, Extra) -> end, [], Keys). %%%---------------------------------------------------------------- -log_error(Reason) -> - Report = io_lib:format("Erlang ssh connection handler failed with reason:~n" - " ~p~n" - "Stacktrace:~n" - " ~p~n", - [Reason, erlang:get_stacktrace()]), - error_logger:error_report(Report). +log(Tag, D, Reason) -> + case atom_to_list(Tag) of % Dialyzer-technical reasons... + "error" -> do_log(error_msg, Reason, D); + "warning" -> do_log(warning_msg, Reason, D); + "info" -> do_log(info_msg, Reason, D) + end. + +do_log(F, Reason, #data{ssh_params = #ssh{role = Role} = S + }) -> + VSN = + case application:get_key(ssh,vsn) of + {ok,Vsn} -> Vsn; + undefined -> "" + end, + PeerVersion = + case Role of + server -> S#ssh.c_version; + client -> S#ssh.s_version + end, + CryptoInfo = + try + [{_,_,CI}] = crypto:info_lib(), + <<"(",CI/binary,")">> + catch + _:_ -> "" + end, + Other = + case Role of + server -> "Client"; + client -> "Server" + end, + error_logger:F("Erlang SSH ~p ~s ~s.~n" + "~s: ~p~n" + "~s~n", + [Role, VSN, CryptoInfo, + Other, PeerVersion, + Reason]). %%%---------------------------------------------------------------- not_connected_filter({connection_reply, _Data}) -> true; @@ -1944,7 +2070,7 @@ get_repl(X, Acc) -> %%%---------------------------------------------------------------- -define(CALL_FUN(Key,D), catch (?GET_OPT(Key, (D#data.ssh_params)#ssh.opts)) ). -disconnect_fun({disconnect,Msg}, D) -> ?CALL_FUN(disconnectfun,D)(Msg); +%%disconnect_fun({disconnect,Msg}, D) -> ?CALL_FUN(disconnectfun,D)(Msg); disconnect_fun(Reason, D) -> ?CALL_FUN(disconnectfun,D)(Reason). unexpected_fun(UnexpectedMessage, #data{ssh_params = #ssh{peer = {_,Peer} }} = D) -> @@ -2099,3 +2225,137 @@ update_inet_buffers(Socket) -> catch _:_ -> ok end. + +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [terminate, disconnect, connections, connection_events]; + +dbg_trace(flags, connections, A) -> [c] ++ dbg_trace(flags, terminate, A); +dbg_trace(on, connections, A) -> dbg:tp(?MODULE, init_connection_handler, 3, x), + dbg_trace(on, terminate, A); +dbg_trace(off, connections, A) -> dbg:ctpg(?MODULE, init_connection_handler, 3), + dbg_trace(off, terminate, A); +dbg_trace(format, connections, {call, {?MODULE,init_connection_handler, [Role, Sock, Opts]}}) -> + DefaultOpts = ssh_options:handle_options(Role,[]), + ExcludedKeys = [internal_options, user_options], + NonDefaultOpts = + maps:filter(fun(K,V) -> + case lists:member(K,ExcludedKeys) of + true -> + false; + false -> + V =/= (catch maps:get(K,DefaultOpts)) + end + end, + Opts), + {ok, {IPp,Portp}} = inet:peername(Sock), + {ok, {IPs,Ports}} = inet:sockname(Sock), + [io_lib:format("Starting ~p connection:\n",[Role]), + io_lib:format("Socket = ~p, Peer = ~s:~p, Local = ~s:~p,~n" + "Non-default options:~n~p", + [Sock,inet:ntoa(IPp),Portp,inet:ntoa(IPs),Ports, + NonDefaultOpts]) + ]; +dbg_trace(format, connections, F) -> + dbg_trace(format, terminate, F); + +dbg_trace(flags, connection_events, _) -> [c]; +dbg_trace(on, connection_events, _) -> dbg:tp(?MODULE, handle_event, 4, x); +dbg_trace(off, connection_events, _) -> dbg:ctpg(?MODULE, handle_event, 4); +dbg_trace(format, connection_events, {call, {?MODULE,handle_event, [EventType, EventContent, State, _Data]}}) -> + ["Connection event\n", + io_lib:format("EventType: ~p~nEventContent: ~p~nState: ~p~n", [EventType, EventContent, State]) + ]; +dbg_trace(format, connection_events, {return_from, {?MODULE,handle_event,4}, Ret}) -> + ["Connection event result\n", + io_lib:format("~p~n", [event_handler_result(Ret)]) + ]; + +dbg_trace(flags, terminate, _) -> [c]; +dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 3, x); +dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 3); +dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, StateName, D]}}) -> + ExtraInfo = + try + {conn_info(peer,D), + conn_info(user,D), + conn_info(sockname,D)} + of + {{_,{IPp,Portp}}, Usr, {IPs,Ports}} when is_tuple(IPp), is_tuple(IPs), + is_integer(Portp), is_integer(Ports) -> + io_lib:format("Peer=~s:~p, Local=~s:~p, User=~p", + [inet:ntoa(IPp),Portp,inet:ntoa(IPs),Ports,Usr]); + {Peer,Usr,Sockname} -> + io_lib:format("Peer=~p, Local=~p, User=~p",[Peer,Sockname,Usr]) + catch + _:_ -> + "" + end, + if + Reason == normal ; + Reason == shutdown ; + element(1,Reason) == shutdown + -> + ["Connection Terminating:\n", + io_lib:format("Reason: ~p, StateName: ~p~n~s", [Reason, StateName, ExtraInfo]) + ]; + + true -> + ["Connection Terminating:\n", + io_lib:format("Reason: ~p, StateName: ~p~n~s~nStateData = ~p", + [Reason, StateName, ExtraInfo, state_data2proplist(D)]) + ] + end; + +dbg_trace(flags, disconnect, _) -> [c]; +dbg_trace(on, disconnect, _) -> dbg:tpl(?MODULE, send_disconnect, 7, x); +dbg_trace(off, disconnect, _) -> dbg:ctpl(?MODULE, send_disconnect, 7); +dbg_trace(format, disconnect, {call,{?MODULE,send_disconnect, + [Code, Reason, DetailedText, Module, Line, StateName, _D]}}) -> + ["Disconnecting:\n", + io_lib:format(" Module = ~p, Line = ~p, StateName = ~p,~n" + " Code = ~p, Reason = ~p,~n" + " DetailedText =~n" + " ~p", + [Module, Line, StateName, Code, Reason, lists:flatten(DetailedText)]) + ]. + + +event_handler_result({next_state, NextState, _NewData}) -> + {next_state, NextState, "#data{}"}; +event_handler_result({next_state, NextState, _NewData, Actions}) -> + {next_state, NextState, "#data{}", Actions}; +event_handler_result(R) -> + state_callback_result(R). + +state_callback_result({keep_state, _NewData}) -> + {keep_state, "#data{}"}; +state_callback_result({keep_state, _NewData, Actions}) -> + {keep_state, "#data{}", Actions}; +state_callback_result(keep_state_and_data) -> + keep_state_and_data; +state_callback_result({keep_state_and_data, Actions}) -> + {keep_state_and_data, Actions}; +state_callback_result({repeat_state, _NewData}) -> + {repeat_state, "#data{}"}; +state_callback_result({repeat_state, _NewData, Actions}) -> + {repeat_state, "#data{}", Actions}; +state_callback_result(repeat_state_and_data) -> + repeat_state_and_data; +state_callback_result({repeat_state_and_data, Actions}) -> + {repeat_state_and_data, Actions}; +state_callback_result(stop) -> + stop; +state_callback_result({stop, Reason}) -> + {stop, Reason}; +state_callback_result({stop, Reason, _NewData}) -> + {stop, Reason, "#data{}"}; +state_callback_result({stop_and_reply, Reason, Replies}) -> + {stop_and_reply, Reason, Replies}; +state_callback_result({stop_and_reply, Reason, Replies, _NewData}) -> + {stop_and_reply, Reason, Replies, "#data{}"}; +state_callback_result(R) -> + R. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index eb06f05a4a..a2251eab97 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -32,6 +32,8 @@ -export([encode/1, decode/1, decode_keyboard_interactive_prompts/2]). +-export([dbg_trace/3]). + -define('2bin'(X), (if is_binary(X) -> X; is_list(X) -> list_to_binary(X); X==undefined -> <<>> @@ -611,3 +613,86 @@ encode_signature({#'ECPoint'{}, {namedCurve,OID}}, _SigAlg, Signature) -> CurveName = public_key:oid2ssh_curvename(OID), <>), ?Ebinary(Signature)>>. +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [ssh_messages, raw_messages]; + +dbg_trace(flags, ssh_messages, _) -> [c]; +dbg_trace(on, ssh_messages, _) -> dbg:tp(?MODULE,encode,1,x), + dbg:tp(?MODULE,decode,1,x); +dbg_trace(off, ssh_messages, _) -> dbg:ctpg(?MODULE,encode,1), + dbg:ctpg(?MODULE,decode,1); + +dbg_trace(flags, raw_messages, A) -> dbg_trace(flags, ssh_messages, A); +dbg_trace(on, raw_messages, A) -> dbg_trace(on, ssh_messages, A); +dbg_trace(off, raw_messages, A) -> dbg_trace(off, ssh_messages, A); + +dbg_trace(format, ssh_messages, {call,{?MODULE,encode,[Msg]}}) -> + Name = string:to_upper(atom_to_list(element(1,Msg))), + ["Going to send ",Name,":\n", + wr_record(ssh_dbg:shrink_bin(Msg)) + ]; +dbg_trace(format, ssh_messages, {return_from,{?MODULE,decode,1},Msg}) -> + Name = string:to_upper(atom_to_list(element(1,Msg))), + ["Received ",Name,":\n", + wr_record(ssh_dbg:shrink_bin(Msg)) + ]; + +dbg_trace(format, raw_messages, {call,{?MODULE,decode,[BytesPT]}}) -> + ["Received plain text bytes (shown after decryption):\n", + io_lib:format("~p",[BytesPT]) + ]; +dbg_trace(format, raw_messages, {return_from,{?MODULE,encode,1},BytesPT}) -> + ["Going to send plain text bytes (shown before encryption):\n", + io_lib:format("~p",[BytesPT]) + ]. + + +?wr_record(ssh_msg_disconnect); +?wr_record(ssh_msg_ignore); +?wr_record(ssh_msg_unimplemented); +?wr_record(ssh_msg_debug); +?wr_record(ssh_msg_service_request); +?wr_record(ssh_msg_service_accept); +?wr_record(ssh_msg_kexinit); +?wr_record(ssh_msg_kexdh_init); +?wr_record(ssh_msg_kexdh_reply); +?wr_record(ssh_msg_newkeys); +?wr_record(ssh_msg_ext_info); +?wr_record(ssh_msg_kex_dh_gex_request); +?wr_record(ssh_msg_kex_dh_gex_request_old); +?wr_record(ssh_msg_kex_dh_gex_group); +?wr_record(ssh_msg_kex_dh_gex_init); +?wr_record(ssh_msg_kex_dh_gex_reply); +?wr_record(ssh_msg_kex_ecdh_init); +?wr_record(ssh_msg_kex_ecdh_reply); + +?wr_record(ssh_msg_userauth_request); +?wr_record(ssh_msg_userauth_failure); +?wr_record(ssh_msg_userauth_success); +?wr_record(ssh_msg_userauth_banner); +?wr_record(ssh_msg_userauth_passwd_changereq); +?wr_record(ssh_msg_userauth_pk_ok); +?wr_record(ssh_msg_userauth_info_request); +?wr_record(ssh_msg_userauth_info_response); + +?wr_record(ssh_msg_global_request); +?wr_record(ssh_msg_request_success); +?wr_record(ssh_msg_request_failure); +?wr_record(ssh_msg_channel_open); +?wr_record(ssh_msg_channel_open_confirmation); +?wr_record(ssh_msg_channel_open_failure); +?wr_record(ssh_msg_channel_window_adjust); +?wr_record(ssh_msg_channel_data); +?wr_record(ssh_msg_channel_extended_data); +?wr_record(ssh_msg_channel_eof); +?wr_record(ssh_msg_channel_close); +?wr_record(ssh_msg_channel_request); +?wr_record(ssh_msg_channel_success); +?wr_record(ssh_msg_channel_failure); + +wr_record(R) -> io_lib:format('~p~n',[R]). + diff --git a/lib/ssh/src/ssh_no_io.erl b/lib/ssh/src/ssh_no_io.erl index 1da257ed99..25be0023e9 100644 --- a/lib/ssh/src/ssh_no_io.erl +++ b/lib/ssh/src/ssh_no_io.erl @@ -31,35 +31,24 @@ -spec yes_no(any(), any()) -> no_return(). yes_no(_, _) -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction is not allowed"}, - {no_io_allowed, yes_no}). + ?DISCONNECT(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + "User interaction is not allowed"). -spec read_password(any(), any()) -> no_return(). read_password(_, _) -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction is not allowed"}, - {no_io_allowed, read_password}). - + ?DISCONNECT(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + "User interaction is not allowed"). -spec read_line(any(), any()) -> no_return(). read_line(_, _) -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction is not allowed"}, - {no_io_allowed, read_line}). - + ?DISCONNECT(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + "User interaction is not allowed"). -spec format(any(), any()) -> no_return(). format(_, _) -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction is not allowed"}, - {no_io_allowed, format}). - + ?DISCONNECT(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + "User interaction is not allowed"). diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 9e1229dc85..f00c0aed1f 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -52,6 +52,8 @@ %% TODO: Should be placed elsewhere ssh_sftpd should not call functions in ssh_sftp! -export([info_to_attr/1, attr_to_info/1]). +-export([dbg_trace/3]). + -record(state, { xf, @@ -1460,3 +1462,21 @@ format_channel_start_error({shutdown, Reason}) -> Reason; format_channel_start_error(Reason) -> Reason. + +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [terminate]; + +dbg_trace(flags, terminate, _) -> [c]; +dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); +dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); +dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> + ["Sftp Terminating:\n", + io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) + ]. + +?wr_record(state). + diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 427edf01ab..26cf2cb665 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -38,6 +38,8 @@ -export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). +-export([dbg_trace/3]). + -record(state, { xf, % [{channel,ssh_xfer states}...] cwd, % current dir (on first connect) @@ -947,3 +949,20 @@ maybe_increase_recv_window(ConnectionManager, ChannelId, Options) -> Increment =< 0 -> do_nothing end. + +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [terminate]; + +dbg_trace(flags, terminate, _) -> [c]; +dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); +dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); +dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> + ["SftpD Terminating:\n", + io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) + ]. + +?wr_record(state). diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl index 17224b6ef4..085534592d 100644 --- a/lib/ssh/src/ssh_shell.erl +++ b/lib/ssh/src/ssh_shell.erl @@ -22,6 +22,7 @@ -module(ssh_shell). +-include("ssh.hrl"). -include("ssh_connect.hrl"). %%% As this is an user interactive client it behaves like a daemon @@ -34,6 +35,8 @@ %% Spawn export -export([input_loop/2]). +-export([dbg_trace/3]). + -record(state, { io, %% Io process @@ -194,3 +197,20 @@ get_ancestors() -> A when is_list(A) -> A; _ -> [] end. + +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [terminate]; + +dbg_trace(flags, terminate, _) -> [c]; +dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); +dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); +dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> + ["Shell Terminating:\n", + io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) + ]. + +?wr_record(state). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index c221cf5905..f5bba9f824 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -53,6 +53,8 @@ valid_key_sha_alg/2, sha/1, sign/3, verify/5]). +-export([dbg_trace/3]). + %%% For test suites -export([pack/3, adjust_algs_for_peer_version/2]). -export([decompress/2, decrypt_blocks/3, is_valid_mac/3 ]). % FIXME: remove @@ -319,10 +321,11 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, key_exchange_first_msg(Algos#alg.kex, Ssh#ssh{algorithms = Algos}) catch - _:_ -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed"}) + Class:Error -> + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Kexinit failed in client: ~p:~p", + [Class,Error]) + ) end; handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, @@ -335,10 +338,11 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, Algos -> {ok, Ssh#ssh{algorithms = Algos}} catch - _:_ -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed"}) + Class:Error -> + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Kexinit failed in server: ~p:~p", + [Class,Error]) + ) end. @@ -439,12 +443,10 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, session_id = sid(Ssh1, H)}}; true -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'e' out of bounds"}, - {error,bad_e_from_peer} - ) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Kexdh init failed, received 'e' out of bounds~n E=~p~n P=~p", + [E,P]) + ) end. handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, @@ -464,20 +466,16 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, exchanged_hash = H, session_id = sid(Ssh, H)})}; Error -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed"}, - Error) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Kexdh init failed. Verify host key: ~p",[Error]) + ) end; true -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'f' out of bounds"}, - bad_f_from_peer - ) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Kexdh init failed, received 'f' out of bounds~n F=~p~n P=~p", + [F,P]) + ) end. @@ -501,11 +499,9 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, keyex_info = {Min0, Max0, NBits} }}; {error,_} -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "No possible diffie-hellman-group-exchange group found" - }) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("No possible diffie-hellman-group-exchange group found",[]) + ) end; handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, @@ -535,20 +531,14 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, keyex_info = {-1, -1, NBits} % flag for kex_hash calc }}; {error,_} -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "No possible diffie-hellman-group-exchange group found" - }) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("No possible diffie-hellman-group-exchange group found",[]) + ) end; handle_kex_dh_gex_request(_, _) -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, bad values in ssh_msg_kex_dh_gex_request"}, - bad_ssh_msg_kex_dh_gex_request). - + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Key exchange failed, bad values in ssh_msg_kex_dh_gex_request"). adjust_gex_min_max(Min0, Max0, Opts) -> {Min1, Max1} = ?GET_OPT(dh_gex_limits, Opts), @@ -558,11 +548,8 @@ adjust_gex_min_max(Min0, Max0, Opts) -> Min2 =< Max2 -> {Min2, Max2}; Max2 < Min2 -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "No possible diffie-hellman-group-exchange group possible" - }) + ?DISCONNECT(?SSH_DISCONNECT_PROTOCOL_ERROR, + "No possible diffie-hellman-group-exchange group possible") end. @@ -600,18 +587,15 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, session_id = sid(Ssh, H) }}; true -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'K' out of bounds"}, - bad_K) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Kexdh init failed, received 'k' out of bounds" + ) end; true -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'e' out of bounds"}, - bad_e_from_peer) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Kexdh gex init failed, received 'e' out of bounds~n E=~p~n P=~p", + [E,P]) + ) end. handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostKey, @@ -634,28 +618,22 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H)})}; - _Error -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed" - }) + Error -> + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Kexdh gex reply failed. Verify host key: ~p",[Error]) + ) end; true -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'K' out of bounds"}, - bad_K) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Kexdh gex init failed, 'K' out of bounds" + ) end; true -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'f' out of bounds"}, - bad_f_from_peer - ) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Kexdh gex init failed, received 'f' out of bounds~n F=~p~n P=~p", + [F,P]) + ) end. %%%---------------------------------------------------------------- @@ -686,12 +664,11 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, exchanged_hash = H, session_id = sid(Ssh1, H)}} catch - _:_ -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Peer ECDH public key is invalid"}, - invalid_peer_public_key) + Class:Error -> + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("ECDH compute key failed in server: ~p:~p", + [Class,Error]) + ) end. handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, @@ -713,19 +690,16 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, exchanged_hash = H, session_id = sid(Ssh, H)})}; Error -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed"}, - Error) + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("ECDH reply failed. Verify host key: ~p",[Error]) + ) end catch - _:_ -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Peer ECDH public key is invalid"}, - invalid_peer_public_key) + Class:Error -> + ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + io_lib:format("Peer ECDH public key seem invalid: ~p:~p", + [Class,Error]) + ) end. @@ -735,11 +709,11 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> #ssh{} = Ssh -> {ok, Ssh} catch - _C:_Error -> %% TODO: Throw earlier .... - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Install alg failed" - }) + Class:Error -> %% TODO: Throw earlier ... + ?DISCONNECT(?SSH_DISCONNECT_PROTOCOL_ERROR, + io_lib:format("Install alg failed: ~p:~p", + [Class,Error]) + ) end. @@ -1111,10 +1085,9 @@ select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS -> %% algorithms used by client and server (client pref) lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)); select_all(CL, SL) -> - Err = lists:concat(["Received too many algorithms (",length(CL),"+",length(SL)," >= ",?MAX_NUM_ALGORITHMS,")."]), - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = Err}). + Error = lists:concat(["Received too many algorithms (",length(CL),"+",length(SL)," >= ",?MAX_NUM_ALGORITHMS,")."]), + ?DISCONNECT(?SSH_DISCONNECT_PROTOCOL_ERROR, + Error). select([], []) -> @@ -2037,3 +2010,40 @@ trim_tail(Str) -> lists:takewhile(fun(C) -> C=/=$\r andalso C=/=$\n end, Str). + +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [alg, ssh_messages, raw_messages, hello]; + +dbg_trace(flags, hello, _) -> [c]; +dbg_trace(on, hello, _) -> dbg:tp(?MODULE,hello_version_msg,1,x), + dbg:tp(?MODULE,handle_hello_version,1,x); +dbg_trace(off, hello, _) -> dbg:ctpg(?MODULE,hello_version_msg,1), + dbg:ctpg(?MODULE,handle_hello_version,1); + +dbg_trace(C, raw_messages, A) -> dbg_trace(C, hello, A); +dbg_trace(C, ssh_messages, A) -> dbg_trace(C, hello, A); + +dbg_trace(flags, alg, _) -> [c]; +dbg_trace(on, alg, _) -> dbg:tpl(?MODULE,select_algorithm,4,x); +dbg_trace(off, alg, _) -> dbg:ctpl(?MODULE,select_algorithm,4); + + +dbg_trace(format, hello, {return_from,{?MODULE,hello_version_msg,1},Hello}) -> + ["Going to send hello message:\n", + Hello + ]; +dbg_trace(format, hello, {call,{?MODULE,handle_hello_version,[Hello]}}) -> + ["Received hello message:\n", + Hello + ]; + +dbg_trace(format, alg, {return_from,{?MODULE,select_algorithm,4},{ok,Alg}}) -> + ["Negotiated algorithms:\n", + wr_record(Alg) + ]. + +?wr_record(alg). diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index 87c3719514..7d5a4c153e 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -220,6 +220,9 @@ %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-define(DISCONNECT(Code, DetailedText), + ssh_connection_handler:disconnect(Code, DetailedText, ?MODULE, ?LINE)). + -define(SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, 1). -define(SSH_DISCONNECT_PROTOCOL_ERROR, 2). -define(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 3). -- cgit v1.2.3 From 480b36688d6470b35b59c4efed52348acdb05b30 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 5 Apr 2018 12:41:50 +0200 Subject: ssh: Fix ssh_sftpd:handle_op not returning State --- lib/ssh/src/ssh_sftpd.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index fb680fe11c..8589730438 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -384,10 +384,12 @@ handle_op(?SSH_FXP_REMOVE, ReqId, <>, case IsDir of %% This version 6 we still have ver 5 true when Vsn > 5 -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, - ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"); + ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"), + State0; true -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, - ?SSH_FX_FAILURE, "File is a directory"); + ?SSH_FX_FAILURE, "File is a directory"), + State0; false -> {Status, FS1} = FileMod:delete(Path, FS0), State1 = State0#state{file_state = FS1}, -- cgit v1.2.3 From 64f9a0b64a032666db824f139967f7abd5ea7046 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 5 Apr 2018 12:41:50 +0200 Subject: ssh: Fix ssh_sftpd:handle_op not returning State --- lib/ssh/src/ssh_sftpd.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 427edf01ab..a9136e5614 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -360,10 +360,12 @@ handle_op(?SSH_FXP_REMOVE, ReqId, <>, case IsDir of %% This version 6 we still have ver 5 true when Vsn > 5 -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, - ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"); + ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"), + State0; true -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, - ?SSH_FX_FAILURE, "File is a directory"); + ?SSH_FX_FAILURE, "File is a directory"), + State0; false -> {Status, FS1} = FileMod:delete(Path, FS0), State1 = State0#state{file_state = FS1}, -- cgit v1.2.3 From bea68371f7acc28958347cc0c11d52e0f3744d69 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Sat, 14 Apr 2018 10:35:18 +0200 Subject: ssh: Fix server crashes for exit-normal signals --- lib/ssh/src/ssh_connection_handler.erl | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index fc75945a5b..60008b6301 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1280,8 +1280,21 @@ handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D0) -> {keep_state, D, Repls}; %%% So that terminate will be run when supervisor is shutdown -handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> - {stop, {shutdown, Reason}}; +handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) -> + Role = role(StateName), + if + Role == client -> + %% OTP-8111 tells this function clause fixes a problem in + %% clients, but there were no check for that role. + {stop, {shutdown, Reason}}; + + Reason == normal -> + %% An exit normal should not cause a server to crash. This has happend... + keep_state_and_data; + + true -> + {stop, {shutdown, Reason}} + end; handle_event(info, check_cache, _, D) -> {keep_state, cache_check_set_idle_timer(D)}; -- cgit v1.2.3 From 94f3ce9d6a649dfab810105036f0ff0e73e3837c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Sat, 14 Apr 2018 10:35:18 +0200 Subject: ssh: Fix server crashes for exit-normal signals --- lib/ssh/src/ssh_connection_handler.erl | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e11d3adee4..ad23d82ea8 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1379,8 +1379,21 @@ handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D0) -> {keep_state, D, Repls}; %%% So that terminate will be run when supervisor is shutdown -handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> - {stop, {shutdown, Reason}}; +handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) -> + Role = role(StateName), + if + Role == client -> + %% OTP-8111 tells this function clause fixes a problem in + %% clients, but there were no check for that role. + {stop, {shutdown, Reason}}; + + Reason == normal -> + %% An exit normal should not cause a server to crash. This has happend... + keep_state_and_data; + + true -> + {stop, {shutdown, Reason}} + end; handle_event(info, check_cache, _, D) -> {keep_state, cache_check_set_idle_timer(D)}; -- cgit v1.2.3 From e96f00207d9f4dc8a0b0bcad5123e8a83f8a90ed Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 20 Apr 2018 18:03:24 +0200 Subject: ssh: Remove unused ssh_client_key.erl and ssh_server_key.erl --- lib/ssh/src/ssh_client_key.erl | 35 ----------------------------------- lib/ssh/src/ssh_server_key.erl | 34 ---------------------------------- 2 files changed, 69 deletions(-) delete mode 100644 lib/ssh/src/ssh_client_key.erl delete mode 100644 lib/ssh/src/ssh_server_key.erl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_client_key.erl b/lib/ssh/src/ssh_client_key.erl deleted file mode 100644 index 5296ac2a02..0000000000 --- a/lib/ssh/src/ssh_client_key.erl +++ /dev/null @@ -1,35 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --module(ssh_client_key). - --include_lib("public_key/include/public_key.hrl"). --include("ssh.hrl"). - --callback is_host_key(Key :: public_key(), Host :: string(), - Algorithm :: 'ssh-rsa'| 'ssh-dsa'| atom(), Options :: proplists:proplist()) -> - boolean(). - --callback user_key(Algorithm :: 'ssh-rsa'| 'ssh-dsa'| atom(), Options :: list()) -> - {ok, PrivateKey :: term()} | {error, string()}. - - --callback add_host_key(Host :: string(), PublicKey :: term(), Options :: list()) -> - ok | {error, Error::term()}. diff --git a/lib/ssh/src/ssh_server_key.erl b/lib/ssh/src/ssh_server_key.erl deleted file mode 100644 index 2ce0c7e3fe..0000000000 --- a/lib/ssh/src/ssh_server_key.erl +++ /dev/null @@ -1,34 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --module(ssh_server_key). - --include_lib("public_key/include/public_key.hrl"). --include("ssh.hrl"). - --type ssh_algorithm() :: string(). - --callback host_key(Algorithm :: ssh_algorithm(), Options :: list()) -> - {ok, [{public_key(), Attributes::list()}]} | public_key() - | {error, string()}. - --callback is_auth_key(Key :: public_key(), User :: string(), - Algorithm :: ssh_algorithm(), Options :: list()) -> - boolean(). -- cgit v1.2.3 From 9bfeea98188225ca9e17fbb45a49fcd8f6d85d78 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 25 Apr 2018 14:27:22 +0200 Subject: ssh: Correct a call that re-appeared by misstake --- lib/ssh/src/ssh_connection_handler.erl | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 033f11f4a1..209b01f53e 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1535,7 +1535,6 @@ terminate(shutdown, _StateName, D0) -> D = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, description = "Terminated (shutdown) by supervisor"}, D0), - stop_subsystem(D), close_transport(D); terminate(kill, _StateName, D) -> -- cgit v1.2.3 From 5611b6139cb39a4e91a5cc6f61d698658120f49e Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Apr 2018 20:17:08 +0200 Subject: ssh: New -spec and -type --- lib/ssh/src/ssh.erl | 193 ++++++++++++++-------- lib/ssh/src/ssh.hrl | 281 ++++++++++++++++++++++++++++++--- lib/ssh/src/ssh_channel.erl | 6 +- lib/ssh/src/ssh_cli.erl | 4 +- lib/ssh/src/ssh_client_key_api.erl | 35 ++-- lib/ssh/src/ssh_connect.hrl | 4 - lib/ssh/src/ssh_connection.erl | 60 ++++--- lib/ssh/src/ssh_connection_handler.erl | 18 ++- lib/ssh/src/ssh_daemon_channel.erl | 7 +- lib/ssh/src/ssh_file.erl | 21 --- lib/ssh/src/ssh_options.erl | 27 ++-- lib/ssh/src/ssh_server_key_api.erl | 12 +- lib/ssh/src/ssh_sftpd.erl | 16 +- lib/ssh/src/ssh_shell.erl | 15 -- 14 files changed, 481 insertions(+), 218 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 25d537c624..209f53d249 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -41,35 +41,51 @@ shell/1, shell/2, shell/3 ]). +%%% "Deprecated" types export: +-export_type([ssh_daemon_ref/0, ssh_connection_ref/0, ssh_channel_id/0]). +-opaque ssh_daemon_ref() :: daemon_ref(). +-opaque ssh_connection_ref() :: connection_ref(). +-opaque ssh_channel_id() :: channel_id(). + + %%% Type exports --export_type([ssh_daemon_ref/0, - ssh_connection_ref/0, - ssh_channel_id/0, +-export_type([daemon_ref/0, + connection_ref/0, + channel_id/0, + client_options/0, client_option/0, + daemon_options/0, daemon_option/0, + common_options/0, role/0, subsystem_spec/0, - subsystem_name/0, - channel_callback/0, - channel_init_args/0, algs_list/0, + double_algs/1, + modify_algs_list/0, alg_entry/0, - simple_algs/0, - double_algs/0 + kex_alg/0, + pubkey_alg/0, + cipher_alg/0, + mac_alg/0, + compression_alg/0, + ip_port/0 ]). --opaque ssh_daemon_ref() :: daemon_ref() . --opaque ssh_connection_ref() :: connection_ref() . --opaque ssh_channel_id() :: channel_id(). + +-opaque daemon_ref() :: pid() . +-opaque channel_id() :: non_neg_integer(). +-type connection_ref() :: pid(). % should be -opaque, but that gives problems %%-------------------------------------------------------------------- --spec start() -> ok | {error, term()}. --spec start(permanent | transient | temporary) -> ok | {error, term()}. -%% %% Description: Starts the ssh application. Default type %% is temporary. see application(3) %%-------------------------------------------------------------------- +-spec start() -> ok | {error, term()}. + start() -> start(temporary). +-spec start(Type) -> ok | {error, term()} when + Type :: permanent | transient | temporary . + start(Type) -> case application:ensure_all_started(ssh, Type) of {ok, _} -> @@ -79,30 +95,32 @@ start(Type) -> end. %%-------------------------------------------------------------------- --spec stop() -> ok | {error, term()}. -%% %% Description: Stops the ssh application. %%-------------------------------------------------------------------- +-spec stop() -> ok | {error, term()}. + stop() -> application:stop(ssh). %%-------------------------------------------------------------------- --spec connect(inet:socket(), proplists:proplist()) -> ok_error(connection_ref()). +%% Description: Starts an ssh connection. +%%-------------------------------------------------------------------- +-spec connect(OpenTcpSocket, Options) -> {ok,connection_ref()} | {error,term()} when + OpenTcpSocket :: open_socket(), + Options :: client_options(). --spec connect(inet:socket(), proplists:proplist(), timeout()) -> ok_error(connection_ref()) - ; (string(), inet:port_number(), proplists:proplist()) -> ok_error(connection_ref()). +connect(OpenTcpSocket, Options) when is_port(OpenTcpSocket), + is_list(Options) -> + connect(OpenTcpSocket, Options, infinity). --spec connect(string(), inet:port_number(), proplists:proplist(), timeout()) -> ok_error(connection_ref()). -%% -%% Description: Starts an ssh connection. -%%-------------------------------------------------------------------- -connect(Socket, UserOptions) when is_port(Socket), - is_list(UserOptions) -> - connect(Socket, UserOptions, infinity). +-spec connect(open_socket(), client_options(), timeout()) -> + {ok,connection_ref()} | {error,term()} + ; (host(), inet:port_number(), client_options()) -> + {ok,connection_ref()} | {error,term()}. -connect(Socket, UserOptions, Timeout) when is_port(Socket), - is_list(UserOptions) -> +connect(Socket, UserOptions, NegotiationTimeout) when is_port(Socket), + is_list(UserOptions) -> case ssh_options:handle_options(client, UserOptions) of {error, Error} -> {error, Error}; @@ -111,16 +129,23 @@ connect(Socket, UserOptions, Timeout) when is_port(Socket), ok -> {ok, {Host,_Port}} = inet:sockname(Socket), Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), - ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); + ssh_connection_handler:start_connection(client, Socket, Opts, NegotiationTimeout); {error,SockError} -> {error,SockError} end end; -connect(Host, Port, UserOptions) when is_integer(Port), - Port>0, - is_list(UserOptions) -> - connect(Host, Port, UserOptions, infinity). +connect(Host, Port, Options) when is_integer(Port), + Port>0, + is_list(Options) -> + connect(Host, Port, Options, infinity). + + +-spec connect(Host, Port, Options, NegotiationTimeout) -> {ok,connection_ref()} | {error,term()} when + Host :: host(), + Port :: inet:port_number(), + Options :: client_options(), + NegotiationTimeout :: timeout(). connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), Port>0, @@ -148,7 +173,8 @@ connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), end. %%-------------------------------------------------------------------- --spec close(pid()) -> ok. +-spec close(ConnectionRef) -> ok | {error,term()} when + ConnectionRef :: connection_ref() . %% %% Description: Closes an ssh connection. %%-------------------------------------------------------------------- @@ -156,15 +182,25 @@ close(ConnectionRef) -> ssh_connection_handler:stop(ConnectionRef). %%-------------------------------------------------------------------- --spec connection_info(pid(), [atom()]) -> [{atom(), term()}]. -%% %% Description: Retrieves information about a connection. %%-------------------------------------------------------------------- -connection_info(ConnectionRef, Options) -> - ssh_connection_handler:connection_info(ConnectionRef, Options). +-spec connection_info(ConnectionRef, Keys) -> ConnectionInfo when + ConnectionRef :: connection_ref(), + Keys :: [client_version | server_version | user | peer | sockname], + ConnectionInfo :: [{client_version, Version} + | {server_version, Version} + | {user,string()} + | {peer, {inet:hostname(), ip_port()}} + | {sockname, ip_port()} + ], + Version :: {ProtocolVersion, VersionString::string()}, + ProtocolVersion :: {Major::pos_integer(), Minor::non_neg_integer()} . + +connection_info(Connection, Options) -> + ssh_connection_handler:connection_info(Connection, Options). %%-------------------------------------------------------------------- --spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}]. +-spec channel_info(connection_ref(), channel_id(), [atom()]) -> proplists:proplist(). %% %% Description: Retrieves information about a connection. %%-------------------------------------------------------------------- @@ -172,18 +208,17 @@ channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- --spec daemon(inet:port_number()) -> ok_error(daemon_ref()). --spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(daemon_ref()). --spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(daemon_ref()) - ;(socket, inet:socket(), proplists:proplist()) -> ok_error(daemon_ref()) - . - %% Description: Starts a server listening for SSH connections %% on the given port. %%-------------------------------------------------------------------- +-spec daemon(inet:port_number()) -> {ok,daemon_ref()} | {error,term()}. + daemon(Port) -> daemon(Port, []). + +-spec daemon(inet:port_number()|open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}. + daemon(Socket, UserOptions) when is_port(Socket) -> try #{} = Options = ssh_options:handle_options(server, UserOptions), @@ -226,6 +261,10 @@ daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 -> daemon(any, Port, UserOptions). +-spec daemon(any | inet:ip_address(), inet:port_number(), daemon_options()) -> {ok,daemon_ref()} | {error,term()} + ;(socket, open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()} + . + daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535, Host0 == any ; Host0 == loopback ; is_tuple(Host0) -> try @@ -267,7 +306,12 @@ daemon(_, _, _) -> {error, badarg}. %%-------------------------------------------------------------------- --spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ). +-spec daemon_info(Daemon) -> {ok, DaemonInfo} | {error,term()} when + Daemon :: daemon_ref(), + DaemonInfo :: [ {ip, inet:ip_address()} + | {port, inet:port_number()} + | {profile, term()} + ]. daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of @@ -290,16 +334,23 @@ daemon_info(Pid) -> end. %%-------------------------------------------------------------------- --spec stop_listener(daemon_ref()) -> ok. --spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. -%% %% Description: Stops the listener, but leaves %% existing connections started by the listener up and running. %%-------------------------------------------------------------------- +-spec stop_listener(daemon_ref()) -> ok. + stop_listener(SysSup) -> ssh_system_sup:stop_listener(SysSup). + + +-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. + stop_listener(Address, Port) -> stop_listener(Address, Port, ?DEFAULT_PROFILE). + + +-spec stop_listener(any|inet:ip_address(), inet:port_number(), term()) -> ok. + stop_listener(any, Port, Profile) -> map_ip(fun(IP) -> ssh_system_sup:stop_listener(IP, Port, Profile) @@ -310,17 +361,23 @@ stop_listener(Address, Port, Profile) -> end, {address,Address}). %%-------------------------------------------------------------------- --spec stop_daemon(daemon_ref()) -> ok. --spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok. --spec stop_daemon(inet:ip_address(), inet:port_number(), atom()) -> ok. -%% %% Description: Stops the listener and all connections started by %% the listener. %%-------------------------------------------------------------------- +-spec stop_daemon(DaemonRef::daemon_ref()) -> ok. + stop_daemon(SysSup) -> ssh_system_sup:stop_system(SysSup). + + +-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok. + stop_daemon(Address, Port) -> stop_daemon(Address, Port, ?DEFAULT_PROFILE). + + +-spec stop_daemon(any|inet:ip_address(), inet:port_number(), atom()) -> ok. + stop_daemon(any, Port, Profile) -> map_ip(fun(IP) -> ssh_system_sup:stop_system(IP, Port, Profile) @@ -331,33 +388,37 @@ stop_daemon(Address, Port, Profile) -> end, {address,Address}). %%-------------------------------------------------------------------- --spec shell(inet:socket() | string()) -> _. --spec shell(inet:socket() | string(), proplists:proplist()) -> _. --spec shell(string(), inet:port_number(), proplists:proplist()) -> _. - -%% Host = string() -%% Port = integer() -%% Options = [{Option, Value}] -%% %% Description: Starts an interactive shell to an SSH server on the %% given . The function waits for user input, %% and will not return until the remote shell is ended.(e.g. on %% exit from the shell) %%-------------------------------------------------------------------- +-spec shell(open_socket() | host()) -> _. + shell(Socket) when is_port(Socket) -> shell(Socket, []); shell(Host) -> shell(Host, ?SSH_DEFAULT_PORT, []). + +-spec shell(open_socket() | host(), client_options()) -> _. + shell(Socket, Options) when is_port(Socket) -> start_shell( connect(Socket, Options) ); shell(Host, Options) -> shell(Host, ?SSH_DEFAULT_PORT, Options). + +-spec shell(Host, Port, Options) -> _ when + Host :: host(), + Port :: inet:port_number(), + Options :: client_options() . + shell(Host, Port, Options) -> start_shell( connect(Host, Port, Options) ). + start_shell({ok, ConnectionRef}) -> case ssh_connection:session_channel(ConnectionRef, infinity) of {ok,ChannelId} -> @@ -366,10 +427,16 @@ start_shell({ok, ConnectionRef}) -> {init_args,[ConnectionRef, ChannelId]}, {cm, ConnectionRef}, {channel_id, ChannelId}], {ok, State} = ssh_channel:init([Args]), - ssh_channel:enter_loop(State); + try + ssh_channel:enter_loop(State) + catch + exit:normal -> + ok + end; Error -> Error end; + start_shell(Error) -> Error. @@ -380,7 +447,7 @@ default_algorithms() -> ssh_transport:default_algorithms(). %%-------------------------------------------------------------------- --spec chk_algos_opts(list(any())) -> algs_list() . +-spec chk_algos_opts(client_options()|daemon_options()) -> internal_options() | {error,term()}. %%-------------------------------------------------------------------- chk_algos_opts(Opts) -> case lists:foldl( diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 0e118ac13f..a3d9a1b1cb 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -98,35 +98,267 @@ %% Types --type role() :: client | server . --type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} . --type daemon_ref() :: pid() . +-type role() :: client | server . + +-type host() :: string() | inet:ip_address() | loopback . +-type open_socket() :: gen_tcp:socket(). + +-type subsystem_spec() :: {Name::string(), mod_args()} . + +-type algs_list() :: list( alg_entry() ). +-type alg_entry() :: {kex, [kex_alg()]} + | {public_key, [pubkey_alg()]} + | {cipher, double_algs(cipher_alg())} + | {mac, double_algs(mac_alg())} + | {compression, double_algs(compression_alg())} . + +-type kex_alg() :: 'diffie-hellman-group-exchange-sha1' | + 'diffie-hellman-group-exchange-sha256' | + 'diffie-hellman-group1-sha1' | + 'diffie-hellman-group14-sha1' | + 'diffie-hellman-group14-sha256' | + 'diffie-hellman-group16-sha512' | + 'diffie-hellman-group18-sha512' | + 'ecdh-sha2-nistp256' | + 'ecdh-sha2-nistp384' | + 'ecdh-sha2-nistp521' + . + +-type pubkey_alg() :: 'ecdsa-sha2-nistp256' | + 'ecdsa-sha2-nistp384' | + 'ecdsa-sha2-nistp521' | + 'rsa-sha2-256' | + 'rsa-sha2-512' | + 'ssh-dss' | + 'ssh-rsa' + . + +-type cipher_alg() :: '3des-cbc' | + 'AEAD_AES_128_GCM' | + 'AEAD_AES_256_GCM' | + 'aes128-cbc' | + 'aes128-ctr' | + 'aes128-gcm@openssh.com' | + 'aes192-ctr' | + 'aes256-ctr' | + 'aes256-gcm@openssh.com' + . + +-type mac_alg() :: 'AEAD_AES_128_GCM' | + 'AEAD_AES_256_GCM' | + 'hmac-sha1' | + 'hmac-sha2-256' | + 'hmac-sha2-512' + . + +-type compression_alg() :: 'none' | + 'zlib' | + 'zlib@openssh.com' + . + +-type double_algs(AlgType) :: list( {client2server,[AlgType]} | {server2client,[AlgType]} ) + | [AlgType]. + +-type modify_algs_list() :: list( {append,algs_list()} | {prepend,algs_list()} | {rm,algs_list()} ) . + +-type internal_options() :: ssh_options:private_options(). +-type socket_options() :: [gen_tcp:connect_option() | gen_tcp:listen_option()]. + +-type client_options() :: [ client_option() ] . +-type daemon_options() :: [ daemon_option() ]. + + +-type common_options() :: [ common_option() ]. +-type common_option() :: + user_dir_common_option() + | profile_common_option() + | max_idle_time_common_option() + | key_cb_common_option() + | disconnectfun_common_option() + | unexpectedfun_common_option() + | ssh_msg_debug_fun_common_option() + | rekey_limit_common_option() + | id_string_common_option() + | preferred_algorithms_common_option() + | modify_algorithms_common_option() + | auth_methods_common_option() + | inet_common_option() + | fd_common_option() + . + +-define(COMMON_OPTION, common_option()). + + +-type user_dir_common_option() :: {user_dir, false | string()}. +-type profile_common_option() :: {profile, atom() }. +-type max_idle_time_common_option() :: {idle_time, timeout()}. +-type rekey_limit_common_option() :: {rekey_limit, non_neg_integer() }. + +-type key_cb_common_option() :: {key_cb, Module::atom() | {Module::atom(),Opts::[term()]} } . +-type disconnectfun_common_option() :: + {disconnectfun, fun((Reason::term()) -> void | any()) }. +-type unexpectedfun_common_option() :: + {unexpectedfun, fun((Message::term(),{Host::term(),Port::term()}) -> report | skip ) }. +-type ssh_msg_debug_fun_common_option() :: + {ssh_msg_debug_fun, fun((ssh:connection_ref(),AlwaysDisplay::boolean(),Msg::binary(),LanguageTag::binary()) -> any()) } . + +-type id_string_common_option() :: {id_string, string() | random | {random,Nmin::pos_integer(),Nmax::pos_integer()} }. +-type preferred_algorithms_common_option():: {preferred_algorithms, algs_list()}. +-type modify_algorithms_common_option() :: {modify_algorithms, modify_algs_list()}. +-type auth_methods_common_option() :: {auth_methods, string() }. + +-type inet_common_option() :: {inet, inet | inet6} . +-type fd_common_option() :: {fd, gen_tcp:socket()} . + + +-type opaque_common_options() :: + {transport, {atom(),atom(),atom()} } + | {vsn, {non_neg_integer(),non_neg_integer()} } + | {tstflg, list(term())} + | {user_dir_fun, fun()} + | {max_random_length_padding, non_neg_integer()} . + + + +-type client_option() :: + pref_public_key_algs_client_option() + | pubkey_passphrase_client_options() + | host_accepting_client_options() + | authentication_client_options() + | diffie_hellman_group_exchange_client_option() + | connect_timeout_client_option() + | recv_ext_info_client_option() + | opaque_client_options() + | gen_tcp:connect_option() + | ?COMMON_OPTION . + +-type opaque_client_options() :: + {keyboard_interact_fun, fun((term(),term(),term()) -> term())} + | opaque_common_options(). + +-type pref_public_key_algs_client_option() :: {pref_public_key_algs, [pubkey_alg()] } . + +-type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()} + | {rsa_pass_phrase, string()} + | {ecdsa_pass_phrase, string()} . + +-type host_accepting_client_options() :: + {silently_accept_hosts, accept_hosts()} + | {user_interaction, boolean()} + | {save_accepted_host, boolean()} + | {quiet_mode, boolean()} . + +-type accept_hosts() :: boolean() + | accept_callback() + | {HashAlgoSpec::fp_digest_alg(), accept_callback()}. + +-type fp_digest_alg() :: 'md5' | + 'sha' | + 'sha224' | + 'sha256' | + 'sha384' | + 'sha512' + . + +-type accept_callback() :: fun((PeerName::string(), fingerprint() ) -> boolean()) . +-type fingerprint() :: string() | [string()]. + +-type authentication_client_options() :: + {user, string()} + | {password, string()} . + +-type diffie_hellman_group_exchange_client_option() :: + {dh_gex_limits, {Min::pos_integer(), I::pos_integer(), Max::pos_integer()} } . + +-type connect_timeout_client_option() :: {connect_timeout, timeout()} . + +-type recv_ext_info_client_option() :: {recv_ext_info, boolean()} . + + + +-type daemon_option() :: + subsystem_daemon_option() + | shell_daemon_option() + | exec_daemon_option() + | ssh_cli_daemon_option() + | authentication_daemon_options() + | diffie_hellman_group_exchange_daemon_option() + | negotiation_timeout_daemon_option() + | hardening_daemon_options() + | callbacks_daemon_options() + | send_ext_info_daemon_option() + | opaque_daemon_options() + | gen_tcp:listen_option() + | ?COMMON_OPTION . --type subsystem_spec() :: {subsystem_name(), {channel_callback(), channel_init_args()}} . --type subsystem_name() :: string() . --type channel_callback() :: atom() . --type channel_init_args() :: list() . +-type subsystem_daemon_option() :: {subsystems, subsystem_spec()}. --type algs_list() :: list( alg_entry() ). --type alg_entry() :: {kex, simple_algs()} - | {public_key, simple_algs()} - | {cipher, double_algs()} - | {mac, double_algs()} - | {compression, double_algs()} . --type simple_algs() :: list( atom() ) . --type double_algs() :: list( {client2server,simple_algs()} | {server2client,simple_algs()} ) - | simple_algs() . +-type shell_daemon_option() :: {shell, mod_fun_args() | 'shell_fun/1'() | 'shell_fun/2'() }. +-type 'shell_fun/1'() :: fun((User::string()) -> pid()) . +-type 'shell_fun/2'() :: fun((User::string(), PeerAddr::inet:ip_address()) -> pid()). --type options() :: #{socket_options := socket_options(), - internal_options := internal_options(), - option_key() => any() - }. +-type exec_daemon_option() :: {exec, 'exec_fun/1'() | 'exec_fun/2'() | 'exec_fun/3'() }. --type socket_options() :: proplists:proplist(). --type internal_options() :: #{option_key() => any()}. +-type 'exec_fun/1'() :: fun((Cmd::string()) -> exec_result()) . +-type 'exec_fun/2'() :: fun((Cmd::string(), User::string()) -> exec_result()) . +-type 'exec_fun/3'() :: fun((Cmd::string(), User::string(), ClientAddr::ip_port()) -> exec_result()) . +-type exec_result() :: {ok,Result::term()} | {error,Reason::term()} . --type option_key() :: atom(). +-type ssh_cli_daemon_option() :: {ssh_cli, mod_args() | no_cli }. +-type send_ext_info_daemon_option() :: {send_ext_info, boolean()} . + +-type authentication_daemon_options() :: + {system_dir, string()} + | {auth_method_kb_interactive_data, prompt_texts() } + | {user_passwords, [{UserName::string(),Pwd::string()}]} + | {password, string()} + | {pwdfun, pwdfun_2() | pwdfun_4()} . + +-type prompt_texts() :: + kb_int_tuple() + | kb_int_fun_3() + . + +-type kb_int_fun_3() :: fun((Peer::ip_port(), User::string(), Service::string()) -> kb_int_tuple()). +-type kb_int_tuple() :: {Name::string(), Instruction::string(), Prompt::string(), Echo::boolean()}. + +-type pwdfun_2() :: fun((User::string(), Password::string()) -> boolean()) . +-type pwdfun_4() :: fun((User::string(), + Password::string(), + PeerAddress::ip_port(), + State::any()) -> + boolean() | disconnect | {boolean(),NewState::any()} + ) . + +-type diffie_hellman_group_exchange_daemon_option() :: + {dh_gex_groups, [explicit_group()] | explicit_group_file() | ssh_moduli_file()} + | {dh_gex_limits, {Min::pos_integer(), Max::pos_integer()} } . + +-type explicit_group() :: {Size::pos_integer(),G::pos_integer(),P::pos_integer()} . +-type explicit_group_file() :: {file,string()} . +-type ssh_moduli_file() :: {ssh_moduli_file,string()}. + +-type negotiation_timeout_daemon_option() :: {negotiation_timeout, timeout()} . + +-type hardening_daemon_options() :: + {max_sessions, pos_integer()} + | {max_channels, pos_integer()} + | {parallel_login, boolean()} + | {minimal_remote_max_packet_size, pos_integer()}. + +-type callbacks_daemon_options() :: + {failfun, fun((User::string(), PeerAddress::inet:ip_address(), Reason::term()) -> _)} + | {connectfun, fun((User::string(), PeerAddress::inet:ip_address(), Method::string()) ->_)} . + +-type opaque_daemon_options() :: + {infofun, fun()} + | opaque_common_options(). + +-type ip_port() :: {inet:ip_address(), inet:port_number()} . + +-type mod_args() :: {Module::atom(), Args::list()} . +-type mod_fun_args() :: {Module::atom(), Function::atom(), Args::list()} . %% Records @@ -134,8 +366,7 @@ { role :: client | role(), peer :: undefined | - {inet:hostname(), - {inet:ip_address(),inet:port_number()}}, %% string version of peer address + {inet:hostname(),ip_port()}, %% string version of peer address local, %% Local sockname. Need this AFTER a socket is closed by i.e. a crash diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index b90e571448..359e29fdbe 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -50,11 +50,11 @@ {ok, NewState :: term()} | {error, Reason :: term()}. -callback handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. + {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, +-callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), + {stop, ChannelId::ssh:channel_id(), State::term()}. -behaviour(gen_server). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 26c7cb45aa..b45dbc3e37 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -59,10 +59,10 @@ term(). -spec handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. + {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. -spec handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), + {stop, ChannelId::ssh:channel_id(), State::term()}. %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_client_key_api.erl b/lib/ssh/src/ssh_client_key_api.erl index 6e994ff292..d0d8ab25d6 100644 --- a/lib/ssh/src/ssh_client_key_api.erl +++ b/lib/ssh/src/ssh_client_key_api.erl @@ -23,26 +23,25 @@ -include_lib("public_key/include/public_key.hrl"). -include("ssh.hrl"). --export_type([algorithm/0]). - --type algorithm() :: 'ssh-rsa' - | 'ssh-dss' - | 'ecdsa-sha2-nistp256' - | 'ecdsa-sha2-nistp384' - | 'ecdsa-sha2-nistp521' - . - --callback is_host_key(PublicKey :: public_key:public_key(), - Host :: string(), - Algorithm :: algorithm(), - ConnectOptions :: proplists:proplist()) -> +-export_type([client_key_cb_options/0]). + +-type client_key_cb_options() :: [{key_cb_private,term()} | ssh:client_option()]. + +-callback is_host_key(Key :: public_key:public_key(), + Host :: string(), + Algorithm :: ssh:pubkey_alg(), + Options :: client_key_cb_options() + ) -> boolean(). --callback user_key(Algorithm :: algorithm(), - ConnectOptions :: proplists:proplist()) -> - {ok, PrivateKey::public_key:private_key()} | {error, term()}. +-callback user_key(Algorithm :: ssh:pubkey_alg(), + Options :: client_key_cb_options() + ) -> + {ok, PrivateKey :: public_key:private_key()} | {error, string()}. --callback add_host_key(Host :: string(), PublicKey :: public_key:public_key(), - Options :: proplists:proplist()) -> +-callback add_host_key(Host :: string(), + PublicKey :: public_key:public_key(), + Options :: client_key_cb_options() + ) -> ok | {error, Error::term()}. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index a8de5f9a2f..3c61638285 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -22,10 +22,6 @@ %%% Description : SSH connection protocol --type channel_id() :: pos_integer(). --type connection_ref() :: pid(). - - -define(DEFAULT_PACKET_SIZE, 65536). -define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE). diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 2b8780a991..bfe17c7541 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -64,29 +64,32 @@ bound_channel/3, encode_ip/1 ]). +-type connection_ref() :: ssh:connection_ref(). +-type channel_id() :: ssh:channel_id(). + %%-------------------------------------------------------------------- %%% API %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec session_channel(connection_ref(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. --spec session_channel(connection_ref(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. - %% Description: Opens a channel for a ssh session. A session is a %% remote execution of a program. The program may be a shell, an %% application, a system command, or some built-in subsystem. %% -------------------------------------------------------------------- +-spec session_channel(connection_ref(), timeout()) -> + {ok, channel_id()} | {error, timeout | closed}. + session_channel(ConnectionHandler, Timeout) -> - session_channel(ConnectionHandler, - ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, - Timeout). + session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). -session_channel(ConnectionHandler, InitialWindowSize, - MaxPacketSize, Timeout) -> +-spec session_channel(connection_ref(), integer(), integer(), timeout()) -> + {ok, channel_id()} | {error, timeout | closed}. + +session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>, - InitialWindowSize, - MaxPacketSize, Timeout) of + InitialWindowSize, + MaxPacketSize, Timeout) of {open, Channel} -> {ok, Channel}; Error -> @@ -125,24 +128,30 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ChannelId, "subsystem", true, [?string(SubSystem)], TimeOut). %%-------------------------------------------------------------------- --spec send(connection_ref(), channel_id(), iodata()) -> - ok | {error, closed}. --spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> - ok | {error, timeout} | {error, closed}. --spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> - ok | {error, timeout} | {error, closed}. -%% -%% %% Description: Sends channel data. %%-------------------------------------------------------------------- +-spec send(connection_ref(), channel_id(), iodata()) -> + ok | {error, closed}. send(ConnectionHandler, ChannelId, Data) -> send(ConnectionHandler, ChannelId, 0, Data, infinity). + + +-spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> + ok | {error, timeout} | {error, closed}. + send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> send(ConnectionHandler, ChannelId, 0, Data, TimeOut); + send(ConnectionHandler, ChannelId, Data, infinity) -> send(ConnectionHandler, ChannelId, 0, Data, infinity); + send(ConnectionHandler, ChannelId, Type, Data) -> send(ConnectionHandler, ChannelId, Type, Data, infinity). + + +-spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> + ok | {error, timeout} | {error, closed}. + send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). @@ -156,7 +165,7 @@ send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- --spec adjust_window(connection_ref(), channel_id(), integer()) -> ok | {error, closed}. +-spec adjust_window(connection_ref(), channel_id(), integer()) -> ok. %% %% %% Description: Adjusts the ssh flowcontrol window. @@ -197,18 +206,19 @@ reply_request(ConnectionHandler, true, Status, ChannelId) -> reply_request(_,false, _, _) -> ok. +%%-------------------------------------------------------------------- +%% Description: Sends a ssh connection protocol pty_req. %%-------------------------------------------------------------------- -spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> success | failiure | {error, closed}. --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> - success | failiure | {error, timeout} | {error, closed}. -%% -%% -%% Description: Sends a ssh connection protocol pty_req. -%%-------------------------------------------------------------------- ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). + + +-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> + success | failiure | {error, timeout} | {error, closed}. + ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> TermData = backwards_compatible(Options0, []), % FIXME {Width, PixWidth} = pty_default_dimensions(width, TermData), diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 033f11f4a1..1b3763e9c7 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -60,6 +60,9 @@ get_print_info/1 ]). +-type connection_ref() :: ssh:connection_ref(). +-type channel_id() :: ssh:channel_id(). + %%% Behaviour callbacks -export([init/1, callback_mode/0, handle_event/4, terminate/3, format_status/2, code_change/4]). @@ -88,8 +91,8 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec start_link(role(), - inet:socket(), - ssh_options:options() + gen_tcp:socket(), + internal_options() ) -> {ok, pid()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . start_link(Role, Socket, Options) -> @@ -118,8 +121,8 @@ stop(ConnectionHandler)-> %%-------------------------------------------------------------------- -spec start_connection(role(), - inet:socket(), - ssh_options:options(), + gen_tcp:socket(), + internal_options(), timeout() ) -> {ok, connection_ref()} | {error, term()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -359,7 +362,7 @@ alg(ConnectionHandler) -> | undefined, % ex: tcp_closed ssh_params :: #ssh{} | undefined, - socket :: inet:socket() + socket :: gen_tcp:socket() | undefined, decrypted_data_buffer = <<>> :: binary() | undefined, @@ -370,7 +373,6 @@ alg(ConnectionHandler) -> | undefined, last_size_rekey = 0 :: non_neg_integer(), event_queue = [] :: list(), -% opts :: ssh_options:options(), inet_initial_recbuf_size :: pos_integer() | undefined }). @@ -380,8 +382,8 @@ alg(ConnectionHandler) -> %%==================================================================== %%-------------------------------------------------------------------- -spec init_connection_handler(role(), - inet:socket(), - ssh_options:options() + gen_tcp:socket(), + internal_options() ) -> no_return(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . init_connection_handler(Role, Socket, Opts) -> diff --git a/lib/ssh/src/ssh_daemon_channel.erl b/lib/ssh/src/ssh_daemon_channel.erl index 6ca93eff44..72853f2d6a 100644 --- a/lib/ssh/src/ssh_daemon_channel.erl +++ b/lib/ssh/src/ssh_daemon_channel.erl @@ -36,10 +36,10 @@ term(). -callback handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, + {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. +-callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), + {stop, ChannelId::ssh:channel_id(), State::term()}. %%% API @@ -48,6 +48,7 @@ %% gen_server callbacks -export([init/1, terminate/2]). +-spec start(ssh:connection_ref(), ssh:channel_id(), atom(), term()) -> term(). start(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 33792da38f..9cab2fe0bd 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -45,27 +45,6 @@ %%% API -%%% client --spec add_host_key(string(), - public_key:public_key(), - proplists:proplist()) -> ok | {error,term()}. - --spec is_host_key(public_key:public_key(), - string(), - ssh_client_key_api:algorithm(), - proplists:proplist()) -> boolean(). - --spec user_key(ssh_client_key_api:algorithm(), - proplists:proplist()) -> {ok, public_key:private_key()} | {error,term()}. - -%%% server --spec host_key(ssh_server_key_api:algorithm(), - proplists:proplist()) -> {ok, public_key:private_key()} | {error,term()}. - --spec is_auth_key(public_key:public_key(), - string(), proplists:proplist()) -> boolean(). - - %% Used by server host_key(Algorithm, Opts) -> File = file_name(system, file_base_name(Algorithm), Opts), diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index c05293d1ae..4dd9082250 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -32,7 +32,7 @@ handle_options/2 ]). --export_type([options/0 +-export_type([private_options/0 ]). %%%================================================================ @@ -47,16 +47,23 @@ default => any() }. +-type option_key() :: atom(). + -type option_declarations() :: #{ {option_key(),def} := option_declaration() }. -type error() :: {error,{eoptions,any()}} . +-type private_options() :: #{socket_options := socket_options(), + internal_options := internal_options(), + option_key() => any() + }. + %%%================================================================ %%% %%% Get an option %%% --spec get_value(option_class(), option_key(), options(), +-spec get_value(option_class(), option_key(), private_options(), atom(), non_neg_integer()) -> any() | no_return(). get_value(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> @@ -69,7 +76,7 @@ get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). --spec get_value(option_class(), option_key(), options(), fun(() -> any()), +-spec get_value(option_class(), option_key(), private_options(), fun(() -> any()), atom(), non_neg_integer()) -> any() | no_return(). get_value(socket_options, Key, Opts, DefFun, _CallerMod, _CallerLine) when is_map(Opts) -> @@ -91,8 +98,8 @@ get_value(Class, Key, Opts, _DefFun, _CallerMod, _CallerLine) -> %%% Put an option %%% --spec put_value(option_class(), option_in(), options(), - atom(), non_neg_integer()) -> options(). +-spec put_value(option_class(), option_in(), private_options(), + atom(), non_neg_integer()) -> private_options(). put_value(user_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> put_user_value(KeyVal, Opts); @@ -131,8 +138,8 @@ put_socket_value(A, SockOpts) when is_atom(A) -> %%% Delete an option %%% --spec delete_key(option_class(), option_key(), options(), - atom(), non_neg_integer()) -> options(). +-spec delete_key(option_class(), option_key(), private_options(), + atom(), non_neg_integer()) -> private_options(). delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> InternalOpts = maps:get(internal_options,Opts), @@ -144,9 +151,7 @@ delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opt %%% Initialize the options %%% --spec handle_options(role(), proplists:proplist()) -> options() | error() . - --spec handle_options(role(), proplists:proplist(), options()) -> options() | error() . +-spec handle_options(role(), client_options()|daemon_options()) -> private_options() | error() . handle_options(Role, PropList0) -> handle_options(Role, PropList0, #{socket_options => [], @@ -155,7 +160,7 @@ handle_options(Role, PropList0) -> }). handle_options(Role, PropList0, Opts0) when is_map(Opts0), - is_list(PropList0) -> + is_list(PropList0) -> PropList1 = proplists:unfold(PropList0), try OptionDefinitions = default(Role), diff --git a/lib/ssh/src/ssh_server_key_api.erl b/lib/ssh/src/ssh_server_key_api.erl index 3f1b886fa7..a285bf9475 100644 --- a/lib/ssh/src/ssh_server_key_api.erl +++ b/lib/ssh/src/ssh_server_key_api.erl @@ -23,16 +23,18 @@ -include_lib("public_key/include/public_key.hrl"). -include("ssh.hrl"). --export_type([algorithm/0]). +-export_type([daemon_key_cb_options/0]). --type algorithm() :: ssh_client_key_api:algorithm(). +-type daemon_key_cb_options() :: [{key_cb_private,term()} | ssh:daemon_option()]. --callback host_key(Algorithm :: algorithm(), - DaemonOptions :: proplists:proplist()) -> +-callback host_key(Algorithm :: ssh:pubkey_alg(), + DaemonOptions :: daemon_key_cb_options() + ) -> {ok, PrivateKey :: public_key:private_key()} | {error, term()}. -callback is_auth_key(PublicKey :: public_key:public_key(), User :: string(), - DaemonOptions :: proplists:proplist()) -> + DaemonOptions :: daemon_key_cb_options() + ) -> boolean(). diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 945e9f457b..fda9a38a43 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -58,21 +58,7 @@ %%==================================================================== %% API %%==================================================================== --spec init(Args :: term()) -> - {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | - {stop, Reason :: term()} | ignore. - --spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | - term()), - State :: term()) -> - term(). - --spec handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. --spec handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, - State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), - State::term()}. +-spec subsystem_spec(list()) -> subsystem_spec(). subsystem_spec(Options) -> {"sftp", {?MODULE, Options}}. diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl index 085534592d..c7c63c5c43 100644 --- a/lib/ssh/src/ssh_shell.erl +++ b/lib/ssh/src/ssh_shell.erl @@ -48,21 +48,6 @@ %%==================================================================== %% ssh_channel callbacks %%==================================================================== --spec init(Args :: term()) -> - {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | - {stop, Reason :: term()} | ignore. - --spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | - term()), - State :: term()) -> - term(). - --spec handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. --spec handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, - State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), - State::term()}. %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} -- cgit v1.2.3 From b9c0b5c15f71dc1384c8e6b98fb2fae1987b14b5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 23 Apr 2018 15:01:37 +0200 Subject: ssh: Changes suggested by dialyzer --- lib/ssh/src/ssh_acceptor_sup.erl | 2 -- lib/ssh/src/ssh_channel_sup.erl | 2 -- lib/ssh/src/ssh_cli.erl | 15 --------------- lib/ssh/src/ssh_connection.erl | 26 ++++++++++++++------------ 4 files changed, 14 insertions(+), 31 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index fc564a359b..10fd4452bf 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -36,8 +36,6 @@ -define(DEFAULT_TIMEOUT, 50000). --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - %%%========================================================================= %%% API %%%========================================================================= diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl index 8444533fd1..7a12f34049 100644 --- a/lib/ssh/src/ssh_channel_sup.erl +++ b/lib/ssh/src/ssh_channel_sup.erl @@ -50,8 +50,6 @@ start_child(Sup, Callback, Id, Args, Exec) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - init(_Args) -> RestartStrategy = one_for_one, MaxR = 10, diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index b45dbc3e37..382de90ae1 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -49,21 +49,6 @@ %%==================================================================== %% ssh_channel callbacks %%==================================================================== --spec init(Args :: term()) -> - {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | - {stop, Reason :: term()} | ignore. - --spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | - term()), - State :: term()) -> - term(). - --spec handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --spec handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, - State::term()) -> {ok, State::term()} | - {stop, ChannelId::ssh:channel_id(), - State::term()}. %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index bfe17c7541..2261d37d6a 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -97,32 +97,34 @@ session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> end. %%-------------------------------------------------------------------- --spec exec(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. - %% Description: Will request that the server start the %% execution of the given command. %%-------------------------------------------------------------------- +-spec exec(connection_ref(), channel_id(), string(), timeout()) -> + success | failure | {error, timeout | closed}. + exec(ConnectionHandler, ChannelId, Command, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec", true, [?string(Command)], TimeOut). %%-------------------------------------------------------------------- --spec shell(connection_ref(), channel_id()) -> _. - %% Description: Will request that the user's default shell (typically %% defined in /etc/passwd in UNIX systems) be started at the other %% end. %%-------------------------------------------------------------------- +-spec shell(connection_ref(), channel_id()) -> + ok | success | failure | {error, timeout}. + shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "shell", false, <<>>, 0). %%-------------------------------------------------------------------- --spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. %% %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- +-spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> + success | failure | {error, timeout | closed}. + subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "subsystem", @@ -131,13 +133,13 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> %% Description: Sends channel data. %%-------------------------------------------------------------------- -spec send(connection_ref(), channel_id(), iodata()) -> - ok | {error, closed}. + ok | {error, timeout | closed}. send(ConnectionHandler, ChannelId, Data) -> send(ConnectionHandler, ChannelId, 0, Data, infinity). -spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> - ok | {error, timeout} | {error, closed}. + ok | {error, timeout | closed}. send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> send(ConnectionHandler, ChannelId, 0, Data, TimeOut); @@ -150,7 +152,7 @@ send(ConnectionHandler, ChannelId, Type, Data) -> -spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> - ok | {error, timeout} | {error, closed}. + ok | {error, timeout | closed}. send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, @@ -210,14 +212,14 @@ reply_request(_,false, _, _) -> %% Description: Sends a ssh connection protocol pty_req. %%-------------------------------------------------------------------- -spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> - success | failiure | {error, closed}. + success | failure | {error, timeout}. ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). -spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> - success | failiure | {error, timeout} | {error, closed}. + success | failure | {error, timeout | closed}. ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> TermData = backwards_compatible(Options0, []), % FIXME -- cgit v1.2.3 From 8b50978cdc82d404f68384e1aadf7685a9d86af4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 25 Apr 2018 15:51:03 +0200 Subject: ssh: Use ssh_daemon_channel_sup and ssh_damon_channel --- lib/ssh/src/Makefile | 4 +-- lib/ssh/src/ssh.app.src | 2 +- lib/ssh/src/ssh_channel_sup.erl | 62 ---------------------------------- lib/ssh/src/ssh_cli.erl | 4 +-- lib/ssh/src/ssh_connection.erl | 4 +-- lib/ssh/src/ssh_daemon_channel.erl | 29 ++++------------ lib/ssh/src/ssh_daemon_channel_sup.erl | 62 ++++++++++++++++++++++++++++++++++ lib/ssh/src/ssh_info.erl | 8 ++--- lib/ssh/src/ssh_shell.erl | 4 +-- lib/ssh/src/ssh_subsystem_sup.erl | 12 +++---- 10 files changed, 88 insertions(+), 103 deletions(-) delete mode 100644 lib/ssh/src/ssh_channel_sup.erl create mode 100644 lib/ssh/src/ssh_daemon_channel_sup.erl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index bcd13213b3..a2cefe7c15 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -59,7 +59,7 @@ MODULES= \ ssh_shell \ ssh_system_sup \ ssh_subsystem_sup \ - ssh_channel_sup \ + ssh_daemon_channel_sup \ ssh_acceptor_sup \ ssh_acceptor \ ssh_app \ @@ -169,7 +169,7 @@ $(EBIN)/ssh_connection_handler.$(EMULATOR): ssh_connection_handler.erl ssh.hrl \ $(EBIN)/ssh_shell.$(EMULATOR): ssh_shell.erl ssh_connect.hrl $(EBIN)/ssh_system_sup.$(EMULATOR): ssh_system_sup.erl ssh.hrl $(EBIN)/ssh_subsystem_sup.$(EMULATOR): ssh_subsystem_sup.erl -$(EBIN)/ssh_channel_sup.$(EMULATOR): ssh_channel_sup.erl +$(EBIN)/ssh_daemon_channel_sup.$(EMULATOR): ssh_daemon_channel_sup.erl $(EBIN)/ssh_acceptor_sup.$(EMULATOR): ssh_acceptor_sup.erl ssh.hrl $(EBIN)/ssh_acceptor.$(EMULATOR): ssh_acceptor.erl ssh.hrl $(EBIN)/ssh_app.$(EMULATOR): ssh_app.erl diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 4a22322333..b935ea94d5 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -14,7 +14,7 @@ ssh_cli, ssh_client_key_api, ssh_channel, - ssh_channel_sup, + ssh_daemon_channel_sup, ssh_connection, ssh_connection_handler, ssh_connection_sup, diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl deleted file mode 100644 index 7a12f34049..0000000000 --- a/lib/ssh/src/ssh_channel_sup.erl +++ /dev/null @@ -1,62 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - -%% -%%---------------------------------------------------------------------- -%% Purpose: Ssh channel supervisor. -%%---------------------------------------------------------------------- --module(ssh_channel_sup). - --behaviour(supervisor). - --export([start_link/1, start_child/5]). - -%% Supervisor callback --export([init/1]). - -%%%========================================================================= -%%% Internal API -%%%========================================================================= -start_link(Args) -> - supervisor:start_link(?MODULE, [Args]). - -start_child(Sup, Callback, Id, Args, Exec) -> - ChildSpec = - #{id => make_ref(), - start => {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]}, - restart => temporary, - type => worker, - modules => [ssh_channel] - }, - supervisor:start_child(Sup, ChildSpec). - -%%%========================================================================= -%%% Supervisor callback -%%%========================================================================= -init(_Args) -> - RestartStrategy = one_for_one, - MaxR = 10, - MaxT = 3600, - Children = [], - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. - -%%%========================================================================= -%%% Internal functions -%%%========================================================================= diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 382de90ae1..31f6194bf9 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -30,7 +30,7 @@ -include("ssh.hrl"). -include("ssh_connect.hrl"). -%% ssh_channel callbacks +%% ssh_daemon_channel callbacks -export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). -export([dbg_trace/3]). @@ -47,7 +47,7 @@ }). %%==================================================================== -%% ssh_channel callbacks +%% ssh_daemon_channel callbacks %%==================================================================== %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 2261d37d6a..0a07ea778d 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -822,14 +822,14 @@ start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), case max_num_channels_not_exceeded(ChannelSup, Opts) of true -> - ssh_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); + ssh_daemon_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); false -> throw(max_num_channels_exceeded) end. max_num_channels_not_exceeded(ChannelSup, Opts) -> MaxNumChannels = ?GET_OPT(max_channels, Opts), - NumChannels = length([x || {_,_,worker,[ssh_channel]} <- + NumChannels = length([x || {_,_,worker,[ssh_daemon_channel]} <- supervisor:which_children(ChannelSup)]), %% Note that NumChannels is BEFORE starting a new one NumChannels < MaxNumChannels. diff --git a/lib/ssh/src/ssh_daemon_channel.erl b/lib/ssh/src/ssh_daemon_channel.erl index 72853f2d6a..e1d6906bbc 100644 --- a/lib/ssh/src/ssh_daemon_channel.erl +++ b/lib/ssh/src/ssh_daemon_channel.erl @@ -25,7 +25,7 @@ -module(ssh_daemon_channel). -%% API to special server side channel that can be pluged into the erlang ssh daemeon +%% API to server side channel that can be pluged into the erlang ssh daemeon -callback init(Args :: term()) -> {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | {stop, Reason :: term()} | ignore. @@ -42,29 +42,14 @@ {stop, ChannelId::ssh:channel_id(), State::term()}. -%%% API --export([start/4, start/5, start_link/4, start_link/5, enter_loop/1]). - -%% gen_server callbacks --export([init/1, terminate/2]). - --spec start(ssh:connection_ref(), ssh:channel_id(), atom(), term()) -> term(). -start(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> - ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). - -start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). - -start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> - ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). +%%% Internal API +-export([start_link/5, + get_print_info/1 + ]). start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). -enter_loop(State) -> - ssh_channel:enter_loop(State). -init(Args) -> - ssh_channel:init(Args). -terminate(Reason, State) -> - ssh_channel:terminate(Reason, State). +get_print_info(Pid) -> + ssh_channel:get_print_info(Pid). diff --git a/lib/ssh/src/ssh_daemon_channel_sup.erl b/lib/ssh/src/ssh_daemon_channel_sup.erl new file mode 100644 index 0000000000..cd86347501 --- /dev/null +++ b/lib/ssh/src/ssh_daemon_channel_sup.erl @@ -0,0 +1,62 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Ssh channel supervisor. +%%---------------------------------------------------------------------- +-module(ssh_daemon_channel_sup). + +-behaviour(supervisor). + +-export([start_link/1, start_child/5]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% Internal API +%%%========================================================================= +start_link(Args) -> + supervisor:start_link(?MODULE, [Args]). + +start_child(Sup, Callback, Id, Args, Exec) -> + ChildSpec = + #{id => make_ref(), + start => {ssh_daemon_channel, start_link, [self(), Id, Callback, Args, Exec]}, + restart => temporary, + type => worker, + modules => [ssh_daemon_channel] + }, + supervisor:start_child(Sup, ChildSpec). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_Args) -> + RestartStrategy = one_for_one, + MaxR = 10, + MaxT = 3600, + Children = [], + {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. + +%%%========================================================================= +%%% Internal functions +%%%========================================================================= diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl index d464def6fa..e1613f0599 100644 --- a/lib/ssh/src/ssh_info.erl +++ b/lib/ssh/src/ssh_info.erl @@ -140,15 +140,15 @@ print_system_sup({{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, Pid, superv -print_channels({{server,ssh_channel_sup,_,_},Pid,supervisor,[ssh_channel_sup]}) when is_pid(Pid) -> +print_channels({{server,ssh_daemon_channel_sup,_,_},Pid,supervisor,[ssh_daemon_channel_sup]}) when is_pid(Pid) -> Children = supervisor:which_children(Pid), - ChannelPids = [P || {R,P,worker,[ssh_channel]} <- Children, + ChannelPids = [P || {R,P,worker,[ssh_daemon_channel]} <- Children, is_pid(P), is_reference(R)], case ChannelPids of [] -> io_lib:format(?INDENT?INDENT"No channels~n",[]); [Ch1Pid|_] -> - {{ConnManager,_}, _Str} = ssh_channel:get_print_info(Ch1Pid), + {{ConnManager,_}, _Str} = ssh_daemon_channel:get_print_info(Ch1Pid), {{_,Remote},_} = ssh_connection_handler:get_print_info(ConnManager), [io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p~n",[fmt_host_port(Remote),ConnManager]), lists:map(fun print_ch/1, ChannelPids) @@ -159,7 +159,7 @@ print_channels({{server,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_s print_ch(Pid) -> try - {{ConnManager,ChannelID}, Str} = ssh_channel:get_print_info(Pid), + {{ConnManager,ChannelID}, Str} = ssh_daemon_channel:get_print_info(Pid), {_LocalRemote,StrM} = ssh_connection_handler:get_print_info(ConnManager), io_lib:format(?INDENT?INDENT?INDENT"ch ~p ~p: ~s ~s~n",[ChannelID, Pid, StrM, Str]) catch diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl index c7c63c5c43..6470f4c492 100644 --- a/lib/ssh/src/ssh_shell.erl +++ b/lib/ssh/src/ssh_shell.erl @@ -29,7 +29,7 @@ %%% channel inspite of it being a client. -behaviour(ssh_daemon_channel). -%% ssh_channel callbacks +%% ssh_daemon_channel callbacks -export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]). %% Spawn export @@ -46,7 +46,7 @@ ). %%==================================================================== -%% ssh_channel callbacks +%% ssh_daemon_channel callbacks %%==================================================================== %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index 77da240a66..f1e74a24ac 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -48,7 +48,7 @@ connection_supervisor(SupPid) -> channel_supervisor(SupPid) -> Children = supervisor:which_children(SupPid), - ssh_channel_sup(Children). + ssh_daemon_channel_sup(Children). %%%========================================================================= %%% Supervisor callback @@ -78,8 +78,8 @@ ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> }. ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> - #{id => id(Role, ssh_channel_sup, Address, Port), - start => {ssh_channel_sup, start_link, [Options]}, + #{id => id(Role, ssh_daemon_channel_sup, Address, Port), + start => {ssh_daemon_channel_sup, start_link, [Options]}, restart => temporary, type => supervisor }. @@ -92,10 +92,10 @@ ssh_connection_sup([{_, Child, _, [ssh_connection_sup]} | _]) -> ssh_connection_sup([_ | Rest]) -> ssh_connection_sup(Rest). -ssh_channel_sup([{_, Child, _, [ssh_channel_sup]} | _]) -> +ssh_daemon_channel_sup([{_, Child, _, [ssh_daemon_channel_sup]} | _]) -> Child; -ssh_channel_sup([_ | Rest]) -> - ssh_channel_sup(Rest). +ssh_daemon_channel_sup([_ | Rest]) -> + ssh_daemon_channel_sup(Rest). -- cgit v1.2.3 From d2f4de0a8e36e6a25cfd7446ea6fc3623b7f1495 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 26 Apr 2018 11:50:11 +0200 Subject: ssh: ssh_daemon_channel replaced by ssh_server_channel --- lib/ssh/src/Makefile | 42 ++++++++++++----------- lib/ssh/src/ssh.app.src | 3 +- lib/ssh/src/ssh_cli.erl | 6 ++-- lib/ssh/src/ssh_connection.erl | 4 +-- lib/ssh/src/ssh_daemon_channel.erl | 4 +-- lib/ssh/src/ssh_daemon_channel_sup.erl | 62 ---------------------------------- lib/ssh/src/ssh_info.erl | 8 ++--- lib/ssh/src/ssh_server_channel.erl | 55 ++++++++++++++++++++++++++++++ lib/ssh/src/ssh_server_channel_sup.erl | 62 ++++++++++++++++++++++++++++++++++ lib/ssh/src/ssh_sftpd.erl | 2 +- lib/ssh/src/ssh_shell.erl | 6 ++-- lib/ssh/src/ssh_subsystem_sup.erl | 12 +++---- 12 files changed, 162 insertions(+), 104 deletions(-) delete mode 100644 lib/ssh/src/ssh_daemon_channel_sup.erl create mode 100644 lib/ssh/src/ssh_server_channel.erl create mode 100644 lib/ssh/src/ssh_server_channel_sup.erl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index a2cefe7c15..1665e8b554 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -40,42 +40,43 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssh-$(VSN) # Behaviour (api) modules are first so they are compiled when # the compiler reaches a callback module using them. BEHAVIOUR_MODULES= \ - ssh_sftpd_file_api \ - ssh_channel \ - ssh_daemon_channel \ ssh_client_key_api \ - ssh_server_key_api + ssh_daemon_channel \ + ssh_server_channel \ + ssh_server_key_api \ + ssh_sftpd_file_api \ + ssh_channel MODULES= \ ssh \ - ssh_sup \ - sshc_sup \ - sshd_sup \ - ssh_options \ - ssh_connection_sup \ - ssh_connection \ - ssh_connection_handler \ - ssh_dbg \ - ssh_shell \ - ssh_system_sup \ - ssh_subsystem_sup \ - ssh_daemon_channel_sup \ - ssh_acceptor_sup \ ssh_acceptor \ + ssh_acceptor_sup \ ssh_app \ ssh_auth\ ssh_bits \ ssh_cli \ + ssh_connection \ + ssh_connection_handler \ + ssh_connection_sup \ + ssh_dbg \ ssh_file \ - ssh_io \ ssh_info \ + ssh_io \ ssh_message \ ssh_no_io \ + ssh_options \ + ssh_server_channel_sup \ ssh_sftp \ ssh_sftpd \ ssh_sftpd_file\ + ssh_shell \ + ssh_subsystem_sup \ + ssh_sup \ + ssh_system_sup \ ssh_transport \ - ssh_xfer + ssh_xfer \ + sshc_sup \ + sshd_sup HRL_FILES = @@ -169,7 +170,7 @@ $(EBIN)/ssh_connection_handler.$(EMULATOR): ssh_connection_handler.erl ssh.hrl \ $(EBIN)/ssh_shell.$(EMULATOR): ssh_shell.erl ssh_connect.hrl $(EBIN)/ssh_system_sup.$(EMULATOR): ssh_system_sup.erl ssh.hrl $(EBIN)/ssh_subsystem_sup.$(EMULATOR): ssh_subsystem_sup.erl -$(EBIN)/ssh_daemon_channel_sup.$(EMULATOR): ssh_daemon_channel_sup.erl +$(EBIN)/ssh_server_channel_sup.$(EMULATOR): ssh_server_channel_sup.erl $(EBIN)/ssh_acceptor_sup.$(EMULATOR): ssh_acceptor_sup.erl ssh.hrl $(EBIN)/ssh_acceptor.$(EMULATOR): ssh_acceptor.erl ssh.hrl $(EBIN)/ssh_app.$(EMULATOR): ssh_app.erl @@ -210,6 +211,7 @@ $(EBIN)/ssh_xfer.$(EMULATOR): ssh_xfer.erl ssh.hrl ssh_xfer.hrl $(EBIN)/ssh_sftpd_file_api.$(EMULATOR): ssh_sftpd_file_api.erl $(EBIN)/ssh_channel.$(EMULATOR): ssh_channel.erl ssh_connect.hrl $(EBIN)/ssh_daemon_channel.$(EMULATOR): ssh_daemon_channel.erl +$(EBIN)/ssh_server_channel.$(EMULATOR): ssh_server_channel.erl $(EBIN)/ssh_client_key_api.$(EMULATOR): ssh_client_key_api.erl \ ../../public_key/include/public_key.hrl \ ../../public_key/include/OTP-PUB-KEY.hrl \ diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index b935ea94d5..897235e054 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -14,7 +14,6 @@ ssh_cli, ssh_client_key_api, ssh_channel, - ssh_daemon_channel_sup, ssh_connection, ssh_connection_handler, ssh_connection_sup, @@ -27,6 +26,8 @@ ssh_io, ssh_info, ssh_no_io, + ssh_server_channel, + ssh_server_channel_sup, ssh_server_key_api, ssh_sftp, ssh_sftpd, diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 31f6194bf9..fcc1d3d59f 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -25,12 +25,12 @@ -module(ssh_cli). --behaviour(ssh_daemon_channel). +-behaviour(ssh_server_channel). -include("ssh.hrl"). -include("ssh_connect.hrl"). -%% ssh_daemon_channel callbacks +%% ssh_server_channel callbacks -export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). -export([dbg_trace/3]). @@ -47,7 +47,7 @@ }). %%==================================================================== -%% ssh_daemon_channel callbacks +%% ssh_server_channel callbacks %%==================================================================== %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 0a07ea778d..cff9ec3a61 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -822,14 +822,14 @@ start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), case max_num_channels_not_exceeded(ChannelSup, Opts) of true -> - ssh_daemon_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); + ssh_server_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); false -> throw(max_num_channels_exceeded) end. max_num_channels_not_exceeded(ChannelSup, Opts) -> MaxNumChannels = ?GET_OPT(max_channels, Opts), - NumChannels = length([x || {_,_,worker,[ssh_daemon_channel]} <- + NumChannels = length([x || {_,_,worker,[ssh_server_channel]} <- supervisor:which_children(ChannelSup)]), %% Note that NumChannels is BEFORE starting a new one NumChannels < MaxNumChannels. diff --git a/lib/ssh/src/ssh_daemon_channel.erl b/lib/ssh/src/ssh_daemon_channel.erl index e1d6906bbc..fdb6c10971 100644 --- a/lib/ssh/src/ssh_daemon_channel.erl +++ b/lib/ssh/src/ssh_daemon_channel.erl @@ -48,8 +48,8 @@ ]). start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). + ssh_server_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). get_print_info(Pid) -> - ssh_channel:get_print_info(Pid). + ssh_server_channel:get_print_info(Pid). diff --git a/lib/ssh/src/ssh_daemon_channel_sup.erl b/lib/ssh/src/ssh_daemon_channel_sup.erl deleted file mode 100644 index cd86347501..0000000000 --- a/lib/ssh/src/ssh_daemon_channel_sup.erl +++ /dev/null @@ -1,62 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - -%% -%%---------------------------------------------------------------------- -%% Purpose: Ssh channel supervisor. -%%---------------------------------------------------------------------- --module(ssh_daemon_channel_sup). - --behaviour(supervisor). - --export([start_link/1, start_child/5]). - -%% Supervisor callback --export([init/1]). - -%%%========================================================================= -%%% Internal API -%%%========================================================================= -start_link(Args) -> - supervisor:start_link(?MODULE, [Args]). - -start_child(Sup, Callback, Id, Args, Exec) -> - ChildSpec = - #{id => make_ref(), - start => {ssh_daemon_channel, start_link, [self(), Id, Callback, Args, Exec]}, - restart => temporary, - type => worker, - modules => [ssh_daemon_channel] - }, - supervisor:start_child(Sup, ChildSpec). - -%%%========================================================================= -%%% Supervisor callback -%%%========================================================================= -init(_Args) -> - RestartStrategy = one_for_one, - MaxR = 10, - MaxT = 3600, - Children = [], - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. - -%%%========================================================================= -%%% Internal functions -%%%========================================================================= diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl index e1613f0599..ee244f1432 100644 --- a/lib/ssh/src/ssh_info.erl +++ b/lib/ssh/src/ssh_info.erl @@ -140,15 +140,15 @@ print_system_sup({{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, Pid, superv -print_channels({{server,ssh_daemon_channel_sup,_,_},Pid,supervisor,[ssh_daemon_channel_sup]}) when is_pid(Pid) -> +print_channels({{server,ssh_server_channel_sup,_,_},Pid,supervisor,[ssh_server_channel_sup]}) when is_pid(Pid) -> Children = supervisor:which_children(Pid), - ChannelPids = [P || {R,P,worker,[ssh_daemon_channel]} <- Children, + ChannelPids = [P || {R,P,worker,[ssh_server_channel]} <- Children, is_pid(P), is_reference(R)], case ChannelPids of [] -> io_lib:format(?INDENT?INDENT"No channels~n",[]); [Ch1Pid|_] -> - {{ConnManager,_}, _Str} = ssh_daemon_channel:get_print_info(Ch1Pid), + {{ConnManager,_}, _Str} = ssh_server_channel:get_print_info(Ch1Pid), {{_,Remote},_} = ssh_connection_handler:get_print_info(ConnManager), [io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p~n",[fmt_host_port(Remote),ConnManager]), lists:map(fun print_ch/1, ChannelPids) @@ -159,7 +159,7 @@ print_channels({{server,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_s print_ch(Pid) -> try - {{ConnManager,ChannelID}, Str} = ssh_daemon_channel:get_print_info(Pid), + {{ConnManager,ChannelID}, Str} = ssh_server_channel:get_print_info(Pid), {_LocalRemote,StrM} = ssh_connection_handler:get_print_info(ConnManager), io_lib:format(?INDENT?INDENT?INDENT"ch ~p ~p: ~s ~s~n",[ChannelID, Pid, StrM, Str]) catch diff --git a/lib/ssh/src/ssh_server_channel.erl b/lib/ssh/src/ssh_server_channel.erl new file mode 100644 index 0000000000..117b7855e2 --- /dev/null +++ b/lib/ssh/src/ssh_server_channel.erl @@ -0,0 +1,55 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Description: a gen_server implementing a simple +%% terminal (using the group module) for a CLI +%% over SSH + +-module(ssh_server_channel). + +%% API to server side channel that can be pluged into the erlang ssh daemeon +-callback init(Args :: term()) -> + {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | + {stop, Reason :: term()} | ignore. + +-callback terminate(Reason :: (normal | shutdown | {shutdown, term()} | + term()), + State :: term()) -> + term(). + +-callback handle_msg(Msg ::term(), State :: term()) -> + {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. +-callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, + State::term()) -> {ok, State::term()} | + {stop, ChannelId::ssh:channel_id(), + State::term()}. + +%%% Internal API +-export([start_link/5, + get_print_info/1 + ]). + +start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> + ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). + + +get_print_info(Pid) -> + ssh_channel:get_print_info(Pid). diff --git a/lib/ssh/src/ssh_server_channel_sup.erl b/lib/ssh/src/ssh_server_channel_sup.erl new file mode 100644 index 0000000000..70799db714 --- /dev/null +++ b/lib/ssh/src/ssh_server_channel_sup.erl @@ -0,0 +1,62 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Ssh channel supervisor. +%%---------------------------------------------------------------------- +-module(ssh_server_channel_sup). + +-behaviour(supervisor). + +-export([start_link/1, start_child/5]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% Internal API +%%%========================================================================= +start_link(Args) -> + supervisor:start_link(?MODULE, [Args]). + +start_child(Sup, Callback, Id, Args, Exec) -> + ChildSpec = + #{id => make_ref(), + start => {ssh_server_channel, start_link, [self(), Id, Callback, Args, Exec]}, + restart => temporary, + type => worker, + modules => [ssh_server_channel] + }, + supervisor:start_child(Sup, ChildSpec). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_Args) -> + RestartStrategy = one_for_one, + MaxR = 10, + MaxT = 3600, + Children = [], + {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. + +%%%========================================================================= +%%% Internal functions +%%%========================================================================= diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index fda9a38a43..cb2eab1fec 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -24,7 +24,7 @@ -module(ssh_sftpd). --behaviour(ssh_daemon_channel). +-behaviour(ssh_server_channel). -include_lib("kernel/include/file.hrl"). diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl index 6470f4c492..084daa6821 100644 --- a/lib/ssh/src/ssh_shell.erl +++ b/lib/ssh/src/ssh_shell.erl @@ -27,9 +27,9 @@ %%% As this is an user interactive client it behaves like a daemon %%% channel inspite of it being a client. --behaviour(ssh_daemon_channel). +-behaviour(ssh_server_channel). -%% ssh_daemon_channel callbacks +%% ssh_server_channel callbacks -export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]). %% Spawn export @@ -46,7 +46,7 @@ ). %%==================================================================== -%% ssh_daemon_channel callbacks +%% ssh_server_channel callbacks %%==================================================================== %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index f1e74a24ac..f80be7f20b 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -48,7 +48,7 @@ connection_supervisor(SupPid) -> channel_supervisor(SupPid) -> Children = supervisor:which_children(SupPid), - ssh_daemon_channel_sup(Children). + ssh_server_channel_sup(Children). %%%========================================================================= %%% Supervisor callback @@ -78,8 +78,8 @@ ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> }. ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> - #{id => id(Role, ssh_daemon_channel_sup, Address, Port), - start => {ssh_daemon_channel_sup, start_link, [Options]}, + #{id => id(Role, ssh_server_channel_sup, Address, Port), + start => {ssh_server_channel_sup, start_link, [Options]}, restart => temporary, type => supervisor }. @@ -92,10 +92,10 @@ ssh_connection_sup([{_, Child, _, [ssh_connection_sup]} | _]) -> ssh_connection_sup([_ | Rest]) -> ssh_connection_sup(Rest). -ssh_daemon_channel_sup([{_, Child, _, [ssh_daemon_channel_sup]} | _]) -> +ssh_server_channel_sup([{_, Child, _, [ssh_server_channel_sup]} | _]) -> Child; -ssh_daemon_channel_sup([_ | Rest]) -> - ssh_daemon_channel_sup(Rest). +ssh_server_channel_sup([_ | Rest]) -> + ssh_server_channel_sup(Rest). -- cgit v1.2.3 From c2ca477c85e0e88732f634ddfb01ac675a97dddb Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 26 Apr 2018 13:43:45 +0200 Subject: ssh: ssh_channel replaced by ssh_client_channel --- lib/ssh/src/Makefile | 4 +- lib/ssh/src/ssh.app.src | 1 + lib/ssh/src/ssh.erl | 4 +- lib/ssh/src/ssh_channel.erl | 382 +-------------------------- lib/ssh/src/ssh_client_channel.erl | 456 +++++++++++++++++++++++++++++++++ lib/ssh/src/ssh_connection.erl | 46 ++-- lib/ssh/src/ssh_connection_handler.erl | 44 ++-- lib/ssh/src/ssh_server_channel.erl | 4 +- lib/ssh/src/ssh_sftp.erl | 18 +- 9 files changed, 527 insertions(+), 432 deletions(-) create mode 100644 lib/ssh/src/ssh_client_channel.erl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 1665e8b554..5e4efb6b99 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -45,7 +45,8 @@ BEHAVIOUR_MODULES= \ ssh_server_channel \ ssh_server_key_api \ ssh_sftpd_file_api \ - ssh_channel + ssh_channel \ + ssh_client_channel MODULES= \ ssh \ @@ -209,6 +210,7 @@ $(EBIN)/ssh_transport.$(EMULATOR): ssh_transport.erl \ ssh_transport.hrl ssh.hrl $(EBIN)/ssh_xfer.$(EMULATOR): ssh_xfer.erl ssh.hrl ssh_xfer.hrl $(EBIN)/ssh_sftpd_file_api.$(EMULATOR): ssh_sftpd_file_api.erl +$(EBIN)/ssh_client_channel.$(EMULATOR): ssh_client_channel.erl ssh_connect.hrl $(EBIN)/ssh_channel.$(EMULATOR): ssh_channel.erl ssh_connect.hrl $(EBIN)/ssh_daemon_channel.$(EMULATOR): ssh_daemon_channel.erl $(EBIN)/ssh_server_channel.$(EMULATOR): ssh_server_channel.erl diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 897235e054..410061cded 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -12,6 +12,7 @@ ssh_message, ssh_bits, ssh_cli, + ssh_client_channel, ssh_client_key_api, ssh_channel, ssh_connection, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 209f53d249..7ddb1ca5be 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -426,9 +426,9 @@ start_shell({ok, ConnectionRef}) -> Args = [{channel_cb, ssh_shell}, {init_args,[ConnectionRef, ChannelId]}, {cm, ConnectionRef}, {channel_id, ChannelId}], - {ok, State} = ssh_channel:init([Args]), + {ok, State} = ssh_client_channel:init([Args]), try - ssh_channel:enter_loop(State) + ssh_client_channel:enter_loop(State) catch exit:normal -> ok diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index 359e29fdbe..81c495a815 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -56,401 +56,37 @@ State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --behaviour(gen_server). - %%% API -export([start/4, start/5, start_link/4, start_link/5, call/2, call/3, cast/2, reply/2, enter_loop/1]). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - -%% Internal application API --export([cache_create/0, cache_lookup/2, cache_update/2, - cache_delete/1, cache_delete/2, cache_foldl/3, - cache_info/2, cache_find/2, - get_print_info/1]). - --export([dbg_trace/3]). - --record(state, { - cm, - channel_cb, - channel_state, - channel_id, - close_sent = false - }). - %%==================================================================== %% API %%==================================================================== call(ChannelPid, Msg) -> - call(ChannelPid, Msg, infinity). + ssh_client_channel:call(ChannelPid, Msg). call(ChannelPid, Msg, TimeOute) -> - try gen_server:call(ChannelPid, Msg, TimeOute) of - Result -> - Result - catch - exit:{noproc, _} -> - {error, closed}; - exit:{normal, _} -> - {error, closed}; - exit:{shutdown, _} -> - {error, closed}; - exit:{{shutdown, _}, _} -> - {error, closed}; - exit:{timeout, _} -> - {error, timeout} - end. + ssh_client_channel:call(ChannelPid, Msg, TimeOute). cast(ChannelPid, Msg) -> - gen_server:cast(ChannelPid, Msg). - + ssh_client_channel:cast(ChannelPid, Msg). reply(From, Msg) -> - gen_server:reply(From, Msg). + ssh_client_channel:reply(From, Msg). -%%==================================================================== -%% Internal application API -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- start(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> - start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + ssh_client_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs). start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - Options = [{channel_cb, CallBack}, - {channel_id, ChannelId}, - {init_args, CbInitArgs}, - {cm, ConnectionManager}, - {exec, Exec}], - gen_server:start(?MODULE, [Options], []). + ssh_client_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> - start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + ssh_client_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs). start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - Options = [{channel_cb, CallBack}, - {channel_id, ChannelId}, - {init_args, CbInitArgs}, - {cm, ConnectionManager}, - {exec, Exec}], - gen_server:start_link(?MODULE, [Options], []). + ssh_client_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). enter_loop(State) -> - gen_server:enter_loop(?MODULE, [], State). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Options]) -> - Cb = proplists:get_value(channel_cb, Options), - ConnectionManager = proplists:get_value(cm, Options), - ChannelId = proplists:get_value(channel_id, Options), - process_flag(trap_exit, true), - try Cb:init(channel_cb_init_args(Options)) of - {ok, ChannelState} -> - State = #state{cm = ConnectionManager, - channel_cb = Cb, - channel_id = ChannelId, - channel_state = ChannelState}, - self() ! {ssh_channel_up, ChannelId, ConnectionManager}, - {ok, State}; - {ok, ChannelState, Timeout} -> - State = #state{cm = ConnectionManager, - channel_cb = Cb, - channel_id = ChannelId, - channel_state = ChannelState}, - self() ! {ssh_channel_up, ChannelId, ConnectionManager}, - {ok, State, Timeout}; - {stop, Why} -> - {stop, Why} - catch - _:Reason -> - {stop, Reason} - end. - -channel_cb_init_args(Options) -> - case proplists:get_value(exec, Options) of - undefined -> - proplists:get_value(init_args, Options); - Exec -> - proplists:get_value(init_args, Options) ++ [Exec] - end. - -%%-------------------------------------------------------------------- -%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%%-------------------------------------------------------------------- -handle_call(get_print_info, _From, State) -> - Reply = - {{State#state.cm, - State#state.channel_id}, - io_lib:format('CB=~p',[State#state.channel_cb]) - }, - {reply, Reply, State}; - -handle_call(Request, From, #state{channel_cb = Module, - channel_state = ChannelState} = State) -> - try Module:handle_call(Request, From, ChannelState) of - Result -> - handle_cb_result(Result, State) - catch - error:{undef, _} -> - {noreply, State} - end. - - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast(Msg, #state{channel_cb = Module, - channel_state = ChannelState} = State) -> - - try Module:handle_cast(Msg, ChannelState) of - Result -> - handle_cb_result(Result, State) - catch - error:{undef, _} -> - {noreply, State} - end. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info({ssh_cm, ConnectionManager, {closed, _ChannelId}}, - #state{cm = ConnectionManager, - close_sent = true} = State) -> - {stop, normal, State}; -handle_info({ssh_cm, ConnectionManager, {closed, ChannelId}}, - #state{cm = ConnectionManager, - close_sent = false} = State) -> - %% To be on the safe side, i.e. the manager has already been terminated. - (catch ssh_connection:close(ConnectionManager, ChannelId)), - {stop, normal, State#state{close_sent = true}}; - -handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager, - channel_cb = Module, - channel_state = ChannelState0} = State) -> - case Module:handle_ssh_msg(Msg, ChannelState0) of - {ok, ChannelState} -> - adjust_window(Msg), - {noreply, State#state{channel_state = ChannelState}}; - {ok, ChannelState, Timeout} -> - adjust_window(Msg), - {noreply, State#state{channel_state = ChannelState}, Timeout}; - {stop, ChannelId, ChannelState} -> - catch ssh_connection:close(ConnectionManager, ChannelId), - {stop, normal, State#state{close_sent = true, - channel_state = ChannelState}} - end; - -handle_info(Msg, #state{cm = ConnectionManager, channel_cb = Module, - channel_state = ChannelState0} = State) -> - case Module:handle_msg(Msg, ChannelState0) of - {ok, ChannelState} -> - {noreply, State#state{channel_state = ChannelState}}; - {ok, ChannelState, Timeout} -> - {noreply, State#state{channel_state = ChannelState}, Timeout}; - {stop, Reason, ChannelState} when is_atom(Reason)-> - {stop, Reason, State#state{close_sent = true, - channel_state = ChannelState}}; - {stop, ChannelId, ChannelState} -> - Reason = - case Msg of - {'EXIT', _Pid, shutdown} -> - shutdown; - _ -> - normal - end, - (catch ssh_connection:close(ConnectionManager, ChannelId)), - {stop, Reason, State#state{close_sent = true, - channel_state = ChannelState}} - end. - -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -terminate(Reason, #state{cm = ConnectionManager, - channel_id = ChannelId, - close_sent = false} = State) -> - catch ssh_connection:close(ConnectionManager, ChannelId), - terminate(Reason, State#state{close_sent = true}); -terminate(_, #state{channel_cb = Cb, channel_state = ChannelState}) -> - catch Cb:terminate(Cb, ChannelState), - ok. - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(OldVsn, #state{channel_cb = Module, - channel_state = ChannelState0} = State, Extra) -> - {ok, ChannelState} = Module:code_change(OldVsn, ChannelState0, Extra), - {ok, State#state{channel_state = ChannelState}}. - -%%==================================================================== -%% Internal application API -%%==================================================================== -cache_create() -> - ets:new(cm_tab, [set,{keypos, #channel.local_id}]). - -cache_lookup(Cache, Key) -> - case ets:lookup(Cache, Key) of - [Channel] -> - Channel; - [] -> - undefined - end. - -cache_update(Cache, #channel{local_id = Id} = Entry) when Id =/= undefined -> - ets:insert(Cache, Entry). - -cache_delete(Cache, Key) -> - ets:delete(Cache, Key). - -cache_delete(Cache) -> - ets:delete(Cache). - -cache_foldl(Fun, Acc, Cache) -> - ets:foldl(Fun, Acc, Cache). - -cache_info(num_entries, Cache) -> - proplists:get_value(size, ets:info(Cache)). - -cache_find(ChannelPid, Cache) -> - case ets:match_object(Cache, #channel{user = ChannelPid}) of - [] -> - undefined; - [Channel] -> - Channel - end. - -get_print_info(Pid) -> - call(Pid, get_print_info, 1000). - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -handle_cb_result({reply, Reply, ChannelState}, State) -> - {reply, Reply, State#state{channel_state = ChannelState}}; -handle_cb_result({reply, Reply, ChannelState, Timeout}, State) -> - {reply, Reply,State#state{channel_state = ChannelState}, Timeout}; -handle_cb_result({noreply, ChannelState}, State) -> - {noreply, State#state{channel_state = ChannelState}}; -handle_cb_result({noreply, ChannelState, Timeout}, State) -> - {noreply, State#state{channel_state = ChannelState}, Timeout}; -handle_cb_result({stop, Reason, Reply, ChannelState}, State) -> - {stop, Reason, Reply, State#state{channel_state = ChannelState}}; -handle_cb_result({stop, Reason, ChannelState}, State) -> - {stop, Reason, State#state{channel_state = ChannelState}}. - -adjust_window({ssh_cm, ConnectionManager, - {data, ChannelId, _, Data}}) -> - ssh_connection:adjust_window(ConnectionManager, ChannelId, size(Data)); -adjust_window(_) -> - ok. - - -%%%################################################################ -%%%# -%%%# Tracing -%%%# - -dbg_trace(points, _, _) -> [terminate, channels, channel_events]; - - -dbg_trace(flags, channels, A) -> [c] ++ dbg_trace(flags, terminate, A); -dbg_trace(on, channels, A) -> dbg:tp(?MODULE, init, 1, x), - dbg_trace(on, terminate, A); -dbg_trace(off, channels, A) -> dbg:ctpg(?MODULE, init, 1), - dbg_trace(off, terminate, A); -dbg_trace(format, channels, {call, {?MODULE,init, [[KVs]]}}) -> - ["Server Channel Starting:\n", - io_lib:format("Connection: ~p, ChannelId: ~p, CallBack: ~p\nCallBack init args = ~p", - [proplists:get_value(K,KVs) || K <- [cm, channel_id, channel_cb]] - ++ [channel_cb_init_args(KVs)]) - ]; -dbg_trace(format, channels, {return_from, {?MODULE,init,1}, {stop,Reason}}) -> - ["Server Channel Start FAILED!\n", - io_lib:format("Reason = ~p", [Reason]) - ]; -dbg_trace(format, channels, F) -> - dbg_trace(format, terminate, F); - - -dbg_trace(flags, terminate, _) -> [c]; -dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); -dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); -dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> - ["Server Channel Terminating:\n", - io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) - ]; - -dbg_trace(flags, channel_events, _) -> [c]; -dbg_trace(on, channel_events, _) -> dbg:tp(?MODULE, handle_call, 3, x), - dbg:tp(?MODULE, handle_cast, 2, x), - dbg:tp(?MODULE, handle_info, 2, x); -dbg_trace(off, channel_events, _) -> dbg:ctpg(?MODULE, handle_call, 3), - dbg:ctpg(?MODULE, handle_cast, 2), - dbg:ctpg(?MODULE, handle_info, 2); -dbg_trace(format, channel_events, {call, {?MODULE,handle_call, [Call,From,State]}}) -> - [hdr("is called", State), - io_lib:format("From: ~p~nCall: ~p~n", [From, Call]) - ]; -dbg_trace(format, channel_events, {return_from, {?MODULE,handle_call,3}, Ret}) -> - ["Server Channel call returned:\n", - io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) - ]; -dbg_trace(format, channel_events, {call, {?MODULE,handle_cast, [Cast,State]}}) -> - [hdr("got cast", State), - io_lib:format("Cast: ~p~n", [Cast]) - ]; -dbg_trace(format, channel_events, {return_from, {?MODULE,handle_cast,2}, Ret}) -> - ["Server Channel cast returned:\n", - io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) - ]; -dbg_trace(format, channel_events, {call, {?MODULE,handle_info, [Info,State]}}) -> - [hdr("got info", State), - io_lib:format("Info: ~p~n", [Info]) - ]; -dbg_trace(format, channel_events, {return_from, {?MODULE,handle_info,2}, Ret}) -> - ["Server Channel info returned:\n", - io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) - ]. - -hdr(Title, S) -> - io_lib:format("Server Channel (Id=~p, CB=~p) ~s:\n", [S#state.channel_id, S#state.channel_cb, Title]). - -?wr_record(state). - - + ssh_client_channel:enter_loop(State). diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl new file mode 100644 index 0000000000..f20007baaf --- /dev/null +++ b/lib/ssh/src/ssh_client_channel.erl @@ -0,0 +1,456 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssh_client_channel). + +-include("ssh.hrl"). +-include("ssh_connect.hrl"). + +-callback init(Args :: term()) -> + {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | + {stop, Reason :: term()} | ignore. +-callback handle_call(Request :: term(), From :: {pid(), Tag :: term()}, + State :: term()) -> + {reply, Reply :: term(), NewState :: term()} | + {reply, Reply :: term(), NewState :: term(), timeout() | hibernate} | + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate} | + {stop, Reason :: term(), Reply :: term(), NewState :: term()} | + {stop, Reason :: term(), NewState :: term()}. +-callback handle_cast(Request :: term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate} | + {stop, Reason :: term(), NewState :: term()}. + +-callback terminate(Reason :: (normal | shutdown | {shutdown, term()} | + term()), + State :: term()) -> + term(). +-callback code_change(OldVsn :: (term() | {down, term()}), State :: term(), + Extra :: term()) -> + {ok, NewState :: term()} | {error, Reason :: term()}. + +-callback handle_msg(Msg ::term(), State :: term()) -> + {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. + +-callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, + State::term()) -> {ok, State::term()} | + {stop, ChannelId::ssh:channel_id(), + State::term()}. +-behaviour(gen_server). + +%%% API +-export([start/4, start/5, start_link/4, start_link/5, call/2, call/3, + cast/2, reply/2, enter_loop/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%% Internal application API +-export([cache_create/0, cache_lookup/2, cache_update/2, + cache_delete/1, cache_delete/2, cache_foldl/3, + cache_info/2, cache_find/2, + get_print_info/1]). + +-export([dbg_trace/3]). + +-record(state, { + cm, + channel_cb, + channel_state, + channel_id, + close_sent = false + }). + +%%==================================================================== +%% API +%%==================================================================== + +call(ChannelPid, Msg) -> + call(ChannelPid, Msg, infinity). + +call(ChannelPid, Msg, TimeOute) -> + try gen_server:call(ChannelPid, Msg, TimeOute) of + Result -> + Result + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{shutdown, _} -> + {error, closed}; + exit:{{shutdown, _}, _} -> + {error, closed}; + exit:{timeout, _} -> + {error, timeout} + end. + +cast(ChannelPid, Msg) -> + gen_server:cast(ChannelPid, Msg). + + +reply(From, Msg) -> + gen_server:reply(From, Msg). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- +start(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> + start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + +start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> + Options = [{channel_cb, CallBack}, + {channel_id, ChannelId}, + {init_args, CbInitArgs}, + {cm, ConnectionManager}, + {exec, Exec}], + gen_server:start(?MODULE, [Options], []). + +start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> + start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + +start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> + Options = [{channel_cb, CallBack}, + {channel_id, ChannelId}, + {init_args, CbInitArgs}, + {cm, ConnectionManager}, + {exec, Exec}], + gen_server:start_link(?MODULE, [Options], []). + +enter_loop(State) -> + gen_server:enter_loop(?MODULE, [], State). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([Options]) -> + Cb = proplists:get_value(channel_cb, Options), + ConnectionManager = proplists:get_value(cm, Options), + ChannelId = proplists:get_value(channel_id, Options), + process_flag(trap_exit, true), + try Cb:init(channel_cb_init_args(Options)) of + {ok, ChannelState} -> + State = #state{cm = ConnectionManager, + channel_cb = Cb, + channel_id = ChannelId, + channel_state = ChannelState}, + self() ! {ssh_channel_up, ChannelId, ConnectionManager}, + {ok, State}; + {ok, ChannelState, Timeout} -> + State = #state{cm = ConnectionManager, + channel_cb = Cb, + channel_id = ChannelId, + channel_state = ChannelState}, + self() ! {ssh_channel_up, ChannelId, ConnectionManager}, + {ok, State, Timeout}; + {stop, Why} -> + {stop, Why} + catch + _:Reason -> + {stop, Reason} + end. + +channel_cb_init_args(Options) -> + case proplists:get_value(exec, Options) of + undefined -> + proplists:get_value(init_args, Options); + Exec -> + proplists:get_value(init_args, Options) ++ [Exec] + end. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call(get_print_info, _From, State) -> + Reply = + {{State#state.cm, + State#state.channel_id}, + io_lib:format('CB=~p',[State#state.channel_cb]) + }, + {reply, Reply, State}; + +handle_call(Request, From, #state{channel_cb = Module, + channel_state = ChannelState} = State) -> + try Module:handle_call(Request, From, ChannelState) of + Result -> + handle_cb_result(Result, State) + catch + error:{undef, _} -> + {noreply, State} + end. + + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast(Msg, #state{channel_cb = Module, + channel_state = ChannelState} = State) -> + + try Module:handle_cast(Msg, ChannelState) of + Result -> + handle_cb_result(Result, State) + catch + error:{undef, _} -> + {noreply, State} + end. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info({ssh_cm, ConnectionManager, {closed, _ChannelId}}, + #state{cm = ConnectionManager, + close_sent = true} = State) -> + {stop, normal, State}; +handle_info({ssh_cm, ConnectionManager, {closed, ChannelId}}, + #state{cm = ConnectionManager, + close_sent = false} = State) -> + %% To be on the safe side, i.e. the manager has already been terminated. + (catch ssh_connection:close(ConnectionManager, ChannelId)), + {stop, normal, State#state{close_sent = true}}; + +handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager, + channel_cb = Module, + channel_state = ChannelState0} = State) -> + case Module:handle_ssh_msg(Msg, ChannelState0) of + {ok, ChannelState} -> + adjust_window(Msg), + {noreply, State#state{channel_state = ChannelState}}; + {ok, ChannelState, Timeout} -> + adjust_window(Msg), + {noreply, State#state{channel_state = ChannelState}, Timeout}; + {stop, ChannelId, ChannelState} -> + catch ssh_connection:close(ConnectionManager, ChannelId), + {stop, normal, State#state{close_sent = true, + channel_state = ChannelState}} + end; + +handle_info(Msg, #state{cm = ConnectionManager, channel_cb = Module, + channel_state = ChannelState0} = State) -> + case Module:handle_msg(Msg, ChannelState0) of + {ok, ChannelState} -> + {noreply, State#state{channel_state = ChannelState}}; + {ok, ChannelState, Timeout} -> + {noreply, State#state{channel_state = ChannelState}, Timeout}; + {stop, Reason, ChannelState} when is_atom(Reason)-> + {stop, Reason, State#state{close_sent = true, + channel_state = ChannelState}}; + {stop, ChannelId, ChannelState} -> + Reason = + case Msg of + {'EXIT', _Pid, shutdown} -> + shutdown; + _ -> + normal + end, + (catch ssh_connection:close(ConnectionManager, ChannelId)), + {stop, Reason, State#state{close_sent = true, + channel_state = ChannelState}} + end. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(Reason, #state{cm = ConnectionManager, + channel_id = ChannelId, + close_sent = false} = State) -> + catch ssh_connection:close(ConnectionManager, ChannelId), + terminate(Reason, State#state{close_sent = true}); +terminate(_, #state{channel_cb = Cb, channel_state = ChannelState}) -> + catch Cb:terminate(Cb, ChannelState), + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(OldVsn, #state{channel_cb = Module, + channel_state = ChannelState0} = State, Extra) -> + {ok, ChannelState} = Module:code_change(OldVsn, ChannelState0, Extra), + {ok, State#state{channel_state = ChannelState}}. + +%%==================================================================== +%% Internal application API +%%==================================================================== +cache_create() -> + ets:new(cm_tab, [set,{keypos, #channel.local_id}]). + +cache_lookup(Cache, Key) -> + case ets:lookup(Cache, Key) of + [Channel] -> + Channel; + [] -> + undefined + end. + +cache_update(Cache, #channel{local_id = Id} = Entry) when Id =/= undefined -> + ets:insert(Cache, Entry). + +cache_delete(Cache, Key) -> + ets:delete(Cache, Key). + +cache_delete(Cache) -> + ets:delete(Cache). + +cache_foldl(Fun, Acc, Cache) -> + ets:foldl(Fun, Acc, Cache). + +cache_info(num_entries, Cache) -> + proplists:get_value(size, ets:info(Cache)). + +cache_find(ChannelPid, Cache) -> + case ets:match_object(Cache, #channel{user = ChannelPid}) of + [] -> + undefined; + [Channel] -> + Channel + end. + +get_print_info(Pid) -> + call(Pid, get_print_info, 1000). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +handle_cb_result({reply, Reply, ChannelState}, State) -> + {reply, Reply, State#state{channel_state = ChannelState}}; +handle_cb_result({reply, Reply, ChannelState, Timeout}, State) -> + {reply, Reply,State#state{channel_state = ChannelState}, Timeout}; +handle_cb_result({noreply, ChannelState}, State) -> + {noreply, State#state{channel_state = ChannelState}}; +handle_cb_result({noreply, ChannelState, Timeout}, State) -> + {noreply, State#state{channel_state = ChannelState}, Timeout}; +handle_cb_result({stop, Reason, Reply, ChannelState}, State) -> + {stop, Reason, Reply, State#state{channel_state = ChannelState}}; +handle_cb_result({stop, Reason, ChannelState}, State) -> + {stop, Reason, State#state{channel_state = ChannelState}}. + +adjust_window({ssh_cm, ConnectionManager, + {data, ChannelId, _, Data}}) -> + ssh_connection:adjust_window(ConnectionManager, ChannelId, size(Data)); +adjust_window(_) -> + ok. + + +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [terminate, channels, channel_events]; + + +dbg_trace(flags, channels, A) -> [c] ++ dbg_trace(flags, terminate, A); +dbg_trace(on, channels, A) -> dbg:tp(?MODULE, init, 1, x), + dbg_trace(on, terminate, A); +dbg_trace(off, channels, A) -> dbg:ctpg(?MODULE, init, 1), + dbg_trace(off, terminate, A); +dbg_trace(format, channels, {call, {?MODULE,init, [[KVs]]}}) -> + ["Server Channel Starting:\n", + io_lib:format("Connection: ~p, ChannelId: ~p, CallBack: ~p\nCallBack init args = ~p", + [proplists:get_value(K,KVs) || K <- [cm, channel_id, channel_cb]] + ++ [channel_cb_init_args(KVs)]) + ]; +dbg_trace(format, channels, {return_from, {?MODULE,init,1}, {stop,Reason}}) -> + ["Server Channel Start FAILED!\n", + io_lib:format("Reason = ~p", [Reason]) + ]; +dbg_trace(format, channels, F) -> + dbg_trace(format, terminate, F); + + +dbg_trace(flags, terminate, _) -> [c]; +dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); +dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); +dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> + ["Server Channel Terminating:\n", + io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) + ]; + +dbg_trace(flags, channel_events, _) -> [c]; +dbg_trace(on, channel_events, _) -> dbg:tp(?MODULE, handle_call, 3, x), + dbg:tp(?MODULE, handle_cast, 2, x), + dbg:tp(?MODULE, handle_info, 2, x); +dbg_trace(off, channel_events, _) -> dbg:ctpg(?MODULE, handle_call, 3), + dbg:ctpg(?MODULE, handle_cast, 2), + dbg:ctpg(?MODULE, handle_info, 2); +dbg_trace(format, channel_events, {call, {?MODULE,handle_call, [Call,From,State]}}) -> + [hdr("is called", State), + io_lib:format("From: ~p~nCall: ~p~n", [From, Call]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_call,3}, Ret}) -> + ["Server Channel call returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]; +dbg_trace(format, channel_events, {call, {?MODULE,handle_cast, [Cast,State]}}) -> + [hdr("got cast", State), + io_lib:format("Cast: ~p~n", [Cast]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_cast,2}, Ret}) -> + ["Server Channel cast returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]; +dbg_trace(format, channel_events, {call, {?MODULE,handle_info, [Info,State]}}) -> + [hdr("got info", State), + io_lib:format("Info: ~p~n", [Info]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_info,2}, Ret}) -> + ["Server Channel info returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]. + +hdr(Title, S) -> + io_lib:format("Server Channel (Id=~p, CB=~p) ~s:\n", [S#state.channel_id, S#state.channel_cb, Title]). + +?wr_record(state). + + diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index cff9ec3a61..ed03b4e2ed 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -271,7 +271,7 @@ channel_data(ChannelId, DataType, Data, Connection, From) when is_list(Data)-> channel_data(ChannelId, DataType, Data, #connection{channel_cache = Cache} = Connection, From) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of + case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id, sent_close = false} = Channel0 -> {SendList, Channel} = update_send_window(Channel0#channel{flow_control = From}, DataType, @@ -303,9 +303,9 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, #connection{channel_cache = Cache} = Connection0, _) -> #channel{remote_id = undefined} = Channel = - ssh_channel:cache_lookup(Cache, ChannelId), + ssh_client_channel:cache_lookup(Cache, ChannelId), - ssh_channel:cache_update(Cache, Channel#channel{ + ssh_client_channel:cache_update(Cache, Channel#channel{ remote_id = RemoteId, recv_packet_size = max(32768, % rfc4254/5.2 min(PacketSz, Channel#channel.recv_packet_size) @@ -319,8 +319,8 @@ handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId, description = Descr, lang = Lang}, #connection{channel_cache = Cache} = Connection0, _) -> - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - ssh_channel:cache_delete(Cache, ChannelId), + Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), + ssh_client_channel:cache_delete(Cache, ChannelId), reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang}); handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _) -> @@ -335,10 +335,10 @@ handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _) - handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, #connection{channel_cache = Cache} = Connection0, _) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of + case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{sent_close = Closed, remote_id = RemoteId, flow_control = FlowControl} = Channel -> - ssh_channel:cache_delete(Cache, ChannelId), + ssh_client_channel:cache_delete(Cache, ChannelId), {CloseMsg, Connection} = reply_msg(Channel, Connection0, {closed, ChannelId}), ConnReplyMsgs = @@ -379,7 +379,7 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, bytes_to_add = Add}, #connection{channel_cache = Cache} = Connection, _) -> #channel{send_window_size = Size, remote_id = RemoteId} = - Channel0 = ssh_channel:cache_lookup(Cache, ChannelId), + Channel0 = ssh_client_channel:cache_lookup(Cache, ChannelId), {SendList, Channel} = %% TODO: Datatype 0 ? update_send_window(Channel0#channel{send_window_size = Size + Add}, @@ -455,7 +455,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, ?BOOLEAN(_Core), ?DEC_BIN(Err, _ErrLen), ?DEC_BIN(Lang, _LangLen)>> = Data, - Channel = ssh_channel:cache_lookup(Cache, ChannelId), + Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), RemoteId = Channel#channel.remote_id, {Reply, Connection} = reply_msg(Channel, Connection0, {exit_signal, ChannelId, @@ -500,7 +500,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, <> = Data, #channel{remote_id = RemoteId} = Channel0 = - ssh_channel:cache_lookup(Cache, ChannelId), + ssh_client_channel:cache_lookup(Cache, ChannelId), ReplyMsg = {subsystem, ChannelId, WantReply, binary_to_list(SsName)}, @@ -508,7 +508,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, {ok, Pid} = start_subsystem(SsName, Connection, Channel0, ReplyMsg), erlang:monitor(process, Pid), Channel = Channel0#channel{user = Pid}, - ssh_channel:cache_update(Cache, Channel), + ssh_client_channel:cache_update(Cache, Channel), Reply = {connection_reply, channel_success_msg(RemoteId)}, {[Reply], Connection} @@ -588,7 +588,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, want_reply = WantReply}, #connection{channel_cache = Cache} = Connection, _) -> if WantReply == true -> - case ssh_channel:cache_lookup(Cache, ChannelId) of + case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = RemoteId} -> FailMsg = channel_failure_msg(RemoteId), {[{connection_reply, FailMsg}], Connection}; @@ -631,14 +631,14 @@ handle_msg(#ssh_msg_disconnect{code = Code, %%% handle_stop(#connection{channel_cache = Cache} = Connection0) -> {Connection, Replies} = - ssh_channel:cache_foldl( + ssh_client_channel:cache_foldl( fun(Channel, {Connection1, Acc}) -> {Reply, Connection2} = reply_msg(Channel, Connection1, {closed, Channel#channel.local_id}), {Connection2, Reply ++ Acc} end, {Connection0, []}, Cache), - ssh_channel:cache_delete(Cache), + ssh_client_channel:cache_delete(Cache), {Replies, Connection}. %%%---------------------------------------------------------------- @@ -779,7 +779,7 @@ setup_session(#connection{channel_cache = Cache, send_buf = queue:new(), remote_id = RemoteId }, - ssh_channel:cache_update(Cache, Channel), + ssh_client_channel:cache_update(Cache, Channel), OpenConfMsg = channel_open_confirmation_msg(RemoteId, NewChannelID, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE), @@ -868,7 +868,7 @@ update_send_window(#channel{send_buf = SendBuffer} = Channel, DataType, Data, do_update_send_window(Channel0, Cache) -> {SendMsgs, Channel} = get_window(Channel0, []), - ssh_channel:cache_update(Cache, Channel), + ssh_client_channel:cache_update(Cache, Channel), {SendMsgs, Channel}. get_window(#channel{send_window_size = 0 @@ -919,13 +919,13 @@ flow_control(Channel, Cache) -> flow_control([window_adjusted], Channel, Cache). flow_control([], Channel, Cache) -> - ssh_channel:cache_update(Cache, Channel), + ssh_client_channel:cache_update(Cache, Channel), []; flow_control([_|_], #channel{flow_control = From, send_buf = Buffer} = Channel, Cache) when From =/= undefined -> case queue:is_empty(Buffer) of true -> - ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), + ssh_client_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), [{flow_control, Cache, Channel, From, ok}]; false -> [] @@ -1169,14 +1169,14 @@ backwards_compatible([Value| Rest], Acc) -> handle_cli_msg(C0, ChId, Reply0) -> Cache = C0#connection.channel_cache, - Ch0 = ssh_channel:cache_lookup(Cache, ChId), + Ch0 = ssh_client_channel:cache_lookup(Cache, ChId), case Ch0#channel.user of undefined -> case (catch start_cli(C0, ChId)) of {ok, Pid} -> erlang:monitor(process, Pid), Ch = Ch0#channel{user = Pid}, - ssh_channel:cache_update(Cache, Ch), + ssh_client_channel:cache_update(Cache, Ch), reply_msg(Ch, C0, Reply0); _Other -> Reply = {connection_reply, channel_failure_msg(Ch0#channel.remote_id)}, @@ -1194,10 +1194,10 @@ handle_cli_msg(C0, ChId, Reply0) -> %%% channel_data_reply_msg(ChannelId, Connection, DataType, Data) -> - case ssh_channel:cache_lookup(Connection#connection.channel_cache, ChannelId) of + case ssh_client_channel:cache_lookup(Connection#connection.channel_cache, ChannelId) of #channel{recv_window_size = Size} = Channel -> WantedSize = Size - size(Data), - ssh_channel:cache_update(Connection#connection.channel_cache, + ssh_client_channel:cache_update(Connection#connection.channel_cache, Channel#channel{recv_window_size = WantedSize}), reply_msg(Channel, Connection, {data, ChannelId, DataType, Data}); undefined -> @@ -1206,7 +1206,7 @@ channel_data_reply_msg(ChannelId, Connection, DataType, Data) -> reply_msg(ChId, C, Reply) when is_integer(ChId) -> - reply_msg(ssh_channel:cache_lookup(C#connection.channel_cache, ChId), C, Reply); + reply_msg(ssh_client_channel:cache_lookup(C#connection.channel_cache, ChId), C, Reply); reply_msg(Channel, Connection, {open, _} = Reply) -> request_reply_or_data(Channel, Connection, Reply); diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ab7fc1cf46..57641cf74c 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -414,7 +414,7 @@ init([Role,Socket,Opts]) -> case inet:peername(Socket) of {ok, PeerAddr} -> {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), - C = #connection{channel_cache = ssh_channel:cache_create(), + C = #connection{channel_cache = ssh_client_channel:cache_create(), channel_id_seed = 0, port_bindings = [], requests = [], @@ -1109,13 +1109,13 @@ handle_event(cast, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTED(StateName) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_window_pending = Pending, recv_packet_size = PktSize} = Channel when (WinSize-Bytes) >= 2*PktSize -> %% The peer can send at least two more *full* packet, no hurry. - ssh_channel:cache_update(cache(D), + ssh_client_channel:cache_update(cache(D), Channel#channel{recv_window_pending = Pending + Bytes}), keep_state_and_data; @@ -1123,7 +1123,7 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTE recv_window_pending = Pending, remote_id = Id} = Channel -> %% Now we have to update the window - we can't receive so many more pkts - ssh_channel:cache_update(cache(D), + ssh_client_channel:cache_update(cache(D), Channel#channel{recv_window_size = WinSize + Bytes + Pending, recv_window_pending = 0}), @@ -1135,7 +1135,7 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTE end; handle_event(cast, {reply_request,success,ChannelId}, StateName, D) when ?CONNECTED(StateName) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = RemoteId} -> Msg = ssh_connection:channel_success_msg(RemoteId), update_inet_buffers(D#data.socket), @@ -1178,7 +1178,7 @@ handle_event({call,From}, {connection_info, Options}, _, D) -> {keep_state_and_data, [{reply,From,Info}]}; handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{} = Channel -> Info = fold_keys(Options, fun chann_info/2, Channel), {keep_state_and_data, [{reply,From,Info}]}; @@ -1188,14 +1188,14 @@ handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) -> handle_event({call,From}, {info, all}, _, D) -> - Result = ssh_channel:cache_foldl(fun(Channel, Acc) -> + Result = ssh_client_channel:cache_foldl(fun(Channel, Acc) -> [Channel | Acc] end, [], cache(D)), {keep_state_and_data, [{reply, From, {ok,Result}}]}; handle_event({call,From}, {info, ChannelPid}, _, D) -> - Result = ssh_channel:cache_foldl( + Result = ssh_client_channel:cache_foldl( fun(Channel, Acc) when Channel#channel.user == ChannelPid -> [Channel | Acc]; (_, Acc) -> @@ -1241,7 +1241,7 @@ handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) handle_event({call,From}, {eof, ChannelId}, StateName, D0) when ?CONNECTED(StateName) -> - case ssh_channel:cache_lookup(cache(D0), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id, sent_close = false} -> D = send_msg(ssh_connection:channel_eof_msg(Id), D0), {keep_state, D, [{reply,From,ok}]}; @@ -1259,7 +1259,7 @@ handle_event({call,From}, InitialWindowSize, MaxPacketSize, Data), D1), - ssh_channel:cache_update(cache(D2), + ssh_client_channel:cache_update(cache(D2), #channel{type = Type, sys = "none", user = ChannelPid, @@ -1274,7 +1274,7 @@ handle_event({call,From}, handle_event({call,From}, {send_window, ChannelId}, StateName, D) when ?CONNECTED(StateName) -> - Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of + Reply = case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{send_window_size = WinSize, send_packet_size = Packsize} -> {ok, {WinSize, Packsize}}; @@ -1285,7 +1285,7 @@ handle_event({call,From}, {send_window, ChannelId}, StateName, D) handle_event({call,From}, {recv_window, ChannelId}, StateName, D) when ?CONNECTED(StateName) -> - Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of + Reply = case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_packet_size = Packsize} -> {ok, {WinSize, Packsize}}; @@ -1296,10 +1296,10 @@ handle_event({call,From}, {recv_window, ChannelId}, StateName, D) handle_event({call,From}, {close, ChannelId}, StateName, D0) when ?CONNECTED(StateName) -> - case ssh_channel:cache_lookup(cache(D0), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id} = Channel -> D1 = send_msg(ssh_connection:channel_close_msg(Id), D0), - ssh_channel:cache_update(cache(D1), Channel#channel{sent_close = true}), + ssh_client_channel:cache_update(cache(D1), Channel#channel{sent_close = true}), {keep_state, cache_request_idle_timer_check(D1), [{reply,From,ok}]}; undefined -> {keep_state_and_data, [{reply,From,ok}]} @@ -1859,7 +1859,7 @@ is_usable_user_pubkey(A, Ssh) -> %%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = Id, sent_close = false} = Channel -> update_sys(cache(D), Channel, Type, ChannelPid), @@ -1874,7 +1874,7 @@ handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> end. handle_request(ChannelId, Type, Data, WantReply, From, D) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = Id, sent_close = false} -> send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data), @@ -1890,10 +1890,10 @@ handle_request(ChannelId, Type, Data, WantReply, From, D) -> %%%---------------------------------------------------------------- handle_channel_down(ChannelPid, D) -> Cache = cache(D), - ssh_channel:cache_foldl( + ssh_client_channel:cache_foldl( fun(#channel{user=U, local_id=Id}, Acc) when U == ChannelPid -> - ssh_channel:cache_delete(Cache, Id), + ssh_client_channel:cache_delete(Cache, Id), Acc; (_,Acc) -> Acc @@ -1902,7 +1902,7 @@ handle_channel_down(ChannelPid, D) -> update_sys(Cache, Channel, Type, ChannelPid) -> - ssh_channel:cache_update(Cache, + ssh_client_channel:cache_update(Cache, Channel#channel{sys = Type, user = ChannelPid}). add_request(false, _ChannelId, _From, State) -> @@ -1979,7 +1979,7 @@ conn_info(sockname, #data{ssh_params=S}) -> S#ssh.local; %% dbg options ( = not documented): conn_info(socket, D) -> D#data.socket; conn_info(chan_ids, D) -> - ssh_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) -> + ssh_client_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) -> [Id | Acc] end, [], cache(D)). @@ -2070,7 +2070,7 @@ get_repl({channel_data,Pid,Data}, Acc) -> get_repl({channel_request_reply,From,Data}, {CallRepls,S}) -> {[{reply,From,Data}|CallRepls], S}; get_repl({flow_control,Cache,Channel,From,Msg}, {CallRepls,S}) -> - ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), + ssh_client_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), {[{reply,From,Msg}|CallRepls], S}; get_repl({flow_control,From,Msg}, {CallRepls,S}) -> {[{reply,From,Msg}|CallRepls], S}; @@ -2146,7 +2146,7 @@ cache_init_idle_timer(D) -> cache_check_set_idle_timer(D = #data{idle_timer_ref = undefined, idle_timer_value = IdleTime}) -> %% No timer set - shall we set one? - case ssh_channel:cache_info(num_entries, cache(D)) of + case ssh_client_channel:cache_info(num_entries, cache(D)) of 0 when IdleTime == infinity -> %% No. Meaningless to set a timer that fires in an infinite time... D; diff --git a/lib/ssh/src/ssh_server_channel.erl b/lib/ssh/src/ssh_server_channel.erl index 117b7855e2..f1c9a85639 100644 --- a/lib/ssh/src/ssh_server_channel.erl +++ b/lib/ssh/src/ssh_server_channel.erl @@ -48,8 +48,8 @@ ]). start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). + ssh_client_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). get_print_info(Pid) -> - ssh_channel:get_print_info(Pid). + ssh_client_channel:get_print_info(Pid). diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index f00c0aed1f..5984713ec9 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -24,7 +24,7 @@ -module(ssh_sftp). --behaviour(ssh_channel). +-behaviour(ssh_client_channel). -include_lib("kernel/include/file.hrl"). -include("ssh.hrl"). @@ -47,7 +47,7 @@ recv_window/1, list_dir/2, read_file/2, write_file/3, recv_window/2, list_dir/3, read_file/3, write_file/4]). -%% ssh_channel callbacks +%% ssh_client_channel callbacks -export([init/1, handle_call/3, handle_cast/2, code_change/3, handle_msg/2, handle_ssh_msg/2, terminate/2]). %% TODO: Should be placed elsewhere ssh_sftpd should not call functions in ssh_sftp! -export([info_to_attr/1, attr_to_info/1]). @@ -123,7 +123,7 @@ start_channel(Cm, UserOptions) when is_pid(Cm) -> {_SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), case ssh_xfer:attach(Cm, [], ChanOpts) of {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, + case ssh_client_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId, SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of @@ -151,7 +151,7 @@ start_channel(Host, Port, UserOptions) -> proplists:get_value(timeout, SftpOpts, infinity)), case ssh_xfer:connect(Host, Port, SshOpts, ChanOpts, Timeout) of {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,ChannelId,SftpOpts]) of + case ssh_client_channel:start(Cm, ChannelId, ?MODULE, [Cm,ChannelId,SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> @@ -825,7 +825,7 @@ handle_msg({ssh_channel_up, _, _}, #state{opts = Options, xf = Xf} = State) -> %% Version negotiation timed out handle_msg({timeout, undefined, From}, #state{xf = #ssh_xfer{channel = ChannelId}} = State) -> - ssh_channel:reply(From, {error, timeout}), + ssh_client_channel:reply(From, {error, timeout}), {stop, ChannelId, State}; handle_msg({timeout, Id, From}, #state{req_list = ReqList0} = State) -> @@ -834,7 +834,7 @@ handle_msg({timeout, Id, From}, #state{req_list = ReqList0} = State) -> {ok, State}; _ -> ReqList = lists:keydelete(Id, 1, ReqList0), - ssh_channel:reply(From, {error, timeout}), + ssh_client_channel:reply(From, {error, timeout}), {ok, State#state{req_list = ReqList}} end; @@ -882,7 +882,7 @@ handle_options([Opt|Rest], Sftp, Chan, Ssh) -> handle_options(Rest, Sftp, Chan, [Opt|Ssh]). call(Pid, Msg, TimeOut) -> - ssh_channel:call(Pid, {{timeout, TimeOut}, Msg}, infinity). + ssh_client_channel:call(Pid, {{timeout, TimeOut}, Msg}, infinity). handle_reply(State, <>) -> do_handle_reply(State, Reply, Rest); @@ -901,7 +901,7 @@ do_handle_reply(#state{xf = Xf} = State, true -> ok end, - ssh_channel:reply(From, ok) + ssh_client_channel:reply(From, ok) end, State#state{xf = Xf#ssh_xfer{vsn = Version, ext = Ext}, rep_buf = Rest}; @@ -949,7 +949,7 @@ async_reply(ReqID, Reply, _From={To,_}, State) -> State. sync_reply(Reply, From, State) -> - catch (ssh_channel:reply(From, Reply)), + catch (ssh_client_channel:reply(From, Reply)), State. open2(OrigReqID,FileName,Handle,Mode,Async,From,State) -> -- cgit v1.2.3 From e0e2286660cfb644e04f223090b8e2d78d76845d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 2 May 2018 15:27:54 +0200 Subject: ssh: Fix DH group exchange server bug for PuTTY and others following draft-draft-00 from 2001 and not the rfc4419 --- lib/ssh/src/ssh_transport.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 61a1a57063..a160309b95 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1663,13 +1663,13 @@ kex_h(SSH, Curve, Key, Q_c, Q_s, K) -> kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) -> KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), L = if Min==-1; Max==-1 -> - %% flag from 'ssh_msg_kex_dh_gex_request_old' - %% It was like this before that message was supported, - %% why? + %% ssh_msg_kex_dh_gex_request_old <>; + ?Euint32(NBits), + ?Empint(Prime), ?Empint(Gen), ?Empint(E), ?Empint(F), ?Empint(K)>>; true -> + %% ssh_msg_kex_dh_gex_request < Date: Wed, 2 May 2018 15:38:04 +0200 Subject: ssh: Renegotiation -> renegotiate --- lib/ssh/src/ssh_connection_handler.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index fc75945a5b..c146619a06 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1475,7 +1475,7 @@ role({_,Role,_}) -> Role. %%-------------------------------------------------------------------- %% Check the StateName to see if we are in the renegotiation phase -renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; +renegotiation({_,_,ReNeg}) -> ReNeg == renegotiate; renegotiation(_) -> false. %%-------------------------------------------------------------------- -- cgit v1.2.3 From 79e3c477f8d0d8ea397820847c576e0a0aaa5323 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 3 May 2018 13:48:39 +0200 Subject: ssh: Extend rekey_limit to also take an optional time --- lib/ssh/src/ssh.hrl | 5 +++-- lib/ssh/src/ssh_connection_handler.erl | 17 ++++++++++------- lib/ssh/src/ssh_options.erl | 16 +++++++++++++--- 3 files changed, 26 insertions(+), 12 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index a3d9a1b1cb..fc0a3786ac 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -29,7 +29,6 @@ -define(SSH_DEFAULT_PORT, 22). -define(SSH_MAX_PACKET_SIZE, (256*1024)). --define(REKEY_TIMOUT, 3600000). -define(REKEY_DATA_TIMOUT, 60000). -define(DEFAULT_PROFILE, default). @@ -192,7 +191,9 @@ -type user_dir_common_option() :: {user_dir, false | string()}. -type profile_common_option() :: {profile, atom() }. -type max_idle_time_common_option() :: {idle_time, timeout()}. --type rekey_limit_common_option() :: {rekey_limit, non_neg_integer() }. +-type rekey_limit_common_option() :: {rekey_limit, Bytes::non_neg_integer() | + {Minutes::non_neg_integer(), Bytes::non_neg_integer()} + }. -type key_cb_common_option() :: {key_cb, Module::atom() | {Module::atom(),Opts::[term()]} } . -type disconnectfun_common_option() :: diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 57641cf74c..b21c0337ad 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -429,9 +429,6 @@ init([Role,Socket,Opts]) -> }, D = case Role of client -> - %% Start the renegotiation timers - timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), - timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), cache_init_idle_timer(D0); server -> Sups = ?GET_INTERNAL_OPT(supervisors, Opts), @@ -444,6 +441,10 @@ init([Role,Socket,Opts]) -> connection_supervisor = proplists:get_value(connection_sup, Sups) }}) end, + %% Start the renegotiation timers + {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), + timer:apply_after(RekeyTimeout, gen_statem, cast, [self(), renegotiate]), + timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), {ok, {hello,Role}, D}; {error,Error} -> @@ -1066,7 +1067,8 @@ handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, D) - handle_event(cast, renegotiate, {connected,Role}, D) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D#data.ssh_params), send_bytes(SshPacket, D), - timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), + {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, Ssh#ssh.opts), + timer:apply_after(RekeyTimeout, gen_statem, cast, [self(), renegotiate]), {next_state, {kexinit,Role,renegotiate}, D#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; @@ -1074,9 +1076,10 @@ handle_event({call,From}, get_alg, _, D) -> #ssh{algorithms=Algs} = D#data.ssh_params, {keep_state_and_data, [{reply,From,Algs}]}; -handle_event(cast, renegotiate, _, _) -> +handle_event(cast, renegotiate, _, D) -> %% Already in key-exchange so safe to ignore - timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), % FIXME: not here in original + {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), + timer:apply_after(RekeyTimeout, gen_statem, cast, [self(), renegotiate]), keep_state_and_data; @@ -1084,7 +1087,7 @@ handle_event(cast, renegotiate, _, _) -> handle_event(cast, data_size, {connected,Role}, D) -> {ok, [{send_oct,Sent0}]} = inet:getstat(D#data.socket, [send_oct]), Sent = Sent0 - D#data.last_size_rekey, - MaxSent = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), + {_RekeyTimeout,MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), case Sent >= MaxSent of true -> diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 4dd9082250..73287e464a 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -599,9 +599,19 @@ default(common) -> class => user_options }, - {rekey_limit, def} => % FIXME: Why not common? - #{default => 1024000000, - chk => fun check_non_neg_integer/1, + {rekey_limit, def} => + #{default => {3600000, 1024000000}, % {1 hour, 1 GB} + chk => fun({TimeMins, SizBytes}) when is_integer(TimeMins) andalso TimeMins>=0, + is_integer(SizBytes) andalso SizBytes>=0 -> + %% New (>= 21) format + {true, {TimeMins * 60*1000, % To ms + SizBytes}}; + (SizBytes) when is_integer(SizBytes) andalso SizBytes>=0 -> + %% Old (< 21) format + {true, {3600000, SizBytes}}; + (_) -> + false + end, class => user_options }, -- cgit v1.2.3 From 169e26ba4facb637831aaaa02d878b988a090ff0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 2 May 2018 14:44:52 +0200 Subject: ssh: Fix DH group exchange server bug for PuTTY and others following draft-draft-00 from 2001 and not the rfc4419 --- lib/ssh/src/ssh_transport.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 975053d301..edc927e807 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1839,9 +1839,10 @@ kex_alg_dependent({E, F, K}) -> %% diffie-hellman and ec diffie-hellman (with E = Q_c, F = Q_s) <>; -kex_alg_dependent({-1, _, -1, _, _, E, F, K}) -> +kex_alg_dependent({-1, NBits, -1, Prime, Gen, E, F, K}) -> %% ssh_msg_kex_dh_gex_request_old - <>; + <>; kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) -> %% diffie-hellman group exchange -- cgit v1.2.3 From 61cc306b51e26489912eca2ace66d47253084763 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 2 May 2018 15:48:08 +0200 Subject: ssh: Renegotiation -> renegotiate --- lib/ssh/src/ssh_connection_handler.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ad23d82ea8..36152bacf7 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -522,7 +522,7 @@ role({_,Role}) -> Role; role({_,Role,_}) -> Role. -spec renegotiation(state_name()) -> boolean(). -renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; +renegotiation({_,_,ReNeg}) -> ReNeg == renegotiate; renegotiation(_) -> false. -- cgit v1.2.3 From ebcece7f317f6d4e4d580c1f5629f4f46defe272 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 9 May 2018 11:42:49 +0200 Subject: ssh: Add infinity as values to rekey_limit Zero is removed from the Minutes in the new tuple value to remove a loop possibility. The zero is kept for bytes since that is not dangerous and a removal would be an unnecssary incompatibility. --- lib/ssh/src/ssh.hrl | 7 +++++-- lib/ssh/src/ssh_options.erl | 21 +++++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index fc0a3786ac..2efd239aae 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -191,10 +191,13 @@ -type user_dir_common_option() :: {user_dir, false | string()}. -type profile_common_option() :: {profile, atom() }. -type max_idle_time_common_option() :: {idle_time, timeout()}. --type rekey_limit_common_option() :: {rekey_limit, Bytes::non_neg_integer() | - {Minutes::non_neg_integer(), Bytes::non_neg_integer()} +-type rekey_limit_common_option() :: {rekey_limit, Bytes::limit_bytes() | + {Minutes::limit_time(), Bytes::limit_bytes()} }. +-type limit_bytes() :: non_neg_integer() | infinity . % non_neg_integer due to compatibility +-type limit_time() :: pos_integer() | infinity . + -type key_cb_common_option() :: {key_cb, Module::atom() | {Module::atom(),Opts::[term()]} } . -type disconnectfun_common_option() :: {disconnectfun, fun((Reason::term()) -> void | any()) }. diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 73287e464a..fe95d2ac54 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -601,14 +601,19 @@ default(common) -> {rekey_limit, def} => #{default => {3600000, 1024000000}, % {1 hour, 1 GB} - chk => fun({TimeMins, SizBytes}) when is_integer(TimeMins) andalso TimeMins>=0, - is_integer(SizBytes) andalso SizBytes>=0 -> - %% New (>= 21) format - {true, {TimeMins * 60*1000, % To ms - SizBytes}}; - (SizBytes) when is_integer(SizBytes) andalso SizBytes>=0 -> - %% Old (< 21) format - {true, {3600000, SizBytes}}; + chk => fun({infinity, infinity}) -> + true; + ({Mins, infinity}) when is_integer(Mins), Mins>0 -> + {true, {Mins*60*1000, infinity}}; + ({infinity, Bytes}) when is_integer(Bytes), Bytes>=0 -> + true; + ({Mins, Bytes}) when is_integer(Mins), Mins>0, + is_integer(Bytes), Bytes>=0 -> + {true, {Mins*60*1000, Bytes}}; + (infinity) -> + {true, {3600000, infinity}}; + (Bytes) when is_integer(Bytes), Bytes>=0 -> + {true, {3600000, Bytes}}; (_) -> false end, -- cgit v1.2.3 From 1c22f3cb6a170183fd3c8e938162a01bc82a2677 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 27 Apr 2018 14:40:20 +0200 Subject: ssh: idle_timer sends special msg instead of EXIT --- lib/ssh/src/ssh_connection_handler.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index dfdad769ed..311c5a2cd6 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1424,6 +1424,10 @@ handle_event(info, {timeout, {_, From} = Request}, _, handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D0) -> {keep_state, handle_channel_down(ChannelPid, D0)}; + +handle_event(info, idle_timer_timeout, StateName, _) -> + {stop, {shutdown, "Timeout"}}; + %%% So that terminate will be run when supervisor is shutdown handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) -> Role = role(StateName), @@ -2157,7 +2161,7 @@ cache_check_set_idle_timer(D = #data{idle_timer_ref = undefined, %% Yes, we'll set one since the cache is empty and it should not %% be that for a specified time D#data{idle_timer_ref = - erlang:send_after(IdleTime, self(), {'EXIT',[],"Timeout"})}; + erlang:send_after(IdleTime, self(), idle_timer_timeout)}; _ -> %% No - there are entries in the cache D -- cgit v1.2.3 From c4f0b4f2b868b47ceb7cc29b78acc162a353720a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 27 Apr 2018 14:55:05 +0200 Subject: ssh: Unfold function in the single used place --- lib/ssh/src/ssh_connection_handler.erl | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 311c5a2cd6..2ad6f779d3 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1421,9 +1421,17 @@ handle_event(info, {timeout, {_, From} = Request}, _, end; %%% Handle that ssh channels user process goes down -handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D0) -> - {keep_state, handle_channel_down(ChannelPid, D0)}; - +handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D) -> + Cache = cache(D), + ssh_client_channel:cache_foldl( + fun(#channel{user=U, + local_id=Id}, Acc) when U == ChannelPid -> + ssh_client_channel:cache_delete(Cache, Id), + Acc; + (_,Acc) -> + Acc + end, [], Cache), + {keep_state, cache_check_set_idle_timer(D)}; handle_event(info, idle_timer_timeout, StateName, _) -> {stop, {shutdown, "Timeout"}}; @@ -1895,19 +1903,6 @@ handle_request(ChannelId, Type, Data, WantReply, From, D) -> end. %%%---------------------------------------------------------------- -handle_channel_down(ChannelPid, D) -> - Cache = cache(D), - ssh_client_channel:cache_foldl( - fun(#channel{user=U, - local_id=Id}, Acc) when U == ChannelPid -> - ssh_client_channel:cache_delete(Cache, Id), - Acc; - (_,Acc) -> - Acc - end, [], Cache), - cache_check_set_idle_timer(D). - - update_sys(Cache, Channel, Type, ChannelPid) -> ssh_client_channel:cache_update(Cache, Channel#channel{sys = Type, user = ChannelPid}). -- cgit v1.2.3 From 3f77d6d4326122486be1536d5184f1153117598b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 27 Apr 2018 15:48:48 +0200 Subject: ssh: Refactor connection_msg handling --- lib/ssh/src/ssh_connection_handler.erl | 153 ++++++++++++++------------------- 1 file changed, 66 insertions(+), 87 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 2ad6f779d3..c9d389d887 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -559,6 +559,10 @@ renegotiation(_) -> false. #data{} ) -> gen_statem:event_handler_result(state_name()) . +-define(CONNECTION_MSG(Msg), + [{next_event, internal, prepare_next_packet}, + {next_event,internal,{conn_msg,Msg}}]). + %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . callback_mode() -> @@ -1017,52 +1021,50 @@ handle_event(_, #ssh_msg_debug{} = Msg, _, D) -> debug_fun(Msg, D), keep_state_and_data; -handle_event(internal, Msg=#ssh_msg_global_request{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_request_success{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_request_failure{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_open{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_open_confirmation{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_open_failure{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_window_adjust{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_data{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_extended_data{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_eof{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_close{}, {connected,server} = StateName, D) -> - handle_connection_msg(Msg, StateName, cache_request_idle_timer_check(D)); - -handle_event(internal, Msg=#ssh_msg_channel_close{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_request{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); - -handle_event(internal, Msg=#ssh_msg_channel_success{}, StateName, D) -> - update_inet_buffers(D#data.socket), - handle_connection_msg(Msg, StateName, D); +handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User, + connection_state = Connection0, + event_queue = Qev0} = D0) -> + Role = role(StateName), + Rengotation = renegotiation(StateName), + try ssh_connection:handle_msg(Msg, Connection0, Role) of + {disconnect, Reason0, RepliesConn} -> + {Repls, D} = send_replies(RepliesConn, D0), + case {Reason0,Role} of + {{_, Reason}, client} when ((StateName =/= {connected,client}) + and (not Rengotation)) -> + User ! {self(), not_connected, Reason}; + _ -> + ok + end, + {stop_and_reply, {shutdown,normal}, Repls, D}; -handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, D) -> - handle_connection_msg(Msg, StateName, D); + {Replies, Connection} when is_list(Replies) -> + {Repls, D} = + case StateName of + {connected,_} -> + send_replies(Replies, D0#data{connection_state=Connection}); + _ -> + {ConnReplies, NonConnReplies} = lists:splitwith(fun not_connected_filter/1, Replies), + send_replies(NonConnReplies, D0#data{event_queue = Qev0 ++ ConnReplies}) + end, + case Msg of + #ssh_msg_channel_close{} when StateName == {connected,server} -> + {keep_state, cache_request_idle_timer_check(D), Repls}; + #ssh_msg_channel_success{} -> + update_inet_buffers(D#data.socket), + {keep_state, D, Repls}; + _ -> + {keep_state, D, Repls} + end + catch + Class:Error -> + {Repls, D1} = send_replies(ssh_connection:handle_stop(Connection0), D0), + {Shutdown, D} = ?send_disconnect(?SSH_DISCONNECT_BY_APPLICATION, + io_lib:format("Internal error: ~p:~p",[Class,Error]), + StateName, D1), + {stop_and_reply, Shutdown, Repls, D} + end; handle_event(cast, renegotiate, {connected,Role}, D) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D#data.ssh_params), @@ -1319,6 +1321,7 @@ handle_event(info, {Proto, Sock, Info}, {hello,_}, #data{socket = Sock, {keep_state_and_data, [{next_event, internal, {info_line,Info}}]} end; + handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, transport_protocol = Proto}) -> try ssh_transport:handle_packet_part( @@ -1336,13 +1339,29 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, try ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D1)) of - Msg = #ssh_msg_kexinit{} -> + #ssh_msg_kexinit{} = Msg -> {keep_state, D1, [{next_event, internal, prepare_next_packet}, {next_event, internal, {Msg,DecryptedBytes}} ]}; + + #ssh_msg_global_request{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_request_success{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_request_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_open{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_open_confirmation{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_open_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_window_adjust{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_data{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_extended_data{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_eof{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_close{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_request{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + #ssh_msg_channel_success{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; + Msg -> {keep_state, D1, [{next_event, internal, prepare_next_packet}, - {next_event, internal, Msg} + {next_event, internal, Msg} ]} catch C:E -> @@ -1433,7 +1452,7 @@ handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D) -> end, [], Cache), {keep_state, cache_check_set_idle_timer(D)}; -handle_event(info, idle_timer_timeout, StateName, _) -> +handle_event(info, idle_timer_timeout, _, _) -> {stop, {shutdown, "Timeout"}}; %%% So that terminate will be run when supervisor is shutdown @@ -1758,46 +1777,6 @@ call(FsmPid, Event, Timeout) -> end. -handle_connection_msg(Msg, StateName, D0 = #data{starter = User, - connection_state = Connection0, - event_queue = Qev0}) -> - Renegotiation = renegotiation(StateName), - Role = role(StateName), - try ssh_connection:handle_msg(Msg, Connection0, Role) of - {disconnect, Reason0, RepliesConn} -> - {Repls, D} = send_replies(RepliesConn, D0), - case {Reason0,Role} of - {{_, Reason}, client} when ((StateName =/= {connected,client}) and (not Renegotiation)) -> - User ! {self(), not_connected, Reason}; - _ -> - ok - end, - {stop_and_reply, {shutdown,normal}, Repls, D}; - - {[], Connection} -> - {keep_state, D0#data{connection_state = Connection}}; - - {Replies, Connection} when is_list(Replies) -> - {Repls, D} = - case StateName of - {connected,_} -> - send_replies(Replies, D0#data{connection_state=Connection}); - _ -> - {ConnReplies, NonConnReplies} = lists:splitwith(fun not_connected_filter/1, Replies), - send_replies(NonConnReplies, D0#data{event_queue = Qev0 ++ ConnReplies}) - end, - {keep_state, D, Repls} - - catch - Class:Error -> - {Repls, D1} = send_replies(ssh_connection:handle_stop(Connection0), D0), - {Shutdown, D} = ?send_disconnect(?SSH_DISCONNECT_BY_APPLICATION, - io_lib:format("Internal error: ~p:~p",[Class,Error]), - StateName, D1), - {stop_and_reply, Shutdown, Repls, D} - end. - - set_kex_overload_prefix(Msg = <>, #data{ssh_params=SshParams}) when Op == 30; Op == 31 -- cgit v1.2.3 From b5f6912a13dd7deb13bcdd26b42e701339724786 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 27 Apr 2018 17:00:23 +0200 Subject: ssh: Use generic_timer for option idle_time --- lib/ssh/src/ssh_connection_handler.erl | 261 +++++++++++++++++---------------- 1 file changed, 131 insertions(+), 130 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index c9d389d887..f1ff3a70e2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -71,7 +71,7 @@ -export([init_connection_handler/3, % proc_lib:spawn needs this init_ssh_record/3, % Export of this internal function % intended for low-level protocol test suites - renegotiate/1, renegotiate_data/1, alg/1 % Export intended for test cases + renegotiate/1, alg/1 % Export intended for test cases ]). -export([dbg_trace/3]). @@ -325,14 +325,7 @@ close(ConnectionHandler, ChannelId) -> ) -> ok. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . renegotiate(ConnectionHandler) -> - cast(ConnectionHandler, renegotiate). - -%%-------------------------------------------------------------------- --spec renegotiate_data(connection_ref() - ) -> ok. -%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -renegotiate_data(ConnectionHandler) -> - cast(ConnectionHandler, data_size). + cast(ConnectionHandler, force_renegotiate). %%-------------------------------------------------------------------- alg(ConnectionHandler) -> @@ -349,11 +342,6 @@ alg(ConnectionHandler) -> connection_state :: #connection{}, latest_channel_id = 0 :: non_neg_integer() | undefined, - idle_timer_ref :: undefined - | infinity - | reference(), - idle_timer_value = infinity :: infinity - | pos_integer(), transport_protocol :: atom() | undefined, % ex: tcp transport_cb :: atom() @@ -429,22 +417,17 @@ init([Role,Socket,Opts]) -> }, D = case Role of client -> - cache_init_idle_timer(D0); + D0; server -> Sups = ?GET_INTERNAL_OPT(supervisors, Opts), - cache_init_idle_timer( - D0#data{connection_state = - C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), - exec = ?GET_OPT(exec, Opts), - system_supervisor = proplists:get_value(system_sup, Sups), - sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), - connection_supervisor = proplists:get_value(connection_sup, Sups) - }}) + D0#data{connection_state = + C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), + exec = ?GET_OPT(exec, Opts), + system_supervisor = proplists:get_value(system_sup, Sups), + sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), + connection_supervisor = proplists:get_value(connection_sup, Sups) + }} end, - %% Start the renegotiation timers - {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), - timer:apply_after(RekeyTimeout, gen_statem, cast, [self(), renegotiate]), - timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), {ok, {hello,Role}, D}; {error,Error} -> @@ -566,7 +549,8 @@ renegotiation(_) -> false. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . callback_mode() -> - handle_event_function. + [handle_event_function, + state_enter]. handle_event(_, _Event, {init_error,Error}=StateName, D) -> @@ -1047,10 +1031,10 @@ handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User, {ConnReplies, NonConnReplies} = lists:splitwith(fun not_connected_filter/1, Replies), send_replies(NonConnReplies, D0#data{event_queue = Qev0 ++ ConnReplies}) end, - case Msg of - #ssh_msg_channel_close{} when StateName == {connected,server} -> - {keep_state, cache_request_idle_timer_check(D), Repls}; - #ssh_msg_channel_success{} -> + case {Msg, StateName} of + {#ssh_msg_channel_close{}, {connected,_}} -> + {keep_state, D, [cond_set_idle_timer(D)|Repls]}; + {#ssh_msg_channel_success{}, _} -> update_inet_buffers(D#data.socket), {keep_state, D, Repls}; _ -> @@ -1066,49 +1050,46 @@ handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User, {stop_and_reply, Shutdown, Repls, D} end; -handle_event(cast, renegotiate, {connected,Role}, D) -> - {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D#data.ssh_params), - send_bytes(SshPacket, D), - {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, Ssh#ssh.opts), - timer:apply_after(RekeyTimeout, gen_statem, cast, [self(), renegotiate]), - {next_state, {kexinit,Role,renegotiate}, D#data{ssh_params = Ssh, - key_exchange_init_msg = KeyInitMsg}}; - -handle_event({call,From}, get_alg, _, D) -> - #ssh{algorithms=Algs} = D#data.ssh_params, - {keep_state_and_data, [{reply,From,Algs}]}; -handle_event(cast, renegotiate, _, D) -> - %% Already in key-exchange so safe to ignore - {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), - timer:apply_after(RekeyTimeout, gen_statem, cast, [self(), renegotiate]), - keep_state_and_data; - - -%% Rekey due to sent data limit reached? -handle_event(cast, data_size, {connected,Role}, D) -> - {ok, [{send_oct,Sent0}]} = inet:getstat(D#data.socket, [send_oct]), - Sent = Sent0 - D#data.last_size_rekey, - {_RekeyTimeout,MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), - timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), - case Sent >= MaxSent of - true -> - {KeyInitMsg, SshPacket, Ssh} = - ssh_transport:key_exchange_init_msg(D#data.ssh_params), - send_bytes(SshPacket, D), - {next_state, {kexinit,Role,renegotiate}, D#data{ssh_params = Ssh, - key_exchange_init_msg = KeyInitMsg, - last_size_rekey = Sent0}}; - _ -> - keep_state_and_data +handle_event(enter, _OldState, {connected,_}=State, D) -> + %% Entering the state where re-negotiation is possible + init_renegotiate_timers(State, D); + +handle_event(enter, _OldState, {ext_info,_,renegotiate}=State, D) -> + %% Could be hanging in exit_info state if nothing else arrives + init_renegotiate_timers(State, D); + +handle_event(enter, {connected,_}, State, D) -> + %% Exiting the state where re-negotiation is possible + pause_renegotiate_timers(State, D); + +handle_event(cast, force_renegotiate, StateName, D) -> + handle_event({timeout,renegotiate}, undefined, StateName, D); + +handle_event({timeout,renegotiate}, _, StateName, D0) -> + case StateName of + {connected,Role} -> + start_rekeying(Role, D0); + {ext_info,Role,renegotiate} -> + start_rekeying(Role, D0); + _ -> + %% Wrong state for starting a renegotiation, must be in re-negotiation + keep_state_and_data end; -handle_event(cast, data_size, _, _) -> - %% Already in key-exchange so safe to ignore - timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), % FIXME: not here in original - keep_state_and_data; - +handle_event({timeout,check_data_size}, _, StateName, D0) -> + %% Rekey due to sent data limit reached? (Can't be in {ext_info,...} if data is sent) + case StateName of + {connected,Role} -> + check_data_rekeying(Role, D0); + _ -> + %% Wrong state for starting a renegotiation, must be in re-negotiation + keep_state_and_data + end; +handle_event({call,From}, get_alg, _, D) -> + #ssh{algorithms=Algs} = D#data.ssh_params, + {keep_state_and_data, [{reply,From,Algs}]}; handle_event(cast, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; @@ -1223,7 +1204,7 @@ handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, D -> %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), - {keep_state, cache_request_idle_timer_check(D)} + {keep_state, D, cond_set_idle_timer(D)} end; handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0) @@ -1234,7 +1215,7 @@ handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D -> %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), - {keep_state, cache_request_idle_timer_check(D)} + {keep_state, D, cond_set_idle_timer(D)} end; handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) @@ -1275,7 +1256,7 @@ handle_event({call,From}, }), D = add_request(true, ChannelId, From, D2), start_channel_request_timer(ChannelId, From, Timeout), - {keep_state, cache_cancel_idle_timer(D)}; + {keep_state, D, cond_set_idle_timer(D)}; handle_event({call,From}, {send_window, ChannelId}, StateName, D) when ?CONNECTED(StateName) -> @@ -1305,7 +1286,7 @@ handle_event({call,From}, {close, ChannelId}, StateName, D0) #channel{remote_id = Id} = Channel -> D1 = send_msg(ssh_connection:channel_close_msg(Id), D0), ssh_client_channel:cache_update(cache(D1), Channel#channel{sent_close = true}), - {keep_state, cache_request_idle_timer_check(D1), [{reply,From,ok}]}; + {keep_state, D1, [cond_set_idle_timer(D1), {reply,From,ok}]}; undefined -> {keep_state_and_data, [{reply,From,ok}]} end; @@ -1450,9 +1431,9 @@ handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D) -> (_,Acc) -> Acc end, [], Cache), - {keep_state, cache_check_set_idle_timer(D)}; + {keep_state, D, cond_set_idle_timer(D)}; -handle_event(info, idle_timer_timeout, _, _) -> +handle_event({timeout,idle_time}, _Data, _StateName, _D) -> {stop, {shutdown, "Timeout"}}; %%% So that terminate will be run when supervisor is shutdown @@ -1473,7 +1454,7 @@ handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) -> end; handle_event(info, check_cache, _, D) -> - {keep_state, cache_check_set_idle_timer(D)}; + {keep_state, D, cond_set_idle_timer(D)}; handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> case unexpected_fun(UnexpectedMessage, D) of @@ -1520,6 +1501,11 @@ handle_event(internal, {send_disconnect,Code,DetailedText,Module,Line}, StateNam send_disconnect(Code, DetailedText, Module, Line, StateName, D0), {stop, Shutdown, D}; + +handle_event(enter, _OldState, State, D) -> + %% Just skip + {next_state, State, D}; + handle_event(_Type, _Msg, {ext_info,Role,_ReNegFlag}, D) -> %% If something else arrives, goto next state and handle the event in that one {next_state, {connected,Role}, D, [postpone]}; @@ -1900,6 +1886,42 @@ new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} = {Id, State#data{connection_state = Connection#connection{channel_id_seed = Id + 1}}}. + +%%%---------------------------------------------------------------- +start_rekeying(Role, D0) -> + {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params), + send_bytes(SshPacket, D0), + D = D0#data{ssh_params = Ssh, + key_exchange_init_msg = KeyInitMsg}, + {next_state, {kexinit,Role,renegotiate}, D}. + + +init_renegotiate_timers(State, D) -> + {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), + {next_state, State, D, [{{timeout,renegotiate}, RekeyTimeout, none}, + {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none} ]}. + + +pause_renegotiate_timers(State, D) -> + {next_state, State, D, [{{timeout,renegotiate}, infinity, none}, + {{timeout,check_data_size}, infinity, none} ]}. + +check_data_rekeying(Role, D) -> + {ok, [{send_oct,SocketSentTotal}]} = inet:getstat(D#data.socket, [send_oct]), + SentSinceRekey = SocketSentTotal - D#data.last_size_rekey, + {_RekeyTimeout,MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), + case check_data_rekeying_dbg(SentSinceRekey, MaxSent) of + true -> + start_rekeying(Role, D#data{last_size_rekey = SocketSentTotal}); + _ -> + %% Not enough data sent for a re-negotiation. Restart timer. + {keep_state, D, {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none}} + end. + +check_data_rekeying_dbg(SentSinceRekey, MaxSent) -> + %% This function is for the ssh_dbg to trace on. See dbg_trace/3 at the end. + SentSinceRekey >= MaxSent. + %%%---------------------------------------------------------------- %%% This server/client has decided to disconnect via the state machine: %%% The unused arguments are for debugging. @@ -2112,60 +2134,12 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts, %%% Cache idle timer that closes the connection if there are no %%% channels open for a while. -cache_init_idle_timer(D) -> - case ?GET_OPT(idle_time, (D#data.ssh_params)#ssh.opts) of - infinity -> - D#data{idle_timer_value = infinity, - idle_timer_ref = infinity % A flag used later... - }; - IdleTime -> - %% We dont want to set the timeout on first connect - D#data{idle_timer_value = IdleTime} - end. - - -cache_check_set_idle_timer(D = #data{idle_timer_ref = undefined, - idle_timer_value = IdleTime}) -> - %% No timer set - shall we set one? +cond_set_idle_timer(D) -> case ssh_client_channel:cache_info(num_entries, cache(D)) of - 0 when IdleTime == infinity -> - %% No. Meaningless to set a timer that fires in an infinite time... - D; - 0 -> - %% Yes, we'll set one since the cache is empty and it should not - %% be that for a specified time - D#data{idle_timer_ref = - erlang:send_after(IdleTime, self(), idle_timer_timeout)}; - _ -> - %% No - there are entries in the cache - D - end; -cache_check_set_idle_timer(D) -> - %% There is already a timer set or the timeout time is infinite - D. - - -cache_cancel_idle_timer(D) -> - case D#data.idle_timer_ref of - infinity -> - %% The timer is not activated - D; - undefined -> - %% The timer is already cancelled - D; - TimerRef -> - %% The timer is active - erlang:cancel_timer(TimerRef), - D#data{idle_timer_ref = undefined} + 0 -> {{timeout,idle_time}, ?GET_OPT(idle_time, (D#data.ssh_params)#ssh.opts), none}; + _ -> {{timeout,idle_time}, infinity, none} end. - -cache_request_idle_timer_check(D = #data{idle_timer_value = infinity}) -> - D; -cache_request_idle_timer_check(D = #data{idle_timer_value = IdleTime}) -> - erlang:send_after(IdleTime, self(), check_cache), - D. - %%%---------------------------------------------------------------- start_channel_request_timer(_,_, infinity) -> ok; @@ -2226,7 +2200,7 @@ update_inet_buffers(Socket) -> %%%# Tracing %%%# -dbg_trace(points, _, _) -> [terminate, disconnect, connections, connection_events]; +dbg_trace(points, _, _) -> [terminate, disconnect, connections, connection_events, renegotiation]; dbg_trace(flags, connections, A) -> [c] ++ dbg_trace(flags, terminate, A); dbg_trace(on, connections, A) -> dbg:tp(?MODULE, init_connection_handler, 3, x), @@ -2269,6 +2243,33 @@ dbg_trace(format, connection_events, {return_from, {?MODULE,handle_event,4}, Ret io_lib:format("~p~n", [event_handler_result(Ret)]) ]; +dbg_trace(flags, renegotiation, _) -> [c]; +dbg_trace(on, renegotiation, _) -> dbg:tpl(?MODULE, init_renegotiate_timers, 2, x), + dbg:tpl(?MODULE, pause_renegotiate_timers, 2, x), + dbg:tpl(?MODULE, check_data_rekeying_dbg, 2, x), + dbg:tpl(?MODULE, start_rekeying, 2, x); +dbg_trace(off, renegotiation, _) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 2), + dbg:ctpl(?MODULE, pause_renegotiate_timers, 2), + dbg:ctpl(?MODULE, check_data_rekeying_dbg, 2), + dbg:ctpl(?MODULE, start_rekeying, 2); +dbg_trace(format, renegotiation, {call, {?MODULE,init_renegotiate_timers,[_State,D]}}) -> + ["Renegotiation init\n", + io_lib:format("rekey_limit: ~p ({ms,bytes})~ncheck_data_size: ~p (ms)~n", + [?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), + ?REKEY_DATA_TIMOUT]) + ]; +dbg_trace(format, renegotiation, {call, {?MODULE,pause_renegotiate_timers,[_State,_D]}}) -> + ["Renegotiation pause\n"]; +dbg_trace(format, renegotiation, {call, {?MODULE,start_rekeying,[_Role,_D]}}) -> + ["Renegotiation start rekeying\n"]; +dbg_trace(format, renegotiation, {call, {?MODULE,check_data_rekeying_dbg,[SentSinceRekey, MaxSent]}}) -> + ["Renegotiation check data sent\n", + io_lib:format("TotalSentSinceRekey: ~p~nMaxBeforeRekey: ~p~nStartRekey: ~p~n", + [SentSinceRekey, MaxSent, SentSinceRekey >= MaxSent]) + ]; + + + dbg_trace(flags, terminate, _) -> [c]; dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 3, x); dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 3); -- cgit v1.2.3 From e557ef7dc3b97fac48da9d30cae9020226864198 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 9 May 2018 14:49:09 +0200 Subject: ssh: Replace link by monitor for exit supervision --- lib/ssh/src/ssh_sftp.erl | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 5984713ec9..9c391abc43 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -171,21 +171,16 @@ start_channel(Host, Port, UserOptions) -> stop_channel(Pid) -> case is_process_alive(Pid) of true -> - OldValue = process_flag(trap_exit, true), - link(Pid), - exit(Pid, ssh_sftp_stop_channel), - receive - {'EXIT', Pid, normal} -> - ok - after 5000 -> - exit(Pid, kill), - receive - {'EXIT', Pid, killed} -> - ok - end - end, - process_flag(trap_exit, OldValue), - ok; + MonRef = erlang:monitor(process, Pid), + unlink(Pid), + exit(Pid, ssh_sftp_stop_channel), + receive {'DOWN',MonRef,_,_,_} -> ok + after + 1000 -> + exit(Pid, kill), + erlang:demonitor(MonRef, [flush]), + ok + end; false -> ok end. -- cgit v1.2.3 From bf852e0a6f893efbbaa9ef4927ec6ba7ba80c101 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 16 May 2018 13:45:30 +0200 Subject: ssh: Terminate reason was always the name of the called callback module --- lib/ssh/src/ssh_client_channel.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl index f20007baaf..134d3f08bd 100644 --- a/lib/ssh/src/ssh_client_channel.erl +++ b/lib/ssh/src/ssh_client_channel.erl @@ -305,8 +305,8 @@ terminate(Reason, #state{cm = ConnectionManager, close_sent = false} = State) -> catch ssh_connection:close(ConnectionManager, ChannelId), terminate(Reason, State#state{close_sent = true}); -terminate(_, #state{channel_cb = Cb, channel_state = ChannelState}) -> - catch Cb:terminate(Cb, ChannelState), +terminate(Reason, #state{channel_cb = Cb, channel_state = ChannelState}) -> + catch Cb:terminate(Reason, ChannelState), ok. %%-------------------------------------------------------------------- -- cgit v1.2.3 From ddc64c79318a8550c896e551385af039ae02d5e5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 30 May 2018 11:43:13 +0200 Subject: ssh: Change get_stacktrace --- lib/ssh/src/ssh_connection_handler.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f1ff3a70e2..3e224fe13f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1345,11 +1345,11 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, {next_event, internal, Msg} ]} catch - C:E -> + C:E:ST -> {Shutdown, D} = ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p", - [C,E,erlang:get_stacktrace()]), + [C,E,ST]), StateName, D1), {stop, Shutdown, D} end; @@ -1378,10 +1378,10 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, StateName, D0), {stop, Shutdown, D} catch - C:E -> + C:E:ST -> {Shutdown, D} = ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, - io_lib:format("Bad packet: Couldn't decrypt~n~p:~p~n~p",[C,E,erlang:get_stacktrace()]), + io_lib:format("Bad packet: Couldn't decrypt~n~p:~p~n~p",[C,E,ST]), StateName, D0), {stop, Shutdown, D} end; -- cgit v1.2.3 From d3b29633d42df9dd7d8a2f09d5b0c98f637fbacd Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 14 May 2018 15:44:34 +0200 Subject: ssh: Better crash report for bad channel callback module --- lib/ssh/src/ssh_client_channel.erl | 2 ++ lib/ssh/src/ssh_connection.erl | 42 +++++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 19 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl index 134d3f08bd..8b5e196412 100644 --- a/lib/ssh/src/ssh_client_channel.erl +++ b/lib/ssh/src/ssh_client_channel.erl @@ -180,6 +180,8 @@ init([Options]) -> {stop, Why} -> {stop, Why} catch + _:undef -> + {stop, {bad_channel_callback_module,Cb}}; _:Reason -> {stop, Reason} end. diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index ed03b4e2ed..dad7636e3f 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -498,25 +498,24 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, data = Data}, #connection{channel_cache = Cache} = Connection, server) -> <> = Data, - - #channel{remote_id = RemoteId} = Channel0 = + #channel{remote_id=RemoteId} = Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), - - ReplyMsg = {subsystem, ChannelId, WantReply, binary_to_list(SsName)}, - - try - {ok, Pid} = start_subsystem(SsName, Connection, Channel0, ReplyMsg), - erlang:monitor(process, Pid), - Channel = Channel0#channel{user = Pid}, - ssh_client_channel:cache_update(Cache, Channel), - Reply = {connection_reply, - channel_success_msg(RemoteId)}, - {[Reply], Connection} - catch - _:_ -> - ErrorReply = {connection_reply, channel_failure_msg(RemoteId)}, - {[ErrorReply], Connection} - end; + Reply = + try + start_subsystem(SsName, Connection, Channel, + {subsystem, ChannelId, WantReply, binary_to_list(SsName)}) + of + {ok, Pid} -> + erlang:monitor(process, Pid), + ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}), + channel_success_msg(RemoteId); + {error,_Error} -> + channel_failure_msg(RemoteId) + catch + _:_ -> + channel_failure_msg(RemoteId) + end, + {[{connection_reply,Reply}], Connection}; handle_msg(#ssh_msg_channel_request{request_type = "subsystem"}, Connection, client) -> @@ -822,7 +821,12 @@ start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), case max_num_channels_not_exceeded(ChannelSup, Opts) of true -> - ssh_server_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); + case ssh_server_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec) of + {error,{Error,_Info}} -> + throw(Error); + Others -> + Others + end; false -> throw(max_num_channels_exceeded) end. -- cgit v1.2.3 From 67af14a073783bfb43972c18f8e7d972ebf3f6b1 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 25 May 2018 14:08:09 +0200 Subject: ssh: Add experimental eddh Not working --- lib/ssh/src/ssh_connection_handler.erl | 4 ++++ lib/ssh/src/ssh_transport.erl | 33 ++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 3e224fe13f..f16aff947b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1770,6 +1770,10 @@ set_kex_overload_prefix(Msg = <>, #data{ssh_params=SshParams case catch atom_to_list(kex(SshParams)) of "ecdh-sha2-" ++ _ -> <<"ecdh",Msg/binary>>; + "curve25519-" ++ _ -> + <<"ecdh",Msg/binary>>; + "curve448-" ++ _ -> + <<"ecdh",Msg/binary>>; "diffie-hellman-group-exchange-" ++ _ -> <<"dh_gex",Msg/binary>>; "diffie-hellman-group" ++ _ -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 631c4d0213..235f09a178 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -104,7 +104,12 @@ algo_two_spec_class(_) -> false. default_algorithms(kex) -> supported_algorithms(kex, [ - 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 + %% Under devolpment: + 'curve25519-sha256', + 'curve25519-sha256@libssh.org', + 'curve448-sha512', + %% Gone in OpenSSH 7.3.p1: + 'diffie-hellman-group1-sha1' ]); default_algorithms(cipher) -> @@ -126,6 +131,11 @@ supported_algorithms(kex) -> {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {ec_curve,secp384r1}, {hashs,sha384}]}, {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, + %% https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves + %% Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 + {'curve25519-sha256', [{public_keys,eddh}, {curves,x25519}, {hashs,sha256}]}, + {'curve25519-sha256@libssh.org', [{public_keys,eddh}, {curves,x25519}, {hashs,sha256}]}, + {'curve448-sha512', [{public_keys,eddh}, {curves,x448}, {hashs,sha512}]}, {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, {'diffie-hellman-group16-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 {'diffie-hellman-group18-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 @@ -403,7 +413,10 @@ key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-gr key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; Kex == 'ecdh-sha2-nistp384' ; - Kex == 'ecdh-sha2-nistp521' -> + Kex == 'ecdh-sha2-nistp521' ; + Kex == 'curve25519-sha256' ; + Kex == 'curve25519-sha256@libssh.org'; + Kex == 'curve448-sha512' -> Curve = ecdh_curve(Kex), {Public, Private} = generate_key(ecdh, Curve), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_ecdh_init{q_c=Public}, Ssh0), @@ -666,8 +679,10 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, catch Class:Error -> ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - io_lib:format("ECDH compute key failed in server: ~p:~p", - [Class,Error]) + io_lib:format("ECDH compute key failed in server: ~p:~p~n" + "Kex: ~p, Curve: ~p~n" + "PeerPublic: ~p", + [Class,Error,Kex,Curve,PeerPublic]) ) end. @@ -1874,6 +1889,11 @@ sha(?'secp521r1') -> sha(secp521r1); sha('ecdh-sha2-nistp256') -> sha(secp256r1); sha('ecdh-sha2-nistp384') -> sha(secp384r1); sha('ecdh-sha2-nistp521') -> sha(secp521r1); +sha('curve25519-sha256' ) -> sha256; +sha('curve25519-sha256@libssh.org' ) -> sha256; +sha('curve448-sha512') -> sha512; +sha(x25519) -> sha256; +sha(x448) -> sha512; sha(Str) when is_list(Str), length(Str)<50 -> sha(list_to_atom(Str)). @@ -1938,7 +1958,10 @@ dh_bits(#alg{encrypt = Encrypt, ecdh_curve('ecdh-sha2-nistp256') -> secp256r1; ecdh_curve('ecdh-sha2-nistp384') -> secp384r1; -ecdh_curve('ecdh-sha2-nistp521') -> secp521r1. +ecdh_curve('ecdh-sha2-nistp521') -> secp521r1; +ecdh_curve('curve448-sha512' ) -> x448; +ecdh_curve('curve25519-sha256' ) -> x25519; +ecdh_curve('curve25519-sha256@libssh.org' ) -> x25519. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3 From 5ca92e2eac1e84fd22f60e7abc3aa2b0ff1cb42b Mon Sep 17 00:00:00 2001 From: Henrik Nord Date: Mon, 18 Jun 2018 14:51:18 +0200 Subject: Update copyright year --- lib/ssh/src/Makefile | 2 +- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh.hrl | 2 +- lib/ssh/src/ssh_acceptor.erl | 2 +- lib/ssh/src/ssh_acceptor_sup.erl | 2 +- lib/ssh/src/ssh_auth.erl | 2 +- lib/ssh/src/ssh_channel.erl | 2 +- lib/ssh/src/ssh_cli.erl | 2 +- lib/ssh/src/ssh_client_channel.erl | 2 +- lib/ssh/src/ssh_client_key_api.erl | 2 +- lib/ssh/src/ssh_connect.hrl | 2 +- lib/ssh/src/ssh_connection.erl | 2 +- lib/ssh/src/ssh_connection_handler.erl | 2 +- lib/ssh/src/ssh_connection_sup.erl | 2 +- lib/ssh/src/ssh_daemon_channel.erl | 2 +- lib/ssh/src/ssh_dbg.erl | 2 +- lib/ssh/src/ssh_file.erl | 2 +- lib/ssh/src/ssh_info.erl | 2 +- lib/ssh/src/ssh_message.erl | 2 +- lib/ssh/src/ssh_no_io.erl | 2 +- lib/ssh/src/ssh_options.erl | 2 +- lib/ssh/src/ssh_server_channel.erl | 2 +- lib/ssh/src/ssh_server_channel_sup.erl | 2 +- lib/ssh/src/ssh_server_key_api.erl | 2 +- lib/ssh/src/ssh_sftp.erl | 2 +- lib/ssh/src/ssh_sftpd.erl | 2 +- lib/ssh/src/ssh_shell.erl | 2 +- lib/ssh/src/ssh_subsystem_sup.erl | 2 +- lib/ssh/src/ssh_sup.erl | 2 +- lib/ssh/src/ssh_system_sup.erl | 2 +- lib/ssh/src/ssh_transport.erl | 2 +- lib/ssh/src/ssh_transport.hrl | 2 +- lib/ssh/src/sshc_sup.erl | 2 +- lib/ssh/src/sshd_sup.erl | 2 +- 34 files changed, 34 insertions(+), 34 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 5e4efb6b99..6d64a45112 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2004-2017. All Rights Reserved. +# Copyright Ericsson AB 2004-2018. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 7ddb1ca5be..086fa6e5f8 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -1,7 +1,7 @@ % %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2017. All Rights Reserved. +%% Copyright Ericsson AB 2004-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 2efd239aae..9631427749 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2017. All Rights Reserved. +%% Copyright Ericsson AB 2004-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 516a9febaa..11ce80354e 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 10fd4452bf..15a2238dd3 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index bf3f5a68e4..4e4aa440de 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index 81c495a815..443bd05086 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index fcc1d3d59f..af51356355 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2017. All Rights Reserved. +%% Copyright Ericsson AB 2005-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl index 8b5e196412..f985d8e273 100644 --- a/lib/ssh/src/ssh_client_channel.erl +++ b/lib/ssh/src/ssh_client_channel.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_client_key_api.erl b/lib/ssh/src/ssh_client_key_api.erl index d0d8ab25d6..384740b786 100644 --- a/lib/ssh/src/ssh_client_key_api.erl +++ b/lib/ssh/src/ssh_client_key_api.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 3c61638285..9a060b8304 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2017. All Rights Reserved. +%% Copyright Ericsson AB 2005-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index dad7636e3f..83f85b1d8e 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f16aff947b..8e4831a601 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl index 2e8450090a..79804b8630 100644 --- a/lib/ssh/src/ssh_connection_sup.erl +++ b/lib/ssh/src/ssh_connection_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_daemon_channel.erl b/lib/ssh/src/ssh_daemon_channel.erl index fdb6c10971..30c4773a7a 100644 --- a/lib/ssh/src/ssh_daemon_channel.erl +++ b/lib/ssh/src/ssh_daemon_channel.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 2ee4237e05..b53c09b17d 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2017. All Rights Reserved. +%% Copyright Ericsson AB 2004-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 9cab2fe0bd..832952ed52 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2017. All Rights Reserved. +%% Copyright Ericsson AB 2005-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl index ee244f1432..79cd95e422 100644 --- a/lib/ssh/src/ssh_info.erl +++ b/lib/ssh/src/ssh_info.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index a2251eab97..55c0548c9b 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2017. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_no_io.erl b/lib/ssh/src/ssh_no_io.erl index 25be0023e9..31963118cb 100644 --- a/lib/ssh/src/ssh_no_io.erl +++ b/lib/ssh/src/ssh_no_io.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index fe95d2ac54..bc9f2156bc 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2017. All Rights Reserved. +%% Copyright Ericsson AB 2004-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_server_channel.erl b/lib/ssh/src/ssh_server_channel.erl index f1c9a85639..555080e9ee 100644 --- a/lib/ssh/src/ssh_server_channel.erl +++ b/lib/ssh/src/ssh_server_channel.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_server_channel_sup.erl b/lib/ssh/src/ssh_server_channel_sup.erl index 70799db714..ff74061bb3 100644 --- a/lib/ssh/src/ssh_server_channel_sup.erl +++ b/lib/ssh/src/ssh_server_channel_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_server_key_api.erl b/lib/ssh/src/ssh_server_key_api.erl index a285bf9475..ef1fe7d69c 100644 --- a/lib/ssh/src/ssh_server_key_api.erl +++ b/lib/ssh/src/ssh_server_key_api.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 9c391abc43..6e720a47b7 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2017. All Rights Reserved. +%% Copyright Ericsson AB 2005-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index cb2eab1fec..7ee762dcee 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2017. All Rights Reserved. +%% Copyright Ericsson AB 2005-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl index 084daa6821..cdc9a6df5b 100644 --- a/lib/ssh/src/ssh_shell.erl +++ b/lib/ssh/src/ssh_shell.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2016. All Rights Reserved. +%% Copyright Ericsson AB 2009-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index f80be7f20b..5fc8f7e764 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 8183016ba5..61afbcd2ed 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 469f9560e9..ed7c0c2bd5 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 235f09a178..9ec16b420d 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2017. All Rights Reserved. +%% Copyright Ericsson AB 2004-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index 7d5a4c153e..f424a4ff63 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index f4b39dbbdc..869de244ac 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 779a861a54..b5361abba5 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From fef7ad89ab1802114f9c69709a1227fac92aa6a0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 20 Jun 2018 12:46:26 +0200 Subject: ssh: Fix ssh_xfer decode_ATTR error for Vsn=4 --- lib/ssh/src/ssh_xfer.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index e1680c120e..7bb9c2d101 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -734,7 +734,7 @@ decode_ATTR(Vsn, <>) -> {Type,Tail2} = if Vsn =< 3 -> {?SSH_FILEXFER_TYPE_UNKNOWN, Tail}; - Vsn >= 5 -> + true -> <> = Tail, {T, TL} end, -- cgit v1.2.3 From 182c7b954620e9287ba2ddce9b1cbaceb1bddbaf Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 21 Jun 2018 10:38:51 +0200 Subject: ssh: Bug fix sftp error codes Report the signal name if the signal error message is "" Do not report a return code of 0 as an error. --- lib/ssh/src/ssh_sftp.erl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 9e1229dc85..79586141b2 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -801,13 +801,22 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) -> %% Ignore signals according to RFC 4254 section 6.9. {ok, State}; -handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, Signal, Error0, _}}, State0) -> + Error = + case Error0 of + "" -> Signal; + _ -> Error0 + end, State = reply_all(State0, {error, Error}), {stop, ChannelId, State}; handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State0) -> - State = reply_all(State0, {error, {exit_status, Status}}), + State = + case State0 of + 0 -> State0; + _ -> reply_all(State0, {error, {exit_status, Status}}) + end, {stop, ChannelId, State}. %%-------------------------------------------------------------------- -- cgit v1.2.3 From f8cbae5c79f988de4110e4827420ad073a1c7fcb Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 21 Jun 2018 10:39:27 +0200 Subject: ssh: Report the signal name if there is an exit-signal to sftpd --- lib/ssh/src/ssh_sftpd.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index a9136e5614..9e172fdb9c 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -137,9 +137,9 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) -> %% Ignore signals according to RFC 4254 section 6.9. {ok, State}; -handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) -> - Report = io_lib:format("Connection closed by peer ~n Error ~p~n", - [Error]), +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, Signal, Error, _}}, State) -> + Report = io_lib:format("Connection closed by peer signal ~p~n Error ~p~n", + [Signal,Error]), error_logger:error_report(Report), {stop, ChannelId, State}; -- cgit v1.2.3 From 6bd4fc6943112db14c0b71fac707cf3def22aa83 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 8 Aug 2018 19:31:27 +0200 Subject: ssh: Circular trace buffer primitives --- lib/ssh/src/ssh.hrl | 25 +++++++++++ lib/ssh/src/ssh_dbg.erl | 108 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 9631427749..01c44cb371 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -492,4 +492,29 @@ -define(wr_record(N), ?wr_record(N, [])). +%% Circular trace buffer macros + +-record(circ_buf_entry, + { + module, + line, + function, + pid = self(), + value + }). + +-define(CIRC_BUF_IN(VALUE), + ssh_dbg:cbuf_in( + #circ_buf_entry{module = ?MODULE, + line = ?LINE, + function = {?FUNCTION_NAME,?FUNCTION_ARITY}, + pid = self(), + value = (VALUE) + }) + ). + +-define(CIRC_BUF_IN_ONCE(VALUE), + ((fun(V) -> ?CIRC_BUF_IN(V), V end)(VALUE)) + ). + -endif. % SSH_HRL defined diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index b53c09b17d..3681e03cc2 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -54,7 +54,13 @@ start_tracer/0, start_tracer/1, on/1, on/0, off/1, off/0, - go_on/0 + go_on/0, + %% Circular buffer + cbuf_start/0, cbuf_start/1, + cbuf_stop_clear/0, + cbuf_in/1, + cbuf_list/0, + fmt_cbuf_items/0, fmt_cbuf_item/1 ]). -export([shrink_bin/1, @@ -331,3 +337,103 @@ ts({_,_,Usec}=Now) when is_integer(Usec) -> io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); ts(_) -> "-". + +%%%================================================================ +-define(CIRC_BUF, circ_buf). + +cbuf_start() -> + cbuf_start(20). + +cbuf_start(CbufMaxLen) -> + put(?CIRC_BUF, {CbufMaxLen,queue:new()}), + ok. + + +cbuf_stop_clear() -> + case erase(?CIRC_BUF) of + undefined -> + []; + {_CbufMaxLen,Queue} -> + queue:to_list(Queue) + end. + + +cbuf_in(Value) -> + case get(?CIRC_BUF) of + undefined -> + disabled; + {CbufMaxLen,Queue} -> + UpdatedQueue = + try queue:head(Queue) of + {Value, TS0, Cnt0} -> + %% Same Value as last saved in the queue + queue:in_r({Value, TS0, Cnt0+1}, + queue:drop(Queue) + ); + _ -> + queue:in_r({Value, erlang:timestamp(), 1}, + truncate_cbuf(Queue, CbufMaxLen) + ) + catch + error:empty -> + queue:in_r({Value, erlang:timestamp(), 1}, Queue) + end, + put(?CIRC_BUF, {CbufMaxLen,UpdatedQueue}), + ok + end. + + +cbuf_list() -> + case get(?CIRC_BUF) of + undefined -> + []; + {_CbufMaxLen,Queue} -> + queue:to_list(Queue) + end. + + +truncate_cbuf(Q, CbufMaxLen) -> + case queue:len(Q) of + N when N>=CbufMaxLen -> + truncate_cbuf(element(2,queue:out_r(Q)), CbufMaxLen); + _ -> + Q + end. + +fmt_cbuf_items() -> + lists:flatten( + io_lib:format("Circular trace buffer. Latest item first.~n~s~n", + [case get(?CIRC_BUF) of + {Max,_} -> + L = cbuf_list(), + [io_lib:format("==== ~.*w: ~s~n",[num_digits(Max),N,fmt_cbuf_item(X)]) || + {N,X} <- lists:zip(lists:seq(1,length(L)), L) + ]; + _ -> + io_lib:format("Not started.~n",[]) + end])). + + +num_digits(0) -> 1; +num_digits(N) when N>0 -> 1+trunc(math:log10(N)). + + +fmt_cbuf_item({Value, TimeStamp, N}) -> + io_lib:format("~s~s~n~s~n", + [fmt_ts(TimeStamp), + [io_lib:format(" (Repeated ~p times)",[N]) || N>1], + fmt_value(Value)]). + + +fmt_ts(TS = {_,_,Us}) -> + {{YY,MM,DD},{H,M,S}} = calendar:now_to_universal_time(TS), + io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~.6.0w UTC",[YY,MM,DD,H,M,S,Us]). + +fmt_value(#circ_buf_entry{module = M, + line = L, + function = {F,A}, + pid = Pid, + value = V}) -> + io_lib:format("~p:~p ~p/~p ~p~n~s",[M,L,F,A,Pid,fmt_value(V)]); +fmt_value(Value) -> + io_lib:format("~p",[Value]). -- cgit v1.2.3 From e93371b769905e46a579f5117086d47a1181a05e Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 14 Aug 2018 18:05:17 +0200 Subject: ssh: prolong the timeout in ssh_dbg gen_server calls --- lib/ssh/src/ssh_dbg.erl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 3681e03cc2..4fe15b24d3 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -77,6 +77,8 @@ -behaviour(gen_server). -define(SERVER, ?MODULE). +-define(CALL_TIMEOUT, 15000). % 3x the default + %%%================================================================ -define(ALL_DBG_TYPES, get_all_dbg_types()). @@ -113,7 +115,7 @@ start_tracer(WriteFun) when is_function(WriteFun,3) -> start_tracer(WriteFun, InitAcc) when is_function(WriteFun, 3) -> Handler = fun(Arg, Acc0) -> - try_all_types_in_all_modules(gen_server:call(?SERVER, get_on), + try_all_types_in_all_modules(gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT), Arg, WriteFun, Acc0) end, @@ -128,7 +130,7 @@ off() -> off(?ALL_DBG_TYPES). % A bit overkill... off(Type) -> switch(off, Type). go_on() -> - IsOn = gen_server:call(?SERVER, get_on), + IsOn = gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT), on(IsOn). %%%---------------------------------------------------------------- @@ -259,7 +261,7 @@ switch(X, Types) when is_list(Types) -> end, case lists:usort(Types) -- ?ALL_DBG_TYPES of [] -> - gen_server:call(?SERVER, {switch,X,Types}); + gen_server:call(?SERVER, {switch,X,Types}, ?CALL_TIMEOUT); L -> {error, {unknown, L}} end. -- cgit v1.2.3 From a6436b00e545e388326c621f1884bad3918a79b4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 14 Aug 2018 13:25:14 +0200 Subject: ssh: Use the new crypto:supports/0 tag 'curves' --- lib/ssh/src/ssh_transport.erl | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 9ec16b420d..562f204de4 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -128,9 +128,9 @@ supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()]. supported_algorithms(kex) -> select_crypto_supported( [ - {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {ec_curve,secp384r1}, {hashs,sha384}]}, - {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, - {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, + {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {curves,secp384r1}, {hashs,sha384}]}, + {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {curves,secp521r1}, {hashs,sha512}]}, + {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {curves,secp256r1}, {hashs,sha256}]}, %% https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves %% Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 {'curve25519-sha256', [{public_keys,eddh}, {curves,x25519}, {hashs,sha256}]}, @@ -147,9 +147,9 @@ supported_algorithms(kex) -> supported_algorithms(public_key) -> select_crypto_supported( [ - {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, - {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, - {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {curves,secp384r1}]}, + {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {curves,secp521r1}]}, + {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {curves,secp256r1}]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]}, {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]}, @@ -1978,15 +1978,10 @@ supported_algorithms(Key, BlackList) -> select_crypto_supported(L) -> - Sup = [{ec_curve,crypto_supported_curves()} | crypto:supports()], + Sup = crypto:supports(), [Name || {Name,CryptoRequires} <- L, crypto_supported(CryptoRequires, Sup)]. -crypto_supported_curves() -> - try crypto:ec_curves() - catch _:_ -> [] - end. - crypto_supported(Conditions, Supported) -> lists:all( fun({Tag,CryptoName}) when is_atom(CryptoName) -> crypto_name_supported(Tag,CryptoName,Supported); @@ -1996,7 +1991,11 @@ crypto_supported(Conditions, Supported) -> end, Conditions). crypto_name_supported(Tag, CryptoName, Supported) -> - lists:member(CryptoName, proplists:get_value(Tag,Supported,[])). + Vs = case proplists:get_value(Tag,Supported,[]) of + [] when Tag == curves -> crypto:ec_curves(); + L -> L + end, + lists:member(CryptoName, Vs). len_supported(Name, Len) -> try -- cgit v1.2.3 From ced9c4ce61fc4b1bc3f9150d86da43485e590de5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 14 Aug 2018 13:29:23 +0200 Subject: ssh: Use the 'macs' tag in crypto:supports/0 --- lib/ssh/src/ssh_transport.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 562f204de4..b6d7aa0b1b 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -174,9 +174,9 @@ supported_algorithms(cipher) -> supported_algorithms(mac) -> same( select_crypto_supported( - [{'hmac-sha2-256', [{hashs,sha256}]}, - {'hmac-sha2-512', [{hashs,sha512}]}, - {'hmac-sha1', [{hashs,sha}]}, + [{'hmac-sha2-256', [{macs,hmac}, {hashs,sha256}]}, + {'hmac-sha2-512', [{macs,hmac}, {hashs,sha512}]}, + {'hmac-sha1', [{macs,hmac}, {hashs,sha}]}, {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]}, {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]} ] -- cgit v1.2.3 From f3fde4cba0bca14a8718f39e7f7c68acd85f4078 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 31 Jul 2018 16:22:24 +0200 Subject: ssh: Add chacha20-poly1305@openssh.com cipher --- lib/ssh/src/ssh_connection_handler.erl | 15 ++- lib/ssh/src/ssh_transport.erl | 177 ++++++++++++++++++++++++++------- 2 files changed, 149 insertions(+), 43 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8e4831a601..4b41c10cbb 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -356,6 +356,8 @@ alg(ConnectionHandler) -> | undefined, encrypted_data_buffer = <<>> :: binary() | undefined, + aead_data = <<>> :: binary() + | undefined, undecrypted_packet_length :: undefined | non_neg_integer(), key_exchange_init_msg :: #ssh_msg_kexinit{} | undefined, @@ -1308,14 +1310,16 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, try ssh_transport:handle_packet_part( D0#data.decrypted_data_buffer, <<(D0#data.encrypted_data_buffer)/binary, NewData/binary>>, - D0#data.undecrypted_packet_length, + D0#data.aead_data, + D0#data.undecrypted_packet_length, D0#data.ssh_params) of {packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} -> D1 = D0#data{ssh_params = Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)}, decrypted_data_buffer = <<>>, - undecrypted_packet_length = undefined, + undecrypted_packet_length = undefined, + aead_data = <<>>, encrypted_data_buffer = EncryptedDataRest}, try ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D1)) @@ -1353,14 +1357,15 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, StateName, D1), {stop, Shutdown, D} end; - - {get_more, DecryptedBytes, EncryptedDataRest, RemainingSshPacketLen, Ssh1} -> + + {get_more, DecryptedBytes, EncryptedDataRest, AeadData, RemainingSshPacketLen, Ssh1} -> %% Here we know that there are not enough bytes in %% EncryptedDataRest to use. We must wait for more. inet:setopts(Sock, [{active, once}]), {keep_state, D0#data{encrypted_data_buffer = EncryptedDataRest, decrypted_data_buffer = DecryptedBytes, - undecrypted_packet_length = RemainingSshPacketLen, + undecrypted_packet_length = RemainingSshPacketLen, + aead_data = AeadData, ssh_params = Ssh1}}; {bad_mac, Ssh1} -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index b6d7aa0b1b..d6b9142529 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -36,7 +36,7 @@ default_algorithms/0, default_algorithms/1, algo_classes/0, algo_class/1, algo_two_spec_classes/0, algo_two_spec_class/1, - handle_packet_part/4, + handle_packet_part/5, handle_hello_version/1, key_exchange_init_msg/1, key_init/3, new_keys_message/1, @@ -114,7 +114,8 @@ default_algorithms(kex) -> default_algorithms(cipher) -> supported_algorithms(cipher, same(['AEAD_AES_128_GCM', - 'AEAD_AES_256_GCM'])); + 'AEAD_AES_256_GCM' + ])); default_algorithms(mac) -> supported_algorithms(mac, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); @@ -160,6 +161,7 @@ supported_algorithms(cipher) -> same( select_crypto_supported( [ + {'chacha20-poly1305@openssh.com', [{ciphers,chacha20}, {macs,poly1305}]}, {'aes256-gcm@openssh.com', [{ciphers,{aes_gcm,256}}]}, {'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, @@ -982,13 +984,14 @@ select_algorithm(Role, Client, Server, Opts) -> %%% the exchanged MAC algorithms are ignored and there doesn't have to be %%% a matching MAC. -aead_gcm_simultan('aes128-gcm@openssh.com', _) -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'}; -aead_gcm_simultan('aes256-gcm@openssh.com', _) -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'}; -aead_gcm_simultan('AEAD_AES_128_GCM', _) -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'}; -aead_gcm_simultan('AEAD_AES_256_GCM', _) -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'}; -aead_gcm_simultan(_, 'AEAD_AES_128_GCM') -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'}; -aead_gcm_simultan(_, 'AEAD_AES_256_GCM') -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'}; -aead_gcm_simultan(Cipher, Mac) -> {Cipher,Mac}. +aead_gcm_simultan('aes128-gcm@openssh.com', _) -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'}; +aead_gcm_simultan('aes256-gcm@openssh.com', _) -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'}; +aead_gcm_simultan('AEAD_AES_128_GCM'=C, _) -> {C, C}; +aead_gcm_simultan('AEAD_AES_256_GCM'=C, _) -> {C, C}; +aead_gcm_simultan(_, 'AEAD_AES_128_GCM'=C) -> {C, C}; +aead_gcm_simultan(_, 'AEAD_AES_256_GCM'=C) -> {C, C}; +aead_gcm_simultan('chacha20-poly1305@openssh.com'=C, _)-> {C, C}; +aead_gcm_simultan(Cipher, Mac) -> {Cipher,Mac}. select_encrypt_decrypt(client, Client, Server) -> @@ -1136,7 +1139,7 @@ pack(PlainText, encrypt = CryptoAlg} = Ssh0, PacketLenDeviationForTests) when is_binary(PlainText) -> {Ssh1, CompressedPlainText} = compress(Ssh0, PlainText), - {EcryptedPacket, MAC, Ssh3} = + {FinalPacket, Ssh3} = case pkt_type(CryptoAlg) of common -> PaddingLen = padding_length(4+1+size(CompressedPlainText), Ssh0), @@ -1145,16 +1148,15 @@ pack(PlainText, PlainPacketData = <>, {Ssh2, EcryptedPacket0} = encrypt(Ssh1, PlainPacketData), MAC0 = mac(MacAlg, MacKey, SeqNum, PlainPacketData), - {EcryptedPacket0, MAC0, Ssh2}; + {<>, Ssh2}; aead -> PaddingLen = padding_length(1+size(CompressedPlainText), Ssh0), Padding = ssh_bits:random(PaddingLen), PlainPacketLen = 1 + PaddingLen + size(CompressedPlainText) + PacketLenDeviationForTests, PlainPacketData = <>, - {Ssh2, {EcryptedPacket0,MAC0}} = encrypt(Ssh1, {<>,PlainPacketData}), - {<>, MAC0, Ssh2} + {Ssh2, {EcryptedPacket0,MAC0}} = encrypt(Ssh1, <>), + {<>, Ssh2} end, - FinalPacket = [EcryptedPacket, MAC], Ssh = Ssh3#ssh{send_sequence = (SeqNum+1) band 16#ffffffff}, {FinalPacket, Ssh}. @@ -1174,31 +1176,31 @@ padding_length(Size, #ssh{encrypt_block_size = BlockSize, -handle_packet_part(<<>>, Encrypted0, undefined, #ssh{decrypt = CryptoAlg} = Ssh0) -> +handle_packet_part(<<>>, Encrypted0, AEAD0, undefined, #ssh{decrypt = CryptoAlg} = Ssh0) -> %% New ssh packet case get_length(pkt_type(CryptoAlg), Encrypted0, Ssh0) of get_more -> %% too short to get the length - {get_more, <<>>, Encrypted0, undefined, Ssh0}; + {get_more, <<>>, Encrypted0, AEAD0, undefined, Ssh0}; - {ok, PacketLen, _, _, _} when PacketLen > ?SSH_MAX_PACKET_SIZE -> + {ok, PacketLen, _, _, _, _} when PacketLen > ?SSH_MAX_PACKET_SIZE -> %% far too long message than expected {error, {exceeds_max_size,PacketLen}}; - {ok, PacketLen, Decrypted, Encrypted1, + {ok, PacketLen, Decrypted, Encrypted1, AEAD, #ssh{recv_mac_size = MacSize} = Ssh1} -> %% enough bytes so we got the length and can calculate how many %% more bytes to expect for a full packet TotalNeeded = (4 + PacketLen + MacSize), - handle_packet_part(Decrypted, Encrypted1, TotalNeeded, Ssh1) + handle_packet_part(Decrypted, Encrypted1, AEAD, TotalNeeded, Ssh1) end; -handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, Ssh0) +handle_packet_part(DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0) when (size(DecryptedPfx)+size(EncryptedBuffer)) < TotalNeeded -> %% need more bytes to finalize the packet - {get_more, DecryptedPfx, EncryptedBuffer, TotalNeeded, Ssh0}; + {get_more, DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0}; -handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, +handle_packet_part(DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, #ssh{recv_mac_size = MacSize, decrypt = CryptoAlg} = Ssh0) -> %% enough bytes to decode the packet. @@ -1216,8 +1218,7 @@ handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh} end; aead -> - PacketLenBin = DecryptedPfx, - case decrypt(Ssh0, {PacketLenBin,EncryptedSfx,Mac}) of + case decrypt(Ssh0, {AEAD,EncryptedSfx,Mac}) of {Ssh1, error} -> {bad_mac, Ssh1}; {Ssh1, DecryptedSfx} -> @@ -1234,21 +1235,29 @@ get_length(common, EncryptedBuffer, #ssh{decrypt_block_size = BlockSize} = Ssh0) <> = EncryptedBuffer, {Ssh, <> = Decrypted} = decrypt(Ssh0, EncBlock), - {ok, PacketLen, Decrypted, EncryptedRest, Ssh}; + {ok, PacketLen, Decrypted, EncryptedRest, <<>>, Ssh}; false -> get_more end; + get_length(aead, EncryptedBuffer, Ssh) -> - case size(EncryptedBuffer) >= 4 of - true -> + case {size(EncryptedBuffer) >= 4, Ssh#ssh.decrypt} of + {true, 'chacha20-poly1305@openssh.com'} -> + <> = EncryptedBuffer, + {Ssh1, PacketLenBin} = decrypt(Ssh, {length,EncryptedLen}), + <> = PacketLenBin, + {ok, PacketLen, PacketLenBin, EncryptedRest, EncryptedLen, Ssh1}; + {true, _} -> <> = EncryptedBuffer, - {ok, PacketLen, <>, EncryptedRest, Ssh}; - false -> + {ok, PacketLen, <>, EncryptedRest, <>, Ssh}; + {false, _} -> get_more end. + pkt_type('AEAD_AES_128_GCM') -> aead; pkt_type('AEAD_AES_256_GCM') -> aead; +pkt_type('chacha20-poly1305@openssh.com') -> aead; pkt_type(_) -> common. payload(<>) -> @@ -1353,11 +1362,32 @@ cipher('aes192-ctr') -> cipher('aes256-ctr') -> #cipher_data{key_bytes = 32, iv_bytes = 16, - block_bytes = 16}. + block_bytes = 16}; + +cipher('chacha20-poly1305@openssh.com') -> % FIXME: Verify!! + #cipher_data{key_bytes = 32, + iv_bytes = 12, + block_bytes = 8}. + encrypt_init(#ssh{encrypt = none} = Ssh) -> {ok, Ssh}; +encrypt_init(#ssh{encrypt = 'chacha20-poly1305@openssh.com', role = client} = Ssh) -> + %% chacha20-poly1305@openssh.com uses two independent crypto streams, one (chacha20) + %% for the length used in stream mode, and the other (chacha20-poly1305) as AEAD for + %% the payload and to MAC the length||payload. + %% See draft-josefsson-ssh-chacha20-poly1305-openssh-00 + <> = hash(Ssh, "C", 512), + {ok, Ssh#ssh{encrypt_keys = {K1,K2} + % encrypt_block_size = 16, %default = 8. What to set it to? 64 (openssl chacha.h) + % ctx and iv is setup for each packet + }}; +encrypt_init(#ssh{encrypt = 'chacha20-poly1305@openssh.com', role = server} = Ssh) -> + <> = hash(Ssh, "D", 512), + {ok, Ssh#ssh{encrypt_keys = {K1,K2} + % encrypt_block_size = 16, %default = 8. What to set it to? + }}; encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> IV = hash(Ssh, "A", 12*8), <> = hash(Ssh, "C", 128), @@ -1458,18 +1488,40 @@ encrypt_final(Ssh) -> encrypt(#ssh{encrypt = none} = Ssh, Data) -> {Ssh, Data}; +encrypt(#ssh{encrypt = 'chacha20-poly1305@openssh.com', + encrypt_keys = {K1,K2}, + send_sequence = Seq} = Ssh, + <>) -> + %% Encrypt length + IV1 = <<0:8/unit:8, Seq:8/unit:8>>, + {_,EncLen} = crypto:stream_encrypt(crypto:stream_init(chacha20, K1, IV1), + LenData), + %% Encrypt payload + IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>, + {_,EncPayloadData} = crypto:stream_encrypt(crypto:stream_init(chacha20, K2, IV2), + PayloadData), + + %% MAC tag + {_,PolyKey} = crypto:stream_encrypt(crypto:stream_init(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>), + <<0:32/unit:8>>), + EncBytes = <>, + Ctag = crypto:poly1305(PolyKey, EncBytes), + %% Result + {Ssh, {EncBytes,Ctag}}; encrypt(#ssh{encrypt = 'AEAD_AES_128_GCM', encrypt_keys = K, - encrypt_ctx = IV0} = Ssh, Data={_AAD,_Ptext}) -> - Enc = {_Ctext,_Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, Data), + encrypt_ctx = IV0} = Ssh, + <>) -> + {Ctext,Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, {LenData,PayloadData}), IV = next_gcm_iv(IV0), - {Ssh#ssh{encrypt_ctx = IV}, Enc}; + {Ssh#ssh{encrypt_ctx = IV}, {<>,Ctag}}; encrypt(#ssh{encrypt = 'AEAD_AES_256_GCM', encrypt_keys = K, - encrypt_ctx = IV0} = Ssh, Data={_AAD,_Ptext}) -> - Enc = {_Ctext,_Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, Data), + encrypt_ctx = IV0} = Ssh, + <>) -> + {Ctext,Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, {LenData,PayloadData}), IV = next_gcm_iv(IV0), - {Ssh#ssh{encrypt_ctx = IV}, Enc}; + {Ssh#ssh{encrypt_ctx = IV}, {<>,Ctag}}; encrypt(#ssh{encrypt = '3des-cbc', encrypt_keys = {K1,K2,K3}, encrypt_ctx = IV0} = Ssh, Data) -> @@ -1502,6 +1554,14 @@ encrypt(#ssh{encrypt = 'aes256-ctr', decrypt_init(#ssh{decrypt = none} = Ssh) -> {ok, Ssh}; +decrypt_init(#ssh{decrypt = 'chacha20-poly1305@openssh.com', role = client} = Ssh) -> + <> = hash(Ssh, "D", 512), + {ok, Ssh#ssh{decrypt_keys = {K1,K2} + }}; +decrypt_init(#ssh{decrypt = 'chacha20-poly1305@openssh.com', role = server} = Ssh) -> + <> = hash(Ssh, "C", 512), + {ok, Ssh#ssh{decrypt_keys = {K1,K2} + }}; decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> IV = hash(Ssh, "B", 12*8), <> = hash(Ssh, "D", 128), @@ -1602,6 +1662,31 @@ decrypt_final(Ssh) -> decrypt(Ssh, <<>>) -> {Ssh, <<>>}; +decrypt(#ssh{decrypt = 'chacha20-poly1305@openssh.com', + decrypt_keys = {K1,_K2}, + recv_sequence = Seq} = Ssh, {length,EncryptedLen}) -> + {_State,PacketLenBin} = + crypto:stream_decrypt(crypto:stream_init(chacha20, K1, <<0:8/unit:8, Seq:8/unit:8>>), + EncryptedLen), + {Ssh, PacketLenBin}; +decrypt(#ssh{decrypt = 'chacha20-poly1305@openssh.com', + decrypt_keys = {_K1,K2}, + recv_sequence = Seq} = Ssh, {AAD,Ctext,Ctag}) -> + %% The length is already decoded and used to divide the input + %% Check the mac (important that it is timing-safe): + {_,PolyKey} = + crypto:stream_encrypt(crypto:stream_init(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>), + <<0:32/unit:8>>), + case equal_const_time(Ctag, crypto:poly1305(PolyKey, <>)) of + true -> + %% MAC is ok, decode + IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>, + {_,PlainText} = + crypto:stream_decrypt(crypto:stream_init(chacha20,K2,IV2), Ctext), + {Ssh, PlainText}; + false -> + {Ssh,error} + end; decrypt(#ssh{decrypt = none} = Ssh, Data) -> {Ssh, Data}; decrypt(#ssh{decrypt = 'AEAD_AES_128_GCM', @@ -1744,7 +1829,7 @@ send_mac_init(SSH) -> Key = hash(SSH, "F", KeySize), {ok, SSH#ssh { send_mac_key = Key }} end; - aead -> + _ -> %% Not applicable {ok, SSH} end. @@ -1765,7 +1850,7 @@ recv_mac_init(SSH) -> Key = hash(SSH, "E", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }} end; - aead -> + _ -> %% Not applicable {ok, SSH} end. @@ -1905,6 +1990,7 @@ mac_key_bytes('hmac-sha2-256')-> 32; mac_key_bytes('hmac-sha2-512')-> 64; mac_key_bytes('AEAD_AES_128_GCM') -> 0; mac_key_bytes('AEAD_AES_256_GCM') -> 0; +mac_key_bytes('chacha20-poly1305@openssh.com') -> 0; mac_key_bytes(none) -> 0. mac_digest_size('hmac-sha1') -> 20; @@ -1915,6 +2001,7 @@ mac_digest_size('hmac-sha2-256') -> 32; mac_digest_size('hmac-sha2-512') -> 64; mac_digest_size('AEAD_AES_128_GCM') -> 16; mac_digest_size('AEAD_AES_256_GCM') -> 16; +mac_digest_size('chacha20-poly1305@openssh.com') -> 16; mac_digest_size(none) -> 0. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2026,6 +2113,20 @@ same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Compare two binaries in a timing safe maner. +%%% The time spent in comparing should not be different depending on where in the binaries they differ. +%%% This is to avoid a certain side-channel attac. +equal_const_time(X1, X2) -> equal_const_time(X1, X2, true). + +equal_const_time(<>, <>, Truth) -> + equal_const_time(R1, R2, Truth and (B1 == B2)); +equal_const_time(<<>>, <<>>, Truth) -> + Truth; +equal_const_time(_, _, _) -> + false. + +%%%-------- Remove CR, LF and following characters from a line + trim_tail(Str) -> lists:takewhile(fun(C) -> C=/=$\r andalso C=/=$\n -- cgit v1.2.3 From 9b2685fd56dc6886f75d1cdfd4c1a061a52716a7 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 2 Aug 2018 13:32:38 +0200 Subject: ssh: -spec --- lib/ssh/src/ssh.hrl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 01c44cb371..4ac5b7a2c1 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -140,7 +140,8 @@ 'aes128-gcm@openssh.com' | 'aes192-ctr' | 'aes256-ctr' | - 'aes256-gcm@openssh.com' + 'aes256-gcm@openssh.com' | + 'chacha20-poly1305@openssh.com' . -type mac_alg() :: 'AEAD_AES_128_GCM' | -- cgit v1.2.3 From 062f6c8333a02c615da16d9732690560f53beb80 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 27 Jun 2018 20:18:47 +0200 Subject: ssh: Remove the term 'eddh' --- lib/ssh/src/ssh_transport.erl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index b6d7aa0b1b..0add320518 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -133,13 +133,9 @@ supported_algorithms(kex) -> {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {curves,secp256r1}, {hashs,sha256}]}, %% https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves %% Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 - {'curve25519-sha256', [{public_keys,eddh}, {curves,x25519}, {hashs,sha256}]}, - {'curve25519-sha256@libssh.org', [{public_keys,eddh}, {curves,x25519}, {hashs,sha256}]}, - {'curve448-sha512', [{public_keys,eddh}, {curves,x448}, {hashs,sha512}]}, - {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, - {'diffie-hellman-group16-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 - {'diffie-hellman-group18-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 - {'diffie-hellman-group14-sha256', [{public_keys,dh}, {hashs,sha256}]}, % In OpenSSH 7.3.p1 + {'curve25519-sha256', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]}, + {'curve25519-sha256@libssh.org', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]}, + {'curve448-sha512', [{public_keys,ecdh}, {curves,x448}, {hashs,sha512}]}, {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} -- cgit v1.2.3 From bd78bea89c1583af283b058247370ddfd06833a9 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 27 Jun 2018 13:41:50 +0200 Subject: ssh: Enable curve448-sha512 and curve25519-sha256 --- lib/ssh/src/ssh_transport.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 0add320518..4bef5e2cbb 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -104,10 +104,6 @@ algo_two_spec_class(_) -> false. default_algorithms(kex) -> supported_algorithms(kex, [ - %% Under devolpment: - 'curve25519-sha256', - 'curve25519-sha256@libssh.org', - 'curve448-sha512', %% Gone in OpenSSH 7.3.p1: 'diffie-hellman-group1-sha1' ]); @@ -131,6 +127,10 @@ supported_algorithms(kex) -> {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {curves,secp384r1}, {hashs,sha384}]}, {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {curves,secp521r1}, {hashs,sha512}]}, {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {curves,secp256r1}, {hashs,sha256}]}, + {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, + {'diffie-hellman-group16-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 + {'diffie-hellman-group18-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 + {'diffie-hellman-group14-sha256', [{public_keys,dh}, {hashs,sha256}]}, % In OpenSSH 7.3.p1 %% https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves %% Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 {'curve25519-sha256', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]}, -- cgit v1.2.3 From 159b55f2fc7e61e1beebc904eec41a066c41508b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 27 Jun 2018 13:44:07 +0200 Subject: ssh: Fix non-conforming key generation in kex for x25519 and x448 That is, curve25519-sha256, curve25519-sha256@libssh.org and curve448-sha512 --- lib/ssh/src/ssh_message.erl | 8 ++++---- lib/ssh/src/ssh_transport.erl | 12 ++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 55c0548c9b..da4027a763 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -289,12 +289,12 @@ encode(#ssh_msg_kex_dh_gex_reply{ <>; encode(#ssh_msg_kex_ecdh_init{q_c = Q_c}) -> - <>; + <>; encode(#ssh_msg_kex_ecdh_reply{public_host_key = {Key,SigAlg}, q_s = Q_s, h_sig = Sign}) -> EncKey = public_key:ssh_encode(Key, ssh2_pubkey), EncSign = encode_signature(Key, SigAlg, Sign), - <>; + <>; encode(#ssh_msg_ignore{data = Data}) -> <>; @@ -504,13 +504,13 @@ decode(<>) -> +decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT), ?DEC_BIN(Q_c,__0)>>) -> #ssh_msg_kex_ecdh_init{ q_c = Q_c }; decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_REPLY), - ?DEC_BIN(Key,__1), ?DEC_MPINT(Q_s,__2), ?DEC_BIN(Sig,__3)>>) -> + ?DEC_BIN(Key,__1), ?DEC_BIN(Q_s,__2), ?DEC_BIN(Sig,__3)>>) -> #ssh_msg_kex_ecdh_reply{ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey), q_s = Q_s, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 4bef5e2cbb..9aebf4cb3c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1808,6 +1808,7 @@ hash(K, H, Ki, N, HashAlg) -> kex_hash(SSH, Key, HashAlg, Args) -> crypto:hash(HashAlg, kex_plaintext(SSH,Key,Args)). + kex_plaintext(SSH, Key, Args) -> EncodedKey = public_key:ssh_encode(Key, ssh2_pubkey), < ?Ebinary(EncodedKey), (kex_alg_dependent(Args))/binary>>. + +kex_alg_dependent({Q_c, Q_s, K}) when is_binary(Q_c), is_binary(Q_s) -> + %% ecdh + <>; + kex_alg_dependent({E, F, K}) -> - %% diffie-hellman and ec diffie-hellman (with E = Q_c, F = Q_s) + %% diffie-hellman <>; kex_alg_dependent({-1, NBits, -1, Prime, Gen, E, F, K}) -> @@ -1933,11 +1939,13 @@ parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}}, Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}. +generate_key(ecdh = Algorithm, Args) -> + crypto:generate_key(Algorithm, Args); generate_key(Algorithm, Args) -> {Public,Private} = crypto:generate_key(Algorithm, Args), {crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}. - + compute_key(Algorithm, OthersPublic, MyPrivate, Args) -> Shared = crypto:compute_key(Algorithm, OthersPublic, MyPrivate, Args), crypto:bytes_to_integer(Shared). -- cgit v1.2.3 From aa354f71a10087d161f69aa05dbcb685387289ad Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Aug 2018 12:10:26 +0200 Subject: ssh: spec curve25519 & curve448 --- lib/ssh/src/ssh.hrl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 01c44cb371..d62ae82f4b 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -118,6 +118,9 @@ 'diffie-hellman-group14-sha256' | 'diffie-hellman-group16-sha512' | 'diffie-hellman-group18-sha512' | + 'curve25519-sha256' | + 'curve25519-sha256@libssh.org' | + 'curve448-sha512' | 'ecdh-sha2-nistp256' | 'ecdh-sha2-nistp384' | 'ecdh-sha2-nistp521' -- cgit v1.2.3 From df39504bc6f9b106a20959b969b258db327261a5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 29 Aug 2018 14:18:41 +0200 Subject: ssh: Use exported crypto types --- lib/ssh/src/ssh.hrl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 66dbf0b144..94b9f3a196 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -260,13 +260,7 @@ | accept_callback() | {HashAlgoSpec::fp_digest_alg(), accept_callback()}. --type fp_digest_alg() :: 'md5' | - 'sha' | - 'sha224' | - 'sha256' | - 'sha384' | - 'sha512' - . +-type fp_digest_alg() :: 'md5' | crypto:sha1() | crypto:sha2() . -type accept_callback() :: fun((PeerName::string(), fingerprint() ) -> boolean()) . -type fingerprint() :: string() | [string()]. -- cgit v1.2.3 From d04b53936d5e9f84a3066ecd466993671b1428bf Mon Sep 17 00:00:00 2001 From: Henrik Nord Date: Fri, 21 Sep 2018 12:23:25 +0200 Subject: Update copyright year --- lib/ssh/src/ssh_xfer.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index 7bb9c2d101..1d77ccb311 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From d655a343837f4a05ca7a9683d57245734d7482ac Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 22 Oct 2018 16:12:02 +0200 Subject: ssh: Move some option's documentation to ssh_file user_dir user_dir_fun (missing previously) *_passphrase system_dir --- lib/ssh/src/ssh.hrl | 14 ++++---------- lib/ssh/src/ssh_file.erl | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 94b9f3a196..3ac74c4925 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -173,7 +173,7 @@ -type common_options() :: [ common_option() ]. -type common_option() :: - user_dir_common_option() + ssh_file:user_dir_common_option() | profile_common_option() | max_idle_time_common_option() | key_cb_common_option() @@ -191,8 +191,6 @@ -define(COMMON_OPTION, common_option()). - --type user_dir_common_option() :: {user_dir, false | string()}. -type profile_common_option() :: {profile, atom() }. -type max_idle_time_common_option() :: {idle_time, timeout()}. -type rekey_limit_common_option() :: {rekey_limit, Bytes::limit_bytes() | @@ -223,14 +221,14 @@ {transport, {atom(),atom(),atom()} } | {vsn, {non_neg_integer(),non_neg_integer()} } | {tstflg, list(term())} - | {user_dir_fun, fun()} + | ssh_file:user_dir_fun_common_option() | {max_random_length_padding, non_neg_integer()} . -type client_option() :: pref_public_key_algs_client_option() - | pubkey_passphrase_client_options() + | ssh_file:pubkey_passphrase_client_options() | host_accepting_client_options() | authentication_client_options() | diffie_hellman_group_exchange_client_option() @@ -246,10 +244,6 @@ -type pref_public_key_algs_client_option() :: {pref_public_key_algs, [pubkey_alg()] } . --type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()} - | {rsa_pass_phrase, string()} - | {ecdsa_pass_phrase, string()} . - -type host_accepting_client_options() :: {silently_accept_hosts, accept_hosts()} | {user_interaction, boolean()} @@ -311,7 +305,7 @@ -type send_ext_info_daemon_option() :: {send_ext_info, boolean()} . -type authentication_daemon_options() :: - {system_dir, string()} + ssh_file:system_dir_daemon_option() | {auth_method_kb_interactive_data, prompt_texts() } | {user_passwords, [{UserName::string(),Pwd::string()}]} | {password, string()} diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 832952ed52..954d5b68b6 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -39,6 +39,21 @@ is_auth_key/3]). +-export_type([system_dir_daemon_option/0, + user_dir_common_option/0, + user_dir_fun_common_option/0, + pubkey_passphrase_client_options/0 + ]). + +-type system_dir_daemon_option() :: {system_dir, string()}. +-type user_dir_common_option() :: {user_dir, false | string()}. +-type user_dir_fun_common_option() :: {user_dir_fun, fun()}. +-type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()} + | {rsa_pass_phrase, string()} + | {ecdsa_pass_phrase, string()} . + + + -define(PERM_700, 8#700). -define(PERM_644, 8#644). -- cgit v1.2.3 From 4f80074408ca5d21a56b0b234ff7434c8d155836 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 23 Oct 2018 10:42:00 +0200 Subject: ssh: Re-phrase and adjust the documentation (ssh_file.xml) --- lib/ssh/src/ssh_file.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 954d5b68b6..669b0f9be2 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -46,8 +46,10 @@ ]). -type system_dir_daemon_option() :: {system_dir, string()}. --type user_dir_common_option() :: {user_dir, false | string()}. --type user_dir_fun_common_option() :: {user_dir_fun, fun()}. +-type user_dir_common_option() :: {user_dir, string()}. +-type user_dir_fun_common_option() :: {user_dir_fun, user2dir()}. +-type user2dir() :: fun((RemoteUserName::string()) -> UserDir :: string()) . + -type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()} | {rsa_pass_phrase, string()} | {ecdsa_pass_phrase, string()} . -- cgit v1.2.3 From 203d20aeb89e513b71624d0c12952352e6ca3525 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 2 Nov 2018 12:49:03 +0100 Subject: ssh: Fix SSH_MSG_EXT_INFO bug for OTP SSH as client The message could not be received in connected state --- lib/ssh/src/ssh_connection_handler.erl | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 36152bacf7..84719ebc97 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -952,6 +952,10 @@ handle_event(_, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive %%% ######## {connected, client|server} #### +%% Skip ext_info messages in connected state (for example from OpenSSH >= 7.7) +handle_event(_, #ssh_msg_ext_info{}, {connected,_Role}, D) -> + {keep_state, D}; + handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params), D = D0#data{ssh_params = Ssh, -- cgit v1.2.3 From 434090c14c0d1692c3eaeebb97c9b86c3dff01a5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 7 Nov 2018 15:01:39 +0100 Subject: ssh: Fix SSH_MSG_EXT_INFO bug for OTP SSH as server The wrong set of supported public keys was sent to the client. --- lib/ssh/src/ssh_transport.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index edc927e807..6820f534cb 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -772,8 +772,7 @@ ext_info_message(#ssh{role=server, send_ext_info=true, opts = Opts} = Ssh0) -> AlgsList = lists:map(fun erlang:atom_to_list/1, - proplists:get_value(public_key, - ?GET_OPT(preferred_algorithms, Opts))), + ?GET_OPT(pref_public_key_algs, Opts)), Msg = #ssh_msg_ext_info{nr_extensions = 1, data = [{"server-sig-algs", string:join(AlgsList,",")}] }, -- cgit v1.2.3 From a53a4d0bae3e39877f3edf4c0f33a350b34a8137 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 7 Nov 2018 15:02:22 +0100 Subject: ssh: Fix ssh_options checking for ext_info A bug for SSH_MSG_EXT_INFO was fixed both for client and server. Before that fix, wrong option was read for the information sent to the peer. This commit adapts the option checking so that the correct option now used is available not only for servers but also for clients. --- lib/ssh/src/ssh_options.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 1e10f72956..3417466c4d 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -445,12 +445,6 @@ default(client) -> class => user_options }, - {pref_public_key_algs, def} => - #{default => ssh_transport:default_algorithms(public_key), - chk => fun check_pref_public_key_algs/1, - class => user_options - }, - {dh_gex_limits, def} => #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? chk => fun({Min,I,Max}) -> @@ -516,6 +510,12 @@ default(common) -> class => user_options }, + {pref_public_key_algs, def} => + #{default => ssh_transport:default_algorithms(public_key), + chk => fun check_pref_public_key_algs/1, + class => user_options + }, + {preferred_algorithms, def} => #{default => ssh:default_algorithms(), chk => fun check_preferred_algorithms/1, -- cgit v1.2.3 From 74bfc54aaa15b07cd3c09ba3aa0470d3c986cf6e Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 6 Nov 2018 11:38:50 +0100 Subject: ssh: Fix doc error in daemon 'exec' option --- lib/ssh/src/ssh.hrl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 3ac74c4925..233f3caa5d 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -293,8 +293,9 @@ -type 'shell_fun/1'() :: fun((User::string()) -> pid()) . -type 'shell_fun/2'() :: fun((User::string(), PeerAddr::inet:ip_address()) -> pid()). --type exec_daemon_option() :: {exec, 'exec_fun/1'() | 'exec_fun/2'() | 'exec_fun/3'() }. - +-type exec_daemon_option() :: {exec, exec_spec()} . +-type exec_spec() :: {direct, exec_fun()} . +-type exec_fun() :: 'exec_fun/1'() | 'exec_fun/2'() | 'exec_fun/3'(). -type 'exec_fun/1'() :: fun((Cmd::string()) -> exec_result()) . -type 'exec_fun/2'() :: fun((Cmd::string(), User::string()) -> exec_result()) . -type 'exec_fun/3'() :: fun((Cmd::string(), User::string(), ClientAddr::ip_port()) -> exec_result()) . -- cgit v1.2.3 From c37c4814295aedfc416ec8739bd1b8af8425bace Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 7 Nov 2018 15:00:54 +0100 Subject: ssh: Fix documentation for public key in ext_info --- lib/ssh/src/ssh.hrl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 233f3caa5d..f645201c4f 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -182,6 +182,7 @@ | ssh_msg_debug_fun_common_option() | rekey_limit_common_option() | id_string_common_option() + | pref_public_key_algs_common_option() | preferred_algorithms_common_option() | modify_algorithms_common_option() | auth_methods_common_option() @@ -209,6 +210,7 @@ {ssh_msg_debug_fun, fun((ssh:connection_ref(),AlwaysDisplay::boolean(),Msg::binary(),LanguageTag::binary()) -> any()) } . -type id_string_common_option() :: {id_string, string() | random | {random,Nmin::pos_integer(),Nmax::pos_integer()} }. +-type pref_public_key_algs_common_option() :: {pref_public_key_algs, [pubkey_alg()] } . -type preferred_algorithms_common_option():: {preferred_algorithms, algs_list()}. -type modify_algorithms_common_option() :: {modify_algorithms, modify_algs_list()}. -type auth_methods_common_option() :: {auth_methods, string() }. @@ -227,8 +229,7 @@ -type client_option() :: - pref_public_key_algs_client_option() - | ssh_file:pubkey_passphrase_client_options() + ssh_file:pubkey_passphrase_client_options() | host_accepting_client_options() | authentication_client_options() | diffie_hellman_group_exchange_client_option() @@ -242,8 +243,6 @@ {keyboard_interact_fun, fun((term(),term(),term()) -> term())} | opaque_common_options(). --type pref_public_key_algs_client_option() :: {pref_public_key_algs, [pubkey_alg()] } . - -type host_accepting_client_options() :: {silently_accept_hosts, accept_hosts()} | {user_interaction, boolean()} -- cgit v1.2.3 From e923747f392a3c3a4da0aae2f3766d3173b80f6e Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 21 May 2018 12:54:43 +0200 Subject: ssh: Add public keys 'ssh-ed25519' and 'ssh-ed448' Requires OpenSSL-1.1.1 https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-ed448-00 --- lib/ssh/src/ssh.hrl | 2 ++ lib/ssh/src/ssh_file.erl | 13 ++++++++++++- lib/ssh/src/ssh_message.erl | 8 +++++++- lib/ssh/src/ssh_options.erl | 12 ++++++++++++ lib/ssh/src/ssh_transport.erl | 19 ++++++++++++++++++- 5 files changed, 51 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index f645201c4f..27a8cfedbc 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -129,6 +129,8 @@ -type pubkey_alg() :: 'ecdsa-sha2-nistp256' | 'ecdsa-sha2-nistp384' | 'ecdsa-sha2-nistp521' | + 'ssh-ed25519' | + 'ssh-ed448' | 'rsa-sha2-256' | 'rsa-sha2-512' | 'ssh-dss' | diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 669b0f9be2..072a42d91e 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -52,10 +52,11 @@ -type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()} | {rsa_pass_phrase, string()} +%% Not yet implemented: | {ed25519_pass_phrase, string()} +%% Not yet implemented: | {ed448_pass_phrase, string()} | {ecdsa_pass_phrase, string()} . - -define(PERM_700, 8#700). -define(PERM_644, 8#644). @@ -120,6 +121,8 @@ file_base_name('ssh-dss' ) -> "ssh_host_dsa_key"; file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key"; file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key"; file_base_name('ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key"; +file_base_name('ssh-ed25519' ) -> "ssh_host_ed25519_key"; +file_base_name('ssh-ed448' ) -> "ssh_host_ed448_key"; file_base_name(_ ) -> "ssh_host_key". decode(File, Password) -> @@ -257,6 +260,8 @@ identity_key_filename('ssh-rsa' ) -> "id_rsa"; identity_key_filename('rsa-sha2-256' ) -> "id_rsa"; identity_key_filename('rsa-sha2-384' ) -> "id_rsa"; identity_key_filename('rsa-sha2-512' ) -> "id_rsa"; +identity_key_filename('ssh-ed25519' ) -> "id_ed25519"; +identity_key_filename('ssh-ed448' ) -> "id_ed448"; identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa"; identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa"; identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa". @@ -266,6 +271,8 @@ identity_pass_phrase("ssh-rsa" ) -> rsa_pass_phrase; identity_pass_phrase("rsa-sha2-256" ) -> rsa_pass_phrase; identity_pass_phrase("rsa-sha2-384" ) -> rsa_pass_phrase; identity_pass_phrase("rsa-sha2-512" ) -> rsa_pass_phrase; +%% Not yet implemented: identity_pass_phrase("ssh-ed25519" ) -> ed25519_pass_phrase; +%% Not yet implemented: identity_pass_phrase("ssh-ed448" ) -> ed448_pass_phrase; identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase; identity_pass_phrase(P) when is_atom(P) -> identity_pass_phrase(atom_to_list(P)). @@ -318,6 +325,10 @@ key_match({#'ECPoint'{},{namedCurve,Curve}}, Alg) -> _ -> false end; +key_match({ed_pub,ed25519,_}, 'ssh-ed25519') -> + true; +key_match({ed_pub,ed448,_}, 'ssh-ed448') -> + true; key_match(_, _) -> false. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index da4027a763..d95e58c1bb 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -611,7 +611,13 @@ encode_signature({_, #'Dss-Parms'{}}, _SigAlg, Signature) -> <>), ?Ebinary(Signature)>>; encode_signature({#'ECPoint'{}, {namedCurve,OID}}, _SigAlg, Signature) -> CurveName = public_key:oid2ssh_curvename(OID), - <>), ?Ebinary(Signature)>>. + <>), ?Ebinary(Signature)>>; +encode_signature({ed_pub, ed25519,_}, _SigAlg, Signature) -> + <>), ?Ebinary(Signature)>>; +encode_signature({ed_pub, ed448,_}, _SigAlg, Signature) -> + <>), ?Ebinary(Signature)>>. + + %%%################################################################ %%%# diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index bc9b0b6eda..1010c9be55 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -434,6 +434,18 @@ default(client) -> class => user_options }, +%%% Not yet implemented {ed25519_pass_phrase, def} => +%%% Not yet implemented #{default => undefined, +%%% Not yet implemented chk => fun check_string/1, +%%% Not yet implemented class => user_options +%%% Not yet implemented }, +%%% Not yet implemented +%%% Not yet implemented {ed448_pass_phrase, def} => +%%% Not yet implemented #{default => undefined, +%%% Not yet implemented chk => fun check_string/1, +%%% Not yet implemented class => user_options +%%% Not yet implemented }, +%%% Not yet implemented {silently_accept_hosts, def} => #{default => false, chk => fun check_silently_accept_hosts/1, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7424c9bcaf..b99f79a47c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -147,6 +147,8 @@ supported_algorithms(public_key) -> {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {curves,secp384r1}]}, {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {curves,secp521r1}]}, {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {curves,secp256r1}]}, + {'ssh-ed25519', [{public_keys,eddsa}, {curves,ed25519} ]}, + {'ssh-ed448', [{public_keys,eddsa}, {curves,ed448} ]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]}, {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]}, @@ -798,6 +800,8 @@ extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID}, publicKey = Q}) -> {#'ECPoint'{point=Q}, {namedCurve,OID}}; +extract_public_key({ed_pri, Alg, Pub, _Priv}) -> + {ed_pub, Alg, Pub}; extract_public_key(#{engine:=_, key_id:=_, algorithm:=Alg} = M) -> case {Alg, crypto:privkey_to_pubkey(Alg, M)} of {rsa, [E,N]} -> @@ -863,6 +867,8 @@ yes_no(Ssh, Prompt) -> fmt_hostkey('ssh-rsa') -> "RSA"; fmt_hostkey('ssh-dss') -> "DSA"; +fmt_hostkey('ssh-ed25519') -> "ED25519"; +fmt_hostkey('ssh-ed448') -> "ED448"; fmt_hostkey(A) when is_atom(A) -> fmt_hostkey(atom_to_list(A)); fmt_hostkey("ecdsa"++_) -> "ECDSA"; fmt_hostkey(X) -> X. @@ -1936,6 +1942,11 @@ valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true; valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true; valid_key_sha_alg(#'DSAPrivateKey'{}, 'ssh-dss') -> true; +valid_key_sha_alg({ed_pub, ed25519,_}, 'ssh-ed25519') -> true; +valid_key_sha_alg({ed_pri, ed25519,_,_},'ssh-ed25519') -> true; +valid_key_sha_alg({ed_pub, ed448,_}, 'ssh-ed448') -> true; +valid_key_sha_alg({ed_pri, ed448,_,_}, 'ssh-ed448') -> true; + valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg); valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg); valid_key_sha_alg(_, _) -> false. @@ -1945,12 +1956,17 @@ valid_key_sha_alg_ec(OID, Alg) -> Alg == list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). +-dialyzer({no_match, public_algo/1}). + public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-curdle-rsa-sha2 public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; +public_algo({ed_pub, ed25519,_}) -> 'ssh-ed25519'; +public_algo({ed_pub, ed448,_}) -> 'ssh-ed448'; public_algo({#'ECPoint'{},{namedCurve,OID}}) -> Curve = public_key:oid2ssh_curvename(OID), list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). + sha('ssh-rsa') -> sha; sha('rsa-sha2-256') -> sha256; sha('rsa-sha2-384') -> sha384; @@ -1959,6 +1975,8 @@ sha('ssh-dss') -> sha; sha('ecdsa-sha2-nistp256') -> sha(secp256r1); sha('ecdsa-sha2-nistp384') -> sha(secp384r1); sha('ecdsa-sha2-nistp521') -> sha(secp521r1); +sha('ssh-ed25519') -> undefined; % Included in the spec of ed25519 +sha('ssh-ed448') -> undefined; % Included in the spec of ed448 sha(secp256r1) -> sha256; sha(secp384r1) -> sha384; sha(secp521r1) -> sha512; @@ -2053,7 +2071,6 @@ ecdh_curve('curve448-sha512' ) -> x448; ecdh_curve('curve25519-sha256' ) -> x25519; ecdh_curve('curve25519-sha256@libssh.org' ) -> x25519. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% Utils for default_algorithms/1 and supported_algorithms/1 -- cgit v1.2.3 From aa1562a060cb91214acb639b9be9e1c6a59db884 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 31 Oct 2018 13:11:37 +0100 Subject: ssh: Add a common key_cb call function --- lib/ssh/src/ssh.hrl | 1 - lib/ssh/src/ssh_auth.erl | 12 +++--------- lib/ssh/src/ssh_connection_handler.erl | 1 - lib/ssh/src/ssh_transport.erl | 33 ++++++++++++++------------------- 4 files changed, 17 insertions(+), 30 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index f645201c4f..b0a35e0eab 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -380,7 +380,6 @@ algorithms, %% #alg{} - key_cb, %% Private/Public key callback module io_cb, %% Interaction callback module send_mac = none, %% send MAC algorithm diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 4e4aa440de..a2f016a837 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -137,9 +137,7 @@ keyboard_interactive_msg([#ssh{user = User, get_public_key(SigAlg, #ssh{opts = Opts}) -> KeyAlg = key_alg(SigAlg), - {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), - UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + case ssh_transport:call_KeyCb(user_key, [KeyAlg], Opts) of {ok, PrivKey} -> try %% Check the key - the KeyCb may be a buggy plugin @@ -498,9 +496,7 @@ get_password_option(Opts, User) -> pre_verify_sig(User, KeyBlob, Opts) -> try Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception - {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), - UserOpts = ?GET_OPT(user_options, Opts), - KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) + ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts) catch _:_ -> false @@ -509,10 +505,8 @@ pre_verify_sig(User, KeyBlob, Opts) -> verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts = Opts} = Ssh) -> try Alg = binary_to_list(AlgBin), - {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), - UserOpts = ?GET_OPT(user_options, Opts), Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception - true = KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]), + true = ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts), PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg), <> = SigWLen, < init_ssh_record(Role, Socket, PeerAddr, Opts) -> AuthMethods = ?GET_OPT(auth_methods, Opts), S0 = #ssh{role = Role, - key_cb = ?GET_OPT(key_cb, Opts), opts = Opts, userauth_supported_methods = AuthMethods, available_host_keys = available_hkey_algorithms(Role, Opts), diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7424c9bcaf..96b03abb94 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -51,7 +51,8 @@ extract_public_key/1, ssh_packet/2, pack/2, valid_key_sha_alg/2, - sha/1, sign/3, verify/5]). + sha/1, sign/3, verify/5, + call_KeyCb/3]). -export([dbg_trace/3]). @@ -777,10 +778,8 @@ sid(#ssh{session_id = Id}, _) -> Id. %% %% The host key should be read from storage %% -get_host_key(SSH, SignAlg) -> - #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts} = SSH, - UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:host_key(SignAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of +get_host_key(#ssh{opts=Opts}, SignAlg) -> + case call_KeyCb(host_key, [SignAlg], Opts) of {ok, PrivHostKey} -> %% Check the key - the KeyCb may be a buggy plugin case valid_key_sha_alg(PrivHostKey, SignAlg) of @@ -791,6 +790,11 @@ get_host_key(SSH, SignAlg) -> exit({error, {Result, unsupported_key_type}}) end. +call_KeyCb(F, Args, Opts) -> + {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), + UserOpts = ?GET_OPT(user_options, Opts), + apply(KeyCb, F, Args ++ [[{key_cb_private,KeyCbOpts}|UserOpts]]). + extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> #'RSAPublicKey'{modulus = N, publicExponent = E}; extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> @@ -868,18 +872,16 @@ fmt_hostkey("ecdsa"++_) -> "ECDSA"; fmt_hostkey(X) -> X. -known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}} = Ssh, +known_host_key(#ssh{opts = Opts, peer = {PeerName,_}} = Ssh, Public, Alg) -> - UserOpts = ?GET_OPT(user_options, Opts), - case is_host_key(KeyCb, Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of - {_,true} -> + case call_KeyCb(is_host_key, [Public, PeerName, Alg], Opts) of + true -> ok; - {_,false} -> + false -> DoAdd = ?GET_OPT(save_accepted_host, Opts), case accepted_host(Ssh, PeerName, Public, Opts) of true when DoAdd == true -> - {_,R} = add_host_key(KeyCb, PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]), - R; + call_KeyCb(add_host_key, [PeerName, Public], Opts); true when DoAdd == false -> ok; false -> @@ -889,13 +891,6 @@ known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_} end end. -is_host_key(KeyCb, Public, PeerName, Alg, Data) -> - {KeyCb, KeyCb:is_host_key(Public, PeerName, Alg, Data)}. - -add_host_key(KeyCb, PeerName, Public, Data) -> - {KeyCb, KeyCb:add_host_key(PeerName, Public, Data)}. - - %% Each of the algorithm strings MUST be a comma-separated list of %% algorithm names (see ''Algorithm Naming'' in [SSH-ARCH]). Each %% supported (allowed) algorithm MUST be listed in order of preference. -- cgit v1.2.3 From 4c3e66d5969e1abe2c8827b756edd860555038a8 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 31 Oct 2018 15:34:39 +0100 Subject: ssh: Cleaning and polishing of ssh_auth No intentional api changes. Only to make the code less hard to read. --- lib/ssh/src/ssh.hrl | 2 - lib/ssh/src/ssh_auth.erl | 94 +++++++++++++++++++++------------- lib/ssh/src/ssh_connection_handler.erl | 10 ++-- lib/ssh/src/ssh_transport.erl | 5 +- 4 files changed, 66 insertions(+), 45 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index b0a35e0eab..aa4b3ef098 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -380,8 +380,6 @@ algorithms, %% #alg{} - io_cb, %% Interaction callback module - send_mac = none, %% send MAC algorithm send_mac_key, %% key used in send MAC algorithm send_mac_size = 0, diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index a2f016a837..1972468b8b 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -91,8 +91,10 @@ unique(L) -> %%%---- userauth_request_msg "callbacks" -password_msg([#ssh{opts = Opts, io_cb = IoCb, - user = User, service = Service} = Ssh0]) -> +password_msg([#ssh{opts = Opts, + user = User, + service = Service} = Ssh0]) -> + IoCb = ?GET_INTERNAL_OPT(io_cb, Opts), {Password,Ssh} = case ?GET_OPT(password, Opts) of undefined when IoCb == ssh_no_io -> @@ -385,11 +387,9 @@ handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name, instruction = Instr, num_prompts = NumPrompts, data = Data}, - #ssh{opts = Opts, - io_cb = IoCb - } = Ssh) -> + #ssh{opts=Opts} = Ssh) -> PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data), - case keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) of + case keyboard_interact_get_responses(Opts, Name, Instr, PromptInfos) of not_ok -> not_ok; Responses -> @@ -530,56 +530,78 @@ build_sig_data(SessionId, User, Service, KeyBlob, Alg) -> +key_alg('rsa-sha2-256') -> 'ssh-rsa'; +key_alg('rsa-sha2-512') -> 'ssh-rsa'; +key_alg(Alg) -> Alg. + +%%%================================================================ +%%% +%%% Keyboard-interactive +%%% + decode_keyboard_interactive_prompts(_NumPrompts, Data) -> ssh_message:decode_keyboard_interactive_prompts(Data, []). -keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> - NumPrompts = length(PromptInfos), +keyboard_interact_get_responses(Opts, Name, Instr, PromptInfos) -> keyboard_interact_get_responses(?GET_OPT(user_interaction, Opts), ?GET_OPT(keyboard_interact_fun, Opts), - ?GET_OPT(password, Opts), IoCb, Name, - Instr, PromptInfos, Opts, NumPrompts). + ?GET_OPT(password, Opts), + Name, + Instr, + PromptInfos, + Opts). -keyboard_interact_get_responses(_, _, not_ok, _, _, _, _, _, _) -> +%% Don't re-try an already rejected password. This could happen if both keyboard-interactive +%% and password methods are tried: +keyboard_interact_get_responses(_, _, not_ok, _, _, _, _) -> not_ok; -keyboard_interact_get_responses(_, undefined, Password, _, _, _, _, _, - 1) when Password =/= undefined -> - [Password]; %% Password auth implemented with keyboard-interaction and passwd is known -keyboard_interact_get_responses(_, _, _, _, _, _, _, _, 0) -> + +%% Only one password requestedm and we have got one via the 'password' option for the daemon: +keyboard_interact_get_responses(_, undefined, Pwd, _, _, [_], _) when Pwd =/= undefined -> + [Pwd]; %% Password auth implemented with keyboard-interaction and passwd is known + +%% No password requested (keyboard-interactive): +keyboard_interact_get_responses(_, _, _, _, _, [], _) -> []; -keyboard_interact_get_responses(false, undefined, undefined, _, _, _, [Prompt|_], Opts, _) -> - ssh_no_io:read_line(Prompt, Opts); %% Throws error as keyboard interaction is not allowed -keyboard_interact_get_responses(true, undefined, _,IoCb, Name, Instr, PromptInfos, Opts, _) -> - keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); -keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos, _Opts, NumPrompts) -> - keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts). - -keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> + +%% user_interaction is forbidden (by option user_interaction) and we have to ask +%% the user for one or more. +%% Throw an error: +keyboard_interact_get_responses(false, undefined, undefined, _, _, [Prompt|_], Opts) -> + ssh_no_io:read_line(Prompt, Opts); + +%% One or more passwords are requested, we may prompt the user and no fun is used +%% to get the responses: +keyboard_interact_get_responses(true, undefined, _, Name, Instr, PromptInfos, Opts) -> + prompt_user_for_passwords(Name, Instr, PromptInfos, Opts); + +%% The passwords are provided with a fun. Use that one! +keyboard_interact_get_responses(true, Fun, _Pwd, Name, Instr, PromptInfos, _Opts) -> + keyboard_interact_fun(Fun, Name, Instr, PromptInfos). + + + +prompt_user_for_passwords(Name, Instr, PromptInfos, Opts) -> + IoCb = ?GET_INTERNAL_OPT(io_cb, Opts), write_if_nonempty(IoCb, Name), write_if_nonempty(IoCb, Instr), lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts); ({Prompt, false}) -> IoCb:read_password(Prompt, Opts) end, - Prompts). - -write_if_nonempty(_, "") -> ok; -write_if_nonempty(_, <<>>) -> ok; -write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]). - + PromptInfos). -keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> - Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, - PromptInfos), +keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos) -> + Prompts = lists:map(fun({Prompt,_Echo}) -> Prompt end, PromptInfos), case KbdInteractFun(Name, Instr, Prompts) of - Rs when length(Rs) == NumPrompts -> + Rs when length(Rs) == length(PromptInfos) -> Rs; _Rs -> nok end. -key_alg('rsa-sha2-256') -> 'ssh-rsa'; -key_alg('rsa-sha2-512') -> 'ssh-rsa'; -key_alg(Alg) -> Alg. +write_if_nonempty(_, "") -> ok; +write_if_nonempty(_, <<>>) -> ok; +write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]). diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index b64b799327..e23df6ceca 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -471,10 +471,11 @@ init_ssh_record(Role, Socket, PeerAddr, Opts) -> S1 = S0#ssh{c_vsn = Vsn, c_version = Version, - io_cb = case ?GET_OPT(user_interaction, Opts) of - true -> ssh_io; - false -> ssh_no_io - end, + opts = ?PUT_INTERNAL_OPT({io_cb, case ?GET_OPT(user_interaction, Opts) of + true -> ssh_io; + false -> ssh_no_io + end}, + Opts), userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), peer = {PeerName, PeerAddr}, local = LocalName @@ -487,7 +488,6 @@ init_ssh_record(Role, Socket, PeerAddr, Opts) -> server -> S0#ssh{s_vsn = Vsn, s_version = Version, - io_cb = ?GET_INTERNAL_OPT(io_cb, Opts, ssh_io), userauth_methods = string:tokens(AuthMethods, ","), kb_tries_left = 3, peer = {undefined, PeerAddr}, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 96b03abb94..f49b49b2df 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -861,8 +861,9 @@ accepted_host(Ssh, PeerName, Public, Opts) -> end. -yes_no(Ssh, Prompt) -> - (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts). +yes_no(#ssh{opts=Opts}, Prompt) -> + IoCb = ?GET_INTERNAL_OPT(io_cb, Opts, ssh_io), + IoCb:yes_no(Prompt, Opts). fmt_hostkey('ssh-rsa') -> "RSA"; -- cgit v1.2.3 From 709534a5417f20f8edda1d3664669ed9d3129354 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Nov 2018 14:44:44 +0100 Subject: ssh: Generalize unpublished test support option Also rename the corresponding testcase in ssh_options_SUITE and add logging. --- lib/ssh/src/ssh.hrl | 7 ++++++- lib/ssh/src/ssh_auth.erl | 10 +++++----- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index aa4b3ef098..3e9b1add3b 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -240,7 +240,12 @@ | ?COMMON_OPTION . -type opaque_client_options() :: - {keyboard_interact_fun, fun((term(),term(),term()) -> term())} + {keyboard_interact_fun, fun((Name::iodata(), + Instruction::iodata(), + Prompts::[{Prompt::iodata(),Echo::boolean()}] + ) -> + [Response::iodata()] + )} | opaque_common_options(). -type host_accepting_client_options() :: diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 1972468b8b..9632168e65 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -592,11 +592,11 @@ prompt_user_for_passwords(Name, Instr, PromptInfos, Opts) -> PromptInfos). keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos) -> - Prompts = lists:map(fun({Prompt,_Echo}) -> Prompt end, PromptInfos), - case KbdInteractFun(Name, Instr, Prompts) of - Rs when length(Rs) == length(PromptInfos) -> - Rs; - _Rs -> + case KbdInteractFun(Name, Instr, PromptInfos) of + Responses when is_list(Responses), + length(Responses) == length(PromptInfos) -> + Responses; + _ -> nok end. -- cgit v1.2.3 From b4ad3a9eb7a1b375d2dbbf93069ea9ae038d121f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 14 Nov 2018 12:57:46 +0100 Subject: ssh: Make host and user key pre-checking better This will prevent crashes in ssh_file for public key types which have no passphrase option although being supported. Also centralize host key checking to avoid code duplication. This was already done for user keys. --- lib/ssh/src/ssh_connection_handler.erl | 50 +++++++++++++++++----------------- lib/ssh/src/ssh_file.erl | 3 +- lib/ssh/src/ssh_transport.erl | 18 +++++++----- 3 files changed, 38 insertions(+), 33 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e23df6ceca..7c87591cf2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1685,18 +1685,19 @@ peer_role(client) -> server; peer_role(server) -> client. %%-------------------------------------------------------------------- -available_hkey_algorithms(Role, Options) -> - KeyCb = ?GET_OPT(key_cb, Options), - case [A || A <- available_hkey_algos(Options), - (Role==client) orelse available_host_key(KeyCb, A, Options) - ] of - - [] when Role==client -> - error({shutdown, "No public key algs"}); - - [] when Role==server -> - error({shutdown, "No host key available"}); +available_hkey_algorithms(client, Options) -> + case available_hkey_algos(Options) of + [] -> + error({shutdown, "No public key algs"}); + Algs -> + [atom_to_list(A) || A<-Algs] + end; +available_hkey_algorithms(server, Options) -> + case [A || A <- available_hkey_algos(Options), + is_usable_host_key(A, Options)] of + [] -> + error({shutdown, "No host key available"}); Algs -> [atom_to_list(A) || A<-Algs] end. @@ -1712,18 +1713,6 @@ available_hkey_algos(Options) -> AvailableAndSupported. -%% Alg :: atom() -available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) -> - UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of - {ok,Key} -> - %% Check the key - the KeyCb may be a buggy plugin - ssh_transport:valid_key_sha_alg(Key, Alg); - _ -> - false - end. - - send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), send_bytes(Bytes, State), @@ -1843,10 +1832,21 @@ ext_info(_, D0) -> D0. %%%---------------------------------------------------------------- -is_usable_user_pubkey(A, Ssh) -> - case ssh_auth:get_public_key(A, Ssh) of +is_usable_user_pubkey(Alg, Ssh) -> + try ssh_auth:get_public_key(Alg, Ssh) of {ok,_} -> true; _ -> false + catch + _:_ -> false + end. + +%%%---------------------------------------------------------------- +is_usable_host_key(Alg, Opts) -> + try ssh_transport:get_host_key(Alg, Opts) + of + _PrivHostKey -> true + catch + _:_ -> false end. %%%---------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 669b0f9be2..c5824b2519 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -268,7 +268,8 @@ identity_pass_phrase("rsa-sha2-384" ) -> rsa_pass_phrase; identity_pass_phrase("rsa-sha2-512" ) -> rsa_pass_phrase; identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase; identity_pass_phrase(P) when is_atom(P) -> - identity_pass_phrase(atom_to_list(P)). + identity_pass_phrase(atom_to_list(P)); +identity_pass_phrase(_) -> undefined. lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) -> case io:get_line(Fd, '') of diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index f49b49b2df..cc3ef46fc2 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -52,6 +52,7 @@ ssh_packet/2, pack/2, valid_key_sha_alg/2, sha/1, sign/3, verify/5, + get_host_key/2, call_KeyCb/3]). -export([dbg_trace/3]). @@ -432,7 +433,8 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0 = #ssh{algorithms = #alg{kex=Kex, - hkey=SignAlg} = Algs}) -> + hkey=SignAlg} = Algs, + opts = Opts}) -> %% server {G, P} = dh_group(Kex), if @@ -440,7 +442,7 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Sz = dh_bits(Algs), {Public, Private} = generate_key(dh, [P,G,2*Sz]), K = compute_key(dh, E, Private, [P,G]), - MyPrivHostKey = get_host_key(Ssh0, SignAlg), + MyPrivHostKey = get_host_key(SignAlg, Opts), MyPubHostKey = extract_public_key(MyPrivHostKey), H = kex_hash(Ssh0, MyPubHostKey, sha(Kex), {E,Public,K}), H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), @@ -579,14 +581,15 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, #ssh{keyex_key = {{Private, Public}, {G, P}}, keyex_info = {Min, Max, NBits}, algorithms = #alg{kex=Kex, - hkey=SignAlg}} = Ssh0) -> + hkey=SignAlg}, + opts = Opts} = Ssh0) -> %% server if 1= K = compute_key(dh, E, Private, [P,G]), if 1 - MyPrivHostKey = get_host_key(Ssh0, SignAlg), + MyPrivHostKey = get_host_key(SignAlg, Opts), MyPubHostKey = extract_public_key(MyPrivHostKey), H = kex_hash(Ssh0, MyPubHostKey, sha(Kex), {Min,NBits,Max,P,G,E,Public,K}), H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), @@ -654,7 +657,8 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK %%% handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, Ssh0 = #ssh{algorithms = #alg{kex=Kex, - hkey=SignAlg}}) -> + hkey=SignAlg}, + opts = Opts}) -> %% at server Curve = ecdh_curve(Kex), {MyPublic, MyPrivate} = generate_key(ecdh, Curve), @@ -662,7 +666,7 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, compute_key(ecdh, PeerPublic, MyPrivate, Curve) of K -> - MyPrivHostKey = get_host_key(Ssh0, SignAlg), + MyPrivHostKey = get_host_key(SignAlg, Opts), MyPubHostKey = extract_public_key(MyPrivHostKey), H = kex_hash(Ssh0, MyPubHostKey, sha(Curve), {PeerPublic, MyPublic, K}), H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), @@ -778,7 +782,7 @@ sid(#ssh{session_id = Id}, _) -> Id. %% %% The host key should be read from storage %% -get_host_key(#ssh{opts=Opts}, SignAlg) -> +get_host_key(SignAlg, Opts) -> case call_KeyCb(host_key, [SignAlg], Opts) of {ok, PrivHostKey} -> %% Check the key - the KeyCb may be a buggy plugin -- cgit v1.2.3