diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/ssh.appup.src | 6 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 89 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.erl | 67 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.hrl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_cli.erl | 3 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection.erl | 76 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 28 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_manager.erl | 152 | ||||
-rw-r--r-- | lib/ssh/src/ssh_io.erl | 61 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 2 |
10 files changed, 366 insertions, 120 deletions
diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index d08dbafc32..6ba32e018f 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -19,10 +19,12 @@ {"%VSN%", [ + {<<"2.1.1">>, [{restart_application, ssh}]}, {<<"2.1">>, [{load_module, ssh_sftpd_file_api, soft_purge, soft_purge, []}, {load_module, ssh_connection, soft_purge, soft_purge, []}, {load_module, ssh_connection_manager, soft_purge, soft_purge, []}, {load_module, ssh_auth, soft_purge, soft_purge, []}, + {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, {load_module, ssh_channel, soft_purge, soft_purge, []}, {load_module, ssh_file, soft_purge, soft_purge, []}]}, {load_module, ssh, soft_purge, soft_purge, []}]}, @@ -30,14 +32,16 @@ {<<"1\\.*">>, [{restart_application, ssh}]} ], [ + {<<"2.1.1">>, [{restart_application, ssh}]}, {<<"2.1">>,[{load_module, ssh_sftpd_file_api, soft_purge, soft_purge, []}, {load_module, ssh_connection, soft_purge, soft_purge, []}, {load_module, ssh_connection_manager, soft_purge, soft_purge, []}, {load_module, ssh_auth, soft_purge, soft_purge, []}, + {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, {load_module, ssh_channel, soft_purge, soft_purge, []}, {load_module, ssh_file, soft_purge, soft_purge, []}]}, {load_module, ssh, soft_purge, soft_purge, []}]}, {<<"2.0\\.*">>, [{restart_application, ssh}]}, {<<"1\\.*">>, [{restart_application, ssh}]} ] -}. +}.
\ No newline at end of file diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 3395f73884..a569298056 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -79,7 +79,7 @@ connect(Host, Port, Options, Timeout) -> DisableIpv6 = proplists:get_value(ip_v6_disabled, SshOptions, false), Inet = inetopt(DisableIpv6), do_connect(Host, Port, [Inet | SocketOptions], - [{host, Host} | SshOptions], Timeout, DisableIpv6) + [{user_pid, self()}, {host, Host} | fix_idle_time(SshOptions)], Timeout, DisableIpv6) end. do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) -> @@ -91,30 +91,39 @@ do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) -> {ok, ConnectionSup} -> {ok, Manager} = ssh_connection_sup:connection_manager(ConnectionSup), - receive - {Manager, is_connected} -> - {ok, Manager}; - %% When the connection fails - %% ssh_connection_sup:connection_manager - %% might return undefined as the connection manager - %% could allready have terminated, so we will not - %% match the Manager in this case - {_, not_connected, {error, econnrefused}} when DisableIpv6 == false -> - do_connect(Host, Port, proplists:delete(inet6, SocketOptions), - SshOptions, Timeout, true); - {_, not_connected, {error, Reason}} -> - {error, Reason}; - {_, not_connected, Other} -> - {error, Other} - after Timeout -> - ssh_connection_manager:stop(Manager), - {error, timeout} - end + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) catch exit:{noproc, _} -> {error, ssh_not_started} end. - +msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) -> + receive + {Manager, is_connected} -> + {ok, Manager}; + %% When the connection fails + %% ssh_connection_sup:connection_manager + %% might return undefined as the connection manager + %% could allready have terminated, so we will not + %% match the Manager in this case + {_, not_connected, {error, econnrefused}} when DisableIpv6 == false -> + do_connect(Host, Port, proplists:delete(inet6, SocketOptions), + SshOptions, Timeout, true); + {_, not_connected, {error, Reason}} -> + {error, Reason}; + {_, not_connected, Other} -> + {error, Other}; + {From, user_password} -> + Pass = io:get_password(), + From ! Pass, + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout); + {From, question} -> + Answer = io:get_line(""), + From ! Answer, + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) + after Timeout -> + ssh_connection_manager:stop(Manager), + {error, timeout} + end. %%-------------------------------------------------------------------- %% Function: close(ConnectionRef) -> ok %% @@ -237,6 +246,13 @@ shell(Host, Port, Options) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +fix_idle_time(SshOptions) -> + case proplists:get_value(idle_time, SshOptions) of + undefined -> + [{idle_time, infinity}|SshOptions]; + _ -> + SshOptions + end. start_daemon(Host, Port, Options, Inet) -> case handle_options(Options) of {error, _Reason} = Error -> @@ -342,6 +358,12 @@ 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([{pref_public_key_algs, _} = 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([Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). @@ -357,6 +379,13 @@ handle_ssh_option({user_interaction, Value} = Opt) when Value == true; Value == Opt; handle_ssh_option({public_key_alg, Value} = Opt) when Value == ssh_rsa; Value == ssh_dsa -> Opt; +handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 -> + case check_pref_algs(Value) of + true -> + Opt; + _ -> + throw({error, {eoptions, Opt}}) + end; handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> Opt; handle_ssh_option({user, Value} = Opt) when is_list(Value) -> @@ -407,6 +436,11 @@ handle_ssh_option({shell, {Module, Function, _}} = Opt) when is_atom(Module), Opt; handle_ssh_option({shell, Value} = Opt) when is_function(Value) -> Opt; +handle_ssh_option({quiet_mode, Value} = Opt) when Value == true; + Value == false -> + Opt; +handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> + Opt; handle_ssh_option(Opt) -> throw({error, {eoptions, Opt}}). @@ -424,7 +458,18 @@ handle_inet_option({reuseaddr, _} = Opt) -> %% Option verified by inet handle_inet_option(Opt) -> Opt. - +%% Check preferred algs +check_pref_algs([]) -> + true; +check_pref_algs([H|T]) -> + case H of + ssh_dsa -> + check_pref_algs(T); + ssh_rsa -> + check_pref_algs(T); + _ -> + false + end. %% Has IPv6 been disabled? inetopt(true) -> inet; diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index aa452a8e09..c436793dc4 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -71,7 +71,7 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb, ssh_bits:install_messages(userauth_passwd_messages()), Password = case proplists:get_value(password, Opts) of undefined -> - user_interaction(IoCb); + user_interaction(IoCb, Ssh); PW -> PW end, @@ -89,10 +89,10 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb, Ssh) end. -user_interaction(ssh_no_io) -> +user_interaction(ssh_no_io, _) -> not_ok; -user_interaction(IoCb) -> - IoCb:read_password("ssh password: "). +user_interaction(IoCb, Ssh) -> + IoCb:read_password("ssh password: ", Ssh). %% See RFC 4256 for info on keyboard-interactive @@ -118,15 +118,37 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> service = "ssh-connection", method = "none", data = <<>>}, - FirstAlg = algorithm(proplists:get_value(public_key_alg, Opts, - ?PREFERRED_PK_ALG)), - SecondAlg = other_alg(FirstAlg), - AllowUserInt = proplists:get_value(user_interaction, Opts, true), - Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt), - ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, - userauth_preference = Prefs, - userauth_methods = none, - service = "ssh-connection"}); + case proplists:get_value(pref_public_key_algs, Opts, false) of + false -> + FirstAlg = algorithm(proplists:get_value(public_key_alg, Opts, + ?PREFERRED_PK_ALG)), + SecondAlg = other_alg(FirstAlg), + AllowUserInt = proplists:get_value(user_interaction, Opts, true), + Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt), + ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, + userauth_preference = Prefs, + userauth_methods = none, + service = "ssh-connection"}); + Algs -> + FirstAlg = algorithm(lists:nth(1, Algs)), + case length(Algs) =:= 2 of + true -> + SecondAlg = other_alg(FirstAlg), + AllowUserInt = proplists:get_value(user_interaction, Opts, true), + Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt), + ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, + userauth_preference = Prefs, + userauth_methods = none, + service = "ssh-connection"}); + _ -> + AllowUserInt = proplists:get_value(user_interaction, Opts, true), + Prefs = method_preference(FirstAlg, AllowUserInt), + ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, + userauth_preference = Prefs, + userauth_methods = none, + service = "ssh-connection"}) + end + end; {error, no_user} -> ErrStr = "Could not determine the users name", throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, @@ -287,6 +309,15 @@ method_preference(Alg1, Alg2, false) -> {"publickey", ?MODULE, publickey_msg,[Alg2]}, {"password", ?MODULE, password_msg, []} ]. +method_preference(Alg1, true) -> + [{"publickey", ?MODULE, publickey_msg, [Alg1]}, + {"password", ?MODULE, password_msg, []}, + {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} + ]; +method_preference(Alg1, false) -> + [{"publickey", ?MODULE, publickey_msg, [Alg1]}, + {"password", ?MODULE, password_msg, []} + ]. user_name(Opts) -> Env = case os:type() of @@ -370,11 +401,11 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> %% Special case/fallback for just one prompt %% (assumed to be the password prompt) case proplists:get_value(password, Opts) of - undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos); + undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); PW -> [PW] end; undefined -> - keyboard_interact(IoCb, Name, Instr, PromptInfos); + keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); KbdInteractFun -> Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, PromptInfos), @@ -388,15 +419,15 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> end end. -keyboard_interact(IoCb, Name, Instr, Prompts) -> +keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> if Name /= "" -> IoCb:format("~s", [Name]); true -> ok end, if Instr /= "" -> IoCb:format("~s", [Instr]); true -> ok end, - lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt); - ({Prompt, false}) -> IoCb:read_password(Prompt) + lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts); + ({Prompt, false}) -> IoCb:read_password(Prompt, Opts) end, Prompts). diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl index 7d7bad4436..e74ee10041 100644 --- a/lib/ssh/src/ssh_auth.hrl +++ b/lib/ssh/src/ssh_auth.hrl @@ -21,7 +21,7 @@ %%% Description: Ssh User Authentication Protocol --define(SUPPORTED_AUTH_METHODS, "publickey,keyboard_interactive,password"). +-define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). -define(PREFERRED_PK_ALG, ssh_rsa). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 781e01b9d1..c8c610f8ef 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -81,7 +81,8 @@ handle_ssh_msg({ssh_cm, ConnectionManager, height = not_zero(Height, 24), pixel_width = PixWidth, pixel_height = PixHeight, - modes = Modes}}, + modes = Modes}, + buf = empty_buf()}, set_echo(State), ssh_connection:reply_request(ConnectionManager, WantReply, success, ChannelId), diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index c2a7c63cbe..9424cdd423 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -177,7 +177,7 @@ close(ConnectionManager, ChannelId) -> %% Description: Send status replies to requests that want such replies. %%-------------------------------------------------------------------- reply_request(ConnectionManager, true, Status, ChannelId) -> - ConnectionManager ! {ssh_cm, self(), {Status, ChannelId}}, + ssh_connection_manager:reply_request(ConnectionManager, Status, ChannelId), ok; reply_request(_,false, _, _) -> ok. @@ -318,21 +318,22 @@ channel_data(ChannelId, DataType, Data, From) -> case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = Id} = Channel0 -> - {SendList, Channel} = update_send_window(Channel0, DataType, + #channel{remote_id = Id, sent_close = false} = Channel0 -> + {SendList, Channel} = update_send_window(Channel0#channel{flow_control = From}, DataType, Data, Connection), Replies = lists:map(fun({SendDataType, SendData}) -> - {connection_reply, ConnectionPid, - channel_data_msg(Id, - SendDataType, - SendData)} + {connection_reply, ConnectionPid, + channel_data_msg(Id, + SendDataType, + SendData)} end, SendList), FlowCtrlMsgs = flow_control(Replies, - Channel#channel{flow_control = From}, + Channel, Cache), {{replies, Replies ++ FlowCtrlMsgs}, Connection}; - undefined -> + _ -> + gen_server:reply(From, {error, closed}), {noreply, Connection} end. @@ -386,20 +387,30 @@ handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, ConnectionPid, _) -> case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{sent_close = Closed, remote_id = RemoteId} = Channel -> + #channel{sent_close = Closed, remote_id = RemoteId, flow_control = FlowControl} = Channel -> ssh_channel:cache_delete(Cache, ChannelId), {CloseMsg, Connection} = reply_msg(Channel, Connection0, {closed, ChannelId}), + + ConnReplyMsgs = case Closed of - true -> - {{replies, [CloseMsg]}, Connection}; + true -> []; false -> RemoteCloseMsg = channel_close_msg(RemoteId), - {{replies, - [{connection_reply, - ConnectionPid, RemoteCloseMsg}, - CloseMsg]}, Connection} - end; + [{connection_reply, ConnectionPid, RemoteCloseMsg}] + end, + + %% if there was a send() in progress, make it fail + SendReplyMsgs = + case FlowControl of + undefined -> []; + From -> + [{flow_control, From, {error, closed}}] + end, + + Replies = ConnReplyMsgs ++ [CloseMsg] ++ SendReplyMsgs, + {{replies, Replies}, Connection}; + undefined -> {{replies, []}, Connection0} end; @@ -441,7 +452,7 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, {SendList, Channel} = %% TODO: Datatype 0 ? update_send_window(Channel0#channel{send_window_size = Size + Add}, - 0, <<>>, Connection), + 0, undefined, Connection), Replies = lists:map(fun({Type, Data}) -> {connection_reply, ConnectionPid, @@ -1073,14 +1084,15 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid}, false -> {{channel_data, ChannelPid, Reply}, Connection} end. +update_send_window(Channel, _, undefined, + #connection{channel_cache = Cache}) -> + do_update_send_window(Channel, Channel#channel.send_buf, Cache); -update_send_window(Channel0, DataType, Data, - #connection{channel_cache = Cache}) -> - Buf0 = if Data == <<>> -> - Channel0#channel.send_buf; - true -> - Channel0#channel.send_buf ++ [{DataType, Data}] - end, +update_send_window(Channel, DataType, Data, + #connection{channel_cache = Cache}) -> + do_update_send_window(Channel, Channel#channel.send_buf ++ [{DataType, Data}], Cache). + +do_update_send_window(Channel0, Buf0, Cache) -> {Buf1, NewSz, Buf2} = get_window(Buf0, Channel0#channel.send_packet_size, Channel0#channel.send_window_size), @@ -1125,13 +1137,13 @@ flow_control(Channel, Cache) -> flow_control([], Channel, Cache) -> ssh_channel:cache_update(Cache, Channel), []; -flow_control([_|_], #channel{flow_control = From} = Channel, Cache) -> - case From of - undefined -> - []; - _ -> - [{flow_control, Cache, Channel, From, ok}] - end. + +flow_control([_|_], #channel{flow_control = From, + send_buf = []} = Channel, Cache) when From =/= undefined -> + [{flow_control, Cache, Channel, From, ok}]; +flow_control(_,_,_) -> + []. + encode_pty_opts(Opts) -> Bin = list_to_binary(encode_pty_opts2(Opts)), diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 5b3d1b8a1b..d8950a7b67 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -718,8 +718,18 @@ init_ssh(server = Role, Vsn, Version, Options, Socket) -> available_host_keys = supported_host_keys(Role, KeyCb, Options) }. -supported_host_keys(client, _, _) -> - ["ssh-rsa", "ssh-dss"]; +supported_host_keys(client, _, Options) -> + try + case extract_algs(proplists:get_value(pref_public_key_algs, Options, false), []) of + false -> + ["ssh-rsa", "ssh-dss"]; + Algs -> + Algs + end + catch + exit:Reason -> + {stop, {shutdown, Reason}} + end; supported_host_keys(server, KeyCb, Options) -> lists:foldl(fun(Type, Acc) -> case available_host_key(KeyCb, Type, Options) of @@ -731,7 +741,19 @@ supported_host_keys(server, KeyCb, Options) -> end, [], %% Prefered alg last so no need to reverse ["ssh-dss", "ssh-rsa"]). - +extract_algs(false, _) -> + false; +extract_algs([],[]) -> + false; +extract_algs([], NewList) -> + lists:reverse(NewList); +extract_algs([H|T], NewList) -> + case H of + ssh_dsa -> + extract_algs(T, ["ssh-dss"|NewList]); + ssh_rsa -> + extract_algs(T, ["ssh-rsa"|NewList]) + end. available_host_key(KeyCb, "ssh-dss"= Alg, Opts) -> case KeyCb:host_key('ssh-dss', Opts) of {ok, _} -> diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl index e53cd4f4f7..0c1eee5186 100644 --- a/lib/ssh/src/ssh_connection_manager.erl +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -40,7 +40,7 @@ close/2, stop/1, send/5, send_eof/2]). --export([open_channel/6, request/6, request/7, global_request/4, event/2, +-export([open_channel/6, reply_request/3, request/6, request/7, global_request/4, event/2, cast/2]). %% Internal application API and spawn @@ -62,6 +62,7 @@ latest_channel_id = 0, opts, channel_args, + idle_timer_ref, % timerref connected }). @@ -95,6 +96,9 @@ request(ConnectionManager, ChannelId, Type, true, Data, Timeout) -> request(ConnectionManager, ChannelId, Type, false, Data, _) -> cast(ConnectionManager, {request, ChannelId, Type, Data}). +reply_request(ConnectionManager, Status, ChannelId) -> + cast(ConnectionManager, {reply_request, Status, ChannelId}). + global_request(ConnectionManager, Type, true = Reply, Data) -> case call(ConnectionManager, {global_request, self(), Type, Reply, Data}) of @@ -163,7 +167,7 @@ send(ConnectionManager, ChannelId, Type, Data, Timeout) -> call(ConnectionManager, {data, ChannelId, Type, Data}, Timeout). send_eof(ConnectionManager, ChannelId) -> - cast(ConnectionManager, {eof, ChannelId}). + call(ConnectionManager, {eof, ChannelId}). %%==================================================================== %% gen_server callbacks @@ -200,6 +204,8 @@ init([client, Opts]) -> ChannelPid = proplists:get_value(channel_pid, Opts), self() ! {start_connection, client, [Parent, Address, Port, SocketOpts, Options]}, + TimerRef = get_idle_time(Options), + {ok, #state{role = client, client = ChannelPid, connection_state = #connection{channel_cache = Cache, @@ -208,6 +214,7 @@ init([client, Opts]) -> connection_supervisor = Parent, requests = []}, opts = Opts, + idle_timer_ref = TimerRef, connected = false}}. %%-------------------------------------------------------------------- @@ -227,6 +234,13 @@ handle_call({request, ChannelPid, ChannelId, Type, Data}, From, State0) -> %% channel is sent later when reply arrives from the connection %% handler. lists:foreach(fun send_msg/1, Replies), + SshOpts = proplists:get_value(ssh_opts, State0#state.opts), + case proplists:get_value(idle_time, SshOpts) of + infinity -> + ok; + _IdleTime -> + erlang:send_after(5000, self(), {check_cache, [], []}) + end, {noreply, State}; handle_call({request, ChannelId, Type, Data}, From, State0) -> @@ -295,6 +309,18 @@ handle_call({data, ChannelId, Type, Data}, From, channel_data(ChannelId, Type, Data, Connection0, ConnectionPid, From, State); +handle_call({eof, ChannelId}, _From, + #state{connection = Pid, connection_state = + #connection{channel_cache = Cache}} = State) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = Id, sent_close = false} -> + send_msg({connection_reply, Pid, + ssh_connection:channel_eof_msg(Id)}), + {reply, ok, State}; + _ -> + {reply, {error,closed}, State} + end; + handle_call({connection_info, Options}, From, #state{connection = Connection} = State) -> ssh_connection_handler:connection_info(Connection, From, Options), @@ -343,7 +369,7 @@ handle_call({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data}, recv_packet_size = MaxPacketSize}, ssh_channel:cache_update(Cache, Channel), State = add_request(true, ChannelId, From, State1), - {noreply, State}; + {noreply, remove_timer_ref(State)}; handle_call({send_window, ChannelId}, _From, #state{connection_state = @@ -388,6 +414,13 @@ handle_call({close, ChannelId}, _, send_msg({connection_reply, Pid, ssh_connection:channel_close_msg(Id)}), ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}), + SshOpts = proplists:get_value(ssh_opts, State#state.opts), + case proplists:get_value(idle_time, SshOpts) of + infinity -> + ok; + _IdleTime -> + erlang:send_after(5000, self(), {check_cache, [], []}) + end, {reply, ok, State}; undefined -> {reply, ok, State} @@ -431,6 +464,16 @@ handle_cast({request, ChannelId, Type, Data}, State0) -> lists:foreach(fun send_msg/1, Replies), {noreply, State}; +handle_cast({reply_request, Status, ChannelId}, #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + State = case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = RemoteId} -> + cm_message({Status, RemoteId}, State0); + undefined -> + State0 + end, + {noreply, State}; + handle_cast({global_request, _, _, _, _} = Request, State0) -> State = handle_global_request(Request, State0), {noreply, State}; @@ -453,18 +496,6 @@ handle_cast({adjust_window, ChannelId, Bytes}, end, {noreply, State}; -handle_cast({eof, ChannelId}, - #state{connection = Pid, connection_state = - #connection{channel_cache = Cache}} = State) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = Id} -> - send_msg({connection_reply, Pid, - ssh_connection:channel_eof_msg(Id)}), - {noreply, State}; - undefined -> - {noreply, State} - end; - handle_cast({success, ChannelId}, #state{connection = Pid} = State) -> Msg = ssh_connection:channel_success_msg(ChannelId), send_msg({connection_reply, Pid, Msg}), @@ -510,7 +541,10 @@ handle_info({start_connection, client, Pid ! {self(), not_connected, Reason}, {stop, {shutdown, normal}, State} end; - +handle_info({check_cache, _ , _}, + #state{connection_state = + #connection{channel_cache = Cache}} = State) -> + {noreply, check_cache(State, Cache)}; handle_info({ssh_cm, _Sender, Msg}, State0) -> %% Backwards compatibility! State = cm_message(Msg, State0), @@ -523,7 +557,7 @@ handle_info({same_user, _}, State) -> handle_info(ssh_connected, #state{role = client, client = Pid} = State) -> Pid ! {self(), is_connected}, - {noreply, State#state{connected = true}}; + {noreply, State#state{connected = true, opts = handle_password(State#state.opts)}}; handle_info(ssh_connected, #state{role = server} = State) -> {noreply, State#state{connected = true}}; @@ -536,6 +570,47 @@ handle_info({'DOWN', _Ref, process, ChannelPid, _Reason}, State) -> handle_info({'EXIT', _Sup, Reason}, State) -> {stop, Reason, State}. +handle_password(Opts) -> + handle_rsa_password(handle_dsa_password(handle_normal_password(Opts))). +handle_normal_password(Opts) -> + case proplists:get_value(ssh_opts, Opts, false) of + false -> + Opts; + SshOpts -> + case proplists:get_value(password, SshOpts, false) of + false -> + Opts; + _Password -> + NewOpts = [{password, undefined}|lists:keydelete(password, 1, SshOpts)], + [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)] + end + end. +handle_dsa_password(Opts) -> + case proplists:get_value(ssh_opts, Opts, false) of + false -> + Opts; + SshOpts -> + case proplists:get_value(dsa_pass_phrase, SshOpts, false) of + false -> + Opts; + _Password -> + NewOpts = [{dsa_pass_phrase, undefined}|lists:keydelete(dsa_pass_phrase, 1, SshOpts)], + [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)] + end + end. +handle_rsa_password(Opts) -> + case proplists:get_value(ssh_opts, Opts, false) of + false -> + Opts; + SshOpts -> + case proplists:get_value(rsa_pass_phrase, SshOpts, false) of + false -> + Opts; + _Password -> + NewOpts = [{rsa_pass_phrase, undefined}|lists:keydelete(rsa_pass_phrase, 1, SshOpts)], + [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)] + end + end. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() %% Description: This function is called by a gen_server when it is about to @@ -567,6 +642,45 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +get_idle_time(SshOptions) -> + case proplists:get_value(idle_time, SshOptions) of + infinity -> + infinity; + _IdleTime -> %% We dont want to set the timeout on first connect + undefined + end. +check_cache(State, Cache) -> + %% Check the number of entries in Cache + case proplists:get_value(size, ets:info(Cache)) of + 0 -> + Opts = proplists:get_value(ssh_opts, State#state.opts), + case proplists:get_value(idle_time, Opts) of + infinity -> + State; + undefined -> + State; + Time -> + case State#state.idle_timer_ref of + undefined -> + TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), + State#state{idle_timer_ref=TimerRef}; + _ -> + State + end + end; + _ -> + State + end. +remove_timer_ref(State) -> + case State#state.idle_timer_ref of + infinity -> %% If the timer is not activated + State; + undefined -> %% If we already has cancelled the timer + State; + TimerRef -> %% Timer is active + erlang:cancel_timer(TimerRef), + State#state{idle_timer_ref = undefined} + end. channel_data(Id, Type, Data, Connection0, ConnectionPid, From, State) -> case ssh_connection:channel_data(Id, Type, Data, Connection0, ConnectionPid, From) of @@ -614,6 +728,8 @@ do_send_msg({connection_reply, Pid, Data}) -> ssh_connection_handler:send(Pid, Msg); do_send_msg({flow_control, Cache, Channel, From, Msg}) -> ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), + gen_server:reply(From, Msg); +do_send_msg({flow_control, From, Msg}) -> gen_server:reply(From, Msg). handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, @@ -662,7 +778,7 @@ handle_channel_down(ChannelPid, #state{connection_state = (_,Acc) -> Acc end, [], Cache), - {{replies, []}, State}. + {{replies, []}, check_cache(State, Cache)}. update_sys(Cache, Channel, Type, ChannelPid) -> ssh_channel:cache_update(Cache, diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 1dbd097423..17a7cebb4a 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -23,37 +23,52 @@ -module(ssh_io). --export([yes_no/1, read_password/1, read_line/1, format/2]). +-export([yes_no/2, read_password/2, read_line/2, format/2]). -import(lists, [reverse/1]). +-include("ssh.hrl"). +read_line(Prompt, Ssh) -> + format("~s", [listify(Prompt)]), + proplists:get_value(user_pid, Ssh) ! {self(), question}, + receive + Answer -> + Answer + end. -read_line(Prompt) when is_list(Prompt) -> - io:get_line(list_to_atom(Prompt)); -read_line(Prompt) when is_atom(Prompt) -> - io:get_line(Prompt). - -read_ln(Prompt) -> - trim(read_line(Prompt)). - -yes_no(Prompt) -> +yes_no(Prompt, Ssh) -> io:format("~s [y/n]?", [Prompt]), - case read_ln('') of - "y" -> yes; - "n" -> no; - "Y" -> yes; - "N" -> no; - _ -> - io:format("please answer y or n\n"), - yes_no(Prompt) + proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question}, + receive + Answer -> + case trim(Answer) of + "y" -> yes; + "n" -> no; + "Y" -> yes; + "N" -> no; + y -> yes; + n -> no; + _ -> + io:format("please answer y or n\n"), + yes_no(Prompt, Ssh) + end end. -read_password(Prompt) -> +read_password(Prompt, Ssh) -> format("~s", [listify(Prompt)]), - case io:get_password() of - "" -> - read_password(Prompt); - Pass -> Pass + case is_list(Ssh) of + false -> + proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), user_password}; + _ -> + proplists:get_value(user_pid, Ssh) ! {self(), user_password} + end, + receive + Answer -> + case Answer of + "" -> + read_password(Prompt, Ssh); + Pass -> Pass + end end. listify(A) when is_atom(A) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 1f912c9bdf..7f6e7d9946 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -133,7 +133,7 @@ kex_dh_gex_messages() -> ]. yes_no(Ssh, Prompt) -> - (Ssh#ssh.io_cb):yes_no(Prompt). + (Ssh#ssh.io_cb):yes_no(Prompt, Ssh). connect(ConnectionSup, Address, Port, SocketOpts, Opts) -> Timeout = proplists:get_value(connect_timeout, Opts, infinity), |