aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/src
diff options
context:
space:
mode:
authorSverker Eriksson <[email protected]>2017-08-30 21:00:35 +0200
committerSverker Eriksson <[email protected]>2017-08-30 21:00:35 +0200
commit44a83c8860bbd00878c720a7b9d940b4630bab8a (patch)
tree101b3c52ec505a94f56c8f70e078ecb8a2e8c6cd /lib/ssh/src
parent7c67bbddb53c364086f66260701bc54a61c9659c (diff)
parent040bdce67f88d833bfb59adae130a4ffb4c180f0 (diff)
downloadotp-44a83c8860bbd00878c720a7b9d940b4630bab8a.tar.gz
otp-44a83c8860bbd00878c720a7b9d940b4630bab8a.tar.bz2
otp-44a83c8860bbd00878c720a7b9d940b4630bab8a.zip
Merge tag 'OTP-20.0' into sverker/20/binary_to_atom-utf8-crash/ERL-474/OTP-14590
Diffstat (limited to 'lib/ssh/src')
-rw-r--r--lib/ssh/src/Makefile5
-rw-r--r--lib/ssh/src/ssh.app.src8
-rw-r--r--lib/ssh/src/ssh.appup.src6
-rw-r--r--lib/ssh/src/ssh.erl1048
-rw-r--r--lib/ssh/src/ssh.hrl80
-rw-r--r--lib/ssh/src/ssh_acceptor.erl145
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl63
-rw-r--r--lib/ssh/src/ssh_auth.erl495
-rw-r--r--lib/ssh/src/ssh_bits.erl46
-rw-r--r--lib/ssh/src/ssh_channel.erl9
-rw-r--r--lib/ssh/src/ssh_cli.erl43
-rw-r--r--lib/ssh/src/ssh_connect.hrl6
-rw-r--r--lib/ssh/src/ssh_connection.erl73
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl814
-rw-r--r--lib/ssh/src/ssh_connection_sup.erl30
-rw-r--r--lib/ssh/src/ssh_dbg.erl222
-rw-r--r--lib/ssh/src/ssh_dbg.hrl27
-rw-r--r--lib/ssh/src/ssh_file.erl43
-rw-r--r--lib/ssh/src/ssh_info.erl2
-rw-r--r--lib/ssh/src/ssh_io.erl64
-rw-r--r--lib/ssh/src/ssh_message.erl48
-rw-r--r--lib/ssh/src/ssh_options.erl904
-rw-r--r--lib/ssh/src/ssh_sftp.erl225
-rw-r--r--lib/ssh/src/ssh_sftpd.erl83
-rw-r--r--lib/ssh/src/ssh_sftpd_file_api.erl4
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl77
-rw-r--r--lib/ssh/src/ssh_sup.erl77
-rw-r--r--lib/ssh/src/ssh_system_sup.erl196
-rw-r--r--lib/ssh/src/ssh_transport.erl870
-rw-r--r--lib/ssh/src/ssh_transport.hrl30
-rw-r--r--lib/ssh/src/sshc_sup.erl45
-rw-r--r--lib/ssh/src/sshd_sup.erl111
32 files changed, 3462 insertions, 2437 deletions
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index 69d5a47f83..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.
@@ -51,6 +51,7 @@ MODULES= \
ssh_sup \
sshc_sup \
sshd_sup \
+ ssh_options \
ssh_connection_sup \
ssh_connection \
ssh_connection_handler \
@@ -96,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
+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.app.src b/lib/ssh/src/ssh.app.src
index 3245ba5197..974292fde1 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,
@@ -41,11 +42,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.0"
+ "public_key-1.4",
+ "stdlib-3.3"
]}]}.
-
diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src
index e38cecf226..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.
@@ -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}]}
]
}.
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 0570853a9b..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.
@@ -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,
@@ -40,10 +41,24 @@
]).
%%% Type exports
--export_type([connection_ref/0,
- channel_id/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()}.
@@ -52,16 +67,15 @@
%% is temporary. see application(3)
%%--------------------------------------------------------------------
start() ->
- application:start(crypto),
- application:start(asn1),
- application:start(public_key),
- application:start(ssh).
+ start(temporary).
start(Type) ->
- application:start(crypto, Type),
- application:start(asn1),
- application:start(public_key, Type),
- application:start(ssh, Type).
+ case application:ensure_all_started(ssh, Type) of
+ {ok, _} ->
+ ok;
+ Other ->
+ Other
+ end.
%%--------------------------------------------------------------------
-spec stop() -> ok | {error, term()}.
@@ -72,55 +86,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(inet:socket(), proplists:proplist(), timeout()) -> ok_error(connection_ref())
+ ; (string(), inet:port_number(), 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(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
- ok ->
+ 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,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(Host0, 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)],
+ Host = mangle_connect_address(Host0, SocketOpts),
+ 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} ->
{error, {options, {transport, TransportOpts}}};
exit:badarg ->
- {error, {options, {socket_options, SocketOptions}}}
+ {error, {options, {socket_options, SocketOpts}}}
end
end.
@@ -128,93 +150,191 @@ connect(Host, Port, Options, Timeout) ->
-spec close(pid()) -> ok.
%%
%% Description: Closes an ssh connection.
-%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
close(ConnectionRef) ->
ssh_connection_handler:stop(ConnectionRef).
%%--------------------------------------------------------------------
-spec connection_info(pid(), [atom()]) -> [{atom(), term()}].
%%
-%% Description: Retrieves information about a connection.
-%%--------------------------------------------------------------------
+%% Description: Retrieves information about a connection.
+%%--------------------------------------------------------------------
connection_info(ConnectionRef, Options) ->
ssh_connection_handler:connection_info(ConnectionRef, Options).
%%--------------------------------------------------------------------
-spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}].
%%
-%% Description: Retrieves information about a connection.
-%%--------------------------------------------------------------------
+%% Description: Retrieves information about a connection.
+%%--------------------------------------------------------------------
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(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
+%% Description: Starts a server listening for SSH connections
%% on the given port.
-%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
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(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),
+ finalize_start(IP, Port, ?GET_OPT(profile, Options),
+ ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options),
+ fun(Opts, DefaultResult) ->
+ try ssh_acceptor:handle_established_connection(
+ IP, Port, 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,
+ Host0 == any ; Host0 == loopback ; is_tuple(Host0) ->
+ 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(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(_, _, _) ->
+ {error, badarg}.
+
-daemon(HostAddr, Port, Options0) ->
- Options1 = daemon_shell_opt(Options0),
- {Host, Inet, Options} = daemon_host_inet_opt(HostAddr, Options1),
- start_daemon(Host, Port, Options, Inet).
%%--------------------------------------------------------------------
+-spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ).
+
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},
- _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)],
- {ok, [{port,Port}]};
-
+ [{IP,Port,Profile}] =
+ [{IP,Prt,Prf}
+ || {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]}
+ <- supervisor:which_children(AsupPid),
+ IP <- [case inet:parse_strict_address(Hst) of
+ {ok,IP} -> IP;
+ _ -> Hst
+ end]
+ ],
+ {ok, [{port,Port},
+ {ip,IP},
+ {profile,Profile}
+ ]};
_ ->
{error,bad_daemon_ref}
end.
%%--------------------------------------------------------------------
--spec stop_listener(pid()) -> ok.
--spec stop_listener(inet:ip_address(), integer()) -> ok.
+-spec stop_listener(daemon_ref()) -> ok.
+-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok.
%%
-%% Description: Stops the listener, but leaves
+%% Description: Stops the listener, but leaves
%% existing connections started by the listener up and running.
-%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
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(pid()) -> ok.
--spec stop_daemon(inet:ip_address(), integer()) -> 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.
%%
-%% Description: Stops the listener and all connections started by
+%% Description: Stops the listener and all connections started by
%% the listener.
-%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
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(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()
@@ -243,7 +363,7 @@ start_shell({ok, ConnectionRef}) ->
case ssh_connection:session_channel(ConnectionRef, infinity) of
{ok,ChannelId} ->
success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []),
- Args = [{channel_cb, ssh_shell},
+ Args = [{channel_cb, ssh_shell},
{init_args,[ConnectionRef, ChannelId]},
{cm, ConnectionRef}, {channel_id, ChannelId}],
{ok, State} = ssh_channel:init([Args]),
@@ -255,715 +375,111 @@ start_shell(Error) ->
Error.
%%--------------------------------------------------------------------
+-spec default_algorithms() -> algs_list() .
%%--------------------------------------------------------------------
-default_algorithms() ->
+default_algorithms() ->
ssh_transport: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}}
- end.
-
-is_tcp_socket(Socket) -> {ok,[]} =/= inet:getopts(Socket, [delay_send]).
-
-
-
-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(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};
- _C:_E -> {error,{cannot_start_daemon,_C,_E}}
- end;
- {error,SockError} ->
- {error,SockError}
- end
- 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};
- _C:_E -> {error,{cannot_start_daemon,_C,_E}}
- end
- end.
-
-do_start_daemon(Socket, SshOptions, SocketOptions) ->
- {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}),
- 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,_} ->
- ssh_acceptor:handle_connection(Callback, 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, _} ->
- ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket),
- {ok, Sup};
- Other ->
- Other
- end
- end.
+%% 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.
-do_start_daemon(Host0, Port0, SshOptions, SocketOptions) ->
- {Host,Port1} =
- try
- case proplists:get_value(fd, SocketOptions) of
- undefined ->
- {Host0,Port0};
- Fd when Port0==0 ->
- find_hostport(Fd);
- _ ->
- {Host0,Port0}
- end
- catch
- _:_ -> throw(bad_fd)
- end,
- Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE),
- {Port, WaitRequestControl, Opts0} =
- 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,{_,LPort}} = inet:sockname(LSock),
- {LPort,
- {LSock,Callback},
- [{lsocket,LSock},{lsock_owner,self()}]
- };
- _ ->
- {Port1, false, []}
- end,
- Opts = [{address, Host},
- {port, Port},
- {role, server},
- {socket_opts, SocketOptions},
- {ssh_opts, SshOptions} | Opts0],
- 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,_} ->
- sync_request_control(WaitRequestControl),
- 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, _} ->
- sync_request_control(WaitRequestControl),
- {ok, Sup};
- Other ->
- Other
- end
- end.
+handle_daemon_args(any, Opts) ->
+ case proplists:get_value(ip, Opts) of
+ undefined -> {any, Opts};
+ IP -> {IP, Opts}
+ end;
-sync_request_control(false) ->
- ok;
-sync_request_control({LSock,Callback}) ->
- receive
- {request_control,LSock,ReqPid} ->
- ok = Callback:controlling_process(LSock, ReqPid),
- ReqPid ! {its_yours,LSock},
- ok
+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.
-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.
-
-
-handle_options(Opts) ->
- try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of
- {Inet, Ssh} ->
- {handle_ip(Inet), Ssh}
+%%%----------------------------------------------------------------
+valid_socket_to_use(Socket, {tcp,_,_}) ->
+ %% Is this tcp-socket a valid socket?
+ 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}
catch
- throw:Error ->
- Error
- end.
-
+ _:_ -> {error, bad_socket}
+ 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.
+valid_socket_to_use(_, {L4,_,_}) ->
+ {error, {unsupported,L4}}.
-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({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,
+is_tcp_socket(Socket) ->
+ case inet:getopts(Socket, [delay_send]) of
+ {ok,[_]} -> true;
+ _ -> false
+ 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) ->
try
- handle_ssh_option({dh_gex_groups,GroupDefs})
+ sshd_sup:start_child(Host, Port, Profile, Options0)
+ of
+ {error, {already_started, _}} ->
+ {error, eaddrinuse};
+ {error, Error} ->
+ {error, Error};
+ Result = {ok,_} ->
+ F(Options0, Result)
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}}})
+ exit:{noproc, _} ->
+ {error, ssh_not_started}
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}
+%%%----------------------------------------------------------------
+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.
-
-
-
-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={_,_,_,_,_,_,_,_}) ->
- lists:flatten(string:join([io_lib:format("~.16B",[A]) || A <- tuple_to_list(T)], ":")).
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 868f3a9181..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.
@@ -33,8 +33,11 @@
-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']).
-define(FALSE, 0).
-define(TRUE, 1).
@@ -64,14 +67,73 @@
-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,?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 ) ).
+-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) ).
+
+-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()} .
+-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() .
+
+-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_address(),inet:port_number()}}, %% string version of peer address
c_vsn, %% client version {Major,Minor}
s_vsn, %% server version {Major,Minor}
@@ -82,6 +144,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
@@ -127,7 +192,7 @@
recv_sequence = 0,
keyex_key,
keyex_info,
- random_length_padding = 255, % From RFC 4253 section 6.
+ random_length_padding = ?MAX_RND_PADDING_LEN, % From RFC 4253 section 6.
%% User auth
user,
@@ -135,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,
@@ -153,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_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index 9f3e60bd62..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-2015. 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.
@@ -25,84 +25,86 @@
-include("ssh.hrl").
%% Internal application API
--export([start_link/5,
+-export([start_link/4,
number_of_connections/1,
- callback_listen/3,
- handle_connection/5]).
+ listen/2,
+ handle_established_connection/4]).
%% 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).
+%%%----------------------------------------------------------------
+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, 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()}),
- 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_init(Parent, Port, Address, Opts, AcceptTimeout) ->
+ try
+ ?GET_INTERNAL_OPT(lsocket, Opts)
+ of
+ {LSock, SockOwner} ->
+ case inet:sockname(LSock) of
+ {ok,{_,Port}} -> % A usable, open LSock
+ proc_lib:init_ack(Parent, {ok, self()}),
+ 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.
+
request_ownership(LSock, SockOwner) ->
SockOwner ! {request_control,LSock,self()},
receive
{its_yours,LSock} -> ok
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
- {error, nxdomain} ->
- Callback:listen(Port, lists:delete(inet6, Opts));
- {error, enetunreach} ->
- Callback:listen(Port, lists:delete(inet6, Opts));
- {error, eafnosupport} ->
- Callback:listen(Port, lists:delete(inet6, Opts));
- Other ->
- Other
- end.
-
+%%%----------------------------------------------------------------
acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
case (catch Callback:accept(ListenSocket, AcceptTimeout)) of
{ok, Socket} ->
@@ -119,22 +121,24 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
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),
+ {ok, SubSysSup} =
+ ssh_system_sup:start_subsystem(SystemSup, server, Address, Port, Profile, 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);
@@ -150,7 +154,7 @@ handle_connection(Callback, Address, Port, Options, Socket) ->
{error,max_sessions}
end.
-
+%%%----------------------------------------------------------------
handle_error(timeout) ->
ok;
@@ -177,10 +181,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)
- ]).
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index 129f85a3e0..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.
@@ -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,20 +41,19 @@
%%%=========================================================================
%%% 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, ServerOpts) ->
- Spec = child_spec(ServerOpts),
+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 = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- Profile = proplists:get_value(profile,
- proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ %% 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.
@@ -70,35 +69,29 @@ stop_child(AccSup, Address, Port, Profile) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-init([ServerOpts]) ->
- RestartStrategy = one_for_one,
- MaxR = 10,
- MaxT = 3600,
- Children = [child_spec(ServerOpts)],
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+init([Address, Port, Profile, Options]) ->
+ %% 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(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),
- Name = id(Address, Port, Profile),
- SocketOpts = proplists:get_value(socket_opts, ServerOpts),
- StartFunc = {ssh_acceptor, start_link, [Port, Address, SocketOpts, ServerOpts, Timeout]},
- Restart = transient,
- Shutdown = brutal_kill,
- Modules = [ssh_acceptor],
- Type = worker,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+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]
+ }.
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_auth.erl b/lib/ssh/src/ssh_auth.erl
index 49eec8072f..ac64a7bf14 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.
@@ -28,57 +28,87 @@
-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/3, handle_userauth_info_response/2
+ handle_userauth_info_request/2, handle_userauth_info_response/2
]).
%%--------------------------------------------------------------------
%%% Internal application API
%%--------------------------------------------------------------------
-publickey_msg([Alg, #ssh{user = User,
- session_id = SessionId,
- service = Service,
- opts = Opts} = Ssh]) ->
- Hash = sha, %% Maybe option?!
- KeyCb = proplists:get_value(key_cb, Opts, ssh_file),
- case KeyCb:user_key(Alg, Opts) of
- {ok, PrivKey} ->
- StrAlgo = atom_to_list(Alg),
- case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of
- not_ok ->
- not_ok;
- PubKeyBlob ->
- SigData = build_sig_data(SessionId,
- User, Service, PubKeyBlob, StrAlgo),
- Sig = ssh_transport:sign(SigData, Hash, PrivKey),
- SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]),
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "publickey",
- data = [?TRUE,
- ?string(StrAlgo),
- ?binary(PubKeyBlob),
- ?binary(SigBlob)]},
- Ssh)
- end;
- _Error ->
- not_ok
+%%%----------------------------------------------------------------
+userauth_request_msg(#ssh{userauth_methods = ServerMethods,
+ userauth_supported_methods = UserPrefMethods, % Note: this is not documented as supported for clients
+ 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)};
+
+ [{Pref,Module,Function,Args} | Prefs] ->
+ Ssh = case Pref of
+ "keyboard-interactive" -> Ssh0;
+ _ -> Ssh0#ssh{userauth_preference = Prefs}
+ end,
+ case Module:Function(Args ++ [Ssh]) of
+ {not_ok, Ssh1} ->
+ userauth_request_msg(Ssh1#ssh{userauth_preference = Prefs});
+ Result ->
+ {Pref,Result}
+ end
end.
+
+
+sort_select_mthds(Clients, undefined, Servers) ->
+ %% User has not expressed an opinion via option "auth_methods", use the server's prefs
+ sort_select_mthds1(Clients, Servers, string:tokens(?SUPPORTED_AUTH_METHODS,","));
+
+sort_select_mthds(Clients, Users0, Servers0) ->
+ %% The User has an opinion, use the intersection of that and the Servers whishes but
+ %% in the Users order
+ sort_select_mthds1(Clients, string:tokens(Users0,","), Servers0).
+
+
+sort_select_mthds1(Clients, Users0, Servers0) ->
+ Servers = unique(Servers0),
+ Users = unique(Users0),
+ [C || Key <- Users,
+ lists:member(Key, Servers),
+ C <- Clients,
+ element(1,C) == Key].
+
+unique(L) ->
+ lists:reverse(
+ lists:foldl(fun(E,Acc) ->
+ case lists:member(E,Acc) of
+ true -> Acc;
+ false -> [E|Acc]
+ end
+ end, [], L)).
+
+
+%%%---- userauth_request_msg "callbacks"
password_msg([#ssh{opts = Opts, io_cb = IoCb,
- user = User, service = Service} = Ssh]) ->
- Password = case proplists:get_value(password, Opts) of
- undefined ->
- user_interaction(IoCb, Ssh);
- PW ->
- PW
- end,
+ user = User, service = Service} = Ssh0]) ->
+ {Password,Ssh} =
+ case ?GET_OPT(password, Opts) of
+ undefined when IoCb == ssh_no_io ->
+ {not_ok, Ssh0};
+ undefined ->
+ {IoCb:read_password("ssh password: ",Opts), Ssh0};
+ PW ->
+ %% If "password" option is given it should not be tried again
+ {PW, Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}}
+ end,
case Password of
not_ok ->
- not_ok;
+ {not_ok, Ssh};
_ ->
ssh_transport:ssh_packet(
#ssh_msg_userauth_request{user = User,
@@ -90,84 +120,100 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb,
Ssh)
end.
-user_interaction(ssh_no_io, _) ->
- not_ok;
-user_interaction(IoCb, Ssh) ->
- IoCb:read_password("ssh password: ", Ssh).
-
-
%% See RFC 4256 for info on keyboard-interactive
keyboard_interactive_msg([#ssh{user = User,
+ opts = Opts,
service = Service} = Ssh]) ->
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "keyboard-interactive",
- data = << ?STRING(<<"">>),
- ?STRING(<<>>) >> },
- Ssh).
+ case ?GET_OPT(password, Opts) of
+ not_ok ->
+ {not_ok,Ssh}; % No need to use a failed pwd once more
+ _ ->
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "keyboard-interactive",
+ data = << ?STRING(<<"">>),
+ ?STRING(<<>>) >> },
+ Ssh)
+ end.
+
+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} ->
+ try
+ Key = ssh_transport:extract_public_key(PrivKey),
+ public_key:ssh_encode(Key, ssh2_pubkey)
+ of
+ PubKeyBlob -> {ok,{PrivKey,PubKeyBlob}}
+ catch
+ _:_ ->
+ not_ok
+ end;
+ _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.
+
+%%%----------------------------------------------------------------
service_request_msg(Ssh) ->
ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"},
Ssh#ssh{service = "ssh-userauth"}).
+%%%----------------------------------------------------------------
init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
- case user_name(Opts) of
- {ok, 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),
- %% 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"});
- {error, no_user} ->
+ %% 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})
- end.
-
-userauth_request_msg(#ssh{userauth_preference = []} = Ssh) ->
- 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, Ssh)};
-
-userauth_request_msg(#ssh{userauth_methods = Methods,
- userauth_preference = [{Pref, Module,
- Function, Args} | Prefs]}
- = Ssh0) ->
- Ssh = Ssh0#ssh{userauth_preference = Prefs},
- case lists:member(Pref, Methods) of
- true ->
- case Module:Function(Args ++ [Ssh]) of
- not_ok ->
- userauth_request_msg(Ssh);
- Result ->
- {Pref,Result}
- end;
- false ->
- userauth_request_msg(Ssh)
+ description = ErrStr});
+
+ User ->
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none",
+ data = <<>>},
+ Ssh#ssh{user = User,
+ userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys),
+ userauth_methods = none,
+ service = "ssh-connection"}
+ )
end.
-
-handle_userauth_request(#ssh_msg_service_request{name =
- Name = "ssh-userauth"},
+%%%----------------------------------------------------------------
+%%% called by server
+handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"},
_, Ssh) ->
{ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name},
Ssh#ssh{service = "ssh-connection"})};
@@ -223,32 +269,52 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "publickey",
- data = Data},
- SessionId,
+ data = <<?BYTE(?FALSE),
+ ?UINT32(ALen), BAlg:ALen/binary,
+ ?UINT32(KLen), KeyBlob:KLen/binary,
+ _/binary
+ >>
+ },
+ _SessionId,
#ssh{opts = Opts,
userauth_supported_methods = Methods} = Ssh) ->
- <<?BYTE(HaveSig), ?UINT32(ALen), BAlg:ALen/binary,
- ?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data,
- Alg = binary_to_list(BAlg),
- case HaveSig of
- ?TRUE ->
- case verify_sig(SessionId, User, "ssh-connection", Alg,
- KeyBlob, SigWLen, Opts) of
- true ->
- {authorized, User,
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_success{}, Ssh)};
- false ->
- {not_authorized, {User, undefined},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh)}
- end;
- ?FALSE ->
+
+ case pre_verify_sig(User, KeyBlob, Opts) of
+ true ->
{not_authorized, {User, undefined},
ssh_transport:ssh_packet(
- #ssh_msg_userauth_pk_ok{algorithm_name = Alg,
- key_blob = KeyBlob}, Ssh)}
+ #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg),
+ key_blob = KeyBlob}, Ssh)};
+ false ->
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
+ authentications = Methods,
+ partial_success = false}, Ssh)}
+ end;
+
+handle_userauth_request(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "publickey",
+ data = <<?BYTE(?TRUE),
+ ?UINT32(ALen), BAlg:ALen/binary,
+ ?UINT32(KLen), KeyBlob:KLen/binary,
+ SigWLen/binary>>
+ },
+ SessionId,
+ #ssh{opts = Opts,
+ userauth_supported_methods = Methods} = Ssh) ->
+
+ case verify_sig(SessionId, User, "ssh-connection",
+ BAlg, KeyBlob, SigWLen, Opts) of
+ true ->
+ {authorized, User,
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_success{}, Ssh)};
+ false ->
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
+ authentications = Methods,
+ partial_success = false}, Ssh)}
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -283,7 +349,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 ->
@@ -319,31 +385,54 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
partial_success = false}, Ssh)}.
-
-handle_userauth_info_request(
- #ssh_msg_userauth_info_request{name = Name,
- instruction = Instr,
- num_prompts = NumPrompts,
- data = Data}, IoCb,
- #ssh{opts = Opts} = Ssh) ->
+%%%----------------------------------------------------------------
+%%% keyboard-interactive client
+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) ->
PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data),
- Responses = keyboard_interact_get_responses(IoCb, Opts,
- Name, Instr, PromptInfos),
- {ok,
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_info_response{num_responses = NumPrompts,
- data = Responses}, Ssh)}.
+ case keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) of
+ not_ok ->
+ not_ok;
+ Responses ->
+ {ok,
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_info_response{num_responses = NumPrompts,
+ data = Responses}, Ssh)}
+ end.
+%%%----------------------------------------------------------------
+%%% keyboard-interactive server
handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
data = <<?UINT32(Sz), Password:Sz/binary>>},
#ssh{opts = Opts,
kb_tries_left = KbTriesLeft,
user = User,
userauth_supported_methods = Methods} = Ssh) ->
+ SendOneEmpty =
+ (?GET_OPT(tstflg,Opts) == one_empty)
+ orelse
+ 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 ->
+ Msg = #ssh_msg_userauth_info_request{name = "",
+ instruction = "",
+ language_tag = "",
+ num_prompts = 0,
+ data = <<?BOOLEAN(?FALSE)>>
+ },
+ {authorized_but_one_more, User,
+ ssh_transport:ssh_packet(Msg, Ssh1)};
+
{true,Ssh1} ->
{authorized, User,
ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)};
+
{false,Ssh1} ->
{not_authorized, {User, {error,"Bad user or password"}},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
@@ -353,6 +442,11 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
)}
end;
+handle_userauth_info_response({extra,#ssh_msg_userauth_info_response{}},
+ #ssh{user = User} = Ssh) ->
+ {authorized, User,
+ ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
+
handle_userauth_info_response(#ssh_msg_userauth_info_response{},
_Auth) ->
ssh_connection_handler:disconnect(
@@ -364,41 +458,17 @@ 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, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
- ],
- 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.
+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 <- SigKeyAlgs],
+ NonPKmethods = [{"password", ?MODULE, password_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
+ ],
+ PubKeyDefs ++ NonPKmethods.
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};
@@ -428,25 +498,37 @@ 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.
-verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) ->
- {ok, Key} = decode_public_key_v2(KeyBlob, Alg),
- KeyCb = proplists:get_value(key_cb, Opts, ssh_file),
+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])
+ catch
+ _:_ ->
+ false
+ end.
- case KeyCb:is_auth_key(Key, User, Opts) of
- true ->
- PlainText = build_sig_data(SessionId, User,
- Service, KeyBlob, Alg),
- <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,
- <<?UINT32(AlgLen), _Alg:AlgLen/binary,
- ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig,
- ssh_transport:verify(PlainText, sha, Sig, Key);
- false ->
+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),
+ 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),
+ <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,
+ <<?UINT32(AlgLen), _Alg:AlgLen/binary,
+ ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig,
+ ssh_transport:verify(PlainText, ssh_transport:sha(Alg), Sig, Key)
+ catch
+ _:_ ->
false
end.
@@ -468,11 +550,14 @@ 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).
+
+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
@@ -486,17 +571,18 @@ keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos
keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts).
keyboard_interact(IoCb, Name, Instr, Prompts, Opts) ->
- if Name /= "" -> IoCb:format("~s~n", [Name]);
- true -> ok
- end,
- if Instr /= "" -> IoCb:format("~s~n", [Instr]);
- true -> ok
- end,
+ 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]).
+
+
keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) ->
Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end,
PromptInfos),
@@ -512,18 +598,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_bits.erl b/lib/ssh/src/ssh_bits.erl
index 8bedaaf0c5..3ce7758447 100644
--- a/lib/ssh/src/ssh_bits.erl
+++ b/lib/ssh/src/ssh_bits.erl
@@ -30,39 +30,31 @@
-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
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 ->
- <<?UINT32((I+1)), (list_to_binary([255|Ds]))/binary>>;
- true ->
- <<?UINT32(I), (list_to_binary(Ds))/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 ->
- <<?UINT32((I+1)), (list_to_binary([0|Ds]))/binary>>;
- true ->
- <<?UINT32(I), (list_to_binary(Ds))/binary>>
+mpint(I) when I>0 ->
+ <<B1,V/binary>> = 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_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,
+ <<W:Sxn1>> = <<1, 0:Sxn>>,
+ <<B1,V/binary>> = 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
diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl
index a8e6ebde16..85b31f3669 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).
@@ -256,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;
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 74cd2e081a..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.
@@ -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) ->
@@ -446,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(_) -> [].
@@ -486,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
@@ -512,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} ->
@@ -521,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_connect.hrl b/lib/ssh/src/ssh_connect.hrl
index 4fb6bc39f3..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.
@@ -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 d0f2d54c06..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-2015. 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.
@@ -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 <ChannelId>.
@@ -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 <ChannelId>.
@@ -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}.
%%
@@ -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
@@ -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}),
@@ -414,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 ->
@@ -571,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});
@@ -688,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),
@@ -816,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
@@ -855,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 ->
@@ -864,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, []};
@@ -1019,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 e952a333ff..8d3ddb09a4 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.
@@ -60,7 +60,8 @@
]).
%%% Behaviour callbacks
--export([handle_event/4, terminate/3, format_status/2, code_change/4]).
+-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
-export([init_connection_handler/3, % proc_lib:spawn needs this
@@ -75,11 +76,15 @@
%%--------------------------------------------------------------------
-spec start_link(role(),
inet:socket(),
- proplists:proplist()
+ ssh_options:options()
) -> {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}]
+ )}.
%%--------------------------------------------------------------------
@@ -98,12 +103,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()}.
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -120,9 +123,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() ->
@@ -335,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(),
@@ -345,7 +346,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
}).
@@ -356,107 +357,121 @@ 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),
- 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],
- handle_event_function,
- {hello,Role},
- S)
- catch
- _:Error ->
- gen_statem:enter_loop(?MODULE,
- [],
- handle_event_function,
- {init_error,Error},
- S0)
+ case init([Role, Socket, Opts]) of
+ {ok, StartState, D} ->
+ process_flag(trap_exit, true),
+ gen_statem:enter_loop(?MODULE,
+ [], %%[{debug,[trace,log,statistics,debug]} ], %% []
+ 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}})
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 = proplists:get_value(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 ->
- D#data{connection_state = init_connection(Role, C, Opts)}
+
+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),
+ connection_state = C,
+ socket = Socket,
+ transport_protocol = Protocol,
+ transport_cb = Callback,
+ transport_close_tag = CloseTag,
+ ssh_params = init_ssh_record(Role, Socket, PeerAddr, 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),
+ 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.
-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),
- 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]}),
- C#connection{cli_spec = CliSpec,
- exec = Exec,
- system_supervisor = SystemSup,
- sub_system_supervisor = SubSystemSup,
- connection_supervisor = ConnectionSup
- }.
-
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, ?SUPPORTED_AUTH_METHODS),
+ %% 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 = ?GET_OPT(auth_methods, Opts),
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),
- S0#ssh{c_vsn = Vsn,
- c_version = Version,
- io_cb = case proplists:get_value(user_interaction, Opts, true) of
- true -> ssh_io;
- false -> ssh_no_io
- end,
- userauth_quiet_mode = proplists:get_value(quiet_mode, Opts, false),
- peer = {PeerName, PeerAddr}
- };
+ PeerName = case ?GET_INTERNAL_OPT(host, Opts) of
+ PeerIP when is_tuple(PeerIP) ->
+ inet_parse:ntoa(PeerIP);
+ PeerName0 when is_atom(PeerName0) ->
+ atom_to_list(PeerName0);
+ PeerName0 when is_list(PeerName0) ->
+ PeerName0
+ end,
+ 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,
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}
@@ -474,51 +489,53 @@ init_ssh_record(Role, Socket, Opts) ->
-type renegotiate_flag() :: init | renegotiate.
-type state_name() ::
- {init_error,any()}
- | {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()}
- | {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
+ element(1,StateName) == ext_info ) ).
-spec handle_event(gen_statem:event_type(),
event_content(),
state_name(),
#data{}
- ) -> handle_event_result().
+ ) -> gen_statem:event_handler_result(state_name()) .
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-%%% ######## Error in the initialisation ####
-
-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;
-
+callback_mode() ->
+ handle_event_function.
%%% ######## {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) ->
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:
@@ -538,12 +555,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;
@@ -558,7 +576,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 ->
@@ -576,7 +594,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
@@ -589,43 +607,53 @@ 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),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- ok = send_bytes(NewKeys, D),
+ send_bytes(KexdhReply, D),
+ {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 = send_bytes(NewKeys, D),
+ {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
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),
+ {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 = send_bytes(GexGroup, D),
+ {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) ->
{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),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- ok = send_bytes(NewKeys, D),
+ send_bytes(KexEcdhReply, D),
+ {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 = send_bytes(NewKeys, D),
+ {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}};
@@ -633,9 +661,11 @@ 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),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- ok = send_bytes(NewKeys, D),
+ send_bytes(KexGexReply, D),
+ {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}};
@@ -643,37 +673,68 @@ 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),
- {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}};
+ 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}};
%%% ######## {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),
- ok = 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{}, {new_keys,Role,renegotiate}, D) ->
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, 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,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};
-%%% ######## {service_request, client|server}
+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]};
+
+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]};
+
+%%% ######## {service_request, client|server} ####
handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D) ->
case ServiceName of
"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}};
_ ->
@@ -685,7 +746,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}};
@@ -702,7 +763,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} ->
@@ -712,7 +773,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},
@@ -720,11 +781,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 ->
@@ -745,6 +806,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}}};
@@ -794,9 +860,13 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) -
handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client},
#data{ssh_params = Ssh0} = D) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, Ssh0#ssh.io_cb, Ssh0),
- send_bytes(Reply, D),
- {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}};
+ case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of
+ {ok, {Reply, Ssh}} ->
+ send_bytes(Reply, D),
+ {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}};
+ not_ok ->
+ {next_state, {userauth,client}, D, [postpone]}
+ end;
handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) ->
case ssh_auth:handle_userauth_info_response(Msg, D#data.ssh_params) of
@@ -809,34 +879,60 @@ handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_inte
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
retry_fun(User, Reason, D),
send_bytes(Reply, D),
- {next_state, {userauth,server}, D#data{ssh_params = Ssh}}
+ {next_state, {userauth,server}, D#data{ssh_params = Ssh}};
+
+ {authorized_but_one_more, _User, {Reply, Ssh}} ->
+ send_bytes(Reply, D),
+ {next_state, {userauth_keyboard_interactive_extra,server}, D#data{ssh_params = Ssh}}
end;
-handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client},
+handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D) ->
+ {authorized, User, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response({extra,Msg}, D#data.ssh_params),
+ send_bytes(Reply, D),
+ D#data.starter ! ssh_connected,
+ connected_fun(User, "keyboard-interactive", D),
+ {next_state, {connected,server}, D#data{auth_user = User,
+ ssh_params = Ssh#ssh{authenticated = true}}};
+
+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(_, #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
+ undefined ->
+ D0;
+ _ ->
+ D0#data{ssh_params =
+ Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} % FIXME:intermodule dependency
+ end,
+ {next_state, {userauth,client}, D, [postpone]};
-handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, D) ->
- {next_state, {userauth,client}, D, [{next_event, internal, Msg}]};
+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(_, 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}, _}} =
@@ -885,6 +981,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);
@@ -892,6 +991,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) ->
@@ -915,7 +1015,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.ssh_params)#ssh.opts),
timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]),
case Sent >= MaxSent of
true ->
@@ -936,12 +1036,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,
@@ -967,23 +1065,24 @@ 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),
+ update_inet_buffers(D#data.socket),
{keep_state, send_msg(Msg,D)};
undefined ->
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)};
@@ -1006,13 +1105,13 @@ handle_event({call,From}, get_print_info, StateName, D) ->
{keep_state_and_data, [{reply,From,Reply}]};
handle_event({call,From}, {connection_info, Options}, _, D) ->
- Info = ssh_info(Options, D, []),
+ Info = fold_keys(Options, fun conn_info/2, 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
#channel{} = Channel ->
- Info = ssh_channel_info(Options, Channel, []),
+ Info = fold_keys(Options, fun chann_info/2, Channel),
{keep_state_and_data, [{reply,From,Info}]};
undefined ->
{keep_state_and_data, [{reply,From,[]}]}
@@ -1044,30 +1143,34 @@ 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}, {connected,_}, D0) ->
+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)};
-handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+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)};
-handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+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}),
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 ?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),
@@ -1078,8 +1181,8 @@ handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) ->
handle_event({call,From},
{open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout},
- {connected,_},
- D0) ->
+ StateName,
+ D0) when ?CONNECTED(StateName) ->
erlang:monitor(process, ChannelPid),
{ChannelId, D1} = new_channel_id(D0),
D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId,
@@ -1099,7 +1202,8 @@ 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 ?CONNECTED(StateName) ->
Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{send_window_size = WinSize,
send_packet_size = Packsize} ->
@@ -1109,7 +1213,8 @@ 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 ?CONNECTED(StateName) ->
Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
recv_packet_size = Packsize} ->
@@ -1119,7 +1224,8 @@ 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 ?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),
@@ -1158,17 +1264,17 @@ 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 ->
disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Encountered unexpected input"},
+ description = "Bad packet"},
StateName, D)
end;
@@ -1183,13 +1289,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 ->
@@ -1206,16 +1311,20 @@ handle_event(internal, prepare_next_packet, _, D) ->
Sz when Sz >= Enough ->
self() ! {D#data.transport_protocol, D#data.socket, <<>>};
_ ->
- inet:setopts(D#data.socket, [{active, once}])
+ ok
end,
+ 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}) ->
- disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "Connection closed"},
- StateName, D);
+ %% 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, {timeout, {_, From} = Request}, _,
#data{connection_state = #connection{requests = Requests} = C0} = D) ->
@@ -1251,11 +1360,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;
@@ -1269,11 +1379,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
@@ -1282,11 +1393,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";
+%% "Message in wrong state";
+ lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName]));
_ ->
"Internal error"
end,
@@ -1315,12 +1431,10 @@ terminate(shutdown, StateName, State0) ->
State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
description = "Application shutdown"},
State0),
-timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead
finalize_termination(StateName, State);
%% terminate({shutdown,Msg}, StateName, State0) when is_record(Msg,ssh_msg_disconnect)->
%% State = send_msg(Msg, State0),
-%% timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead
%% finalize_termination(StateName, Msg, State);
terminate({shutdown,_R}, StateName, State) ->
@@ -1382,12 +1496,12 @@ fmt_stat_rec(FieldNames, Rec, Exclude) ->
state_name(),
#data{},
term()
- ) -> {gen_statem:callback_mode(), state_name(), #data{}}.
+ ) -> {ok, state_name(), #data{}}.
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
code_change(_OldVsn, StateName, State, _Extra) ->
- {handle_event_function, StateName, State}.
+ {ok, StateName, State}.
%%====================================================================
@@ -1397,11 +1511,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.
@@ -1428,46 +1542,45 @@ 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
- 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,
+ ?GET_OPT(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.
+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
+ end.
send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) ->
@@ -1475,8 +1588,11 @@ 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).
+ _ = Transport:send(Socket, Bytes),
+ ok.
handle_version({2, 0} = NumVsn, StrVsn, Ssh0) ->
Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0),
@@ -1580,6 +1696,57 @@ 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({"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)
+ }};
+
+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
#channel{remote_id = Id} = Channel ->
@@ -1635,7 +1802,6 @@ new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} =
disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) ->
State = send_msg(Msg, State0),
disconnect_fun(Description, State),
-timer:sleep(400),
{stop, {shutdown,Description}, State}.
%%%----------------------------------------------------------------
@@ -1644,43 +1810,43 @@ counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) ->
counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) ->
Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}.
-ssh_info([], _State, Acc) ->
- Acc;
-ssh_info([client_version | Rest], #data{ssh_params = #ssh{c_vsn = IntVsn,
- c_version = StringVsn}} = State, Acc) ->
- ssh_info(Rest, State, [{client_version, {IntVsn, StringVsn}} | Acc]);
-
-ssh_info([server_version | Rest], #data{ssh_params =#ssh{s_vsn = IntVsn,
- s_version = StringVsn}} = State, Acc) ->
- ssh_info(Rest, State, [{server_version, {IntVsn, StringVsn}} | Acc]);
-ssh_info([peer | Rest], #data{ssh_params = #ssh{peer = Peer}} = State, Acc) ->
- ssh_info(Rest, State, [{peer, Peer} | Acc]);
-ssh_info([sockname | Rest], #data{socket = Socket} = State, Acc) ->
- {ok, SockName} = inet:sockname(Socket),
- ssh_info(Rest, State, [{sockname, SockName}|Acc]);
-ssh_info([user | Rest], #data{auth_user = User} = State, Acc) ->
- ssh_info(Rest, State, [{user, User}|Acc]);
-ssh_info([ _ | Rest], State, Acc) ->
- ssh_info(Rest, State, Acc).
-
-
-ssh_channel_info([], _, Acc) ->
- Acc;
+%%%----------------------------------------------------------------
+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;
+%% 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) ->
+ [Id | Acc]
+ end, [], cache(D)).
-ssh_channel_info([recv_window | Rest], #channel{recv_window_size = WinSize,
- recv_packet_size = Packsize
- } = Channel, Acc) ->
- ssh_channel_info(Rest, Channel, [{recv_window, {{win_size, WinSize},
- {packet_size, Packsize}}} | Acc]);
-ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize,
- send_packet_size = Packsize
- } = Channel, Acc) ->
- ssh_channel_info(Rest, Channel, [{send_window, {{win_size, WinSize},
- {packet_size, Packsize}}} | Acc]);
-ssh_channel_info([ _ | Rest], Channel, Acc) ->
- ssh_channel_info(Rest, Channel, Acc).
+%%%----------------------------------------------------------------
+chann_info(recv_window, C) ->
+ {{win_size, C#channel.recv_window_size},
+ {packet_size, C#channel.recv_packet_size}};
+chann_info(send_window, C) ->
+ {{win_size, C#channel.send_window_size},
+ {packet_size, C#channel.send_packet_size}};
+%% dbg options ( = not documented):
+chann_info(pid, C) ->
+ C#channel.user.
+%%%----------------------------------------------------------------
+%% Assisting meta function for the *_info functions
+fold_keys(Keys, Fun, Extra) ->
+ lists:foldr(fun(Key, Acc) ->
+ try Fun(Key, Extra) of
+ Value -> [{Key,Value}|Acc]
+ catch
+ _:_ -> Acc
+ end
+ end, [], Keys).
+%%%----------------------------------------------------------------
log_error(Reason) ->
Report = io_lib:format("Erlang ssh connection handler failed with reason:~n"
" ~p~n"
@@ -1689,7 +1855,6 @@ log_error(Reason) ->
[Reason, erlang:get_stacktrace()]),
error_logger:error_report(Report).
-
%%%----------------------------------------------------------------
not_connected_filter({connection_reply, _Data}) -> true;
not_connected_filter(_) -> false.
@@ -1701,6 +1866,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;
@@ -1720,47 +1890,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 proplists:get_value(disconnectfun, Opts) of
- undefined ->
- ok;
- Fun ->
- catch Fun(Reason)
- end.
-
-unexpected_fun(UnexpectedMessage, #data{opts = Opts,
- ssh_params = #ssh{peer = {_,Peer} }
- } ) ->
- case proplists:get_value(unexpectedfun, Opts) of
- undefined ->
- report;
- Fun ->
- catch Fun(UnexpectedMessage, Peer)
- end.
+-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).
+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 proplists:get_value(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 proplists:get_value(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;
@@ -1774,7 +1921,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
@@ -1793,7 +1940,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.ssh_params)#ssh.opts) of
infinity ->
D#data{idle_timer_value = infinity,
idle_timer_ref = infinity % A flag used later...
@@ -1856,9 +2003,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} ->
@@ -1889,3 +2035,15 @@ handshake(Pid, Ref, Timeout) ->
{error, timeout}
end.
+update_inet_buffers(Socket) ->
+ 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_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl
index 0f54053f52..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.
@@ -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_dbg.erl b/lib/ssh/src/ssh_dbg.erl
index 480795cfc7..3f742ad9b6 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.
@@ -22,79 +22,216 @@
-module(ssh_dbg).
--export([messages/0,
- messages/1,
+-export([messages/0, messages/1, messages/2, messages/3,
+ auth/0, auth/1, auth/2, auth/3,
stop/0
]).
+-export([shrink_bin/1,
+ wr_record/3]).
+
-include("ssh.hrl").
-include("ssh_transport.hrl").
-include("ssh_connect.hrl").
-include("ssh_auth.hrl").
--record(data, {
- writer,
- acc = []}).
%%%================================================================
-messages() -> messages(fun(String,_D) -> io:format(String) end).
-%% messages() -> messages(fun(String,Acc) -> [String|Acc] 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).
-messages(Write) when is_function(Write,2) ->
- catch dbg:start(),
- setup_tracer(Write),
- dbg:p(new,c),
- dbg_ssh_messages().
+auth() -> start(auth).
+auth(F) -> start(auth,F).
+auth(F,X) -> start(auth,F,X).
+auth(F,M,I) -> start(auth,F,M,I).
-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).
+stop() -> dbg:stop().
%%%----------------------------------------------------------------
-stop() ->
- dbg:stop().
+start(Type) -> start(Type, fun io:format/2).
+
+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).
+
+%%%----------------------------------------------------------------
+fmt_fun(F) -> fun(Fmt,Args,Data) -> F(Fmt,Args), Data end.
+
+id_fun() -> fun(X) -> X end.
+%%%----------------------------------------------------------------
+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(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]).
+
%%%================================================================
-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) ->
+cond_start(Type, WriteFun, MangleArgFun, Init) ->
+ try
+ dbg:start(),
+ setup_tracer(Type, WriteFun, MangleArgFun, Init),
+ 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(msg, {trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) ->
D;
-msg_formater({trace,_Pid,call,{ssh_message,decode,_}}, D) ->
+msg_formater(msg, {trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) ->
D;
-msg_formater({trace,Pid,return_from,{ssh_message,decode,1},Msg}, D) ->
- fmt("~nRECV ~p ~s~n", [Pid,wr_record(shrink_bin(Msg))], 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(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,_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,_},{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,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg}}, D) ->
- fmt("~nALGORITHMS ~p~n~s~n", [Pid, wr_record(Alg)], D);
-msg_formater({trace,Pid,send,ErlangMsg,Dest}, D) ->
- fmt("~nERL MSG ~p SEND TO ~p~n ~p~n", [Pid,Dest, shrink_bin(ErlangMsg)], 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_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({trace,Pid,'receive',ErlangMsg}, D) ->
- fmt("~nERL MSG ~p RECIEVE~n ~p~n", [Pid,shrink_bin(ErlangMsg)], 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(M, D) ->
- fmt("~nDBG ~n~p~n", [shrink_bin(M)], 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(_, D) ->
-%% 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);
-fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) ->
- D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}.
+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(_, {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.
%%%----------------------------------------------------------------
-setup_tracer(Write) ->
- Handler = fun msg_formater/2,
- InitialData = #data{writer = Write},
+-record(data, {writer,
+ 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, WriteFun, MangleArgFun, Init) ->
+ Handler = fun(Arg, D) ->
+ msg_formater(Type, MangleArgFun(Arg), D)
+ end,
+ InitialData = #data{writer = WriteFun,
+ acc = Init},
{ok,_} = dbg:tracer(process, {Handler, InitialData}),
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)>256 -> {'*** SHRINKED BIN',
+ size(B),
+ element(1,split_binary(B,64)),
+ '...',
+ 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)));
shrink_bin(X) -> X.
@@ -117,6 +254,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_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
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index 216f65f33a..33792da38f 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.
@@ -75,17 +75,9 @@ 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}
- end;
- Other ->
- Other
+ check_key_type(Key, Algorithm);
+ {error,DecodeError} ->
+ {error,DecodeError}
end.
is_auth_key(Key, User,Opts) ->
@@ -109,12 +101,25 @@ 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";
+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";
@@ -192,8 +197,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
@@ -221,6 +226,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} ->
@@ -251,12 +258,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_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_io.erl b/lib/ssh/src/ssh_io.erl
index 026d0f6151..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.
@@ -27,60 +27,57 @@
-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 ->
+ Answer when is_list(Answer) ->
Answer
end.
-yes_no(Prompt, Ssh) ->
- io:format("~s [y/n]?", [Prompt]),
- proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question},
+yes_no(Prompt, Opts) ->
+ format("~s [y/n]?", [Prompt]),
+ ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question},
receive
- Answer ->
+ %% I can't see that the atoms y and n are ever received, but it must
+ %% be investigated before removing
+ y -> yes;
+ n -> no;
+
+ Answer when is_list(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)
+ format("please answer y or n\n",[]),
+ yes_no(Prompt, Opts)
end
end.
-
-read_password(Prompt, Ssh) ->
+read_password(Prompt, Opts) ->
format("~s", [listify(Prompt)]),
- 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,
+ ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), user_password},
receive
- Answer ->
- case Answer of
- "" ->
- read_password(Prompt, Ssh);
- Pass -> Pass
- end
+ Answer when is_list(Answer) ->
+ case trim(Answer) of
+ "" ->
+ read_password(Prompt, Opts);
+ Pwd ->
+ Pwd
+ end
end.
-listify(A) when is_atom(A) ->
- atom_to_list(A);
-listify(L) when is_list(L) ->
- L;
-listify(B) when is_binary(B) ->
- binary_to_list(B).
format(Fmt, Args) ->
io:format(Fmt, Args).
+%%%================================================================
+listify(A) when is_atom(A) -> atom_to_list(A);
+listify(L) when is_list(L) -> L;
+listify(B) when is_binary(B) -> binary_to_list(B).
+
trim(Line) when is_list(Line) ->
lists:reverse(trim1(lists:reverse(trim1(Line))));
@@ -93,6 +90,3 @@ trim1([$\r|Cs]) -> trim(Cs);
trim1([$\n|Cs]) -> trim(Cs);
trim1([$\t|Cs]) -> trim(Cs);
trim1(Cs) -> Cs.
-
-
-
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 562f040477..b1fc05ae33 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.
@@ -215,6 +215,16 @@ encode(#ssh_msg_service_accept{
}) ->
<<?Ebyte(?SSH_MSG_SERVICE_ACCEPT), ?Estring_utf8(Service)>>;
+encode(#ssh_msg_ext_info{
+ nr_extensions = N,
+ data = Data
+ }) ->
+ lists:foldl(fun({ExtName,ExtVal}, Acc) ->
+ <<Acc/binary, ?Estring(ExtName), ?Estring(ExtVal)>>
+ end,
+ <<?Ebyte(?SSH_MSG_EXT_INFO), ?Euint32(N)>>,
+ Data);
+
encode(#ssh_msg_newkeys{}) ->
<<?Ebyte(?SSH_MSG_NEWKEYS)>>;
@@ -435,6 +445,18 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) ->
num_responses = Num,
data = Data};
+decode(<<?BYTE(?SSH_MSG_EXT_INFO), ?UINT32(N), BinData/binary>>) ->
+ Data = bin_foldr(
+ fun(Bin,Acc) when length(Acc) == N ->
+ {Bin,Acc};
+ (<<?DEC_BIN(V0,__0), ?DEC_BIN(V1,__1), Rest/binary>>, 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(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) ->
decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10);
@@ -537,17 +559,28 @@ decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?DEC_BIN(Msg,__0), ?DEC_BIN(Lang,__
%%% Helper functions
%%%
+bin_foldr(Fun, Acc, Bin) ->
+ 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(<<?DEC_BIN(Prompt,__0), ?BYTE(Bool), Bin/binary>>,
Acc) ->
decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]).
+%%%----------------------------------------------------------------
erl_boolean(0) ->
false;
erl_boolean(1) ->
true.
+%%%----------------------------------------------------------------
decode_kex_init(<<?BYTE(Bool), ?UINT32(X)>>, Acc, 0) ->
list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc]));
decode_kex_init(<<?BYTE(Bool)>>, Acc, 0) ->
@@ -565,15 +598,16 @@ decode_kex_init(<<?DEC_BIN(Data,__0), Rest/binary>>, Acc, N) ->
%%% Signature decode/encode
%%%
-decode_signature(<<?DEC_BIN(_Alg,__0), ?UINT32(_), Signature/binary>>) ->
- Signature.
+decode_signature(<<?DEC_BIN(Alg,__0), ?UINT32(_), Signature/binary>>) ->
+ {binary_to_list(Alg), Signature}.
-encode_signature(#'RSAPublicKey'{}, Signature) ->
- <<?Ebinary(<<"ssh-rsa">>), ?Ebinary(Signature)>>;
-encode_signature({_, #'Dss-Parms'{}}, Signature) ->
+encode_signature({#'RSAPublicKey'{},Sign}, Signature) ->
+ SignName = list_to_binary(atom_to_list(Sign)),
+ <<?Ebinary(SignName), ?Ebinary(Signature)>>;
+encode_signature({{_, #'Dss-Parms'{}},_}, Signature) ->
<<?Ebinary(<<"ssh-dss">>), ?Ebinary(Signature)>>;
-encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) ->
+encode_signature({{#'ECPoint'{}, {namedCurve,OID}},_}, Signature) ->
CurveName = public_key:oid2ssh_curvename(OID),
<<?Ebinary(<<"ecdsa-sha2-",CurveName/binary>>), ?Ebinary(Signature)>>.
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
new file mode 100644
index 0000000000..7eeed70739
--- /dev/null
+++ b/lib/ssh/src/ssh_options.erl
@@ -0,0 +1,904 @@
+%%
+%% %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,
+ delete_key/5,
+ handle_options/2
+ ]).
+
+-export_type([options/0
+ ]).
+
+%%%================================================================
+%%% Types
+
+-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 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) ->
+ error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}).
+
+
+-spec get_value(option_class(), option_key(), options(), fun(() -> any()),
+ atom(), non_neg_integer()) -> any() | no_return().
+
+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} -> DefFun()
+ end;
+get_value(Class, Key, Opts, _DefFun, _CallerMod, _CallerLine) ->
+ 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].
+
+%%%================================================================
+%%%
+%%% 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
+%%%
+
+-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);
+
+%% 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_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 => 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}) ->
+ 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
+ },
+
+%%%%% 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=<I2;
+ (V) ->
+ 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
+ },
+
+ {idle_time, def} =>
+ #{default => infinity,
+ chk => fun check_timeout/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
+ },
+
+ {auth_methods, def} =>
+ #{default => ?SUPPORTED_AUTH_METHODS,
+ 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
+ },
+
+%%%%% 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
+ },
+
+ {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
+ }
+ }.
+
+
+%%%================================================================
+%%%================================================================
+%%%================================================================
+
+%%%
+%%% 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 ->
+ 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,
+ 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({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) ->
+ [error_in_check(K,"Bad preferred_algorithms key")
+ || {K,_} <- Algs,
+ not lists:keymember(K,1,ssh:default_algorithms())],
+
+ 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)
+ 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")
+ 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 afc2fb88ff..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.
@@ -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,20 +98,16 @@ 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(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
- {ok,Cm} ->
- case start_channel(Cm, Options) of
+ start_channel(Host, []).
+
+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, UserOptions) of
{ok, Pid} ->
{ok, Pid, Cm};
Error ->
@@ -120,17 +116,17 @@ 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,
+ {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;
@@ -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 ->
@@ -165,7 +163,7 @@ start_channel(Host, Port, Opts) ->
{error, ignore}
end;
Error ->
- Error
+ Error
end.
stop_channel(Pid) ->
@@ -174,12 +172,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 +207,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 +243,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 +256,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,16 +290,16 @@ 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
+%% 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).
%% 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).
@@ -313,12 +311,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).
@@ -367,7 +365,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 +409,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 +480,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 +508,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 +517,7 @@ init([Cm, ChannelId, Options]) ->
Error ->
{stop, {shutdown, Error}}
end.
-
+
%%--------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
@@ -541,7 +539,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) ->
@@ -555,13 +553,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),
@@ -636,7 +634,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 +775,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 +783,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, <<Data0/binary,Data/binary>>),
{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 +801,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 +821,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 +837,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};
@@ -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) ->
@@ -883,10 +884,10 @@ call(Pid, Msg, TimeOut) ->
handle_reply(State, <<?UINT32(Len),Reply:Len/binary,Rest/binary>>) ->
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,
<<?SSH_FXP_VERSION, ?UINT32(Version), BinExt/binary>>, Rest) ->
Ext = ssh_xfer:decode_ext(BinExt),
case Xf#ssh_xfer.vsn of
@@ -899,7 +900,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 +920,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 +999,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) ->
@@ -1062,25 +1063,16 @@ 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) ->
- 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.
@@ -1128,11 +1120,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 +1144,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 +1169,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 +1181,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);
_ ->
@@ -1229,7 +1218,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 +1228,7 @@ lseek_pos({eof, Offset}, _CurOffset, CurSize)
{ok, NewOffset}
end;
lseek_pos(_, _, _) ->
- {error, einval}.
+ {error, einval}.
%%%================================================================
%%%
@@ -1277,13 +1266,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 +1307,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 +1316,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 +1324,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 = <<PlainBuf0/binary, DecodedBin/binary>>,
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 +1338,11 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
%% We have (at least) one chunk of decodable bytes waiting for decodeing.
<<ToDecode:ChunkSize0/binary, EncBuf/binary>> = 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 = <<PlainBuf0/binary, DecodedBin/binary>>,
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 +1359,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 = <<PTB/binary,PlainBin/binary>>}) of
{ok, B} ->
call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout),
@@ -1379,7 +1368,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 +1410,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.
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index dca018f20f..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-2015. 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.
@@ -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
@@ -664,29 +640,25 @@ 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,
- XF = State0#state.xf,
- F = [binary | Flags],
- {IsDir, _FS1} = FileMod:is_dir(Path, FS0),
+ #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0,
+ 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 ->
- AbsPath = case Root of
- "" ->
- Path;
- _ ->
- relate_file_name(Path, State0)
- end,
- {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)),
@@ -742,6 +714,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 +725,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) ->
diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl
index 78f452df67..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.
@@ -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()) ->
diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl
index 637f5f398f..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.
@@ -26,7 +26,9 @@
-behaviour(supervisor).
--export([start_link/1,
+-include("ssh.hrl").
+
+-export([start_link/5,
connection_supervisor/1,
channel_supervisor/1
]).
@@ -37,8 +39,8 @@
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Opts) ->
- supervisor:start_link(?MODULE, [Opts]).
+start_link(Role, Address, Port, Profile, Options) ->
+ supervisor:start_link(?MODULE, [Role, Address, Port, Profile, Options]).
connection_supervisor(SupPid) ->
Children = supervisor:which_children(SupPid),
@@ -51,49 +53,40 @@ channel_supervisor(SupPid) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init([Opts]) ->
- RestartStrategy = one_for_all,
- MaxR = 0,
- MaxT = 3600,
- Children = child_specs(Opts),
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+init([Role, Address, Port, Profile, Options]) ->
+ SupFlags = #{strategy => one_for_all,
+ intensity => 0,
+ period => 3600
+ },
+ ChildSpecs = child_specs(Role, Address, Port, Profile, Options),
+ {ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-child_specs(Opts) ->
- case proplists:get_value(role, Opts) of
- client ->
- [];
- server ->
- [ssh_channel_child_spec(Opts), ssh_connectinon_child_spec(Opts)]
- 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(Opts) ->
- Address = proplists:get_value(address, Opts),
- Port = proplists:get_value(port, Opts),
- Role = proplists:get_value(role, Opts),
- Name = id(Role, ssh_connection_sup, Address, Port),
- StartFunc = {ssh_connection_sup, start_link, [Opts]},
- 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),
- Name = id(Role, ssh_channel_sup, Address, Port),
- StartFunc = {ssh_channel_sup, start_link, [Opts]},
- Restart = temporary,
- Shutdown = infinity,
- Modules = [ssh_channel_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+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]
+ }.
+
+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]
+ }.
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 8b57387589..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.
@@ -31,63 +31,20 @@
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init([]) ->
- SupFlags = {one_for_one, 10, 3600},
- Children = 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]},
- Restart = permanent,
- Shutdown = infinity,
- Modules = [sshd_sup],
- 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.
-
-
-
+init(_) ->
+ SupFlags = #{strategy => one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [#{id => Module,
+ start => {Module, start_link, []},
+ restart => permanent,
+ shutdown => 4000, %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 e97ac7b01a..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.
@@ -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.
%%----------------------------------------------------------------------
@@ -31,64 +31,103 @@
-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,
- 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(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(Address, Port, Profile, Options) ->
Name = make_name(Address, Port, Profile),
- supervisor:start_link({local, Name}, ?MODULE, [ServerOpts]).
+ 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, Options) ->
- Spec = ssh_subsystem_child_spec(Options),
- supervisor:start_child(SystemSup, Spec).
+
+start_subsystem(SystemSup, Role, Address, Port, Profile, Options) ->
+ 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
@@ -106,100 +145,21 @@ 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
-%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init([ServerOpts]) ->
- RestartStrategy = one_for_one,
- MaxR = 0,
- MaxT = 3600,
- Children = case proplists:get_value(asocket,ServerOpts) of
- undefined -> child_specs(ServerOpts);
- _ -> []
- end,
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
-
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-child_specs(ServerOpts) ->
- [ssh_acceptor_child_spec(ServerOpts)].
-
-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),
- Name = id(ssh_acceptor_sup, Address, Port, Profile),
- StartFunc = {ssh_acceptor_sup, start_link, [ServerOpts]},
- Restart = transient,
- Shutdown = infinity,
- Modules = [ssh_acceptor_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-ssh_subsystem_child_spec(ServerOpts) ->
- Name = make_ref(),
- StartFunc = {ssh_subsystem_sup, start_link, [ServerOpts]},
- Restart = temporary,
- Shutdown = infinity,
- Modules = [ssh_subsystem_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-
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", [fmt_host(Address), Port, Profile]))).
-ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) ->
- Child;
-ssh_subsystem_sup([_ | Rest]) ->
- ssh_subsystem_sup(Rest).
+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_acceptor_sup([{_, Child, _, [ssh_acceptor_sup]} | _]) ->
- Child;
-ssh_acceptor_sup([_ | Rest]) ->
- ssh_acceptor_sup(Rest).
-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.
+lookup(SupModule, SystemSup) ->
+ lists:keyfind([SupModule], 4,
+ supervisor:which_children(SystemSup)).
+
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 7cb3b75ac0..412f5de9de 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.
@@ -38,15 +38,18 @@
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,
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]).
+ valid_key_sha_alg/2,
+ sha/1, sign/3, verify/4]).
%%% For test suites
-export([pack/3]).
@@ -78,6 +81,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',
@@ -85,6 +92,7 @@ default_algorithms(cipher) ->
default_algorithms(mac) ->
supported_algorithms(mac, same(['AEAD_AES_128_GCM',
'AEAD_AES_256_GCM']));
+
default_algorithms(Alg) ->
supported_algorithms(Alg, []).
@@ -94,34 +102,41 @@ 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-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-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]},
- {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]},
{'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} ]}
+ {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]},
+ {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]},
+ {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1
]);
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}]},
+ [
+ {'[email protected]', [{ciphers,{aes_gcm,256}}]},
+ {'aes256-ctr', [{ciphers,{aes_ctr,256}}]},
+ {'aes192-ctr', [{ciphers,{aes_ctr,192}}]},
{'[email protected]', [{ciphers,{aes_gcm,128}}]},
- {'[email protected]', [{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}]}
]
));
@@ -143,14 +158,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} ->
@@ -161,7 +176,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;
@@ -171,7 +186,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"].
@@ -190,9 +205,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).
-
format_version({Major,Minor}, SoftwareVersion) ->
"SSH-" ++ integer_to_list(Major) ++ "." ++
integer_to_list(Minor) ++ "-" ++ SoftwareVersion.
@@ -222,14 +234,8 @@ 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,
- kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs).
+ PrefAlgs = ?GET_OPT(preferred_algorithms, Opts),
+ kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs, Opts).
key_init(client, Ssh, Value) ->
Ssh#ssh{c_keyinit = Value};
@@ -237,10 +243,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),
@@ -262,71 +269,92 @@ 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});
- _ ->
- %% TODO: Correct code?
- ssh_connection_handler:disconnect(
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- description = "Selection of key exchange algorithm failed"
- })
+ #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}};
- _ ->
- ssh_connection_handler:disconnect(
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- description = "Selection of key exchange algorithm failed"
- })
+ #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.
-%% 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;
-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}) ->
+ %% 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"}
+ end.
%%%----------------------------------------------------------------
%%%
%%% 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),
- {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} =
- 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
+ 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,
@@ -348,26 +376,31 @@ 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}}) ->
+ Ssh0 = #ssh{algorithms = #alg{kex=Kex,
+ hkey=SignAlg} = Algs}) ->
%% server
{G, P} = dh_group(Kex),
if
1=<E, E=<(P-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),
+ 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),
{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)}};
@@ -383,19 +416,20 @@ 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=<F, F=<(P-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),
- {ok, SshPacket, Ssh#ssh{shared_secret = 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{
@@ -425,14 +459,13 @@ 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
- {ok, {_Sz, {G,P}}} ->
- {Public, Private} = generate_key(dh, [P,G]),
+ ?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),
{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,_} ->
ssh_connection_handler:disconnect(
@@ -449,7 +482,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".
@@ -460,14 +493,13 @@ 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
- {ok, {_Sz, {G,P}}} ->
- {Public, Private} = generate_key(dh, [P,G]),
+ ?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),
{ok, SshPacket,
- Ssh#ssh{keyex_key = {{Private, Public}, {G, P}},
- keyex_info = {-1, -1, NBits} % flag for kex_h hash calc
+ Ssh#ssh{keyex_key = {x, {G, P}},
+ keyex_info = {-1, -1, NBits} % flag for kex_hash calc
}};
{error,_} ->
ssh_connection_handler:disconnect(
@@ -486,28 +518,25 @@ 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.
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)
@@ -516,23 +545,24 @@ 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=<E, E=<(P-1) ->
K = compute_key(dh, E, Private, [P,G]),
if
1<K, K<(P-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 = K,
+ {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
exchanged_hash = H,
session_id = sid(Ssh, H)
}};
@@ -555,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
@@ -563,14 +595,13 @@ 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<K, K<(P-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),
- {ok, SshPacket, Ssh#ssh{shared_secret = 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{
@@ -600,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),
@@ -608,17 +640,17 @@ 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),
{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
@@ -633,20 +665,21 @@ 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),
- {ok, SshPacket, Ssh#ssh{shared_secret = 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{
@@ -666,7 +699,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
@@ -677,34 +710,63 @@ 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,
+ 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 = [{"[email protected]", "Testing,PleaseIgnore"}]
+ },
+ {SshPacket, Ssh} = ssh_packet(Msg, Ssh0),
+ {ok, SshPacket, Ssh};
+ _ ->
+ {ok, "", Ssh0}
+ end;
+
+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))),
+ 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;
-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
%%
-get_host_key(SSH) ->
- #ssh{key_cb = Mod, opts = Opts, algorithms = ALG} = SSH,
-
- case Mod:host_key(ALG#alg.hkey, Opts) of
- {ok, #'RSAPrivateKey'{} = Key} -> Key;
- {ok, #'DSAPrivateKey'{} = Key} -> Key;
- {ok, #'ECPrivateKey'{} = Key} -> Key;
- Result ->
- exit({error, {Result, unsupported_key_type}})
+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}})
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}) ->
@@ -714,45 +776,71 @@ 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
- 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.
-host_key_sha(#'RSAPublicKey'{}) -> sha;
-host_key_sha({_, #'Dss-Parms'{}}) -> sha;
-host_key_sha({#'ECPoint'{},{namedCurve,OID}}) -> sha(OID).
+accepted_host(Ssh, PeerName, Public, Opts) ->
+ case ?GET_OPT(silently_accept_hosts, Opts) of
+
+ %% 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)))
+
+ end.
-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)).
+yes_no(Ssh, Prompt) ->
+ (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts).
-accepted_host(Ssh, PeerName, Opts) ->
- case proplists:get_value(silently_accept_hosts, Opts, false) of
- true ->
- yes;
- false ->
- yes_no(Ssh, "New host " ++ PeerName ++ " accept")
- end.
-known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh,
+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) ->
- PeerName = peer_name(Peer),
- 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, Opts) of
- yes ->
- Mod:add_host_key(PeerName, Public, Opts);
- no ->
+ case accepted_host(Ssh, PeerName, Public, Opts) of
+ true ->
+ KeyCb:add_host_key(PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]);
+ false ->
{error, rejected}
end
end.
@@ -764,7 +852,7 @@ known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh,
%%
%% 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),
@@ -789,17 +877,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
@@ -880,45 +985,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
@@ -993,7 +1119,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.
@@ -1082,29 +1208,37 @@ payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) ->
<<Payload:PayloadLen/binary, _/binary>> = 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),
<<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>;
-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),
<<?Empint(R),?Empint(S)>>;
-sign(SigData, Hash, Key) ->
- public_key:sign(SigData, Hash, Key).
-
-verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) ->
- <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> = 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) ->
- <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8,
- ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> = 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).
+sign(SigData, HashAlg, Key) ->
+ public_key:sign(SigData, HashAlg, Key).
+
+verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) ->
+ case Sig of
+ <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> ->
+ 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) ->
+ case Sig of
+ <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8,
+ ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> ->
+ 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).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1113,6 +1247,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) ->
@@ -1493,11 +1672,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;
@@ -1516,10 +1695,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 ->
@@ -1545,99 +1724,116 @@ mac('hmac-sha2-256', Key, SeqNum, Data) ->
mac('hmac-sha2-512', Key, SeqNum, Data) ->
crypto:hmac(sha512, Key, [<<?UINT32(SeqNum)>>, 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-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) ->
+%%%----------------------------------------------------------------
+%% return N hash bytes (HASH)
+hash(_SSH, _Char, 0) ->
<<>>;
-hash(SSH, Char, N, HASH) ->
- 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,
- <<Key:Sz/binary, _/binary>> = hash(K, H, K1, N-128, HASH),
+ <<Key:Sz/binary, _/binary>> = 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, <<Ki/binary, Kj/binary>>, N-128, HASH).
-
-kex_h(SSH, Key, E, F, K) ->
- KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
- L = <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(E), ?Empint(F), ?Empint(K)>>,
- 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 = <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(Q_c), ?Empint(Q_s), ?Empint(K)>>,
- 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?
- <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(E), ?Empint(F), ?Empint(K)>>;
- true ->
- <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Euint32(Min), ?Euint32(NBits), ?Euint32(Max),
- ?Empint(Prime), ?Empint(Gen), ?Empint(E), ?Empint(F), ?Empint(K)>>
- end,
- crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L).
-
+hash(K, H, Ki, N, HashAlg) ->
+ Kj = crypto:hash(HashAlg, [K, H, Ki]),
+ hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HashAlg).
+
+%%%----------------------------------------------------------------
+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),
+ <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
+ ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit),
+ ?Ebinary(EncodedKey),
+ (kex_alg_dependent(Args))/binary>>.
+
+kex_alg_dependent({E, F, K}) ->
+ %% diffie-hellman and ec diffie-hellman (with E = Q_c, F = Q_s)
+ <<?Empint(E), ?Empint(F), ?Empint(K)>>;
+
+kex_alg_dependent({-1, _, -1, _, _, E, F, K}) ->
+ %% ssh_msg_kex_dh_gex_request_old
+ <<?Empint(E), ?Empint(F), ?Empint(K)>>;
+
+kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) ->
+ %% diffie-hellman group exchange
+ <<?Euint32(Min), ?Euint32(NBits), ?Euint32(Max),
+ ?Empint(Prime), ?Empint(Gen), ?Empint(E), ?Empint(F), ?Empint(K)>>.
+
+%%%----------------------------------------------------------------
+
+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);
+sha('ecdsa-sha2-nistp521') -> sha(secp521r1);
sha(secp256r1) -> sha256;
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);
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.
+sha(?'secp521r1') -> sha(secp521r1);
+sha('ecdh-sha2-nistp256') -> sha(secp256r1);
+sha('ecdh-sha2-nistp384') -> sha(secp384r1);
+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;
+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;
@@ -1649,9 +1845,6 @@ mac_digest_size('AEAD_AES_128_GCM') -> 16;
mac_digest_size('AEAD_AES_256_GCM') -> 16;
mac_digest_size(none) -> 0.
-peer_name({Host, _}) ->
- Host.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Diffie-Hellman utils
@@ -1659,9 +1852,19 @@ 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}},
+ 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)}.
@@ -1672,6 +1875,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.
@@ -1734,10 +1946,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/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
index f91cb1dd63..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.
@@ -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)
@@ -112,7 +127,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 +253,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).
diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl
index 15858f36e1..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.
@@ -27,23 +27,25 @@
-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]).
+-define(SSHC_SUP, ?MODULE).
+
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Args) ->
- supervisor:start_link({local, ?MODULE}, ?MODULE, [Args]).
+start_link() ->
+ 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.
@@ -51,22 +53,17 @@ stop_child(Client) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init(Args) ->
- RestartStrategy = simple_one_for_one,
- MaxR = 0,
- MaxT = 3600,
- {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec(Args)]}}.
-
-%%%=========================================================================
-%%% 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}.
+init(_) ->
+ 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 04d2df30f7..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.
@@ -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,90 +29,79 @@
-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
+]).
%% Supervisor callback
-export([init/1]).
+-define(SSHD_SUP, ?MODULE).
+
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Servers) ->
- supervisor:start_link({local, ?MODULE}, ?MODULE, [Servers]).
+start_link() ->
+ %% 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(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(Address, Port, Profile, Options) ->
case ssh_system_sup:system_supervisor(Address, Port, Profile) of
undefined ->
- Spec = child_spec(Address, Port, ServerOpts),
- 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;
+ %% Here we start listening on a new Host/Port/Profile
+ Spec = child_spec(Address, Port, Profile, Options),
+ supervisor:start_child(?SSHD_SUP, Spec);
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, ServerOpts)
+ 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
%%%=========================================================================
--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 = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- child_spec(Address, Port, ServerOpts)
- end,
- Children = lists:map(Fun, Servers),
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+init(_) ->
+ SupFlags = #{strategy => one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [
+ ],
+ {ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-child_spec(Address, Port, ServerOpts) ->
- Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
- Name = id(Address, Port,Profile),
- StartFunc = {ssh_system_sup, start_link, [ServerOpts]},
- Restart = temporary,
- Shutdown = infinity,
- Modules = [ssh_system_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+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]
+ }.
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}
+ {server, ssh_system_sup, Address, Port, Profile}.
+
+system_name(SysSup) ->
+ case lists:keyfind(SysSup, 2, supervisor:which_children(?SSHD_SUP)) of
+ {Name, SysSup, _, _} -> Name;
+ false -> undefind
end.
-system_name([], _ ) ->
- undefined;
-system_name(SysSup, [{Name, SysSup, _, _} | _]) ->
- Name;
-system_name(SysSup, [_ | Rest]) ->
- system_name(SysSup, Rest).