aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh/src')
-rw-r--r--lib/ssh/src/ssh.appup.src6
-rw-r--r--lib/ssh/src/ssh.erl89
-rw-r--r--lib/ssh/src/ssh_auth.erl67
-rw-r--r--lib/ssh/src/ssh_auth.hrl2
-rw-r--r--lib/ssh/src/ssh_cli.erl3
-rw-r--r--lib/ssh/src/ssh_connection.erl76
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl28
-rw-r--r--lib/ssh/src/ssh_connection_manager.erl152
-rw-r--r--lib/ssh/src/ssh_io.erl61
-rw-r--r--lib/ssh/src/ssh_transport.erl2
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),