aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh/src')
-rw-r--r--lib/ssh/src/Makefile104
-rw-r--r--lib/ssh/src/ssh.app.src11
-rw-r--r--lib/ssh/src/ssh.appup.src73
-rw-r--r--lib/ssh/src/ssh.erl495
-rw-r--r--lib/ssh/src/ssh.hrl48
-rw-r--r--lib/ssh/src/ssh_acceptor.erl79
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl50
-rw-r--r--lib/ssh/src/ssh_app.erl25
-rw-r--r--lib/ssh/src/ssh_auth.erl333
-rw-r--r--lib/ssh/src/ssh_auth.hrl24
-rw-r--r--lib/ssh/src/ssh_bits.erl166
-rw-r--r--lib/ssh/src/ssh_channel.erl26
-rw-r--r--lib/ssh/src/ssh_channel_sup.erl25
-rw-r--r--lib/ssh/src/ssh_cli.erl72
-rw-r--r--lib/ssh/src/ssh_client_key.erl21
-rw-r--r--lib/ssh/src/ssh_client_key_api.erl21
-rw-r--r--lib/ssh/src/ssh_connect.hrl37
-rw-r--r--lib/ssh/src/ssh_connection.erl376
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl2918
-rw-r--r--lib/ssh/src/ssh_connection_sup.erl23
-rw-r--r--lib/ssh/src/ssh_daemon_channel.erl21
-rw-r--r--lib/ssh/src/ssh_dbg.erl140
-rw-r--r--lib/ssh/src/ssh_file.erl115
-rw-r--r--lib/ssh/src/ssh_info.erl335
-rw-r--r--lib/ssh/src/ssh_io.erl21
-rw-r--r--lib/ssh/src/ssh_math.erl41
-rw-r--r--lib/ssh/src/ssh_message.erl373
-rw-r--r--lib/ssh/src/ssh_no_io.erl65
-rw-r--r--lib/ssh/src/ssh_server_key.erl21
-rw-r--r--lib/ssh/src/ssh_server_key_api.erl21
-rw-r--r--lib/ssh/src/ssh_sftp.erl80
-rw-r--r--lib/ssh/src/ssh_sftpd.erl163
-rw-r--r--lib/ssh/src/ssh_sftpd_file.erl21
-rw-r--r--lib/ssh/src/ssh_sftpd_file_api.erl25
-rw-r--r--lib/ssh/src/ssh_shell.erl21
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl21
-rw-r--r--lib/ssh/src/ssh_sup.erl21
-rw-r--r--lib/ssh/src/ssh_system_sup.erl92
-rw-r--r--lib/ssh/src/ssh_transport.erl1422
-rw-r--r--lib/ssh/src/ssh_transport.hrl124
-rw-r--r--lib/ssh/src/ssh_userauth.hrl21
-rw-r--r--lib/ssh/src/ssh_xfer.erl47
-rw-r--r--lib/ssh/src/ssh_xfer.hrl21
-rw-r--r--lib/ssh/src/sshc_sup.erl25
-rw-r--r--lib/ssh/src/sshd_sup.erl48
45 files changed, 4973 insertions, 3259 deletions
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index 90d71107ad..69d5a47f83 100644
--- a/lib/ssh/src/Makefile
+++ b/lib/ssh/src/Makefile
@@ -1,18 +1,19 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2004-2013. All Rights Reserved.
+# Copyright Ericsson AB 2004-2016. All Rights Reserved.
#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
+# 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
#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
+# 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%
#
@@ -53,6 +54,7 @@ MODULES= \
ssh_connection_sup \
ssh_connection \
ssh_connection_handler \
+ ssh_dbg \
ssh_shell \
ssh_system_sup \
ssh_subsystem_sup \
@@ -66,7 +68,6 @@ MODULES= \
ssh_file \
ssh_io \
ssh_info \
- ssh_math \
ssh_message \
ssh_no_io \
ssh_sftp \
@@ -75,7 +76,7 @@ MODULES= \
ssh_transport \
ssh_xfer
-PUBLIC_HRL_FILES= ssh.hrl ssh_userauth.hrl ssh_xfer.hrl
+HRL_FILES =
ERL_FILES= \
$(MODULES:%=%.erl) \
@@ -95,7 +96,7 @@ APP_TARGET= $(EBIN)/$(APP_FILE)
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
-INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl
+INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl
# ----------------------------------------------------
# FLAGS
@@ -140,7 +141,82 @@ release_spec: opt
$(INSTALL_DATA) $(BEHAVIOUR_TARGET_FILES) $(TARGET_FILES) $(APP_TARGET) \
$(APPUP_TARGET) "$(RELSYSDIR)/ebin"
$(INSTALL_DIR) "$(RELSYSDIR)/include"
- $(INSTALL_DATA) $(PUBLIC_HRL_FILES) "$(RELSYSDIR)/include"
+
release_docs_spec:
+
+deps:
+ erlc -M $(ERL_FILES) \
+ | sed 's@$(ERL_TOP)/lib@../..@g' \
+ | sed 's/\.$(EMULATOR)/\.$$\(EMULATOR\)/' \
+ | sed 's@^ssh_@$$(EBIN)/ssh_@'
+
+ssh.$(EMULATOR): ssh.erl ssh.hrl ssh_connect.hrl \
+ ../../public_key/include/public_key.hrl \
+ ../../public_key/include/OTP-PUB-KEY.hrl \
+ ../../public_key/include/PKCS-FRAME.hrl \
+ ../../kernel/include/file.hrl
+$(EBIN)/ssh_sup.$(EMULATOR): ssh_sup.erl
+sshc_sup.$(EMULATOR): sshc_sup.erl
+sshd_sup.$(EMULATOR): sshd_sup.erl ssh.hrl
+$(EBIN)/ssh_connection_sup.$(EMULATOR): ssh_connection_sup.erl
+$(EBIN)/ssh_connection.$(EMULATOR): ssh_connection.erl ssh.hrl ssh_connect.hrl \
+ ssh_transport.hrl
+$(EBIN)/ssh_connection_handler.$(EMULATOR): ssh_connection_handler.erl ssh.hrl \
+ ssh_transport.hrl ssh_auth.hrl ssh_connect.hrl
+$(EBIN)/ssh_shell.$(EMULATOR): ssh_shell.erl ssh_connect.hrl
+$(EBIN)/ssh_system_sup.$(EMULATOR): ssh_system_sup.erl ssh.hrl
+$(EBIN)/ssh_subsystem_sup.$(EMULATOR): ssh_subsystem_sup.erl
+$(EBIN)/ssh_channel_sup.$(EMULATOR): ssh_channel_sup.erl
+$(EBIN)/ssh_acceptor_sup.$(EMULATOR): ssh_acceptor_sup.erl ssh.hrl
+$(EBIN)/ssh_acceptor.$(EMULATOR): ssh_acceptor.erl ssh.hrl
+$(EBIN)/ssh_app.$(EMULATOR): ssh_app.erl
+$(EBIN)/ssh_auth.$(EMULATOR): ssh_auth.erl \
+ ../../public_key/include/public_key.hrl \
+ ../../public_key/include/OTP-PUB-KEY.hrl \
+ ../../public_key/include/PKCS-FRAME.hrl \
+ ssh.hrl ssh_auth.hrl ssh_transport.hrl
+$(EBIN)/ssh_bits.$(EMULATOR): ssh_bits.erl ssh.hrl
+$(EBIN)/ssh_cli.$(EMULATOR): ssh_cli.erl ssh.hrl ssh_connect.hrl
+$(EBIN)/ssh_file.$(EMULATOR): ssh_file.erl \
+ ../../public_key/include/public_key.hrl \
+ ../../public_key/include/OTP-PUB-KEY.hrl \
+ ../../public_key/include/PKCS-FRAME.hrl \
+ ../../kernel/include/file.hrl ssh.hrl
+$(EBIN)/ssh_io.$(EMULATOR): ssh_io.erl ssh.hrl
+$(EBIN)/ssh_info.$(EMULATOR): ssh_info.erl
+$(EBIN)/ssh_message.$(EMULATOR): ssh_message.erl \
+ ../../public_key/include/public_key.hrl \
+ ../../public_key/include/OTP-PUB-KEY.hrl \
+ ../../public_key/include/PKCS-FRAME.hrl \
+ ssh.hrl ssh_connect.hrl ssh_auth.hrl ssh_transport.hrl
+$(EBIN)/ssh_no_io.$(EMULATOR): ssh_no_io.erl ssh_transport.hrl
+$(EBIN)/ssh_sftp.$(EMULATOR): ssh_sftp.erl \
+ ../../kernel/include/file.hrl ssh.hrl \
+ ssh_xfer.hrl
+$(EBIN)/ssh_sftpd.$(EMULATOR): ssh_sftpd.erl \
+ ../../kernel/include/file.hrl ssh.hrl \
+ ssh_xfer.hrl
+$(EBIN)/ssh_sftpd_file.$(EMULATOR): ssh_sftpd_file.erl
+$(EBIN)/ssh_transport.$(EMULATOR): ssh_transport.erl \
+ ../../public_key/include/public_key.hrl \
+ ../../public_key/include/OTP-PUB-KEY.hrl \
+ ../../public_key/include/PKCS-FRAME.hrl \
+ ../../kernel/include/inet.hrl \
+ ssh_transport.hrl ssh.hrl
+$(EBIN)/ssh_xfer.$(EMULATOR): ssh_xfer.erl ssh.hrl ssh_xfer.hrl
+$(EBIN)/ssh_sftpd_file_api.$(EMULATOR): ssh_sftpd_file_api.erl
+$(EBIN)/ssh_channel.$(EMULATOR): ssh_channel.erl ssh_connect.hrl
+$(EBIN)/ssh_daemon_channel.$(EMULATOR): ssh_daemon_channel.erl
+$(EBIN)/ssh_client_key_api.$(EMULATOR): ssh_client_key_api.erl \
+ ../../public_key/include/public_key.hrl \
+ ../../public_key/include/OTP-PUB-KEY.hrl \
+ ../../public_key/include/PKCS-FRAME.hrl \
+ ssh.hrl
+$(EBIN)/ssh_server_key_api.$(EMULATOR): ssh_server_key_api.erl \
+ ../../public_key/include/public_key.hrl \
+ ../../public_key/include/OTP-PUB-KEY.hrl \
+ ../../public_key/include/PKCS-FRAME.hrl \
+ ssh.hrl
+
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index bc01c539e0..3245ba5197 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -18,13 +18,13 @@
ssh_connection_handler,
ssh_connection_sup,
ssh_daemon_channel,
+ ssh_dbg,
ssh_shell,
sshc_sup,
sshd_sup,
ssh_file,
ssh_io,
ssh_info,
- ssh_math,
ssh_no_io,
ssh_server_key_api,
ssh_sftp,
@@ -40,7 +40,12 @@
{applications, [kernel, stdlib, crypto, public_key]},
{env, []},
{mod, {ssh_app, []}},
- {runtime_dependencies, ["stdlib-2.3","public_key-0.22","kernel-3.0",
- "erts-6.0","crypto-3.3"]}]}.
+ {runtime_dependencies, [
+ "crypto-3.3",
+ "erts-6.0",
+ "kernel-3.0",
+ "public_key-1.1",
+ "stdlib-3.0"
+ ]}]}.
diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src
index b2b2994eed..e38cecf226 100644
--- a/lib/ssh/src/ssh.appup.src
+++ b/lib/ssh/src/ssh.appup.src
@@ -1,79 +1,28 @@
%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
{"%VSN%",
[
- {"3.0.8", [{load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_xfer]},
- {load_module, ssh_connection_handler, soft_purge, soft_purge, []},
- {load_module, ssh, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_xfer, soft_purge, soft_purge, []}
- ]},
- {"3.0.7", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_connection_handler, soft_purge, soft_purge, []},
- {load_module, ssh_info, soft_purge, soft_purge, []},
- {load_module, ssh_message, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_io, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_xfer, soft_purge, soft_purge, [ssh_connection_handler]}]},
- {"3.0.6", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_connection_handler, soft_purge, soft_purge, []},
- {load_module, ssh_info, soft_purge, soft_purge, []},
- {load_module, ssh_message, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_io, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_xfer, soft_purge, soft_purge, [ssh_connection_handler]}]},
{<<".*">>, [{restart_application, ssh}]}
],
[
- {"3.0.8", [{load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_sftp, soft_purge, soft_purge, []},
- {load_module, ssh_connection_handler, soft_purge, soft_purge, []},
- {load_module, ssh, soft_purge, soft_purge, []},
- {load_module, ssh_xfer, soft_purge, soft_purge, []}
- ]},
- {"3.0.7", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_connection_handler, soft_purge, soft_purge, []},
- {load_module, ssh_info, soft_purge, soft_purge, []},
- {load_module, ssh_message, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_io, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_xfer, soft_purge, soft_purge, [ssh_connection_handler]}]},
- {"3.0.6", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_connection_handler, soft_purge, soft_purge, []},
- {load_module, ssh_info, soft_purge, soft_purge, []},
- {load_module, ssh_message, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_io, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_connection_handler]},
- {load_module, ssh_xfer, soft_purge, soft_purge, [ssh_connection_handler]}]},
{<<".*">>, [{restart_application, ssh}]}
]
}.
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index eae33e3683..09b07b7a2a 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -1,18 +1,19 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -24,12 +25,22 @@
-include("ssh.hrl").
-include("ssh_connect.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/file.hrl").
-export([start/0, start/1, stop/0, connect/3, connect/4, close/1, connection_info/2,
channel_info/3,
daemon/1, daemon/2, daemon/3,
- stop_listener/1, stop_listener/2, stop_daemon/1, stop_daemon/2,
- shell/1, shell/2, shell/3]).
+ daemon_info/1,
+ default_algorithms/0,
+ stop_listener/1, stop_listener/2, stop_listener/3,
+ stop_daemon/1, stop_daemon/2, stop_daemon/3,
+ shell/1, shell/2, shell/3
+ ]).
+
+%%% Type exports
+-export_type([connection_ref/0,
+ channel_id/0
+ ]).
%%--------------------------------------------------------------------
-spec start() -> ok | {error, term()}.
@@ -76,7 +87,7 @@ connect(Host, Port, Options, Timeout) ->
ConnectionTimeout = proplists:get_value(connect_timeout, Options, infinity),
try Transport:connect(Host, Port, [ {active, false} | SocketOptions], ConnectionTimeout) of
{ok, Socket} ->
- Opts = [{user_pid, self()}, {host, Host} | fix_idle_time(SshOptions)],
+ Opts = [{user_pid,self()}, {host,Host} | SshOptions],
ssh_connection_handler:start_connection(client, Socket, Opts, Timeout);
{error, Reason} ->
{error, Reason}
@@ -113,9 +124,9 @@ channel_info(ConnectionRef, ChannelId, Options) ->
ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options).
%%--------------------------------------------------------------------
--spec daemon(integer()) -> {ok, pid()}.
--spec daemon(integer(), proplists:proplist()) -> {ok, pid()}.
--spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()}.
+-spec daemon(integer()) -> {ok, pid()} | {error, term()}.
+-spec daemon(integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}.
+-spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}.
%% Description: Starts a server listening for SSH connections
%% on the given port.
@@ -148,6 +159,19 @@ daemon(HostAddr, Port, Options0) ->
start_daemon(Host, Port, Options, Inet).
%%--------------------------------------------------------------------
+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}]};
+
+ _ ->
+ {error,bad_daemon_ref}
+ end.
+
+%%--------------------------------------------------------------------
-spec stop_listener(pid()) -> ok.
-spec stop_listener(inet:ip_address(), integer()) -> ok.
%%
@@ -157,7 +181,9 @@ daemon(HostAddr, Port, Options0) ->
stop_listener(SysSup) ->
ssh_system_sup:stop_listener(SysSup).
stop_listener(Address, Port) ->
- ssh_system_sup:stop_listener(Address, Port).
+ stop_listener(Address, Port, ?DEFAULT_PROFILE).
+stop_listener(Address, Port, Profile) ->
+ ssh_system_sup:stop_listener(Address, Port, Profile).
%%--------------------------------------------------------------------
-spec stop_daemon(pid()) -> ok.
@@ -169,8 +195,9 @@ stop_listener(Address, Port) ->
stop_daemon(SysSup) ->
ssh_system_sup:stop_system(SysSup).
stop_daemon(Address, Port) ->
- ssh_system_sup:stop_system(Address, Port).
-
+ ssh_system_sup:stop_system(Address, Port, ?DEFAULT_PROFILE).
+stop_daemon(Address, Port, Profile) ->
+ ssh_system_sup:stop_system(Address, Port, Profile).
%%--------------------------------------------------------------------
-spec shell(string()) -> _.
-spec shell(string(), proplists:proplist()) -> _.
@@ -208,36 +235,72 @@ shell(Host, Port, Options) ->
end.
%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+default_algorithms() ->
+ ssh_transport:default_algorithms().
+
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-fix_idle_time(SshOptions) ->
- case proplists:get_value(idle_time, SshOptions) of
- undefined ->
- [{idle_time, infinity}|SshOptions];
- _ ->
- SshOptions
- end.
start_daemon(Host, Port, Options, Inet) ->
case handle_options(Options) of
{error, _Reason} = Error ->
Error;
{SocketOptions, SshOptions}->
- do_start_daemon(Host, Port,[{role, server} |SshOptions] , [Inet | SocketOptions])
+ 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(Host, Port, Options, SocketOptions) ->
- case ssh_system_sup:system_supervisor(Host, Port) of
+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, Opts} =
+ 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,
+ 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([{address, Host},
- {port, Port}, {role, server},
+ {port, Port},
+ {role, server},
{socket_opts, SocketOptions},
- {ssh_opts, Options}]) of
+ {ssh_opts, SshOptions}
+ | Opts]) of
{error, {already_started, _}} ->
{error, eaddrinuse};
- Result = {Code, _} when (Code == ok) or (Code == error) ->
+ Result = {ok,_} ->
+ sync_request_control(WaitRequestControl),
+ Result;
+ Result = {error, _} ->
Result
catch
exit:{noproc, _} ->
@@ -246,20 +309,42 @@ do_start_daemon(Host, Port, Options, SocketOptions) ->
Sup ->
AccPid = ssh_system_sup:acceptor_supervisor(Sup),
case ssh_acceptor_sup:start_child(AccPid, [{address, Host},
- {port, Port}, {role, server},
+ {port, Port},
+ {role, server},
{socket_opts, SocketOptions},
- {ssh_opts, Options}]) of
+ {ssh_opts, SshOptions}
+ | Opts]) of
{error, {already_started, _}} ->
{error, eaddrinuse};
{ok, _} ->
+ sync_request_control(WaitRequestControl),
{ok, Sup};
Other ->
Other
end
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
+ 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(proplists:unfold(Opts), [], []) of
+ try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of
{Inet, Ssh} ->
{handle_ip(Inet), Ssh}
catch
@@ -267,6 +352,30 @@ handle_options(Opts) ->
Error
end.
+
+algs_compatibility(Os0) ->
+ %% Take care of old options 'public_key_alg' and 'pref_public_key_algs'
+ case proplists:get_value(public_key_alg, Os0) of
+ undefined ->
+ Os0;
+ A when is_atom(A) ->
+ %% Skip public_key_alg if pref_public_key_algs is defined:
+ Os = lists:keydelete(public_key_alg, 1, Os0),
+ case proplists:get_value(pref_public_key_algs,Os) of
+ undefined when A == 'ssh-rsa' ; A==ssh_rsa ->
+ [{pref_public_key_algs,['ssh-rsa','ssh-dss']} | Os];
+ undefined when A == 'ssh-dss' ; A==ssh_dsa ->
+ [{pref_public_key_algs,['ssh-dss','ssh-rsa']} | Os];
+ undefined ->
+ throw({error, {eoptions, {public_key_alg,A} }});
+ _ ->
+ Os
+ end;
+ V ->
+ throw({error, {eoptions, {public_key_alg,V} }})
+ end.
+
+
handle_option([], SocketOptions, SshOptions) ->
{SocketOptions, SshOptions};
handle_option([{system_dir, _} = Opt | Rest], SocketOptions, SshOptions) ->
@@ -279,8 +388,6 @@ handle_option([{silently_accept_hosts, _} = Opt | Rest], SocketOptions, SshOptio
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([{public_key_alg, _} = 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) ->
@@ -295,11 +402,13 @@ 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, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{role, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{compression, _} = Opt | Rest], SocketOptions, 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) ->
@@ -310,8 +419,12 @@ 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]);
@@ -329,8 +442,16 @@ 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) ->
@@ -339,35 +460,107 @@ 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]);
-handle_option([parallel_login|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | 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([Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions).
-handle_ssh_option({system_dir, Value} = Opt) when is_list(Value) ->
+
+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) ->
- Opt;
+ 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({public_key_alg, ssh_dsa}) ->
- {public_key_alg, 'ssh-dss'};
-handle_ssh_option({public_key_alg, ssh_rsa}) ->
- {public_key_alg, 'ssh-rsa'};
-handle_ssh_option({public_key_alg, Value} = Opt) when Value == 'ssh-rsa'; Value == 'ssh-dss' ->
+handle_ssh_option({preferred_algorithms,[_|_]} = Opt) ->
+ handle_pref_algs(Opt);
+
+handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) ->
+ {dh_gex_groups,
+ collect_per_size(
+ lists:foldl(
+ fun({N,G,P}, Acc) when is_integer(N),N>0,
+ is_integer(G),G>0,
+ is_integer(P),P>0 ->
+ [{N,{G,P}} | Acc];
+ ({N,{G,P}}, Acc) when is_integer(N),N>0,
+ is_integer(G),G>0,
+ is_integer(P),P>0 ->
+ [{N,{G,P}} | Acc];
+ ({N,GPs}, Acc) when is_list(GPs) ->
+ lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0,
+ is_integer(Pi),Pi>0 ->
+ [{N,{Gi,Pi}} | Acci]
+ end, Acc, GPs)
+ end, [], L0))};
+
+handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0,
+ Tag == file ;
+ Tag == ssh_moduli_file ->
+ {ok,GroupDefs} =
+ case Tag of
+ file ->
+ file:consult(File);
+ ssh_moduli_file ->
+ case file:open(File,[read]) of
+ {ok,D} ->
+ try
+ {ok,Moduli} = read_moduli_file(D, 1, []),
+ file:close(D),
+ {ok, Moduli}
+ catch
+ _:_ ->
+ throw({error, {{eoptions, Opt}, "Bad format in file "++File}})
+ end;
+ {error,enoent} ->
+ throw({error, {{eoptions, Opt}, "File not found:"++File}});
+ {error,Error} ->
+ throw({error, {{eoptions, Opt}, io_lib:format("Error reading file ~s: ~p",[File,Error])}})
+ end
+ end,
+
+ try
+ handle_ssh_option({dh_gex_groups,GroupDefs})
+ catch
+ _:_ ->
+ throw({error, {{eoptions, Opt}, "Bad format in file: "++File}})
+ end;
+
+
+handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0,
+ is_integer(Max), Max>=Min ->
+ %% Server
+ Opt;
+handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0,
+ is_integer(I), I>=Min,
+ is_integer(Max), Max>=I ->
+ %% Client
Opt;
handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 ->
- case handle_pref_algs(Value, []) of
+ case handle_user_pref_pubkey_algs(Value, []) of
{true, NewOpts} ->
- NewOpts;
+ {pref_public_key_algs, NewOpts};
_ ->
throw({error, {eoptions, Opt}})
end;
@@ -375,6 +568,8 @@ handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value
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 ->
@@ -389,10 +584,17 @@ 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) ->
+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),
@@ -402,14 +604,25 @@ 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) ->
+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}});
@@ -434,9 +647,21 @@ 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"
@@ -450,23 +675,83 @@ handle_inet_option({reuseaddr, _} = Opt) ->
%% Option verified by inet
handle_inet_option(Opt) ->
Opt.
+
+
%% Check preferred algs
-handle_pref_algs([], Acc) ->
- {true, lists:reverse(Acc)};
-handle_pref_algs([H|T], Acc) ->
- case H of
- ssh_dsa ->
- handle_pref_algs(T, ['ssh-dss'| Acc]);
- ssh_rsa ->
- handle_pref_algs(T, ['ssh-rsa'| Acc]);
- 'ssh-dss' ->
- handle_pref_algs(T, ['ssh-dss'| Acc]);
- 'ssh-rsa' ->
- handle_pref_algs(T, ['ssh-rsa'| Acc]);
- _ ->
- false
+
+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 ->
@@ -479,4 +764,74 @@ handle_ip(Inet) -> %% Default to ipv4
[inet | Inet]
end
end.
-
+
+check_dir({_,Dir} = Opt) ->
+ case directory_exist_readable(Dir) of
+ ok ->
+ Opt;
+ {error,Error} ->
+ throw({error, {eoptions,{Opt,Error}}})
+ end.
+
+directory_exist_readable(Dir) ->
+ case file:read_file_info(Dir) of
+ {ok, #file_info{type = directory,
+ access = Access}} ->
+ case Access of
+ read -> ok;
+ read_write -> ok;
+ _ -> {error, eacces}
+ end;
+
+ {ok, #file_info{}}->
+ {error, enotdir};
+
+ {error, Error} ->
+ {error, Error}
+ end.
+
+
+
+collect_per_size(L) ->
+ lists:foldr(
+ fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc];
+ ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc]
+ end, [], lists:sort(L)).
+
+read_moduli_file(D, I, Acc) ->
+ case io:get_line(D,"") of
+ {error,Error} ->
+ {error,Error};
+ eof ->
+ {ok, Acc};
+ "#" ++ _ -> read_moduli_file(D, I+1, Acc);
+ <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc);
+ Data ->
+ Line = if is_binary(Data) -> binary_to_list(Data);
+ is_list(Data) -> Data
+ end,
+ try
+ [_Time,_Type,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"),
+ M = {list_to_integer(Size),
+ {list_to_integer(G), list_to_integer(P,16)}
+ },
+ read_moduli_file(D, I+1, [M|Acc])
+ catch
+ _:_ ->
+ read_moduli_file(D, I+1, Acc)
+ end
+ end.
+
+handle_user_pref_pubkey_algs([], Acc) ->
+ {true, lists:reverse(Acc)};
+handle_user_pref_pubkey_algs([H|T], Acc) ->
+ case lists:member(H, ?SUPPORTED_USER_KEYS) of
+ true ->
+ handle_user_pref_pubkey_algs(T, [H| Acc]);
+
+ false when H==ssh_dsa -> handle_user_pref_pubkey_algs(T, ['ssh-dss'| Acc]);
+ false when H==ssh_rsa -> handle_user_pref_pubkey_algs(T, ['ssh-rsa'| Acc]);
+
+ false ->
+ false
+ end.
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 0c4d34f89c..868f3a9181 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -28,20 +29,26 @@
-define(SSH_DEFAULT_PORT, 22).
-define(SSH_MAX_PACKET_SIZE, (256*1024)).
--define(SSH_LENGHT_INDICATOR_SIZE, 4).
-define(REKEY_TIMOUT, 3600000).
-define(REKEY_DATA_TIMOUT, 60000).
+-define(DEFAULT_PROFILE, default).
+
+-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).
%% basic binary constructors
--define(BOOLEAN(X), X:8/unsigned-big-integer).
--define(BYTE(X), X:8/unsigned-big-integer).
--define(UINT16(X), X:16/unsigned-big-integer).
--define(UINT32(X), X:32/unsigned-big-integer).
--define(UINT64(X), X:64/unsigned-big-integer).
+-define(BOOLEAN(X), (X):8/unsigned-big-integer).
+-define(BYTE(X), (X):8/unsigned-big-integer).
+-define(UINT16(X), (X):16/unsigned-big-integer).
+-define(UINT32(X), (X):32/unsigned-big-integer).
+-define(UINT64(X), (X):64/unsigned-big-integer).
-define(STRING(X), ?UINT32((size(X))), (X)/binary).
+-define(DEC_BIN(X,Len), ?UINT32(Len), X:Len/binary ).
+-define(DEC_MPINT(I,Len), ?UINT32(Len), I:Len/big-signed-integer-unit:8 ).
+
%% building macros
-define(boolean(X),
case X of
@@ -63,8 +70,6 @@
-record(ssh,
{
- %%state, %% what it's waiting for
-
role, %% client | server
peer, %% string version of peer address
@@ -122,15 +127,18 @@
recv_sequence = 0,
keyex_key,
keyex_info,
+ random_length_padding = 255, % From RFC 4253 section 6.
%% User auth
user,
service,
userauth_quiet_mode, % boolean()
- userauth_supported_methods , %
- userauth_methods,
+ userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"]
+ userauth_supported_methods, % string() eg "keyboard-interactive,password"
+ kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive"
userauth_preference,
available_host_keys,
+ pwdfun_user_state,
authenticated = false
}).
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index 6c443eeb9c..90fd951dcd 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -21,9 +22,12 @@
-module(ssh_acceptor).
+-include("ssh.hrl").
+
%% Internal application API
-export([start_link/5,
- number_of_connections/1]).
+ number_of_connections/1,
+ callback_listen/3]).
%% spawn export
-export([acceptor_init/6, acceptor_loop/6]).
@@ -43,17 +47,50 @@ start_link(Port, Address, SockOpts, Opts, AcceptTimeout) ->
acceptor_init(Parent, Port, Address, SockOpts, Opts, AcceptTimeout) ->
{_, Callback, _} =
proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}),
- case (catch do_socket_listen(Callback, Port, SockOpts)) of
- {ok, ListenSocket} ->
+
+ SockOwner = proplists:get_value(lsock_owner, Opts),
+ LSock = proplists:get_value(lsocket, Opts),
+ UseExistingSocket =
+ case catch inet:sockname(LSock) of
+ {ok,{_,Port}} -> is_pid(SockOwner);
+ _ -> false
+ end,
+
+ case UseExistingSocket of
+ true ->
proc_lib:init_ack(Parent, {ok, self()}),
- acceptor_loop(Callback,
- Port, Address, Opts, ListenSocket, AcceptTimeout);
- Error ->
- proc_lib:init_ack(Parent, Error),
- error
+ 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
end.
+
+request_ownership(LSock, SockOwner) ->
+ SockOwner ! {request_control,LSock,self()},
+ receive
+ {its_yours,LSock} -> ok
+ end.
+
-do_socket_listen(Callback, Port, Opts) ->
+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));
@@ -82,8 +119,10 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
end.
handle_connection(Callback, Address, Port, Options, Socket) ->
- SystemSup = ssh_system_sup:system_supervisor(Address, Port),
SSHopts = proplists:get_value(ssh_opts, Options, []),
+ Profile = proplists:get_value(profile, SSHopts, ?DEFAULT_PROFILE),
+ SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile),
+
MaxSessions = proplists:get_value(max_sessions,SSHopts,infinity),
case number_of_connections(SystemSup) < MaxSessions of
true ->
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index 46fdef07d0..4f76dbe6f0 100644
--- a/lib/ssh/src/ssh_acceptor_sup.erl
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -26,7 +27,9 @@
-module(ssh_acceptor_sup).
-behaviour(supervisor).
--export([start_link/1, start_child/2, stop_child/3]).
+-include("ssh.hrl").
+
+-export([start_link/1, start_child/2, stop_child/4]).
%% Supervisor callback
-export([init/1]).
@@ -45,14 +48,16 @@ start_child(AccSup, ServerOpts) ->
{error, already_present} ->
Address = proplists:get_value(address, ServerOpts),
Port = proplists:get_value(port, ServerOpts),
- stop_child(AccSup, Address, Port),
+ Profile = proplists:get_value(profile,
+ proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ stop_child(AccSup, Address, Port, Profile),
supervisor:start_child(AccSup, Spec);
Reply ->
Reply
end.
-stop_child(AccSup, Address, Port) ->
- Name = id(Address, Port),
+stop_child(AccSup, Address, Port, Profile) ->
+ Name = id(Address, Port, Profile),
case supervisor:terminate_child(AccSup, Name) of
ok ->
supervisor:delete_child(AccSup, Name);
@@ -77,18 +82,21 @@ child_spec(ServerOpts) ->
Address = proplists:get_value(address, ServerOpts),
Port = proplists:get_value(port, ServerOpts),
Timeout = proplists:get_value(timeout, ServerOpts, ?DEFAULT_TIMEOUT),
- Name = id(Address, Port),
+ 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,
- [{active, false},
- {reuseaddr, true}] ++ SocketOpts,
- ServerOpts, Timeout]},
+ 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}.
-id(Address, Port) ->
- {ssh_acceptor_sup, Address, Port}.
+id(Address, Port, Profile) ->
+ case is_list(Address) of
+ true ->
+ {ssh_acceptor_sup, any, Port, Profile};
+ false ->
+ {ssh_acceptor_sup, Address, Port, Profile}
+ end.
diff --git a/lib/ssh/src/ssh_app.erl b/lib/ssh/src/ssh_app.erl
index 38659b1a2d..d3680e20da 100644
--- a/lib/ssh/src/ssh_app.erl
+++ b/lib/ssh/src/ssh_app.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index 45c4d52d7e..49eec8072f 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -40,27 +41,29 @@ 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, Key} ->
- StrAlgo = algorithm_string(Alg),
- PubKeyBlob = encode_public_key(Key),
- SigData = build_sig_data(SessionId,
- User, Service, PubKeyBlob, StrAlgo),
- Sig = ssh_transport:sign(SigData, Hash, Key),
- 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);
+ {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
end.
@@ -115,38 +118,26 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
service = "ssh-connection",
method = "none",
data = <<>>},
- case proplists:get_value(pref_public_key_algs, Opts, false) of
- false ->
- FirstAlg = proplists:get_value(public_key_alg, Opts, ?PREFERRED_PK_ALG),
- SecondAlg = other_alg(FirstAlg),
- Prefs = method_preference(FirstAlg, SecondAlg),
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
- userauth_preference = Prefs,
- userauth_methods = none,
- service = "ssh-connection"});
- Algs ->
- FirstAlg = lists:nth(1, Algs),
- case length(Algs) =:= 2 of
- true ->
- SecondAlg = other_alg(FirstAlg),
- Prefs = method_preference(FirstAlg, SecondAlg),
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
- userauth_preference = Prefs,
- userauth_methods = none,
- service = "ssh-connection"});
- _ ->
- Prefs = method_preference(FirstAlg),
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
- userauth_preference = Prefs,
- userauth_methods = none,
- service = "ssh-connection"})
- end
- end;
+ 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} ->
ErrStr = "Could not determine the users name",
- throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME,
- description = ErrStr,
- language = "en"})
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME,
+ description = ErrStr})
end.
userauth_request_msg(#ssh{userauth_preference = []} = Ssh) ->
@@ -168,7 +159,7 @@ userauth_request_msg(#ssh{userauth_methods = Methods,
not_ok ->
userauth_request_msg(Ssh);
Result ->
- Result
+ {Pref,Result}
end;
false ->
userauth_request_msg(Ssh)
@@ -185,17 +176,18 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "password",
data = <<?FALSE, ?UINT32(Sz), BinPwd:Sz/binary>>}, _,
- #ssh{opts = Opts} = Ssh) ->
+ #ssh{opts = Opts,
+ userauth_supported_methods = Methods} = Ssh) ->
Password = unicode:characters_to_list(BinPwd),
- case check_password(User, Password, Opts) of
- true ->
+ case check_password(User, Password, Opts, Ssh) of
+ {true,Ssh1} ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
- false ->
+ 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{
- authentications = "",
- partial_success = false}, Ssh)}
+ authentications = Methods,
+ partial_success = false}, Ssh1)}
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -207,7 +199,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
%% ?UINT32(Sz2), NewBinPwd:Sz2/binary
>>
}, _,
- Ssh) ->
+ #ssh{userauth_supported_methods = Methods} = Ssh) ->
%% Password change without us having sent SSH_MSG_USERAUTH_PASSWD_CHANGEREQ (because we never do)
%% RFC 4252 says:
%% SSH_MSG_USERAUTH_FAILURE without partial success - The password
@@ -216,7 +208,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
{not_authorized, {User, {error,"Password change not supported"}},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = "",
+ authentications = Methods,
partial_success = false}, Ssh)};
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -232,7 +224,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "publickey",
data = Data},
- SessionId, #ssh{opts = Opts} = Ssh) ->
+ SessionId,
+ #ssh{opts = Opts,
+ userauth_supported_methods = Methods} = Ssh) ->
<<?BYTE(HaveSig), ?UINT32(ALen), BAlg:ALen/binary,
?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data,
Alg = binary_to_list(BAlg),
@@ -247,7 +241,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
false ->
{not_authorized, {User, undefined},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications="publickey,password",
+ authentications = Methods,
partial_success = false}, Ssh)}
end;
?FALSE ->
@@ -259,6 +253,64 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
+ method = "keyboard-interactive",
+ data = _},
+ _, #ssh{opts = Opts,
+ kb_tries_left = KbTriesLeft,
+ userauth_supported_methods = Methods} = Ssh) ->
+ case KbTriesLeft of
+ N when N<1 ->
+ {not_authorized, {User, {authmethod, "keyboard-interactive"}},
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh)};
+
+ _ ->
+ %% RFC4256
+ %% The data field contains:
+ %% - language tag (deprecated). If =/=[] SHOULD use it however. We skip
+ %% it for simplicity.
+ %% - submethods. "... the user can give a hint of which actual methods
+ %% he wants to use. ...". It's a "MAY use" so we skip
+ %% it. It also needs an understanding between the client
+ %% and the server.
+ %%
+ %% "The server MUST reply with an SSH_MSG_USERAUTH_SUCCESS,
+ %% SSH_MSG_USERAUTH_FAILURE, or SSH_MSG_USERAUTH_INFO_REQUEST message."
+ Default = {"SSH server",
+ "Enter password for \""++User++"\"",
+ "password: ",
+ false},
+
+ {Name, Instruction, Prompt, Echo} =
+ case proplists:get_value(auth_method_kb_interactive_data, Opts) of
+ undefined ->
+ Default;
+ {_,_,_,_}=V ->
+ V;
+ F when is_function(F) ->
+ {_,PeerName} = Ssh#ssh.peer,
+ F(PeerName, User, "ssh-connection")
+ end,
+ EchoEnc = case Echo of
+ true -> <<?TRUE>>;
+ false -> <<?FALSE>>
+ end,
+ Msg = #ssh_msg_userauth_info_request{name = unicode:characters_to_list(Name),
+ instruction = unicode:characters_to_list(Instruction),
+ language_tag = "",
+ num_prompts = 1,
+ data = <<?STRING(unicode:characters_to_binary(Prompt)),
+ EchoEnc/binary
+ >>
+ },
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User
+ })}
+ end;
+
+handle_userauth_request(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
method = Other}, _,
#ssh{userauth_supported_methods = Methods} = Ssh) ->
{not_authorized, {User, {authmethod, Other}},
@@ -266,6 +318,8 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
#ssh_msg_userauth_failure{authentications = Methods,
partial_success = false}, Ssh)}.
+
+
handle_userauth_info_request(
#ssh_msg_userauth_info_request{name = Name,
instruction = Instr,
@@ -280,27 +334,49 @@ handle_userauth_info_request(
#ssh_msg_userauth_info_response{num_responses = NumPrompts,
data = Responses}, Ssh)}.
+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) ->
+ case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of
+ {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{
+ authentications = Methods,
+ partial_success = false},
+ Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}
+ )}
+ end;
+
handle_userauth_info_response(#ssh_msg_userauth_info_response{},
_Auth) ->
- throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
- description = "Server does not support"
- "keyboard-interactive",
- language = "en"}).
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "Server does not support keyboard-interactive"
+ }).
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-method_preference(Alg1, Alg2) ->
- [{"publickey", ?MODULE, publickey_msg, [Alg1]},
- {"publickey", ?MODULE, publickey_msg,[Alg2]},
- {"password", ?MODULE, password_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
- ].
-method_preference(Alg1) ->
- [{"publickey", ?MODULE, publickey_msg, [Alg1]},
- {"password", ?MODULE, password_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
- ].
+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
@@ -321,13 +397,34 @@ user_name(Opts) ->
{ok, User}
end.
-check_password(User, Password, Opts) ->
+check_password(User, Password, Opts, Ssh) ->
case proplists:get_value(pwdfun, Opts) of
undefined ->
Static = get_password_option(Opts, User),
- Password == Static;
- Cheker ->
- Cheker(User, Password)
+ {Password == Static, Ssh};
+
+ Checker when is_function(Checker,2) ->
+ {Checker(User, Password), Ssh};
+
+ Checker when is_function(Checker,4) ->
+ #ssh{pwdfun_user_state = PrivateState,
+ peer = {_,PeerAddr={_,_}}
+ } = Ssh,
+ case Checker(User, Password, PeerAddr, PrivateState) of
+ true ->
+ {true,Ssh};
+ false ->
+ {false,Ssh};
+ {true,NewState} ->
+ {true, Ssh#ssh{pwdfun_user_state=NewState}};
+ {false,NewState} ->
+ {false, Ssh#ssh{pwdfun_user_state=NewState}};
+ disconnect ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "Unable to connect using the available authentication methods"
+ })
+ end
end.
get_password_option(Opts, User) ->
@@ -364,10 +461,7 @@ build_sig_data(SessionId, User, Service, KeyBlob, Alg) ->
?binary(KeyBlob)],
list_to_binary(Sig).
-algorithm_string('ssh-rsa') ->
- "ssh-rsa";
-algorithm_string('ssh-dss') ->
- "ssh-dss".
+
decode_keyboard_interactive_prompts(_NumPrompts, Data) ->
ssh_message:decode_keyboard_interactive_prompts(Data, []).
@@ -383,19 +477,19 @@ keyboard_interact_get_responses(_, undefined, Password, _, _, _, _, _,
1) when Password =/= undefined ->
[Password]; %% Password auth implemented with keyboard-interaction and passwd is known
keyboard_interact_get_responses(_, _, _, _, _, _, _, _, 0) ->
- [""];
+ [];
keyboard_interact_get_responses(false, undefined, undefined, _, _, _, [Prompt|_], Opts, _) ->
ssh_no_io:read_line(Prompt, Opts); %% Throws error as keyboard interaction is not allowed
keyboard_interact_get_responses(true, undefined, _,IoCb, Name, Instr, PromptInfos, Opts, _) ->
keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts);
-keyboard_interact_get_responses(true, Fun, _, Name, Instr, PromptInfos, _, _, NumPrompts) ->
+keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos, _Opts, NumPrompts) ->
keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts).
keyboard_interact(IoCb, Name, Instr, Prompts, Opts) ->
- if Name /= "" -> IoCb:format("~s", [Name]);
+ if Name /= "" -> IoCb:format("~s~n", [Name]);
true -> ok
end,
- if Instr /= "" -> IoCb:format("~s", [Instr]);
+ if Instr /= "" -> IoCb:format("~s~n", [Instr]);
true -> ok
end,
lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts);
@@ -418,33 +512,18 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) ->
language = "en"}})
end.
-other_alg('ssh-rsa') ->
- 'ssh-dss';
-other_alg('ssh-dss') ->
- 'ssh-rsa'.
-decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary,
- ?UINT32(Len1), BinE:Len1/binary,
- ?UINT32(Len2), BinN:Len2/binary>>
- ,"ssh-rsa") ->
- E = ssh_bits:erlint(Len1, BinE),
- N = ssh_bits:erlint(Len2, BinN),
- {ok, #'RSAPublicKey'{publicExponent = E, modulus = N}};
-decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary,
- ?UINT32(Len1), BinP:Len1/binary,
- ?UINT32(Len2), BinQ:Len2/binary,
- ?UINT32(Len3), BinG:Len3/binary,
- ?UINT32(Len4), BinY:Len4/binary>>
- , "ssh-dss") ->
- P = ssh_bits:erlint(Len1, BinP),
- Q = ssh_bits:erlint(Len2, BinQ),
- G = ssh_bits:erlint(Len3, BinG),
- Y = ssh_bits:erlint(Len4, BinY),
- {ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}};
-
-decode_public_key_v2(_, _) ->
- {error, bad_format}.
-
-encode_public_key(#'RSAPrivateKey'{publicExponent = E, modulus = N}) ->
- ssh_bits:encode(["ssh-rsa",E,N], [string,mpint,mpint]);
-encode_public_key(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) ->
- ssh_bits:encode(["ssh-dss",P,Q,G,Y], [string,mpint,mpint,mpint,mpint]).
+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.
diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl
index 6cd8e6bf14..56314ca6d9 100644
--- a/lib/ssh/src/ssh_auth.hrl
+++ b/lib/ssh/src/ssh_auth.hrl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -21,9 +22,6 @@
%%% Description: Ssh User Authentication Protocol
--define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password").
-
--define(PREFERRED_PK_ALG, 'ssh-rsa').
-define(SSH_MSG_USERAUTH_REQUEST, 50).
-define(SSH_MSG_USERAUTH_FAILURE, 51).
diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl
index 8aaff93b9f..8bedaaf0c5 100644
--- a/lib/ssh/src/ssh_bits.erl
+++ b/lib/ssh/src/ssh_bits.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -25,52 +26,30 @@
-include("ssh.hrl").
--export([encode/2]).
--export([mpint/1, erlint/2, string/1, name_list/1]).
+-export([mpint/1, name_list/1]).
-export([random/1]).
--define(name_list(X),
- (fun(B) -> ?binary(B) end)(list_to_binary(name_concat(X)))).
-
-
-name_concat([Name]) when is_atom(Name) -> atom_to_list(Name);
-name_concat([Name]) when is_list(Name) -> Name;
-name_concat([Name|Ns]) ->
- if is_atom(Name) ->
- [atom_to_list(Name),"," | name_concat(Ns)];
- is_list(Name) ->
- [Name,"," | name_concat(Ns)]
- end;
-name_concat([]) -> [].
-
-
-name_list(Ns) ->
- ?name_list(Ns).
+%%%----------------------------------------------------------------
+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.
+
+%%%----------------------------------------------------------------
+%%% 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,[]).
-
-string(Str) ->
- ?string(Str).
-
-
-%% MP representaion (SSH2)
-mpint(X) when X < 0 ->
- if X == -1 ->
- <<0,0,0,1,16#ff>>;
- true ->
- mpint_neg(X,0,[])
- end;
-mpint(X) ->
- if X == 0 ->
- <<0,0,0,0>>;
- true ->
- mpint_pos(X,0,[])
- end.
-
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>>)
+ <<?UINT32(I), (list_to_binary(Ds))/binary>>
end;
mpint_neg(X,I,Ds) ->
mpint_neg(X bsr 8,I+1,[(X band 255)|Ds]).
@@ -79,100 +58,17 @@ 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>>)
+ <<?UINT32(I), (list_to_binary(Ds))/binary>>
end;
mpint_pos(X,I,Ds) ->
mpint_pos(X bsr 8,I+1,[(X band 255)|Ds]).
-encode(List, Types) ->
- list_to_binary(enc(List, Types)).
-
-%%
-%% Encode record element
-%%
-enc(Xs, Ts) ->
- enc(Xs, Ts, 0).
-
-enc(Xs, [boolean|Ts], Offset) ->
- X = hd(Xs),
- [?boolean(X) | enc(tl(Xs), Ts, Offset+1)];
-enc(Xs, [byte|Ts], Offset) ->
- X = hd(Xs),
- [?byte(X) | enc(tl(Xs), Ts,Offset+1)];
-enc(Xs, [uint16|Ts], Offset) ->
- X = hd(Xs),
- [?uint16(X) | enc(tl(Xs), Ts,Offset+2)];
-enc(Xs, [uint32 |Ts], Offset) ->
- X = hd(Xs),
- [?uint32(X) | enc(tl(Xs), Ts,Offset+4)];
-enc(Xs, [uint64|Ts], Offset) ->
- X = hd(Xs),
- [?uint64(X) | enc(tl(Xs), Ts,Offset+8)];
-enc(Xs, [mpint|Ts], Offset) ->
- Y = mpint(hd(Xs)),
- [Y | enc(tl(Xs), Ts,Offset+size(Y))];
-enc(Xs, [string|Ts], Offset) ->
- X0 = hd(Xs),
- Y = ?string(X0),
- [Y | enc(tl(Xs),Ts,Offset+size(Y))];
-enc(Xs, [string_utf8|Ts], Offset) ->
- X0 = hd(Xs),
- Y = ?string_utf8(X0),
- [Y | enc(tl(Xs),Ts,Offset+size(Y))];
-enc(Xs, [binary|Ts], Offset) ->
- X0 = hd(Xs),
- Y = ?binary(X0),
- [Y | enc(tl(Xs), Ts,Offset+size(Y))];
-enc(Xs, [name_list|Ts], Offset) ->
- X0 = hd(Xs),
- Y = ?name_list(X0),
- [Y | enc(tl(Xs), Ts, Offset+size(Y))];
-enc(Xs, [cookie|Ts], Offset) ->
- [random(16) | enc(tl(Xs), Ts, Offset+16)];
-enc(Xs, [{pad,N}|Ts], Offset) ->
- K = (N - (Offset rem N)) rem N,
- [fill_bits(K,0) | enc(Xs, Ts, Offset+K)];
-enc(Xs, ['...'| []], _Offset) ->
- X = hd(Xs),
- if is_binary(X) ->
- [X];
- is_list(X) ->
- [list_to_binary(X)];
- X==undefined ->
- []
- end;
-enc([], [],_) ->
- [].
-
-erlint(Len, BinInt) ->
- Sz = Len*8,
- <<Int:Sz/big-signed-integer>> = BinInt,
- Int.
-
-%%
-%% Create a binary with constant bytes
-%%
-fill_bits(N,C) ->
- list_to_binary(fill(N,C)).
-
-fill(0,_C) -> [];
-fill(1,C) -> [C];
-fill(N,C) ->
- Cs = fill(N div 2, C),
- Cs1 = [Cs,Cs],
- if N band 1 == 0 ->
- Cs1;
- true ->
- [C,Cs,Cs]
- end.
-
-
+%%%----------------------------------------------------------------
%% random/1
%% Generate N random bytes
%%
-random(N) ->
- crypto:strong_rand_bytes(N).
+random(N) -> crypto:strong_rand_bytes(N).
diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl
index 5c24f362b1..a8e6ebde16 100644
--- a/lib/ssh/src/ssh_channel.erl
+++ b/lib/ssh/src/ssh_channel.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -67,7 +68,7 @@
%% Internal application API
-export([cache_create/0, cache_lookup/2, cache_update/2,
cache_delete/1, cache_delete/2, cache_foldl/3,
- cache_find/2,
+ cache_info/2, cache_find/2,
get_print_info/1]).
-record(state, {
@@ -334,6 +335,9 @@ cache_delete(Cache) ->
cache_foldl(Fun, Acc, Cache) ->
ets:foldl(Fun, Acc, Cache).
+cache_info(num_entries, Cache) ->
+ proplists:get_value(size, ets:info(Cache)).
+
cache_find(ChannelPid, Cache) ->
case ets:match_object(Cache, #channel{user = ChannelPid}) of
[] ->
diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl
index ee37ed35f8..8eaa85f795 100644
--- a/lib/ssh/src/ssh_channel_sup.erl
+++ b/lib/ssh/src/ssh_channel_sup.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
%%
%% %CopyrightEnd%
%%
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 18841e3d2d..2d60008de6 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -98,7 +99,7 @@ handle_ssh_msg({ssh_cm, ConnectionHandler,
Pty = Pty0#ssh_pty{width = Width, height = Height,
pixel_width = PixWidth,
pixel_height = PixHeight},
- {Chars, NewBuf} = io_request({window_change, Pty0}, Buf, Pty),
+ {Chars, NewBuf} = io_request({window_change, Pty0}, Buf, Pty, undefined),
write_chars(ConnectionHandler, ChannelId, Chars),
{ok, State#state{pty = Pty, buf = NewBuf}};
@@ -188,7 +189,7 @@ handle_msg({Group, tty_geometry}, #state{group = Group,
handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
cm = ConnectionHandler,
channel = ChannelId} = State) ->
- {Chars, NewBuf} = io_request(Req, Buf, Pty),
+ {Chars, NewBuf} = io_request(Req, Buf, Pty, Group),
write_chars(ConnectionHandler, ChannelId, Chars),
{ok, State#state{buf = NewBuf}};
@@ -263,40 +264,49 @@ eval(Error) ->
%%% displaying device...
%%% We are *not* really unicode aware yet, we just filter away characters
%%% beyond the latin1 range. We however handle the unicode binaries...
-io_request({window_change, OldTty}, Buf, Tty) ->
+io_request({window_change, OldTty}, Buf, Tty, _Group) ->
window_change(Tty, OldTty, Buf);
-io_request({put_chars, Cs}, Buf, Tty) ->
+io_request({put_chars, Cs}, Buf, Tty, _Group) ->
put_chars(bin_to_list(Cs), Buf, Tty);
-io_request({put_chars, unicode, Cs}, Buf, Tty) ->
+io_request({put_chars, unicode, Cs}, Buf, Tty, _Group) ->
put_chars(unicode:characters_to_list(Cs,unicode), Buf, Tty);
-io_request({insert_chars, Cs}, Buf, Tty) ->
+io_request({insert_chars, Cs}, Buf, Tty, _Group) ->
insert_chars(bin_to_list(Cs), Buf, Tty);
-io_request({insert_chars, unicode, Cs}, Buf, Tty) ->
+io_request({insert_chars, unicode, Cs}, Buf, Tty, _Group) ->
insert_chars(unicode:characters_to_list(Cs,unicode), Buf, Tty);
-io_request({move_rel, N}, Buf, Tty) ->
+io_request({move_rel, N}, Buf, Tty, _Group) ->
move_rel(N, Buf, Tty);
-io_request({delete_chars,N}, Buf, Tty) ->
+io_request({delete_chars,N}, Buf, Tty, _Group) ->
delete_chars(N, Buf, Tty);
-io_request(beep, Buf, _Tty) ->
+io_request(beep, Buf, _Tty, _Group) ->
{[7], Buf};
%% New in R12
-io_request({get_geometry,columns},Buf,Tty) ->
+io_request({get_geometry,columns},Buf,Tty, _Group) ->
{ok, Tty#ssh_pty.width, Buf};
-io_request({get_geometry,rows},Buf,Tty) ->
+io_request({get_geometry,rows},Buf,Tty, _Group) ->
{ok, Tty#ssh_pty.height, Buf};
-io_request({requests,Rs}, Buf, Tty) ->
- io_requests(Rs, Buf, Tty, []);
-io_request(tty_geometry, Buf, Tty) ->
- io_requests([{move_rel, 0}, {put_chars, unicode, [10]}], Buf, Tty, []);
+io_request({requests,Rs}, Buf, Tty, Group) ->
+ io_requests(Rs, Buf, Tty, [], Group);
+io_request(tty_geometry, Buf, Tty, Group) ->
+ io_requests([{move_rel, 0}, {put_chars, unicode, [10]}],
+ Buf, Tty, [], Group);
%{[], Buf};
-io_request(_R, Buf, _Tty) ->
+
+%% New in 18
+io_request({put_chars_sync, Class, Cs, Reply}, Buf, Tty, Group) ->
+ %% We handle these asynchronous for now, if we need output guarantees
+ %% we have to handle these synchronously
+ Group ! {reply, Reply},
+ io_request({put_chars, Class, Cs}, Buf, Tty, Group);
+
+io_request(_R, Buf, _Tty, _Group) ->
{[], Buf}.
-io_requests([R|Rs], Buf, Tty, Acc) ->
- {Chars, NewBuf} = io_request(R, Buf, Tty),
- io_requests(Rs, NewBuf, Tty, [Acc|Chars]);
-io_requests([], Buf, _Tty, Acc) ->
+io_requests([R|Rs], Buf, Tty, Acc, Group) ->
+ {Chars, NewBuf} = io_request(R, Buf, Tty, Group),
+ io_requests(Rs, NewBuf, Tty, [Acc|Chars], Group);
+io_requests([], Buf, _Tty, Acc, _Group) ->
{Acc, Buf}.
%%% return commands for cursor navigation, assume everything is ansi
diff --git a/lib/ssh/src/ssh_client_key.erl b/lib/ssh/src/ssh_client_key.erl
index 2c48884dc2..5296ac2a02 100644
--- a/lib/ssh/src/ssh_client_key.erl
+++ b/lib/ssh/src/ssh_client_key.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_client_key_api.erl b/lib/ssh/src/ssh_client_key_api.erl
index a17c7cbc77..039a7dea9b 100644
--- a/lib/ssh/src/ssh_client_key_api.erl
+++ b/lib/ssh/src/ssh_client_key_api.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl
index d14f7ce27d..4fb6bc39f3 100644
--- a/lib/ssh/src/ssh_connect.hrl
+++ b/lib/ssh/src/ssh_connect.hrl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -21,12 +22,15 @@
%%% Description : SSH connection protocol
--type channel_id() :: integer().
+-type role() :: client | server .
+-type connection_ref() :: pid().
+-type channel_id() :: pos_integer().
+
+-define(DEFAULT_PACKET_SIZE, 65536).
+-define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE).
--define(DEFAULT_PACKET_SIZE, 32768).
--define(DEFAULT_WINDOW_SIZE, 2*?DEFAULT_PACKET_SIZE).
-define(DEFAULT_TIMEOUT, 5000).
--define(MAX_PROTO_VERSION, 255).
+-define(MAX_PROTO_VERSION, 255). % Max length of the hello string
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
@@ -239,7 +243,7 @@
-record(channel,
{
- type, %% "session", "x11", "forwarded-tcpip", "direct-tcpip"
+ type, %% "session"
sys, %% "none", "shell", "exec" "subsystem"
user, %% "user" process id (default to cm user)
flow_control,
@@ -247,6 +251,9 @@
local_id, %% local channel id
recv_window_size,
+ recv_window_pending = 0, %% Sum of window size updates that has not
+ %% yet been sent. This limits the number
+ %% of sent update msgs.
recv_packet_size,
recv_close = false,
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 01141622d6..d0f2d54c06 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -37,8 +38,7 @@
%% Potential API currently unsupported and not tested
-export([window_change/4, window_change/6,
- direct_tcpip/6, direct_tcpip/8, tcpip_forward/3,
- cancel_tcpip_forward/3, signal/3, exit_status/3]).
+ signal/3, exit_status/3]).
%% Internal application API
-export([channel_data/5, handle_msg/3, channel_eof_msg/1,
@@ -47,7 +47,7 @@
channel_adjust_window_msg/2, channel_data_msg/3,
channel_open_msg/5, channel_open_confirmation_msg/4,
channel_open_failure_msg/4, channel_request_msg/4,
- global_request_msg/3, request_failure_msg/0,
+ request_failure_msg/0,
request_success_msg/1, bind/4, unbind/3, unbind_channel/2,
bound_channel/3, encode_ip/1]).
@@ -56,8 +56,8 @@
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec session_channel(pid(), timeout()) -> {ok, channel_id()} | {error, term()}.
--spec session_channel(pid(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, term()}.
+-spec session_channel(pid(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}.
+-spec session_channel(pid(), 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,8 @@ session_channel(ConnectionHandler, InitialWindowSize,
end.
%%--------------------------------------------------------------------
--spec exec(pid(), channel_id(), string(), timeout()) -> success | failure.
+-spec exec(pid(), channel_id(), string(), timeout()) ->
+ success | failure | {error, timeout | closed}.
%% Description: Will request that the server start the
%% execution of the given command.
@@ -101,8 +102,8 @@ shell(ConnectionHandler, ChannelId) ->
ssh_connection_handler:request(ConnectionHandler, self(), ChannelId,
"shell", false, <<>>, 0).
%%--------------------------------------------------------------------
--spec subsystem(pid(), channel_id(), string(), timeout()) ->
- success | failure | {error, timeout}.
+-spec subsystem(pid(), channel_id(), string(), timeout()) ->
+ success | failure | {error, timeout | closed}.
%%
%% Description: Executes a predefined subsystem.
%%--------------------------------------------------------------------
@@ -142,7 +143,7 @@ send_eof(ConnectionHandler, Channel) ->
ssh_connection_handler:send_eof(ConnectionHandler, Channel).
%%--------------------------------------------------------------------
--spec adjust_window(pid(), channel_id(), integer()) -> ok.
+-spec adjust_window(pid(), channel_id(), integer()) -> ok | {error, closed}.
%%
%%
%% Description: Adjusts the ssh flowcontrol window.
@@ -151,7 +152,8 @@ adjust_window(ConnectionHandler, Channel, Bytes) ->
ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes).
%%--------------------------------------------------------------------
--spec setenv(pid(), channel_id(), string(), string(), timeout()) -> success | failure.
+-spec setenv(pid(), channel_id(), string(), string(), timeout()) ->
+ success | failure | {error, timeout | closed}.
%%
%%
%% Description: Environment variables may be passed to the shell/command to be
@@ -183,22 +185,27 @@ reply_request(_,false, _, _) ->
ok.
%%--------------------------------------------------------------------
--spec ptty_alloc(pid(), channel_id(), proplists:proplist()) -> success | failiure.
+-spec ptty_alloc(pid(), channel_id(), proplists:proplist()) ->
+ success | failiure | {error, closed}.
+-spec ptty_alloc(pid(), channel_id(), proplists:proplist(), timeout()) ->
+ success | failiure | {error, timeout} | {error, closed}.
+
%%
%%
%% Description: Sends a ssh connection protocol pty_req.
%%--------------------------------------------------------------------
ptty_alloc(ConnectionHandler, Channel, Options) ->
ptty_alloc(ConnectionHandler, Channel, Options, infinity).
-ptty_alloc(ConnectionHandler, Channel, Options, TimeOut) ->
+ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) ->
+ Options = backwards_compatible(Options0, []),
{Width, PixWidth} = pty_default_dimensions(width, Options),
- {Hight, PixHight} = pty_default_dimensions(hight, Options),
+ {Height, PixHeight} = pty_default_dimensions(height, Options),
pty_req(ConnectionHandler, Channel,
- proplists:get_value(term, Options, default_term()),
+ proplists:get_value(term, Options, os:getenv("TERM", ?DEFAULT_TERMINAL)),
proplists:get_value(width, Options, Width),
- proplists:get_value(hight, Options, Hight),
+ proplists:get_value(height, Options, Height),
proplists:get_value(pixel_widh, Options, PixWidth),
- proplists:get_value(pixel_hight, Options, PixHight),
+ proplists:get_value(pixel_height, Options, PixHeight),
proplists:get_value(pty_opts, Options, []), TimeOut
).
%%--------------------------------------------------------------------
@@ -224,52 +231,6 @@ exit_status(ConnectionHandler, Channel, Status) ->
ssh_connection_handler:request(ConnectionHandler, Channel,
"exit-status", false, [?uint32(Status)], 0).
-direct_tcpip(ConnectionHandler, RemoteHost,
- RemotePort, OrigIP, OrigPort, Timeout) ->
- direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort,
- ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
-
-direct_tcpip(ConnectionHandler, RemoteIP, RemotePort, OrigIP, OrigPort,
- InitialWindowSize, MaxPacketSize, Timeout) ->
- case {encode_ip(RemoteIP), encode_ip(OrigIP)} of
- {false, _} ->
- {error, einval};
- {_, false} ->
- {error, einval};
- {RIP, OIP} ->
- ssh_connection_handler:open_channel(ConnectionHandler,
- "direct-tcpip",
- [?string(RIP),
- ?uint32(RemotePort),
- ?string(OIP),
- ?uint32(OrigPort)],
- InitialWindowSize,
- MaxPacketSize,
- Timeout)
- end.
-
-tcpip_forward(ConnectionHandler, BindIP, BindPort) ->
- case encode_ip(BindIP) of
- false ->
- {error, einval};
- IPStr ->
- ssh_connection_handler:global_request(ConnectionHandler,
- "tcpip-forward", true,
- [?string(IPStr),
- ?uint32(BindPort)])
- end.
-
-cancel_tcpip_forward(ConnectionHandler, BindIP, Port) ->
- case encode_ip(BindIP) of
- false ->
- {error, einval};
- IPStr ->
- ssh_connection_handler:global_request(ConnectionHandler,
- "cancel-tcpip-forward", true,
- [?string(IPStr),
- ?uint32(Port)])
- end.
-
%%--------------------------------------------------------------------
%%% Internal API
%%--------------------------------------------------------------------
@@ -292,22 +253,11 @@ l2b([]) ->
channel_data(ChannelId, DataType, Data, Connection, From)
when is_list(Data)->
- channel_data(ChannelId, DataType,
-%% list_to_binary(Data), Connection, From);
- l2b(Data), Connection, From);
- %% try list_to_binary(Data)
- %% of
- %% B -> B
- %% catch
- %% _:_ -> io:format('BAD BINARY: ~p~n',[Data]),
- %% unicode:characters_to_binary(Data)
- %% end,
- %% Connection, From);
+ channel_data(ChannelId, DataType, l2b(Data), Connection, From);
channel_data(ChannelId, DataType, Data,
#connection{channel_cache = Cache} = Connection,
From) ->
-
case ssh_channel:cache_lookup(Cache, ChannelId) of
#channel{remote_id = Id, sent_close = false} = Channel0 ->
{SendList, Channel} =
@@ -320,13 +270,10 @@ channel_data(ChannelId, DataType, Data,
SendDataType,
SendData)}
end, SendList),
- FlowCtrlMsgs = flow_control(Replies,
- Channel,
- Cache),
+ FlowCtrlMsgs = flow_control(Replies, Channel, Cache),
{{replies, Replies ++ FlowCtrlMsgs}, Connection};
_ ->
- gen_fsm:reply(From, {error, closed}),
- {noreply, Connection}
+ {{replies,[{channel_request_reply,From,{error,closed}}]}, Connection}
end.
handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId,
@@ -464,23 +411,37 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type,
sender_channel = RemoteId,
initial_window_size = WindowSz,
- maximum_packet_size = PacketSz}, Connection0, server) ->
-
- try setup_session(Connection0, RemoteId,
- Type, WindowSz, PacketSz) of
- Result ->
- Result
- catch _:_ ->
+ maximum_packet_size = PacketSz},
+ #connection{options = SSHopts} = Connection0,
+ server) ->
+ MinAcceptedPackSz = proplists:get_value(minimal_remote_max_packet_size, SSHopts, 0),
+
+ if
+ MinAcceptedPackSz =< PacketSz ->
+ try setup_session(Connection0, RemoteId,
+ Type, WindowSz, PacketSz) of
+ Result ->
+ Result
+ catch _:_ ->
+ FailMsg = channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ "Connection refused", "en"),
+ {{replies, [{connection_reply, FailMsg}]},
+ Connection0}
+ end;
+
+ MinAcceptedPackSz > PacketSz ->
FailMsg = channel_open_failure_msg(RemoteId,
- ?SSH_OPEN_CONNECT_FAILED,
- "Connection refused", "en"),
- {{replies, [{connection_reply, FailMsg}]},
- Connection0}
+ ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
+ lists:concat(["Maximum packet size below ",MinAcceptedPackSz,
+ " not supported"]), "en"),
+ {{replies, [{connection_reply, FailMsg}]}, Connection0}
end;
handle_msg(#ssh_msg_channel_open{channel_type = "session",
sender_channel = RemoteId},
- Connection, client) ->
+ Connection,
+ client) ->
%% Client implementations SHOULD reject any session channel open
%% requests to make it more difficult for a corrupt server to attack the
%% client. See See RFC 4254 6.1.
@@ -490,57 +451,6 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session",
{{replies, [{connection_reply, FailMsg}]},
Connection};
-handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip" = Type,
- sender_channel = RemoteId,
- initial_window_size = RWindowSz,
- maximum_packet_size = RPacketSz,
- data = Data},
- #connection{channel_cache = Cache} = Connection0, server) ->
- <<?UINT32(ALen), Address:ALen/binary, ?UINT32(Port),
- ?UINT32(OLen), Orig:OLen/binary, ?UINT32(OrigPort)>> = Data,
-
- case bound_channel(Address, Port, Connection0) of
- undefined ->
- FailMsg = channel_open_failure_msg(RemoteId,
- ?SSH_OPEN_CONNECT_FAILED,
- "Connection refused", "en"),
- {{replies,
- [{connection_reply, FailMsg}]}, Connection0};
- ChannelPid ->
- {ChannelId, Connection1} = new_channel_id(Connection0),
- LWindowSz = ?DEFAULT_WINDOW_SIZE,
- LPacketSz = ?DEFAULT_PACKET_SIZE,
- Channel = #channel{type = Type,
- sys = "none",
- user = ChannelPid,
- local_id = ChannelId,
- recv_window_size = LWindowSz,
- recv_packet_size = LPacketSz,
- send_window_size = RWindowSz,
- send_packet_size = RPacketSz},
- ssh_channel:cache_update(Cache, Channel),
- OpenConfMsg = channel_open_confirmation_msg(RemoteId, ChannelId,
- LWindowSz, LPacketSz),
- {OpenMsg, Connection} =
- reply_msg(Channel, Connection1,
- {open, Channel, {forwarded_tcpip,
- decode_ip(Address), Port,
- decode_ip(Orig), OrigPort}}),
- {{replies, [{connection_reply, OpenConfMsg},
- OpenMsg]}, Connection}
- end;
-
-handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip",
- sender_channel = RemoteId},
- Connection, client) ->
- %% Client implementations SHOULD reject direct TCP/IP open requests for
- %% security reasons. See RFC 4254 7.2.
- FailMsg = channel_open_failure_msg(RemoteId,
- ?SSH_OPEN_CONNECT_FAILED,
- "Connection refused", "en"),
- {{replies, [{connection_reply, FailMsg}]}, Connection};
-
-
handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _) ->
FailMsg = channel_open_failure_msg(RemoteId,
?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
@@ -627,7 +537,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
ReplyMsg = {subsystem, ChannelId, WantReply, binary_to_list(SsName)},
try
- {ok, Pid} = start_subsytem(SsName, Connection, Channel0, ReplyMsg),
+ {ok, Pid} = start_subsystem(SsName, Connection, Channel0, ReplyMsg),
erlang:monitor(process, Pid),
Channel = Channel0#channel{user = Pid},
ssh_channel:cache_update(Cache, Channel),
@@ -751,11 +661,11 @@ handle_msg(#ssh_msg_global_request{name = _Type,
handle_msg(#ssh_msg_request_failure{},
#connection{requests = [{_, From} | Rest]} = Connection, _) ->
- {{replies, [{channel_requst_reply, From, {failure, <<>>}}]},
+ {{replies, [{channel_request_reply, From, {failure, <<>>}}]},
Connection#connection{requests = Rest}};
handle_msg(#ssh_msg_request_success{data = Data},
#connection{requests = [{_, From} | Rest]} = Connection, _) ->
- {{replies, [{channel_requst_reply, From, {success, Data}}]},
+ {{replies, [{channel_request_reply, From, {success, Data}}]},
Connection#connection{requests = Rest}};
handle_msg(#ssh_msg_disconnect{code = Code,
@@ -851,10 +761,6 @@ channel_request_msg(ChannelId, Type, WantReply, Data) ->
want_reply = WantReply,
data = Data}.
-global_request_msg(Type, WantReply, Data) ->
- #ssh_msg_global_request{name = Type,
- want_reply = WantReply,
- data = Data}.
request_failure_msg() ->
#ssh_msg_request_failure{}.
@@ -900,18 +806,32 @@ encode_ip(Addr) when is_list(Addr) ->
end
end.
-start_channel(Cb, Id, Args, SubSysSup) ->
- start_channel(Cb, Id, Args, SubSysSup, undefined).
+start_channel(Cb, Id, Args, SubSysSup, Opts) ->
+ start_channel(Cb, Id, Args, SubSysSup, undefined, Opts).
-start_channel(Cb, Id, Args, SubSysSup, Exec) ->
+start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) ->
ChildSpec = child_spec(Cb, Id, Args, Exec),
ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup),
+ assert_limit_num_channels_not_exceeded(ChannelSup, Opts),
ssh_channel_sup:start_child(ChannelSup, ChildSpec).
+assert_limit_num_channels_not_exceeded(ChannelSup, Opts) ->
+ MaxNumChannels = proplists:get_value(max_channels, Opts, infinity),
+ NumChannels = length([x || {_,_,worker,[ssh_channel]} <-
+ supervisor:which_children(ChannelSup)]),
+ if
+ %% Note that NumChannels is BEFORE starting a new one
+ NumChannels < MaxNumChannels ->
+ ok;
+ true ->
+ throw(max_num_channels_exceeded)
+ end.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-setup_session(#connection{channel_cache = Cache} = Connection0,
+setup_session(#connection{channel_cache = Cache
+ } = Connection0,
RemoteId,
Type, WindowSize, PacketSize) ->
{ChannelId, Connection} = new_channel_id(Connection0),
@@ -923,6 +843,7 @@ setup_session(#connection{channel_cache = Cache} = Connection0,
recv_packet_size = ?DEFAULT_PACKET_SIZE,
send_window_size = WindowSize,
send_packet_size = PacketSize,
+ send_buf = queue:new(),
remote_id = RemoteId
},
ssh_channel:cache_update(Cache, Channel),
@@ -961,17 +882,19 @@ child_spec(Callback, Id, Args, Exec) ->
start_cli(#connection{cli_spec = no_cli}, _) ->
{error, cli_disabled};
-start_cli(#connection{cli_spec = {CbModule, Args}, exec = Exec,
+start_cli(#connection{options = Options,
+ cli_spec = {CbModule, Args},
+ exec = Exec,
sub_system_supervisor = SubSysSup}, ChannelId) ->
- start_channel(CbModule, ChannelId, Args, SubSysSup, Exec).
+ start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options).
-start_subsytem(BinName, #connection{options = Options,
+start_subsystem(BinName, #connection{options = Options,
sub_system_supervisor = SubSysSup},
#channel{local_id = ChannelId}, _ReplyMsg) ->
Name = binary_to_list(BinName),
case check_subsystem(Name, Options) of
{Callback, Opts} when is_atom(Callback), Callback =/= none ->
- start_channel(Callback, ChannelId, Opts, SubSysSup);
+ start_channel(Callback, ChannelId, Opts, SubSysSup, Options);
{Other, _} when Other =/= none ->
{error, legacy_option_not_supported}
end.
@@ -1007,7 +930,7 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid},
Connection, Reply) ->
case lists:keysearch(ChannelId, 1, Requests) of
{value, {ChannelId, From}} ->
- {{channel_requst_reply, From, Reply},
+ {{channel_request_reply, From, Reply},
Connection#connection{requests =
lists:keydelete(ChannelId, 1, Requests)}};
false when (Reply == success) or (Reply == failure) ->
@@ -1018,63 +941,74 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid},
update_send_window(Channel, _, undefined,
#connection{channel_cache = Cache}) ->
- do_update_send_window(Channel, Channel#channel.send_buf, Cache);
+ do_update_send_window(Channel, Cache);
-update_send_window(Channel, DataType, Data,
+update_send_window(#channel{send_buf = SendBuffer} = Channel, DataType, Data,
#connection{channel_cache = Cache}) ->
- do_update_send_window(Channel, Channel#channel.send_buf ++ [{DataType, Data}], Cache).
-
-do_update_send_window(Channel0, Buf0, Cache) ->
- {Buf1, NewSz, Buf2} = get_window(Buf0,
- Channel0#channel.send_packet_size,
- Channel0#channel.send_window_size),
+ do_update_send_window(Channel#channel{send_buf = queue:in({DataType, Data}, SendBuffer)},
+ Cache).
- Channel = Channel0#channel{send_window_size = NewSz, send_buf = Buf2},
+do_update_send_window(Channel0, Cache) ->
+ {SendMsgs, Channel} = get_window(Channel0, []),
ssh_channel:cache_update(Cache, Channel),
- {Buf1, Channel}.
-
-get_window(Bs, PSz, WSz) ->
- get_window(Bs, PSz, WSz, []).
-
-get_window(Bs, _PSz, 0, Acc) ->
- {lists:reverse(Acc), 0, Bs};
-get_window([B0 = {DataType, Bin} | Bs], PSz, WSz, Acc) ->
- BSz = size(Bin),
- if BSz =< WSz -> %% will fit into window
- if BSz =< PSz -> %% will fit into a packet
- get_window(Bs, PSz, WSz-BSz, [B0|Acc]);
- true -> %% split into packet size
- <<Bin1:PSz/binary, Bin2/binary>> = Bin,
- get_window([setelement(2, B0, Bin2) | Bs],
- PSz, WSz-PSz,
- [{DataType, Bin1}|Acc])
+ {SendMsgs, Channel}.
+
+get_window(#channel{send_window_size = 0
+ } = Channel, Acc) ->
+ {lists:reverse(Acc), Channel};
+get_window(#channel{send_packet_size = 0
+ } = Channel, Acc) ->
+ {lists:reverse(Acc), Channel};
+get_window(#channel{send_buf = Buffer,
+ send_packet_size = PacketSize,
+ send_window_size = WindowSize0
+ } = Channel, Acc0) ->
+ case queue:out(Buffer) of
+ {{value, {_, Data} = Msg}, NewBuffer} ->
+ case handle_send_window(Msg, size(Data), PacketSize, WindowSize0, Acc0) of
+ {WindowSize, Acc, {_, <<>>}} ->
+ {lists:reverse(Acc), Channel#channel{send_window_size = WindowSize,
+ send_buf = NewBuffer}};
+ {WindowSize, Acc, Rest} ->
+ get_window(Channel#channel{send_window_size = WindowSize,
+ send_buf = queue:in_r(Rest, NewBuffer)}, Acc)
end;
- WSz =< PSz -> %% use rest of window
- <<Bin1:WSz/binary, Bin2/binary>> = Bin,
- get_window([setelement(2, B0, Bin2) | Bs],
- PSz, WSz-WSz,
- [{DataType, Bin1}|Acc]);
- true -> %% use packet size
- <<Bin1:PSz/binary, Bin2/binary>> = Bin,
- get_window([setelement(2, B0, Bin2) | Bs],
- PSz, WSz-PSz,
- [{DataType, Bin1}|Acc])
+ {empty, NewBuffer} ->
+ {[], Channel#channel{send_buf = NewBuffer}}
+ end.
+
+handle_send_window(Msg = {Type, Data}, Size, PacketSize, WindowSize, Acc) when Size =< WindowSize ->
+ case Size =< PacketSize of
+ true ->
+ {WindowSize - Size, [Msg | Acc], {Type, <<>>}};
+ false ->
+ <<Msg1:PacketSize/binary, Msg2/binary>> = Data,
+ {WindowSize - PacketSize, [{Type, Msg1} | Acc], {Type, Msg2}}
end;
-get_window([], _PSz, WSz, Acc) ->
- {lists:reverse(Acc), WSz, []}.
+handle_send_window({Type, Data}, _, PacketSize, WindowSize, Acc) when WindowSize =< PacketSize ->
+ <<Msg1:WindowSize/binary, Msg2/binary>> = Data,
+ {WindowSize - WindowSize, [{Type, Msg1} | Acc], {Type, Msg2}};
+handle_send_window({Type, Data}, _, PacketSize, WindowSize, Acc) ->
+ <<Msg1:PacketSize/binary, Msg2/binary>> = Data,
+ {WindowSize - PacketSize, [{Type, Msg1} | Acc], {Type, Msg2}}.
flow_control(Channel, Cache) ->
flow_control([window_adjusted], Channel, Cache).
-
+
flow_control([], Channel, Cache) ->
ssh_channel:cache_update(Cache, Channel),
[];
-
flow_control([_|_], #channel{flow_control = From,
- send_buf = []} = Channel, Cache) when From =/= undefined ->
- [{flow_control, Cache, Channel, From, ok}];
+ send_buf = Buffer} = Channel, Cache) when From =/= undefined ->
+ case queue:is_empty(Buffer) of
+ true ->
+ ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}),
+ [{flow_control, Cache, Channel, From, ok}];
+ false ->
+ []
+ end;
flow_control(_,_,_) ->
- [].
+ [].
pty_req(ConnectionHandler, Channel, Term, Width, Height,
PixWidth, PixHeight, PtyOpts, TimeOut) ->
@@ -1288,16 +1222,12 @@ decode_pty_opts2(<<Code, ?UINT32(Value), Tail/binary>>) ->
end,
[{Op, Value} | decode_pty_opts2(Tail)].
-decode_ip(Addr) when is_binary(Addr) ->
- case inet_parse:address(binary_to_list(Addr)) of
- {error,_} -> Addr;
- {ok,A} -> A
- end.
-default_term() ->
- case os:getenv("TERM") of
- false ->
- ?DEFAULT_TERMINAL;
- Str when is_list(Str)->
- Str
- end.
+backwards_compatible([], Acc) ->
+ Acc;
+backwards_compatible([{hight, Value} | Rest], Acc) ->
+ backwards_compatible(Rest, [{height, Value} | Acc]);
+backwards_compatible([{pixel_hight, Value} | Rest], Acc) ->
+ backwards_compatible(Rest, [{height, Value} | Acc]);
+backwards_compatible([Value| Rest], Acc) ->
+ backwards_compatible(Rest, [ Value | Acc]).
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index fdb9d3b3e6..e952a333ff 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -27,77 +28,90 @@
-module(ssh_connection_handler).
--behaviour(gen_fsm).
+-behaviour(gen_statem).
-include("ssh.hrl").
-include("ssh_transport.hrl").
-include("ssh_auth.hrl").
-include("ssh_connect.hrl").
--export([start_link/3]).
+%%====================================================================
+%%% Exports
+%%====================================================================
-%% Internal application API
--export([open_channel/6, reply_request/3, request/6, request/7,
- global_request/4, send/5, send_eof/2, info/1, info/2,
- connection_info/2, channel_info/3,
- adjust_window/3, close/2, stop/1, renegotiate/1, renegotiate_data/1,
- start_connection/4,
- get_print_info/1]).
-
-%% gen_fsm callbacks
--export([hello/2, kexinit/2, key_exchange/2, new_keys/2,
- userauth/2, connected/2,
- error/2]).
-
--export([init/1, handle_event/3,
- handle_sync_event/4, handle_info/3, terminate/3, format_status/2, code_change/4]).
-
--record(state, {
- role,
- client,
- starter,
- auth_user,
- connection_state,
- latest_channel_id = 0,
- idle_timer_ref,
- transport_protocol, % ex: tcp
- transport_cb,
- transport_close_tag,
- ssh_params, % #ssh{} - from ssh.hrl
- socket, % socket()
- decoded_data_buffer, % binary()
- encoded_data_buffer, % binary()
- undecoded_packet_length, % integer()
- key_exchange_init_msg, % #ssh_msg_kexinit{}
- renegotiate = false, % boolean()
- connection_queue,
- address,
- port,
- opts,
- recbuf
- }).
-
--type state_name() :: hello | kexinit | key_exchange | new_keys | userauth | connection.
--type gen_fsm_state_return() :: {next_state, state_name(), term()} |
- {next_state, state_name(), term(), timeout()} |
- {stop, term(), term()}.
+%%% Start and stop
+-export([start_link/3,
+ stop/1
+ ]).
+
+%%% Internal application API
+-export([start_connection/4,
+ open_channel/6,
+ request/6, request/7,
+ reply_request/3,
+ send/5,
+ send_eof/2,
+ info/1, info/2,
+ connection_info/2,
+ channel_info/3,
+ adjust_window/3, close/2,
+ disconnect/1, disconnect/2,
+ get_print_info/1
+ ]).
+
+%%% Behaviour callbacks
+-export([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
+ init_ssh_record/3, % Export of this internal function
+ % intended for low-level protocol test suites
+ renegotiate/1, renegotiate_data/1 % Export intended for test cases
+ ]).
%%====================================================================
-%% Internal application API
+%% Start / stop
%%====================================================================
+%%--------------------------------------------------------------------
+-spec start_link(role(),
+ inet:socket(),
+ proplists:proplist()
+ ) -> {ok, pid()}.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+start_link(Role, Socket, Options) ->
+ {ok, proc_lib:spawn_link(?MODULE, init_connection_handler, [Role, Socket, Options])}.
+
%%--------------------------------------------------------------------
--spec start_connection(client| server, port(), proplists:proplist(),
- timeout()) -> {ok, pid()} | {error, term()}.
+-spec stop(connection_ref()
+ ) -> ok | {error, term()}.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+stop(ConnectionHandler)->
+ case call(ConnectionHandler, stop) of
+ {error, closed} ->
+ ok;
+ Other ->
+ Other
+ end.
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+-define(DefaultTransport, {tcp, gen_tcp, tcp_closed} ).
+
%%--------------------------------------------------------------------
+-spec start_connection(role(),
+ inet:socket(),
+ proplists:proplist(),
+ timeout()
+ ) -> {ok, connection_ref()} | {error, term()}.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
start_connection(client = Role, Socket, Options, Timeout) ->
try
{ok, Pid} = sshc_sup:start_child([Role, Socket, Options]),
- {_, Callback, _} =
- proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}),
- ok = socket_control(Socket, Pid, Callback),
- Ref = erlang:monitor(process, Pid),
- handshake(Pid, Ref, Timeout)
+ ok = socket_control(Socket, Pid, Options),
+ handshake(Pid, erlang:monitor(process,Pid), Timeout)
catch
exit:{noproc, _} ->
{error, ssh_not_started};
@@ -110,8 +124,8 @@ start_connection(server = Role, Socket, Options, Timeout) ->
try
case proplists:get_value(parallel_login, SSH_Opts, false) of
true ->
- HandshakerPid =
- spawn_link(fun() ->
+ HandshakerPid =
+ spawn_link(fun() ->
receive
{do_handshake, Pid} ->
handshake(Pid, erlang:monitor(process,Pid), Timeout)
@@ -130,1060 +144,1341 @@ start_connection(server = Role, Socket, Options, Timeout) ->
{error, Error}
end.
-start_the_connection_child(UserPid, Role, Socket, Options) ->
- Sups = proplists:get_value(supervisors, Options),
- 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]),
- {_, Callback, _} = proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}),
- socket_control(Socket, Pid, Callback),
- Pid.
-
-
-start_link(Role, Socket, Options) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Socket, Options]])}.
+%%--------------------------------------------------------------------
+%%% Some other module has decided to disconnect.
+-spec disconnect(#ssh_msg_disconnect{}) -> no_return().
+-spec disconnect(#ssh_msg_disconnect{}, iodata()) -> no_return().
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+disconnect(Msg = #ssh_msg_disconnect{}) ->
+ throw({keep_state_and_data,
+ [{next_event, internal, {disconnect, Msg, Msg#ssh_msg_disconnect.description}}]}).
-init([Role, Socket, SshOpts]) ->
- process_flag(trap_exit, true),
- {NumVsn, StrVsn} = ssh_transport:versions(Role, SshOpts),
- {Protocol, Callback, CloseTag} =
- proplists:get_value(transport, SshOpts, {tcp, gen_tcp, tcp_closed}),
- Cache = ssh_channel:cache_create(),
- State0 = #state{
- role = Role,
- connection_state = #connection{channel_cache = Cache,
- channel_id_seed = 0,
- port_bindings = [],
- requests = [],
- options = SshOpts},
- socket = Socket,
- decoded_data_buffer = <<>>,
- encoded_data_buffer = <<>>,
- transport_protocol = Protocol,
- transport_cb = Callback,
- transport_close_tag = CloseTag,
- opts = SshOpts
- },
-
- State = init_role(State0),
-
- try init_ssh(Role, NumVsn, StrVsn, SshOpts, Socket) of
- Ssh ->
- gen_fsm:enter_loop(?MODULE, [], hello,
- State#state{ssh_params = Ssh})
- catch
- _:Error ->
- gen_fsm:enter_loop(?MODULE, [], error, {Error, State})
- end.
+disconnect(Msg = #ssh_msg_disconnect{}, ExtraInfo) ->
+ throw({keep_state_and_data,
+ [{next_event, internal, {disconnect, Msg, {Msg#ssh_msg_disconnect.description,ExtraInfo}}}]}).
-%% Temporary fix for the Nessus error. SYN-> <-SYNACK ACK-> RST-> ?
-error(_Event, {Error,State=#state{}}) ->
- case Error of
- {badmatch,{error,enotconn}} ->
- %% {error,enotconn} probably from inet:peername in
- %% init_ssh(server,..)/5 called from init/1
- {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}, State};
- _ ->
- {stop, {shutdown,{init,Error}}, State}
- end;
-error(Event, State) ->
- %% State deliberately not checked beeing #state. This is a panic-clause...
- {stop, {shutdown,{init,{spurious_error,Event}}}, State}.
%%--------------------------------------------------------------------
--spec open_channel(pid(), string(), iodata(), integer(), integer(),
- timeout()) -> {open, channel_id()} | {error, term()}.
-%%--------------------------------------------------------------------
-open_channel(ConnectionHandler, ChannelType, ChannelSpecificData,
- InitialWindowSize,
- MaxPacketSize, Timeout) ->
- sync_send_all_state_event(ConnectionHandler, {open, self(), ChannelType,
- InitialWindowSize, MaxPacketSize,
- ChannelSpecificData,
- Timeout}).
-%%--------------------------------------------------------------------
--spec request(pid(), pid(), channel_id(), string(), boolean(), iodata(),
- timeout()) -> success | failure | ok | {error, term()}.
+-spec open_channel(connection_ref(),
+ string(),
+ iodata(),
+ pos_integer(),
+ pos_integer(),
+ timeout()
+ ) -> {open, channel_id()} | {error, term()}.
+
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+open_channel(ConnectionHandler,
+ ChannelType, ChannelSpecificData, InitialWindowSize, MaxPacketSize,
+ Timeout) ->
+ call(ConnectionHandler,
+ {open,
+ self(),
+ ChannelType, InitialWindowSize, MaxPacketSize, ChannelSpecificData,
+ Timeout}).
+
%%--------------------------------------------------------------------
+-spec request(connection_ref(),
+ pid(),
+ channel_id(),
+ string(),
+ boolean(),
+ iodata(),
+ timeout()
+ ) -> success | failure | ok | {error,timeout}.
+
+-spec request(connection_ref(),
+ channel_id(),
+ string(),
+ boolean(),
+ iodata(),
+ timeout()
+ ) -> success | failure | ok | {error,timeout}.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
request(ConnectionHandler, ChannelPid, ChannelId, Type, true, Data, Timeout) ->
- sync_send_all_state_event(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data,
- Timeout});
+ call(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data, Timeout});
request(ConnectionHandler, ChannelPid, ChannelId, Type, false, Data, _) ->
- send_all_state_event(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data}).
+ cast(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data}).
-%%--------------------------------------------------------------------
--spec request(pid(), channel_id(), string(), boolean(), iodata(),
- timeout()) -> success | failure | {error, timeout}.
-%%--------------------------------------------------------------------
request(ConnectionHandler, ChannelId, Type, true, Data, Timeout) ->
- sync_send_all_state_event(ConnectionHandler, {request, ChannelId, Type, Data, Timeout});
+ call(ConnectionHandler, {request, ChannelId, Type, Data, Timeout});
request(ConnectionHandler, ChannelId, Type, false, Data, _) ->
- send_all_state_event(ConnectionHandler, {request, ChannelId, Type, Data}).
+ cast(ConnectionHandler, {request, ChannelId, Type, Data}).
%%--------------------------------------------------------------------
--spec reply_request(pid(), success | failure, channel_id()) -> ok.
-%%--------------------------------------------------------------------
+-spec reply_request(connection_ref(),
+ success | failure,
+ channel_id()
+ ) -> ok.
+
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
reply_request(ConnectionHandler, Status, ChannelId) ->
- send_all_state_event(ConnectionHandler, {reply_request, Status, ChannelId}).
-
-%%--------------------------------------------------------------------
--spec global_request(pid(), string(), boolean(), iolist()) -> ok | error.
-%%--------------------------------------------------------------------
-global_request(ConnectionHandler, Type, true = Reply, Data) ->
- case sync_send_all_state_event(ConnectionHandler,
- {global_request, self(), Type, Reply, Data}) of
- {ssh_cm, ConnectionHandler, {success, _}} ->
- ok;
- {ssh_cm, ConnectionHandler, {failure, _}} ->
- error
- end;
-global_request(ConnectionHandler, Type, false = Reply, Data) ->
- send_all_state_event(ConnectionHandler, {global_request, self(), Type, Reply, Data}).
+ cast(ConnectionHandler, {reply_request, Status, ChannelId}).
%%--------------------------------------------------------------------
--spec send(pid(), channel_id(), integer(), iodata(), timeout()) ->
- ok | {error, timeout} | {error, closed}.
-%%--------------------------------------------------------------------
+-spec send(connection_ref(),
+ channel_id(),
+ non_neg_integer(),
+ iodata(),
+ timeout()
+ ) -> ok | {error, timeout|closed}.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
send(ConnectionHandler, ChannelId, Type, Data, Timeout) ->
- sync_send_all_state_event(ConnectionHandler, {data, ChannelId, Type, Data, Timeout}).
+ call(ConnectionHandler, {data, ChannelId, Type, Data, Timeout}).
%%--------------------------------------------------------------------
--spec send_eof(pid(), channel_id()) -> ok | {error, closed}.
-%%--------------------------------------------------------------------
+-spec send_eof(connection_ref(),
+ channel_id()
+ ) -> ok | {error,closed}.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
send_eof(ConnectionHandler, ChannelId) ->
- sync_send_all_state_event(ConnectionHandler, {eof, ChannelId}).
+ call(ConnectionHandler, {eof, ChannelId}).
%%--------------------------------------------------------------------
--spec connection_info(pid(), [atom()]) -> proplists:proplist().
+-spec info(connection_ref()
+ ) -> {ok, [#channel{}]} .
+
+-spec info(connection_ref(),
+ pid() | all
+ ) -> {ok, [#channel{}]} .
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+info(ConnectionHandler) ->
+ info(ConnectionHandler, all).
+
+info(ConnectionHandler, ChannelProcess) ->
+ call(ConnectionHandler, {info, ChannelProcess}).
+
%%--------------------------------------------------------------------
+-type local_sock_info() :: {inet:ip_address(), non_neg_integer()} | string().
+-type peer_sock_info() :: {inet:ip_address(), non_neg_integer()} | string().
+-type state_info() :: iolist().
+
+-spec get_print_info(connection_ref()
+ ) -> {{local_sock_info(), peer_sock_info()},
+ state_info()
+ }.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
get_print_info(ConnectionHandler) ->
- sync_send_all_state_event(ConnectionHandler, get_print_info, 1000).
+ call(ConnectionHandler, get_print_info, 1000).
+%%--------------------------------------------------------------------
+-spec connection_info(connection_ref(),
+ [atom()]
+ ) -> proplists:proplist().
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
connection_info(ConnectionHandler, Options) ->
- sync_send_all_state_event(ConnectionHandler, {connection_info, Options}).
+ call(ConnectionHandler, {connection_info, Options}).
%%--------------------------------------------------------------------
--spec channel_info(pid(), channel_id(), [atom()]) -> proplists:proplist().
-%%--------------------------------------------------------------------
+-spec channel_info(connection_ref(),
+ channel_id(),
+ [atom()]
+ ) -> proplists:proplist().
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
channel_info(ConnectionHandler, ChannelId, Options) ->
- sync_send_all_state_event(ConnectionHandler, {channel_info, ChannelId, Options}).
+ call(ConnectionHandler, {channel_info, ChannelId, Options}).
%%--------------------------------------------------------------------
--spec adjust_window(pid(), channel_id(), integer()) -> ok.
-%%--------------------------------------------------------------------
+-spec adjust_window(connection_ref(),
+ channel_id(),
+ integer()
+ ) -> ok.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
adjust_window(ConnectionHandler, Channel, Bytes) ->
- send_all_state_event(ConnectionHandler, {adjust_window, Channel, Bytes}).
+ cast(ConnectionHandler, {adjust_window, Channel, Bytes}).
+
%%--------------------------------------------------------------------
--spec renegotiate(pid()) -> ok.
+-spec close(connection_ref(),
+ channel_id()
+ ) -> ok.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+close(ConnectionHandler, ChannelId) ->
+ case call(ConnectionHandler, {close, ChannelId}) of
+ ok ->
+ ok;
+ {error, closed} ->
+ ok
+ end.
+
+%%====================================================================
+%% Test support
+%%====================================================================
%%--------------------------------------------------------------------
+-spec renegotiate(connection_ref()
+ ) -> ok.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
renegotiate(ConnectionHandler) ->
- send_all_state_event(ConnectionHandler, renegotiate).
+ cast(ConnectionHandler, renegotiate).
%%--------------------------------------------------------------------
--spec renegotiate_data(pid()) -> ok.
-%%--------------------------------------------------------------------
+-spec renegotiate_data(connection_ref()
+ ) -> ok.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
renegotiate_data(ConnectionHandler) ->
- send_all_state_event(ConnectionHandler, data_size).
+ cast(ConnectionHandler, data_size).
-%%--------------------------------------------------------------------
--spec close(pid(), channel_id()) -> ok.
-%%--------------------------------------------------------------------
-close(ConnectionHandler, ChannelId) ->
- sync_send_all_state_event(ConnectionHandler, {close, ChannelId}).
+%%====================================================================
+%% Internal process state
+%%====================================================================
+-record(data, {
+ starter :: pid(),
+ auth_user :: string()
+ | undefined,
+ connection_state :: #connection{},
+ latest_channel_id = 0 :: non_neg_integer(),
+ idle_timer_ref :: undefined
+ | infinity
+ | reference(),
+ idle_timer_value = infinity :: infinity
+ | pos_integer(),
+ transport_protocol :: atom(), % ex: tcp
+ transport_cb :: atom(), % ex: gen_tcp
+ transport_close_tag :: atom(), % ex: tcp_closed
+ ssh_params :: #ssh{}
+ | undefined,
+ socket :: inet:socket(),
+ decrypted_data_buffer = <<>> :: binary(),
+ encrypted_data_buffer = <<>> :: binary(),
+ undecrypted_packet_length :: undefined | non_neg_integer(),
+ key_exchange_init_msg :: #ssh_msg_kexinit{}
+ | undefined,
+ last_size_rekey = 0 :: non_neg_integer(),
+ event_queue = [] :: list(),
+ opts :: proplists:proplist(),
+ inet_initial_recbuf_size :: pos_integer()
+ | undefined
+ }).
+
+%%====================================================================
+%% Intitialisation
+%%====================================================================
%%--------------------------------------------------------------------
--spec stop(pid()) -> ok | {error, term()}.
-%%--------------------------------------------------------------------
-stop(ConnectionHandler)->
- case sync_send_all_state_event(ConnectionHandler, stop) of
- {error, closed} ->
- ok;
- Other ->
- Other
+-spec init_connection_handler(role(),
+ inet:socket(),
+ proplists:proplist()
+ ) -> 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)
end.
-info(ConnectionHandler) ->
- info(ConnectionHandler, {info, all}).
-info(ConnectionHandler, ChannelProcess) ->
- sync_send_all_state_event(ConnectionHandler, {info, ChannelProcess}).
+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)}
+ 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),
+ 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)
+ },
+
+ {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}
+ };
+
+ server ->
+ S0#ssh{s_vsn = Vsn,
+ s_version = Version,
+ io_cb = proplists:get_value(io_cb, Opts, ssh_io),
+ userauth_methods = string:tokens(AuthMethods, ","),
+ kb_tries_left = 3,
+ peer = {undefined, PeerAddr}
+ }
+ end.
+
%%====================================================================
-%% gen_fsm callbacks
+%% gen_statem callbacks
%%====================================================================
-
-%%--------------------------------------------------------------------
--spec hello(socket_control | {info_line, list()} | {version_exchange, list()},
- #state{}) -> gen_fsm_state_return().
%%--------------------------------------------------------------------
+-type event_content() :: any().
+
+-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()}
+ | {key_exchange_dh_gex_reply, client, renegotiate_flag()}
+ | {new_keys, role()}
+ | {service_request, role()}
+ | {userauth, role()}
+ | {userauth_keyboard_interactive, role()}
+ | {connected, role()}
+ .
+
+-type handle_event_result() :: gen_statem:handle_event_result().
+
+-spec handle_event(gen_statem:event_type(),
+ event_content(),
+ state_name(),
+ #data{}
+ ) -> handle_event_result().
+
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+%%% ######## 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;
+
+
+%%% ######## {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),
+ case inet:getopts(Socket=D#data.socket, [recbuf]) of
+ {ok, [{recbuf,Size}]} ->
+ %% Set the socket to the hello text line handling mode:
+ inet:setopts(Socket, [{packet, line},
+ {active, once},
+ % Expecting the version string which might
+ % be max ?MAX_PROTO_VERSION bytes:
+ {recbuf, ?MAX_PROTO_VERSION},
+ {nodelay,true}]),
+ {keep_state, D#data{inet_initial_recbuf_size=Size}};
-hello(socket_control, #state{socket = Socket, ssh_params = Ssh} = State) ->
- VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)),
- send_msg(VsnMsg, State),
- {ok, [{recbuf, Size}]} = inet:getopts(Socket, [recbuf]),
- inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]),
- {next_state, hello, State#state{recbuf = Size}};
-
-hello({info_line, _Line},#state{role = client, socket = Socket} = State) ->
- %% The server may send info lines before the version_exchange
- inet:setopts(Socket, [{active, once}]),
- {next_state, hello, State};
-
-hello({info_line, _Line},#state{role = server} = State) ->
- DisconnectMsg =
- #ssh_msg_disconnect{code =
- ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Did not receive expected protocol version exchange",
- language = "en"},
- handle_disconnect(DisconnectMsg, State);
-
-hello({version_exchange, Version}, #state{ssh_params = Ssh0,
- socket = Socket,
- recbuf = Size} = State) ->
+ Other ->
+ {stop, {shutdown,{unexpected_getopts_return, Other}}}
+ end;
+
+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
+ 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),
+ {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}}
+ end;
+
+handle_event(_, {version_exchange,Version}, {hello,Role}, D) ->
{NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version),
- case handle_version(NumVsn, StrVsn, Ssh0) of
+ case handle_version(NumVsn, StrVsn, D#data.ssh_params) of
{ok, Ssh1} ->
- inet:setopts(Socket, [{packet,0}, {mode,binary}, {active, once}, {recbuf, Size}]),
+ %% Since the hello part is finnished correctly, we set the
+ %% socket to the packet handling mode (including recbuf size):
+ inet:setopts(D#data.socket, [{packet,0},
+ {mode,binary},
+ {active, once},
+ {recbuf, D#data.inet_initial_recbuf_size}]),
{KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1),
- send_msg(SshPacket, State),
- {next_state, kexinit, next_packet(State#state{ssh_params = Ssh,
- key_exchange_init_msg =
- KeyInitMsg})};
+ ok = send_bytes(SshPacket, D),
+ {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg}};
not_supported ->
- DisconnectMsg =
- #ssh_msg_disconnect{code =
- ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
- description = "Protocol version " ++ StrVsn
- ++ " not supported",
- language = "en"},
- handle_disconnect(DisconnectMsg, State)
- end.
+ disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
+ description = ["Protocol version ",StrVsn," not supported"]},
+ {next_state, {hello,Role}, D})
+ end;
-%%--------------------------------------------------------------------
--spec kexinit({#ssh_msg_kexinit{}, binary()}, #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
-kexinit({#ssh_msg_kexinit{} = Kex, Payload},
- #state{ssh_params = #ssh{role = Role} = Ssh0,
- key_exchange_init_msg = OwnKex} =
- State) ->
- Ssh1 = ssh_transport:key_init(opposite_role(Role), Ssh0, Payload),
- case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of
- {ok, NextKexMsg, Ssh} when Role == client ->
- send_msg(NextKexMsg, State),
- {next_state, key_exchange,
- next_packet(State#state{ssh_params = Ssh})};
- {ok, Ssh} when Role == server ->
- {next_state, key_exchange,
- next_packet(State#state{ssh_params = Ssh})}
- end.
+
+%%% ######## {kexinit, client|server, init|renegotiate} ####
-%%--------------------------------------------------------------------
--spec key_exchange(#ssh_msg_kexdh_init{} | #ssh_msg_kexdh_reply{} |
- #ssh_msg_kex_dh_gex_group{} | #ssh_msg_kex_dh_gex_request{} |
- #ssh_msg_kex_dh_gex_request{} | #ssh_msg_kex_dh_gex_reply{}, #state{})
- -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
+handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
+ D = #data{key_exchange_init_msg = OwnKex}) ->
+ 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),
+ Ssh2;
+ {ok, Ssh2} when Role==server ->
+ Ssh2
+ end,
+ {next_state, {key_exchange,Role,ReNeg}, D#data{ssh_params=Ssh}};
-key_exchange(#ssh_msg_kexdh_init{} = Msg,
- #state{ssh_params = #ssh{role = server} = Ssh0} = State) ->
- case ssh_transport:handle_kexdh_init(Msg, Ssh0) of
- {ok, KexdhReply, Ssh1} ->
- send_msg(KexdhReply, State),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- send_msg(NewKeys, State),
- {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}
- end;
-key_exchange(#ssh_msg_kexdh_reply{} = Msg,
- #state{ssh_params = #ssh{role = client} = Ssh0} = State) ->
- {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, Ssh0),
- send_msg(NewKeys, State),
- {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})};
+%%% ######## {key_exchange, client|server, init|renegotiate} ####
+
+%%%---- 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),
+ {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),
+ {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),
+ {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),
+ {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),
+ {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),
+ {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),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
+
-key_exchange(#ssh_msg_kex_dh_gex_group{} = Msg,
- #state{ssh_params = #ssh{role = server} = Ssh0} = State) ->
- {ok, NextKexMsg, Ssh1} = ssh_transport:handle_kex_dh_gex_group(Msg, Ssh0),
- send_msg(NextKexMsg, State),
+%%% ######## {key_exchange_dh_gex_init, server, init|renegotiate} ####
+
+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),
- send_msg(NewKeys, State),
- {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})};
+ ok = send_bytes(NewKeys, D),
+ {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
-key_exchange(#ssh_msg_kex_dh_gex_request{} = Msg,
- #state{ssh_params = #ssh{role = client} = Ssh0} = State) ->
- {ok, NextKexMsg, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0),
- send_msg(NextKexMsg, State),
- {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})};
-key_exchange(#ssh_msg_kex_dh_gex_reply{} = Msg,
- #state{ssh_params = #ssh{role = client} = Ssh0} = State) ->
- {ok, NewKeys, Ssh} = ssh_transport:handle_kex_dh_gex_reply(Msg, Ssh0),
- send_msg(NewKeys, State),
- {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}.
+%%% ######## {key_exchange_dh_gex_reply, client, init|renegotiate} ####
-%%--------------------------------------------------------------------
--spec new_keys(#ssh_msg_newkeys{}, #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
+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}};
-new_keys(#ssh_msg_newkeys{} = Msg, #state{ssh_params = Ssh0} = State0) ->
- {ok, Ssh} = ssh_transport:handle_new_keys(Msg, Ssh0),
- {NextStateName, State} =
- after_new_keys(State0#state{ssh_params = Ssh}),
- {next_state, NextStateName, next_packet(State)}.
-%%--------------------------------------------------------------------
--spec userauth(#ssh_msg_service_request{} | #ssh_msg_service_accept{} |
- #ssh_msg_userauth_request{} | #ssh_msg_userauth_info_request{} |
- #ssh_msg_userauth_info_response{} | #ssh_msg_userauth_success{} |
- #ssh_msg_userauth_failure{} | #ssh_msg_userauth_banner{},
- #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
+%%% ######## {new_keys, client|server} ####
+
+%% First key exchange round:
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,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}};
+
+%% Subsequent key exchange rounds (renegotiation):
+handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, D) ->
+ {next_state, {connected,Role}, D};
-userauth(#ssh_msg_service_request{name = "ssh-userauth"} = Msg,
- #state{ssh_params = #ssh{role = server,
- session_id = SessionId} = Ssh0} = State) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
- send_msg(Reply, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
-
-userauth(#ssh_msg_service_accept{name = "ssh-userauth"},
- #state{ssh_params = #ssh{role = client,
- service = "ssh-userauth"} = Ssh0} =
- State) ->
+%%% ######## {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),
+ {next_state, {userauth,server}, D#data{ssh_params = Ssh}};
+
+ _ ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "Unknown service"},
+ StateName, D)
+ end;
+
+handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client},
+ #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) ->
{Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0),
- send_msg(Msg, State),
- {next_state, userauth, next_packet(State#state{auth_user = Ssh#ssh.user, ssh_params = Ssh})};
-
-userauth(#ssh_msg_userauth_request{service = "ssh-connection",
- method = "none"} = Msg,
- #state{ssh_params = #ssh{session_id = SessionId, role = server,
- service = "ssh-connection"} = Ssh0
- } = State) ->
- {not_authorized, {_User, _Reason}, {Reply, Ssh}} =
- ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
- send_msg(Reply, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
-
-userauth(#ssh_msg_userauth_request{service = "ssh-connection",
- method = Method} = Msg,
- #state{ssh_params = #ssh{session_id = SessionId, role = server,
- service = "ssh-connection",
- peer = {_, Address}} = Ssh0,
- opts = Opts, starter = Pid} = State) ->
- case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of
- {authorized, User, {Reply, Ssh}} ->
- send_msg(Reply, State),
- Pid ! ssh_connected,
- connected_fun(User, Address, Method, Opts),
- {next_state, connected,
- next_packet(State#state{auth_user = User, ssh_params = Ssh})};
- {not_authorized, {User, Reason}, {Reply, Ssh}} ->
- retry_fun(User, Address, Reason, Opts),
- send_msg(Reply, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ ok = send_bytes(Msg, State),
+ {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}};
+
+
+%%% ######## {userauth, client|server} ####
+
+%%---- userauth request to server
+handle_event(_,
+ Msg = #ssh_msg_userauth_request{service = ServiceName, method = Method},
+ StateName = {userauth,server},
+ D = #data{ssh_params=Ssh0}) ->
+
+ case {ServiceName, Ssh0#ssh.service, Method} of
+ {"ssh-connection", "ssh-connection", "none"} ->
+ %% Probably the very first userauth_request but we deny unauthorized login
+ {not_authorized, _, {Reply,Ssh}} =
+ ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0),
+ ok = send_bytes(Reply, D),
+ {keep_state, D#data{ssh_params = Ssh}};
+
+ {"ssh-connection", "ssh-connection", Method} ->
+ %% Userauth request with a method like "password" or so
+ case lists:member(Method, Ssh0#ssh.userauth_methods) of
+ true ->
+ %% 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),
+ D#data.starter ! ssh_connected,
+ connected_fun(User, Method, D),
+ {next_state, {connected,server},
+ D#data{auth_user = User,
+ ssh_params = Ssh#ssh{authenticated = true}}};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" ->
+ retry_fun(User, Reason, D),
+ ok = send_bytes(Reply, D),
+ {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} ->
+ retry_fun(User, Reason, D),
+ ok = send_bytes(Reply, D),
+ {keep_state, D#data{ssh_params = Ssh}}
+ end;
+ false ->
+ %% No we do not support this method (=/= none)
+ %% At least one non-erlang client does like this. Retry as the next event
+ {keep_state_and_data,
+ [{next_event, internal, Msg#ssh_msg_userauth_request{method="none"}}]
+ }
+ end;
+
+ %% {"ssh-connection", Expected, Method} when Expected =/= ServiceName -> Do what?
+ %% {ServiceName, Expected, Method} when Expected =/= ServiceName -> Do what?
+
+ {ServiceName, _, _} when ServiceName =/= "ssh-connection" ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "Unknown service"},
+ StateName, D)
end;
-userauth(#ssh_msg_userauth_info_request{} = Msg,
- #state{ssh_params = #ssh{role = client,
- io_cb = IoCb} = Ssh0} = State) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0),
- send_msg(Reply, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
-
-userauth(#ssh_msg_userauth_info_response{} = Msg,
- #state{ssh_params = #ssh{role = server} = Ssh0} = State) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response(Msg, Ssh0),
- send_msg(Reply, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
-
-userauth(#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client} = Ssh,
- starter = Pid} = State) ->
- Pid ! ssh_connected,
- {next_state, connected, next_packet(State#state{ssh_params =
- Ssh#ssh{authenticated = true}})};
-userauth(#ssh_msg_userauth_failure{},
- #state{ssh_params = #ssh{role = client,
- userauth_methods = []}}
- = State) ->
- Msg = #ssh_msg_disconnect{code =
- ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+%%---- userauth success to client
+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}}};
+
+
+%%---- userauth failure response to client
+handle_event(_, #ssh_msg_userauth_failure{}, {userauth,client}=StateName,
+ D = #data{ssh_params = #ssh{userauth_methods = []}}) ->
+ Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
description = "Unable to connect using the available"
- " authentication methods",
- language = "en"},
- handle_disconnect(Msg, State);
-
-%% Server tells us which authentication methods that are allowed
-userauth(#ssh_msg_userauth_failure{authentications = Methodes},
- #state{ssh_params = #ssh{role = client,
- userauth_methods = none} = Ssh0} = State) ->
- AuthMethods = string:tokens(Methodes, ","),
- Ssh1 = Ssh0#ssh{userauth_methods = AuthMethods},
+ " authentication methods"},
+ disconnect(Msg, StateName, D);
+
+handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client},
+ D = #data{ssh_params = Ssh0}) ->
+ %% The prefered authentication method failed try next method
+ Ssh1 = case Ssh0#ssh.userauth_methods of
+ none ->
+ %% Server tells us which authentication methods that are allowed
+ Ssh0#ssh{userauth_methods = string:tokens(Methods, ",")};
+ _ ->
+ %% We already know...
+ Ssh0
+ end,
case ssh_auth:userauth_request_msg(Ssh1) of
{disconnect, DisconnectMsg, {Msg, Ssh}} ->
- send_msg(Msg, State),
- handle_disconnect(DisconnectMsg, State#state{ssh_params = Ssh});
- {Msg, Ssh} ->
- send_msg(Msg, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ send_bytes(Msg, D),
+ disconnect(DisconnectMsg, StateName, D#data{ssh_params = Ssh});
+ {"keyboard-interactive", {Msg, Ssh}} ->
+ send_bytes(Msg, D),
+ {next_state, {userauth_keyboard_interactive,client}, D#data{ssh_params = Ssh}};
+ {_Method, {Msg, Ssh}} ->
+ send_bytes(Msg, D),
+ {keep_state, D#data{ssh_params = Ssh}}
end;
-%% The prefered authentication method failed try next method
-userauth(#ssh_msg_userauth_failure{},
- #state{ssh_params = #ssh{role = client} = Ssh0} = State) ->
- case ssh_auth:userauth_request_msg(Ssh0) of
- {disconnect, DisconnectMsg,{Msg, Ssh}} ->
- send_msg(Msg, State),
- handle_disconnect(DisconnectMsg, State#state{ssh_params = Ssh});
- {Msg, Ssh} ->
- send_msg(Msg, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+%%---- banner to client
+handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) ->
+ case D#data.ssh_params#ssh.userauth_quiet_mode of
+ false -> io:format("~s", [Msg]);
+ true -> ok
+ end,
+ keep_state_and_data;
+
+
+%%% ######## {userauth_keyboard_interactive, client|server}
+
+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}};
+
+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
+ {authorized, User, {Reply, Ssh}} ->
+ 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}}};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} ->
+ retry_fun(User, Reason, D),
+ send_bytes(Reply, D),
+ {next_state, {userauth,server}, D#data{ssh_params = Ssh}}
end;
-userauth(#ssh_msg_userauth_banner{},
- #state{ssh_params = #ssh{userauth_quiet_mode = true,
- role = client}} = State) ->
- {next_state, userauth, next_packet(State)};
-userauth(#ssh_msg_userauth_banner{message = Msg},
- #state{ssh_params =
- #ssh{userauth_quiet_mode = false, role = client}} = State) ->
- io:format("~s", [Msg]),
- {next_state, userauth, next_packet(State)}.
+handle_event(_, Msg = #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}]};
-%%--------------------------------------------------------------------
--spec connected({#ssh_msg_kexinit{}, binary()}, %%| %% #ssh_msg_kexdh_init{},
- #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
-connected({#ssh_msg_kexinit{}, _Payload} = Event, State) ->
- kexinit(Event, State#state{renegotiate = true}).
-%% ;
-%% connected(#ssh_msg_kexdh_init{} = Event, State) ->
-%% key_exchange(Event, State#state{renegotiate = true}).
+handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth,client}, D, [{next_event, internal, Msg}]};
-%%--------------------------------------------------------------------
--spec handle_event(#ssh_msg_disconnect{} | #ssh_msg_ignore{} | #ssh_msg_debug{} |
- #ssh_msg_unimplemented{} | {adjust_window, integer(), integer()} |
- {reply_request, success | failure, integer()} | renegotiate |
- data_size | {request, pid(), integer(), integer(), iolist()} |
- {request, integer(), integer(), iolist()}, state_name(),
- #state{}) -> gen_fsm_state_return().
+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(_, Msg=#ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth_keyboard_interactive,client}, D, [{next_event, internal, Msg}]};
+
+
+%%% ######## {connected, client|server} ####
+
+handle_event(_, {#ssh_msg_kexinit{},_} = Event, {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}]};
+
+handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) ->
+ {disconnect, _, {{replies,Replies}, _}} =
+ ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName)),
+ {Actions,D} = send_replies(Replies, D0),
+ disconnect_fun(Desc, D),
+ {stop_and_reply, {shutdown,Desc}, Actions, D};
+
+handle_event(_, #ssh_msg_ignore{}, _, _) ->
+ keep_state_and_data;
+
+handle_event(_, #ssh_msg_unimplemented{}, _, _) ->
+ keep_state_and_data;
+
+handle_event(_, #ssh_msg_debug{} = Msg, _, D) ->
+ debug_fun(Msg, D),
+ keep_state_and_data;
+
+handle_event(internal, Msg=#ssh_msg_global_request{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_request_success{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_request_failure{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_open{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_open_confirmation{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_open_failure{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_window_adjust{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_data{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_extended_data{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_eof{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_close{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_request{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_success{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
+
+
+handle_event(cast, renegotiate, {connected,Role}, D) ->
+ {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D#data.ssh_params),
+ send_bytes(SshPacket, D),
+ timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]),
+ {next_state, {kexinit,Role,renegotiate}, D#data{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg}};
+
+handle_event(cast, renegotiate, _, _) ->
+ %% Already in key-exchange so safe to ignore
+ timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), % FIXME: not here in original
+ keep_state_and_data;
-%%--------------------------------------------------------------------
-handle_event(#ssh_msg_disconnect{description = Desc} = DisconnectMsg, _StateName, #state{} = State) ->
- handle_disconnect(peer, DisconnectMsg, State),
- {stop, {shutdown, Desc}, State};
-
-handle_event(#ssh_msg_ignore{}, StateName, State) ->
- {next_state, StateName, next_packet(State)};
-
-handle_event(#ssh_msg_debug{always_display = true, message = DbgMsg},
- StateName, State) ->
- io:format("DEBUG: ~p\n", [DbgMsg]),
- {next_state, StateName, next_packet(State)};
-
-handle_event(#ssh_msg_debug{}, StateName, State) ->
- {next_state, StateName, next_packet(State)};
-
-handle_event(#ssh_msg_unimplemented{}, StateName, State) ->
- {next_state, StateName, next_packet(State)};
-
-handle_event({adjust_window, ChannelId, Bytes}, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- State =
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{recv_window_size = WinSize, remote_id = Id} = Channel ->
- ssh_channel:cache_update(Cache, Channel#channel{recv_window_size =
- WinSize + Bytes}),
- Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes),
- send_replies([{connection_reply, Msg}], State0);
- undefined ->
- State0
- end,
- {next_state, StateName, next_packet(State)};
-
-handle_event({reply_request, success, ChannelId}, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- State = case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = RemoteId} ->
- Msg = ssh_connection:channel_success_msg(RemoteId),
- send_replies([{connection_reply, Msg}], State0);
- undefined ->
- State0
- end,
- {next_state, StateName, State};
-
-handle_event(renegotiate, connected, #state{ssh_params = Ssh0}
- = State) ->
- {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0),
- send_msg(SshPacket, State),
- timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]),
- {next_state, kexinit,
- next_packet(State#state{ssh_params = Ssh,
- key_exchange_init_msg = KeyInitMsg,
- renegotiate = true})};
-
-handle_event(renegotiate, StateName, State) ->
- timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]),
- %% Allready in keyexcahange so ignore
- {next_state, StateName, State};
%% Rekey due to sent data limit reached?
-handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) ->
- {ok, [{send_oct,Sent}]} = inet:getstat(State#state.socket, [send_oct]),
- MaxSent = proplists:get_value(rekey_limit, State#state.opts, 1024000000),
- timer:apply_after(?REKEY_DATA_TIMOUT, gen_fsm, send_all_state_event, [self(), data_size]),
+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),
+ timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]),
case Sent >= MaxSent of
true ->
- {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0),
- send_msg(SshPacket, State),
- {next_state, kexinit,
- next_packet(State#state{ssh_params = Ssh,
- key_exchange_init_msg = KeyInitMsg,
- renegotiate = true})};
+ {KeyInitMsg, SshPacket, Ssh} =
+ ssh_transport:key_exchange_init_msg(D#data.ssh_params),
+ send_bytes(SshPacket, D),
+ {next_state, {kexinit,Role,renegotiate}, D#data{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg,
+ last_size_rekey = Sent0}};
_ ->
- {next_state, connected, next_packet(State)}
+ keep_state_and_data
+ end;
+
+handle_event(cast, data_size, _, _) ->
+ %% Already in key-exchange so safe to ignore
+ timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), % FIXME: not here in original
+ keep_state_and_data;
+
+
+
+handle_event(cast, _, StateName, _) when StateName /= {connected,server},
+ StateName /= {connected,client} ->
+ {keep_state_and_data, [postpone]};
+
+
+handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
+ #channel{recv_window_size = WinSize,
+ recv_window_pending = Pending,
+ recv_packet_size = PktSize} = Channel
+ when (WinSize-Bytes) >= 2*PktSize ->
+ %% The peer can send at least two more *full* packet, no hurry.
+ ssh_channel:cache_update(cache(D),
+ Channel#channel{recv_window_pending = Pending + Bytes}),
+ keep_state_and_data;
+
+ #channel{recv_window_size = WinSize,
+ recv_window_pending = Pending,
+ remote_id = Id} = Channel ->
+ %% Now we have to update the window - we can't receive so many more pkts
+ ssh_channel:cache_update(cache(D),
+ Channel#channel{recv_window_size =
+ WinSize + Bytes + Pending,
+ recv_window_pending = 0}),
+ Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes + Pending),
+ {keep_state, send_msg(Msg,D)};
+
+ undefined ->
+ keep_state_and_data
end;
-handle_event(data_size, StateName, State) ->
- {next_state, StateName, State};
-
-handle_event({request, ChannelPid, ChannelId, Type, Data}, StateName, State0) ->
- {{replies, Replies}, State1} = handle_request(ChannelPid, ChannelId,
- Type, Data,
- false, none, State0),
- State = send_replies(Replies, State1),
- {next_state, StateName, next_packet(State)};
-
-handle_event({request, ChannelId, Type, Data}, StateName, State0) ->
- {{replies, Replies}, State1} = handle_request(ChannelId, Type, Data,
- false, none, State0),
- State = send_replies(Replies, State1),
- {next_state, StateName, next_packet(State)};
-
-handle_event({unknown, Data}, StateName, State) ->
+
+handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
+ #channel{remote_id = RemoteId} ->
+ Msg = ssh_connection:channel_success_msg(RemoteId),
+ {keep_state, send_msg(Msg,D)};
+
+ undefined ->
+ keep_state_and_data
+ end;
+
+handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, {connected,_}, D) ->
+ {keep_state, handle_request(ChannelPid, ChannelId, Type, Data, false, none, D)};
+
+handle_event(cast, {request,ChannelId,Type,Data}, {connected,_}, D) ->
+ {keep_state, handle_request(ChannelId, Type, Data, false, none, D)};
+
+handle_event(cast, {unknown,Data}, {connected,_}, D) ->
Msg = #ssh_msg_unimplemented{sequence = Data},
- send_msg(Msg, State),
- {next_state, StateName, next_packet(State)}.
+ {keep_state, send_msg(Msg,D)};
-%%--------------------------------------------------------------------
--spec handle_sync_event({request, pid(), channel_id(), integer(), binary(), timeout()} |
- {request, channel_id(), integer(), binary(), timeout()} |
- {global_request, pid(), integer(), boolean(), binary()} | {eof, integer()} |
- {open, pid(), integer(), channel_id(), integer(), binary(), _} |
- {send_window, channel_id()} | {recv_window, channel_id()} |
- {connection_info, [client_version | server_version | peer |
- sockname]} | {channel_info, channel_id(), [recv_window |
- send_window]} |
- {close, channel_id()} | stop, term(), state_name(), #state{})
- -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
-handle_sync_event({request, ChannelPid, ChannelId, Type, Data, Timeout}, From, StateName, State0) ->
- {{replies, Replies}, State1} = handle_request(ChannelPid,
- ChannelId, Type, Data,
- true, From, State0),
- %% Note reply to channel will happen later when
- %% reply is recived from peer on the socket
- State = send_replies(Replies, State1),
- start_timeout(ChannelId, From, Timeout),
- handle_idle_timeout(State),
- {next_state, StateName, next_packet(State)};
-
-handle_sync_event({request, ChannelId, Type, Data, Timeout}, From, StateName, State0) ->
- {{replies, Replies}, State1} = handle_request(ChannelId, Type, Data,
- true, From, State0),
- %% Note reply to channel will happen later when
- %% reply is recived from peer on the socket
- State = send_replies(Replies, State1),
- start_timeout(ChannelId, From, Timeout),
- handle_idle_timeout(State),
- {next_state, StateName, next_packet(State)};
-
-handle_sync_event({global_request, Pid, _, _, _} = Request, From, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- State1 = handle_global_request(Request, State0),
- Channel = ssh_channel:cache_find(Pid, Cache),
- State = add_request(true, Channel#channel.local_id, From, State1),
- {next_state, StateName, next_packet(State)};
-
-handle_sync_event({data, ChannelId, Type, Data, Timeout}, From, StateName,
- #state{connection_state = #connection{channel_cache = _Cache}
- = Connection0} = State0) ->
-
- case ssh_connection:channel_data(ChannelId, Type, Data, Connection0, From) of
- {{replies, Replies}, Connection} ->
- State = send_replies(Replies, State0#state{connection_state = Connection}),
- start_timeout(ChannelId, From, Timeout),
- {next_state, StateName, next_packet(State)};
- {noreply, Connection} ->
- start_timeout(ChannelId, From, Timeout),
- {next_state, StateName, next_packet(State0#state{connection_state = Connection})}
+%%% Previously handle_sync_event began here
+handle_event({call,From}, get_print_info, StateName, D) ->
+ Reply =
+ try
+ {inet:sockname(D#data.socket),
+ inet:peername(D#data.socket)
+ }
+ of
+ {{ok,Local}, {ok,Remote}} ->
+ {{Local,Remote},io_lib:format("statename=~p",[StateName])};
+ _ ->
+ {{"-",0},"-"}
+ catch
+ _:_ ->
+ {{"?",0},"?"}
+ end,
+ {keep_state_and_data, [{reply,From,Reply}]};
+
+handle_event({call,From}, {connection_info, Options}, _, D) ->
+ Info = ssh_info(Options, D, []),
+ {keep_state_and_data, [{reply,From,Info}]};
+
+handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
+ #channel{} = Channel ->
+ Info = ssh_channel_info(Options, Channel, []),
+ {keep_state_and_data, [{reply,From,Info}]};
+ undefined ->
+ {keep_state_and_data, [{reply,From,[]}]}
end;
-handle_sync_event({eof, ChannelId}, _From, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
+
+handle_event({call,From}, {info, all}, _, D) ->
+ Result = ssh_channel:cache_foldl(fun(Channel, Acc) ->
+ [Channel | Acc]
+ end,
+ [], cache(D)),
+ {keep_state_and_data, [{reply, From, {ok,Result}}]};
+
+handle_event({call,From}, {info, ChannelPid}, _, D) ->
+ Result = ssh_channel:cache_foldl(
+ fun(Channel, Acc) when Channel#channel.user == ChannelPid ->
+ [Channel | Acc];
+ (_, Acc) ->
+ Acc
+ end, [], cache(D)),
+ {keep_state_and_data, [{reply, From, {ok,Result}}]};
+
+handle_event({call,From}, stop, StateName, D0) ->
+ {disconnect, _Reason, {{replies, Replies}, Connection}} =
+ ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "User closed down connection"},
+ D0#data.connection_state,
+ role(StateName)),
+ {Repls,D} = send_replies(Replies, D0),
+ {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}};
+
+handle_event({call,_}, _, StateName, _) when StateName /= {connected,server},
+ StateName /= {connected,client} ->
+ {keep_state_and_data, [postpone]};
+
+handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+ 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) ->
+ 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) ->
+ {{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) ->
+ case ssh_channel:cache_lookup(cache(D0), ChannelId) of
#channel{remote_id = Id, sent_close = false} ->
- State = send_replies([{connection_reply,
- ssh_connection:channel_eof_msg(Id)}], State0),
- {reply, ok, StateName, next_packet(State)};
+ D = send_msg(ssh_connection:channel_eof_msg(Id), D0),
+ {keep_state, D, [{reply,From,ok}]};
_ ->
- {reply, {error,closed}, StateName, State0}
+ {keep_state, D0, [{reply,From,{error,closed}}]}
end;
-handle_sync_event({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout},
- From, StateName, #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
+handle_event({call,From},
+ {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout},
+ {connected,_},
+ D0) ->
erlang:monitor(process, ChannelPid),
- {ChannelId, State1} = new_channel_id(State0),
- Msg = ssh_connection:channel_open_msg(Type, ChannelId,
- InitialWindowSize,
- MaxPacketSize, Data),
- State2 = send_replies([{connection_reply, Msg}], State1),
- Channel = #channel{type = Type,
- sys = "none",
- user = ChannelPid,
- local_id = ChannelId,
- recv_window_size = InitialWindowSize,
- recv_packet_size = MaxPacketSize},
- ssh_channel:cache_update(Cache, Channel),
- State = add_request(true, ChannelId, From, State2),
- start_timeout(ChannelId, From, Timeout),
- {next_state, StateName, next_packet(remove_timer_ref(State))};
-
-handle_sync_event({send_window, ChannelId}, _From, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State) ->
- Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of
+ {ChannelId, D1} = new_channel_id(D0),
+ D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId,
+ InitialWindowSize,
+ MaxPacketSize, Data),
+ D1),
+ ssh_channel:cache_update(cache(D2),
+ #channel{type = Type,
+ sys = "none",
+ user = ChannelPid,
+ local_id = ChannelId,
+ recv_window_size = InitialWindowSize,
+ recv_packet_size = MaxPacketSize,
+ send_buf = queue:new()
+ }),
+ D = add_request(true, ChannelId, From, D2),
+ start_channel_request_timer(ChannelId, From, Timeout),
+ {keep_state, cache_cancel_idle_timer(D)};
+
+handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) ->
+ Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{send_window_size = WinSize,
send_packet_size = Packsize} ->
{ok, {WinSize, Packsize}};
undefined ->
{error, einval}
end,
- {reply, Reply, StateName, next_packet(State)};
+ {keep_state_and_data, [{reply,From,Reply}]};
-handle_sync_event({recv_window, ChannelId}, _From, StateName,
- #state{connection_state = #connection{channel_cache = Cache}}
- = State) ->
-
- Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of
+handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) ->
+ Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
recv_packet_size = Packsize} ->
{ok, {WinSize, Packsize}};
undefined ->
{error, einval}
end,
- {reply, Reply, StateName, next_packet(State)};
-
-handle_sync_event(get_print_info, _From, StateName, State) ->
- Reply =
- try
- {inet:sockname(State#state.socket),
- inet:peername(State#state.socket)
- }
- of
- {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])};
- _ -> {{"-",0},"-"}
- catch
- _:_ -> {{"?",0},"?"}
- end,
- {reply, Reply, StateName, State};
-
-handle_sync_event({connection_info, Options}, _From, StateName, State) ->
- Info = ssh_info(Options, State, []),
- {reply, Info, StateName, State};
+ {keep_state_and_data, [{reply,From,Reply}]};
-handle_sync_event({channel_info, ChannelId, Options}, _From, StateName,
- #state{connection_state = #connection{channel_cache = Cache}} = State) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{} = Channel ->
- Info = ssh_channel_info(Options, Channel, []),
- {reply, Info, StateName, State};
+handle_event({call,From}, {close, ChannelId}, {connected,_}, D0) ->
+ case ssh_channel:cache_lookup(cache(D0), ChannelId) of
+ #channel{remote_id = Id} = Channel ->
+ D1 = send_msg(ssh_connection:channel_close_msg(Id), D0),
+ ssh_channel:cache_update(cache(D1), Channel#channel{sent_close = true}),
+ {keep_state, cache_request_idle_timer_check(D1), [{reply,From,ok}]};
undefined ->
- {reply, [], StateName, State}
+ {keep_state_and_data, [{reply,From,ok}]}
end;
-handle_sync_event({info, ChannelPid}, _From, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State) ->
- Result = ssh_channel:cache_foldl(
- fun(Channel, Acc) when ChannelPid == all;
- Channel#channel.user == ChannelPid ->
- [Channel | Acc];
- (_, Acc) ->
- Acc
- end, [], Cache),
- {reply, {ok, Result}, StateName, State};
-
-handle_sync_event({close, ChannelId}, _, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- State =
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = Id} = Channel ->
- State1 = send_replies([{connection_reply,
- ssh_connection:channel_close_msg(Id)}], State0),
- ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}),
- handle_idle_timeout(State1),
- State1;
- undefined ->
- State0
- end,
- {reply, ok, StateName, next_packet(State)};
-handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0,
- role = Role,
- opts = Opts} = State0) ->
- {disconnect, Reason, {{replies, Replies}, Connection}} =
- ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "User closed down connection",
- language = "en"}, Connection0, Role),
- State = send_replies(Replies, State0),
- SSHOpts = proplists:get_value(ssh_opts, Opts),
- disconnect_fun(Reason, SSHOpts),
- {stop, normal, ok, State#state{connection_state = Connection}}.
+%%===== Reception of encrypted bytes, decryption and framing
+handle_event(info, {Proto, Sock, Info}, {hello,_}, #data{socket = Sock,
+ transport_protocol = Proto}) ->
+ case Info of
+ "SSH-" ++ _ ->
+ {keep_state_and_data, [{next_event, internal, {version_exchange,Info}}]};
+ _ ->
+ {keep_state_and_data, [{next_event, internal, {info_line,Info}}]}
+ end;
-%%--------------------------------------------------------------------
--spec handle_info({atom(), port(), binary()} | {atom(), port()} |
- term (), state_name(), #state{}) -> gen_fsm_state_return().
-%%--------------------------------------------------------------------
+handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock,
+ transport_protocol = Proto}) ->
+ try ssh_transport:handle_packet_part(
+ D0#data.decrypted_data_buffer,
+ <<(D0#data.encrypted_data_buffer)/binary, NewData/binary>>,
+ D0#data.undecrypted_packet_length,
+ D0#data.ssh_params)
+ of
+ {packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} ->
+ D = D0#data{ssh_params =
+ Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)},
+ decrypted_data_buffer = <<>>,
+ undecrypted_packet_length = undefined,
+ encrypted_data_buffer = EncryptedDataRest},
+ try
+ ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D))
+ of
+ Msg = #ssh_msg_kexinit{} ->
+ {keep_state, D, [{next_event, internal, {Msg,DecryptedBytes}},
+ {next_event, internal, prepare_next_packet}
+ ]};
+ Msg ->
+ {keep_state, D, [{next_event, internal, Msg},
+ {next_event, internal, prepare_next_packet}
+ ]}
+ catch
+ _C:_E ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Encountered unexpected input"},
+ StateName, D)
+ end;
-handle_info({Protocol, Socket, "SSH-" ++ _ = Version}, hello,
- #state{socket = Socket,
- transport_protocol = Protocol} = State ) ->
- event({version_exchange, Version}, hello, State);
-
-handle_info({Protocol, Socket, Info}, hello,
- #state{socket = Socket,
- transport_protocol = Protocol} = State) ->
- event({info_line, Info}, hello, State);
-
-handle_info({Protocol, Socket, Data}, Statename,
- #state{socket = Socket,
- transport_protocol = Protocol,
- ssh_params = #ssh{decrypt_block_size = BlockSize,
- recv_mac_size = MacSize} = Ssh0,
- decoded_data_buffer = <<>>,
- encoded_data_buffer = EncData0} = State0) ->
-
- %% Implementations SHOULD decrypt the length after receiving the
- %% first 8 (or cipher block size, whichever is larger) bytes of a
- %% packet. (RFC 4253: Section 6 - Binary Packet Protocol)
- case size(EncData0) + size(Data) >= erlang:max(8, BlockSize) of
- true ->
- {Ssh, SshPacketLen, DecData, EncData} =
-
- ssh_transport:decrypt_first_block(<<EncData0/binary,
- Data/binary>>, Ssh0),
- case SshPacketLen > ?SSH_MAX_PACKET_SIZE of
- true ->
- DisconnectMsg =
- #ssh_msg_disconnect{code =
- ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad packet length "
- ++ integer_to_list(SshPacketLen),
- language = "en"},
- handle_disconnect(DisconnectMsg, State0);
- false ->
- RemainingSshPacketLen =
- (SshPacketLen + ?SSH_LENGHT_INDICATOR_SIZE) -
- BlockSize + MacSize,
- State = State0#state{ssh_params = Ssh},
- handle_ssh_packet_data(RemainingSshPacketLen,
- DecData, EncData, Statename,
- State)
- end;
- false ->
- {next_state, Statename,
- next_packet(State0#state{encoded_data_buffer =
- <<EncData0/binary, Data/binary>>})}
+ {get_more, DecryptedBytes, EncryptedDataRest, RemainingSshPacketLen, Ssh1} ->
+ %% Here we know that there are not enough bytes in
+ %% EncryptedDataRest to use. We must wait for more.
+ inet:setopts(Sock, [{active, once}]),
+ {keep_state, D0#data{encrypted_data_buffer = EncryptedDataRest,
+ decrypted_data_buffer = DecryptedBytes,
+ undecrypted_packet_length = RemainingSshPacketLen,
+ ssh_params = Ssh1}};
+
+ {bad_mac, Ssh1} ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad mac"},
+ StateName, D0#data{ssh_params=Ssh1});
+
+ {error, {exceeds_max_size,PacketLen}} ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad packet length "
+ ++ integer_to_list(PacketLen)},
+ StateName, D0)
+ catch
+ _C:_E ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad packet"},
+ StateName, D0)
end;
-handle_info({Protocol, Socket, Data}, Statename,
- #state{socket = Socket,
- transport_protocol = Protocol,
- decoded_data_buffer = DecData,
- encoded_data_buffer = EncData,
- undecoded_packet_length = Len} =
- State) when is_integer(Len) ->
- handle_ssh_packet_data(Len, DecData, <<EncData/binary, Data/binary>>,
- Statename, State);
-
-handle_info({CloseTag, _Socket}, _StateName,
- #state{transport_close_tag = CloseTag,
- ssh_params = #ssh{role = _Role, opts = _Opts}} = State) ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "Connection closed",
- language = "en"},
- handle_disconnect(DisconnectMsg, State);
-
-handle_info({timeout, {_, From} = Request}, Statename,
- #state{connection_state = #connection{requests = Requests} = Connection} = State) ->
+
+%%%====
+handle_event(internal, prepare_next_packet, _, D) ->
+ Enough = erlang:max(8, D#data.ssh_params#ssh.decrypt_block_size),
+ case size(D#data.encrypted_data_buffer) of
+ Sz when Sz >= Enough ->
+ self() ! {D#data.transport_protocol, D#data.socket, <<>>};
+ _ ->
+ inet:setopts(D#data.socket, [{active, once}])
+ end,
+ 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);
+
+handle_event(info, {timeout, {_, From} = Request}, _,
+ #data{connection_state = #connection{requests = Requests} = C0} = D) ->
case lists:member(Request, Requests) of
true ->
- gen_fsm:reply(From, {error, timeout}),
- {next_state, Statename,
- State#state{connection_state =
- Connection#connection{requests =
- lists:delete(Request, Requests)}}};
+ %% A channel request is not answered in time. Answer {error,timeout}
+ %% to the caller
+ C = C0#connection{requests = lists:delete(Request, Requests)},
+ {keep_state, D#data{connection_state=C}, [{reply,From,{error,timeout}}]};
false ->
- {next_state, Statename, State}
+ %% The request is answered - just ignore the timeout
+ keep_state_and_data
end;
%%% Handle that ssh channels user process goes down
-handle_info({'DOWN', _Ref, process, ChannelPid, _Reason}, Statename, State0) ->
- {{replies, Replies}, State1} = handle_channel_down(ChannelPid, State0),
- State = send_replies(Replies, State1),
- {next_state, Statename, next_packet(State)};
+handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D0) ->
+ {{replies, Replies}, D1} = handle_channel_down(ChannelPid, D0),
+ {Repls, D} = send_replies(Replies, D1),
+ {keep_state, D, Repls};
%%% So that terminate will be run when supervisor is shutdown
-handle_info({'EXIT', _Sup, Reason}, _StateName, State) ->
- {stop, {shutdown, Reason}, State};
-
-handle_info({check_cache, _ , _},
- StateName, #state{connection_state =
- #connection{channel_cache = Cache}} = State) ->
- {next_state, StateName, check_cache(State, Cache)};
-
-handle_info(UnexpectedMessage, StateName, #state{ssh_params = SshParams} = State) ->
- Msg = lists:flatten(io_lib:format(
- "Unexpected message '~p' received in state '~p'\n"
- "Role: ~p\n"
- "Peer: ~p\n"
- "Local Address: ~p\n", [UnexpectedMessage, StateName,
- SshParams#ssh.role, SshParams#ssh.peer,
- proplists:get_value(address, SshParams#ssh.opts)])),
- error_logger:info_report(Msg),
- {next_state, StateName, State}.
+handle_event(info, {'EXIT', _Sup, Reason}, _, _) ->
+ {stop, {shutdown, Reason}};
+
+handle_event(info, check_cache, _, D) ->
+ {keep_state, cache_check_set_idle_timer(D)};
+
+handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) ->
+ case unexpected_fun(UnexpectedMessage, D) of
+ report ->
+ Msg = lists:flatten(
+ io_lib:format(
+ "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)])),
+ error_logger:info_report(Msg),
+ keep_state_and_data;
+
+ skip ->
+ keep_state_and_data;
+
+ Other ->
+ Msg = lists:flatten(
+ io_lib:format("Call to fun in 'unexpectedfun' failed:~n"
+ "Return: ~p\n"
+ "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)]
+ )),
+ error_logger:error_report(Msg),
+ keep_state_and_data
+ end;
+
+handle_event(internal, {disconnect,Msg,_Reason}, StateName, D) ->
+ disconnect(Msg, StateName, D);
+
+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";
+ _ ->
+ "Internal error"
+ end,
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = Descr},
+ StateName, D).
+
%%--------------------------------------------------------------------
--spec terminate(Reason::term(), state_name(), #state{}) -> _.
-%%--------------------------------------------------------------------
-terminate(normal, _, #state{transport_cb = Transport,
- connection_state = Connection,
- socket = Socket}) ->
- terminate_subsytem(Connection),
- (catch Transport:close(Socket)),
- ok;
+-spec terminate(any(),
+ state_name(),
+ #data{}
+ ) -> finalize_termination_result() .
+
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+terminate(normal, StateName, State) ->
+ finalize_termination(StateName, State);
terminate({shutdown,{init,Reason}}, StateName, State) ->
error_logger:info_report(io_lib:format("Erlang ssh in connection handler init: ~p~n",[Reason])),
- terminate(normal, StateName, State);
-
-%% Terminated by supervisor
-terminate(shutdown, StateName, #state{ssh_params = Ssh0} = State) ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "Application shutdown",
- language = "en"},
- {SshPacket, Ssh} = ssh_transport:ssh_packet(DisconnectMsg, Ssh0),
- send_msg(SshPacket, State),
- terminate(normal, StateName, State#state{ssh_params = Ssh});
-
-terminate({shutdown, #ssh_msg_disconnect{} = Msg}, StateName,
- #state{ssh_params = Ssh0} = State) ->
- {SshPacket, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0),
- send_msg(SshPacket, State),
- terminate(normal, StateName, State#state{ssh_params = Ssh});
-
-terminate({shutdown, _}, StateName, State) ->
- terminate(normal, StateName, State);
-
-terminate(Reason, StateName, #state{ssh_params = Ssh0, starter = _Pid,
- connection_state = Connection} = State) ->
- terminate_subsytem(Connection),
+ finalize_termination(StateName, State);
+
+terminate(shutdown, StateName, State0) ->
+ %% Terminated by supervisor
+ 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) ->
+ finalize_termination(StateName, State);
+
+terminate(Reason, StateName, State0) ->
+ %% Others, e.g undef, {badmatch,_}
log_error(Reason),
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "Internal error",
- language = "en"},
- {SshPacket, Ssh} = ssh_transport:ssh_packet(DisconnectMsg, Ssh0),
- send_msg(SshPacket, State),
- terminate(normal, StateName, State#state{ssh_params = Ssh}).
-
-
-terminate_subsytem(#connection{system_supervisor = SysSup,
- sub_system_supervisor = SubSysSup}) when is_pid(SubSysSup) ->
- ssh_system_sup:stop_subsystem(SysSup, SubSysSup);
-terminate_subsytem(_) ->
- ok.
-
-format_status(normal, [_, State]) ->
- [{data, [{"StateData", State}]}];
-format_status(terminate, [_, State]) ->
- SshParams0 = (State#state.ssh_params),
- SshParams = SshParams0#ssh{c_keyinit = "***",
- s_keyinit = "***",
- send_mac_key = "***",
- send_mac_size = "***",
- recv_mac_key = "***",
- recv_mac_size = "***",
- encrypt_keys = "***",
- encrypt_ctx = "***",
- decrypt_keys = "***",
- decrypt_ctx = "***",
- compress_ctx = "***",
- decompress_ctx = "***",
- shared_secret = "***",
- exchanged_hash = "***",
- session_id = "***",
- keyex_key = "***",
- keyex_info = "***",
- available_host_keys = "***"},
- [{data, [{"StateData", State#state{decoded_data_buffer = "***",
- encoded_data_buffer = "***",
- key_exchange_init_msg = "***",
- opts = "***",
- recbuf = "***",
- ssh_params = SshParams
- }}]}].
+ State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "Internal error"},
+ State0),
+ finalize_termination(StateName, State).
%%--------------------------------------------------------------------
--spec code_change(OldVsn::term(), state_name(), Oldstate::term(), Extra::term()) ->
- {ok, state_name(), #state{}}.
+
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+format_status(normal, [_, _StateName, D]) ->
+ [{data, [{"State", D}]}];
+format_status(terminate, [_, _StateName, D]) ->
+ DataPropList0 = fmt_stat_rec(record_info(fields, data), D,
+ [decrypted_data_buffer,
+ encrypted_data_buffer,
+ key_exchange_init_msg,
+ user_passwords,
+ opts,
+ inet_initial_recbuf_size]),
+ SshPropList = fmt_stat_rec(record_info(fields, ssh), D#data.ssh_params,
+ [c_keyinit,
+ s_keyinit,
+ send_mac_key,
+ send_mac_size,
+ recv_mac_key,
+ recv_mac_size,
+ encrypt_keys,
+ encrypt_ctx,
+ decrypt_keys,
+ decrypt_ctx,
+ compress_ctx,
+ decompress_ctx,
+ shared_secret,
+ exchanged_hash,
+ session_id,
+ keyex_key,
+ keyex_info,
+ available_host_keys]),
+ DataPropList = lists:keyreplace(ssh_params, 1, DataPropList0,
+ {ssh_params,SshPropList}),
+ [{data, [{"State", DataPropList}]}].
+
+
+fmt_stat_rec(FieldNames, Rec, Exclude) ->
+ Values = tl(tuple_to_list(Rec)),
+ [P || {K,_} = P <- lists:zip(FieldNames, Values),
+ not lists:member(K, Exclude)].
+
%%--------------------------------------------------------------------
+-spec code_change(term() | {down,term()},
+ state_name(),
+ #data{},
+ term()
+ ) -> {gen_statem:callback_mode(), state_name(), #data{}}.
+
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
code_change(_OldVsn, StateName, State, _Extra) ->
- {ok, StateName, State}.
+ {handle_event_function, StateName, State}.
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-init_role(#state{role = client, opts = Opts} = State0) ->
- Pid = proplists:get_value(user_pid, Opts),
- TimerRef = get_idle_time(Opts),
- timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]),
- timer:apply_after(?REKEY_DATA_TIMOUT, gen_fsm, send_all_state_event,
- [self(), data_size]),
- State0#state{starter = Pid,
- idle_timer_ref = TimerRef};
-init_role(#state{role = server, opts = Opts, connection_state = Connection} = State) ->
- Sups = proplists:get_value(supervisors, Opts),
- Pid = proplists:get_value(user_pid, Opts),
- SystemSup = proplists:get_value(system_sup, Sups),
- SubSystemSup = proplists:get_value(subsystem_sup, Sups),
+%% Starting
+
+start_the_connection_child(UserPid, Role, Socket, Options) ->
+ Sups = proplists:get_value(supervisors, Options),
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]}),
- State#state{starter = Pid, connection_state = Connection#connection{
- cli_spec = CliSpec,
- exec = Exec,
- system_supervisor = SystemSup,
- sub_system_supervisor = SubSystemSup,
- connection_supervisor = ConnectionSup
- }}.
-
-get_idle_time(SshOptions) ->
- case proplists:get_value(idle_time, SshOptions) of
- infinity ->
- infinity;
- _IdleTime -> %% We dont want to set the timeout on first connect
- undefined
- end.
+ Opts = [{supervisors, Sups}, {user_pid, UserPid} | proplists:get_value(ssh_opts, Options, [])],
+ {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Opts]),
+ ok = socket_control(Socket, Pid, Options),
+ Pid.
-init_ssh(client = Role, Vsn, Version, Options, Socket) ->
- IOCb = case proplists:get_value(user_interaction, Options, true) of
- true ->
- ssh_io;
- false ->
- ssh_no_io
- end,
+%%--------------------------------------------------------------------
+%% Stopping
+-type finalize_termination_result() :: ok .
+
+finalize_termination(_StateName, #data{transport_cb = Transport,
+ connection_state = Connection,
+ socket = Socket}) ->
+ case Connection of
+ #connection{system_supervisor = SysSup,
+ sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) ->
+ ssh_system_sup:stop_subsystem(SysSup, SubSysSup);
+ _ ->
+ do_nothing
+ end,
+ (catch Transport:close(Socket)),
+ ok.
- AuthMethods = proplists:get_value(auth_methods, Options,
- ?SUPPORTED_AUTH_METHODS),
- {ok, PeerAddr} = inet:peername(Socket),
-
- PeerName = proplists:get_value(host, Options),
- KeyCb = proplists:get_value(key_cb, Options, ssh_file),
-
- #ssh{role = Role,
- c_vsn = Vsn,
- c_version = Version,
- key_cb = KeyCb,
- io_cb = IOCb,
- userauth_quiet_mode = proplists:get_value(quiet_mode, Options, false),
- opts = Options,
- userauth_supported_methods = AuthMethods,
- peer = {PeerName, PeerAddr},
- available_host_keys = supported_host_keys(Role, KeyCb, Options)
- };
-
-init_ssh(server = Role, Vsn, Version, Options, Socket) ->
-
- AuthMethods = proplists:get_value(auth_methods, Options,
- ?SUPPORTED_AUTH_METHODS),
- {ok, PeerAddr} = inet:peername(Socket),
- KeyCb = proplists:get_value(key_cb, Options, ssh_file),
-
- #ssh{role = Role,
- s_vsn = Vsn,
- s_version = Version,
- key_cb = KeyCb,
- io_cb = proplists:get_value(io_cb, Options, ssh_io),
- opts = Options,
- userauth_supported_methods = AuthMethods,
- peer = {undefined, PeerAddr},
- available_host_keys = supported_host_keys(Role, KeyCb, Options)
- }.
+%%--------------------------------------------------------------------
+%% "Invert" the Role
+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 extract_algs(proplists:get_value(pref_public_key_algs, Options, false), []) of
- false ->
- ["ssh-rsa", "ssh-dss"];
- Algs ->
- Algs
+ 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
+ of
+ [] ->
+ {stop, {shutdown, "No public key algs"}};
+ Algs ->
+ [atom_to_list(A) || A<-Algs]
catch
exit:Reason ->
{stop, {shutdown, Reason}}
end;
supported_host_keys(server, KeyCb, Options) ->
- lists:foldl(fun(Type, Acc) ->
- case available_host_key(KeyCb, Type, Options) of
- {error, _} ->
- Acc;
- Alg ->
- [Alg | Acc]
- end
- end, [],
- %% Prefered alg last so no need to reverse
- ["ssh-dss", "ssh-rsa"]).
-extract_algs(false, _) ->
- false;
-extract_algs([],[]) ->
- false;
-extract_algs([], NewList) ->
- lists:reverse(NewList);
-extract_algs([H|T], NewList) ->
- case H of
- 'ssh-dss' ->
- extract_algs(T, ["ssh-dss"|NewList]);
- 'ssh-rsa' ->
- extract_algs(T, ["ssh-rsa"|NewList])
- end.
-available_host_key(KeyCb, "ssh-dss"= Alg, Opts) ->
- case KeyCb:host_key('ssh-dss', Opts) of
- {ok, _} ->
- Alg;
- Other ->
- Other
- end;
-available_host_key(KeyCb, "ssh-rsa" = Alg, Opts) ->
- case KeyCb:host_key('ssh-rsa', Opts) of
- {ok, _} ->
- Alg;
- Other ->
- Other
- end.
+ [atom_to_list(A) || A <- proplists:get_value(public_key,
+ proplists:get_value(preferred_algorithms,Options,[]),
+ ssh_transport:default_algorithms(public_key)
+ ),
+ available_host_key(KeyCb, A, Options)
+ ].
+
+%% Alg :: atom()
+available_host_key(KeyCb, Alg, Opts) ->
+ element(1, catch KeyCb:host_key(Alg, Opts)) == ok.
-send_msg(Msg, #state{socket = Socket, transport_cb = Transport}) ->
- Transport:send(Socket, Msg).
-handle_version({2, 0} = NumVsn, StrVsn, Ssh0) ->
+send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) ->
+ {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0),
+ send_bytes(Bytes, State),
+ State#data{ssh_params=Ssh}.
+
+send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) ->
+ Transport:send(Socket, Bytes).
+
+handle_version({2, 0} = NumVsn, StrVsn, Ssh0) ->
Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0),
{ok, Ssh};
handle_version(_,_,_) ->
@@ -1194,401 +1489,181 @@ string_version(#ssh{role = client, c_version = Vsn}) ->
string_version(#ssh{role = server, s_version = Vsn}) ->
Vsn.
-send_event(FsmPid, Event) ->
- gen_fsm:send_event(FsmPid, Event).
-send_all_state_event(FsmPid, Event) ->
- gen_fsm:send_all_state_event(FsmPid, Event).
+cast(FsmPid, Event) ->
+ gen_statem:cast(FsmPid, Event).
-sync_send_all_state_event(FsmPid, Event) ->
- sync_send_all_state_event(FsmPid, Event, infinity).
+call(FsmPid, Event) ->
+ call(FsmPid, Event, infinity).
-sync_send_all_state_event(FsmPid, Event, Timeout) ->
- try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout)
+call(FsmPid, Event, Timeout) ->
+ try gen_statem:call(FsmPid, Event, Timeout) of
+ {closed, _R} ->
+ {error, closed};
+ {killed, _R} ->
+ {error, closed};
+ Result ->
+ Result
catch
- exit:{noproc, _} ->
+ exit:{noproc, _R} ->
{error, closed};
- exit:{normal, _} ->
+ exit:{normal, _R} ->
{error, closed};
- exit:{{shutdown, _},_} ->
+ exit:{{shutdown, _R},_} ->
{error, closed}
end.
-%% simulate send_all_state_event(self(), Event)
-event(#ssh_msg_disconnect{} = Event, StateName, State) ->
- handle_event(Event, StateName, State);
-event(#ssh_msg_ignore{} = Event, StateName, State) ->
- handle_event(Event, StateName, State);
-event(#ssh_msg_debug{} = Event, StateName, State) ->
- handle_event(Event, StateName, State);
-event(#ssh_msg_unimplemented{} = Event, StateName, State) ->
- handle_event(Event, StateName, State);
-%% simulate send_event(self(), Event)
-event(Event, StateName, State) ->
- try
- ?MODULE:StateName(Event, State)
- catch
- throw:#ssh_msg_disconnect{} = DisconnectMsg ->
- handle_disconnect(DisconnectMsg, State);
- throw:{ErrorToDisplay, #ssh_msg_disconnect{} = DisconnectMsg} ->
- handle_disconnect(DisconnectMsg, State, ErrorToDisplay);
- _:Error ->
- log_error(Error),
- handle_disconnect(#ssh_msg_disconnect{code = error_code(StateName),
- description = "Internal error",
- language = "en"}, State)
- end.
-error_code(key_exchange) ->
- ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED;
-error_code(new_keys) ->
- ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED;
-error_code(_) ->
- ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE.
-
-generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName,
- #state{
- role = Role,
- starter = User,
- opts = Opts,
- renegotiate = Renegotiation,
- connection_state = Connection0} = State0, EncData)
- when Byte == ?SSH_MSG_GLOBAL_REQUEST;
- Byte == ?SSH_MSG_REQUEST_SUCCESS;
- Byte == ?SSH_MSG_REQUEST_FAILURE;
- Byte == ?SSH_MSG_CHANNEL_OPEN;
- Byte == ?SSH_MSG_CHANNEL_OPEN_CONFIRMATION;
- Byte == ?SSH_MSG_CHANNEL_OPEN_FAILURE;
- Byte == ?SSH_MSG_CHANNEL_WINDOW_ADJUST;
- Byte == ?SSH_MSG_CHANNEL_DATA;
- Byte == ?SSH_MSG_CHANNEL_EXTENDED_DATA;
- Byte == ?SSH_MSG_CHANNEL_EOF;
- Byte == ?SSH_MSG_CHANNEL_CLOSE;
- Byte == ?SSH_MSG_CHANNEL_REQUEST;
- Byte == ?SSH_MSG_CHANNEL_SUCCESS;
- Byte == ?SSH_MSG_CHANNEL_FAILURE ->
- ConnectionMsg = ssh_message:decode(Msg),
- State1 = generate_event_new_state(State0, EncData),
- try ssh_connection:handle_msg(ConnectionMsg, Connection0, Role) of
+
+handle_connection_msg(Msg, StateName, D0 = #data{starter = User,
+ connection_state = Connection0,
+ event_queue = Qev0}) ->
+ Renegotiation = renegotiation(StateName),
+ Role = role(StateName),
+ try ssh_connection:handle_msg(Msg, Connection0, Role) of
{{replies, Replies}, Connection} ->
- State = send_replies(Replies, State1#state{connection_state = Connection}),
- {next_state, StateName, next_packet(State)};
+ {Repls, D} =
+ case StateName of
+ {connected,_} ->
+ send_replies(Replies, D0#data{connection_state=Connection});
+ _ ->
+ {ConnReplies, NonConnReplies} = lists:splitwith(fun not_connected_filter/1, Replies),
+ send_replies(NonConnReplies, D0#data{event_queue = Qev0 ++ ConnReplies})
+ end,
+ {keep_state, D, Repls};
+
{noreply, Connection} ->
- {next_state, StateName, next_packet(State1#state{connection_state = Connection})};
- {disconnect, {_, Reason}, {{replies, Replies}, Connection}} when
- Role == client andalso ((StateName =/= connected) and (not Renegotiation)) ->
- State = send_replies(Replies, State1#state{connection_state = Connection}),
- User ! {self(), not_connected, Reason},
- {stop, {shutdown, normal},
- next_packet(State#state{connection_state = Connection})};
- {disconnect, Reason, {{replies, Replies}, Connection}} ->
- State = send_replies(Replies, State1#state{connection_state = Connection}),
- SSHOpts = proplists:get_value(ssh_opts, Opts),
- disconnect_fun(Reason, SSHOpts),
- {stop, {shutdown, normal}, State#state{connection_state = Connection}}
+ {keep_state, D0#data{connection_state = Connection}};
+
+ {disconnect, Reason0, {{replies, Replies}, Connection}} ->
+ {Repls, D} = send_replies(Replies, D0#data{connection_state = Connection}),
+ case {Reason0,Role} of
+ {{_, Reason}, client} when ((StateName =/= {connected,client}) and (not Renegotiation)) ->
+ User ! {self(), not_connected, Reason};
+ _ ->
+ ok
+ end,
+ {stop_and_reply, {shutdown,normal}, Repls, D#data{connection_state = Connection}}
+
catch
_:Error ->
- {disconnect, Reason, {{replies, Replies}, Connection}} =
+ {disconnect, _Reason, {{replies, Replies}, Connection}} =
ssh_connection:handle_msg(
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "Internal error",
- language = "en"}, Connection0, Role),
- State = send_replies(Replies, State1#state{connection_state = Connection}),
- SSHOpts = proplists:get_value(ssh_opts, Opts),
- disconnect_fun(Reason, SSHOpts),
- {stop, {shutdown, Error}, State#state{connection_state = Connection}}
+ description = "Internal error"},
+ Connection0, Role),
+ {Repls, D} = send_replies(Replies, D0#data{connection_state = Connection}),
+ {stop_and_reply, {shutdown,Error}, Repls, D#data{connection_state = Connection}}
+ end.
+
+
+set_kex_overload_prefix(Msg = <<?BYTE(Op),_/binary>>, #data{ssh_params=SshParams})
+ when Op == 30;
+ Op == 31
+ ->
+ case catch atom_to_list(kex(SshParams)) of
+ "ecdh-sha2-" ++ _ ->
+ <<"ecdh",Msg/binary>>;
+ "diffie-hellman-group-exchange-" ++ _ ->
+ <<"dh_gex",Msg/binary>>;
+ "diffie-hellman-group" ++ _ ->
+ <<"dh",Msg/binary>>;
+ _ ->
+ Msg
end;
+set_kex_overload_prefix(Msg, _) ->
+ Msg.
-generate_event(Msg, StateName, State0, EncData) ->
- try
- Event = ssh_message:decode(Msg),
- State = generate_event_new_state(State0, EncData),
- case Event of
- #ssh_msg_kexinit{} ->
- %% We need payload for verification later.
- event({Event, Msg}, StateName, State);
- _ ->
- event(Event, StateName, State)
- end
- catch
- _:_ ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Encountered unexpected input",
- language = "en"},
- handle_disconnect(DisconnectMsg, State0)
- end.
-
-
-
-handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
+kex(#ssh{algorithms=#alg{kex=Kex}}) -> Kex;
+kex(_) -> undefined.
+
+cache(#data{connection_state=C}) -> C#connection.channel_cache.
+
+
+%%%----------------------------------------------------------------
+handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{remote_id = Id} = Channel ->
- update_sys(Cache, Channel, Type, ChannelPid),
- Msg = ssh_connection:channel_request_msg(Id, Type,
- WantReply, Data),
- Replies = [{connection_reply, Msg}],
- State = add_request(WantReply, ChannelId, From, State0),
- {{replies, Replies}, State};
+ update_sys(cache(D), Channel, Type, ChannelPid),
+ send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data),
+ add_request(WantReply, ChannelId, From, D));
undefined ->
- {{replies, []}, State0}
+ D
end.
-handle_request(ChannelId, Type, Data, WantReply, From,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = Id} ->
- Msg = ssh_connection:channel_request_msg(Id, Type,
- WantReply, Data),
- Replies = [{connection_reply, Msg}],
- State = add_request(WantReply, ChannelId, From, State0),
- {{replies, Replies}, State};
+handle_request(ChannelId, Type, Data, WantReply, From, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
+ #channel{remote_id = Id} ->
+ send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data),
+ add_request(WantReply, ChannelId, From, D));
undefined ->
- {{replies, []}, State0}
- end.
-
-handle_global_request({global_request, ChannelPid,
- "tcpip-forward" = Type, WantReply,
- <<?UINT32(IPLen),
- IP:IPLen/binary, ?UINT32(Port)>> = Data},
- #state{connection_state =
- #connection{channel_cache = Cache}
- = Connection0} = State) ->
- ssh_channel:cache_update(Cache, #channel{user = ChannelPid,
- type = "forwarded-tcpip",
- sys = none}),
- Connection = ssh_connection:bind(IP, Port, ChannelPid, Connection0),
- Msg = ssh_connection:global_request_msg(Type, WantReply, Data),
- send_replies([{connection_reply, Msg}], State#state{connection_state = Connection});
-
-handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type,
- WantReply, <<?UINT32(IPLen),
- IP:IPLen/binary, ?UINT32(Port)>> = Data},
- #state{connection_state = Connection0} = State) ->
- Connection = ssh_connection:unbind(IP, Port, Connection0),
- Msg = ssh_connection:global_request_msg(Type, WantReply, Data),
- send_replies([{connection_reply, Msg}], State#state{connection_state = Connection});
-
-handle_global_request({global_request, _, "cancel-tcpip-forward" = Type,
- WantReply, Data}, State) ->
- Msg = ssh_connection:global_request_msg(Type, WantReply, Data),
- send_replies([{connection_reply, Msg}], State).
-
-handle_idle_timeout(#state{opts = Opts}) ->
- case proplists:get_value(idle_time, Opts, infinity) of
- infinity ->
- ok;
- IdleTime ->
- erlang:send_after(IdleTime, self(), {check_cache, [], []})
+ D
end.
-handle_channel_down(ChannelPid, #state{connection_state =
- #connection{channel_cache = Cache}} =
- State) ->
+%%%----------------------------------------------------------------
+handle_channel_down(ChannelPid, D) ->
ssh_channel:cache_foldl(
fun(Channel, Acc) when Channel#channel.user == ChannelPid ->
- ssh_channel:cache_delete(Cache,
+ ssh_channel:cache_delete(cache(D),
Channel#channel.local_id),
Acc;
(_,Acc) ->
Acc
- end, [], Cache),
- {{replies, []}, check_cache(State, Cache)}.
+ end, [], cache(D)),
+ {{replies, []}, cache_check_set_idle_timer(D)}.
+
update_sys(Cache, Channel, Type, ChannelPid) ->
ssh_channel:cache_update(Cache,
Channel#channel{sys = Type, user = ChannelPid}).
+
add_request(false, _ChannelId, _From, State) ->
State;
-add_request(true, ChannelId, From, #state{connection_state =
- #connection{requests = Requests0} =
- Connection} = State) ->
+add_request(true, ChannelId, From, #data{connection_state =
+ #connection{requests = Requests0} =
+ Connection} = State) ->
Requests = [{ChannelId, From} | Requests0],
- State#state{connection_state = Connection#connection{requests = Requests}}.
+ State#data{connection_state = Connection#connection{requests = Requests}}.
-new_channel_id(#state{connection_state = #connection{channel_id_seed = Id} =
- Connection}
+new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} =
+ Connection}
= State) ->
- {Id, State#state{connection_state =
- Connection#connection{channel_id_seed = Id + 1}}}.
-generate_event_new_state(#state{ssh_params =
- #ssh{recv_sequence = SeqNum0}
- = Ssh} = State, EncData) ->
- SeqNum = ssh_transport:next_seqnum(SeqNum0),
- State#state{ssh_params = Ssh#ssh{recv_sequence = SeqNum},
- decoded_data_buffer = <<>>,
- encoded_data_buffer = EncData,
- undecoded_packet_length = undefined}.
-
-next_packet(#state{decoded_data_buffer = <<>>,
- encoded_data_buffer = Buff,
- ssh_params = #ssh{decrypt_block_size = BlockSize},
- socket = Socket,
- transport_protocol = Protocol} = State) when Buff =/= <<>> ->
- case size(Buff) >= erlang:max(8, BlockSize) of
- true ->
- %% Enough data from the next packet has been received to
- %% decode the length indicator, fake a socket-recive
- %% message so that the data will be processed
- self() ! {Protocol, Socket, <<>>};
- false ->
- inet:setopts(Socket, [{active, once}])
- end,
- State;
-
-next_packet(#state{socket = Socket} = State) ->
- inet:setopts(Socket, [{active, once}]),
- State.
-
-after_new_keys(#state{renegotiate = true} = State) ->
- {connected, State#state{renegotiate = false}};
-after_new_keys(#state{renegotiate = false,
- ssh_params = #ssh{role = client} = Ssh0} = State) ->
- {Msg, Ssh} = ssh_auth:service_request_msg(Ssh0),
- send_msg(Msg, State),
- {userauth, State#state{ssh_params = Ssh}};
-after_new_keys(#state{renegotiate = false,
- ssh_params = #ssh{role = server}} = State) ->
- {userauth, State}.
-
-handle_ssh_packet_data(RemainingSshPacketLen, DecData, EncData, StateName,
- State) ->
- EncSize = size(EncData),
- case RemainingSshPacketLen > EncSize of
- true ->
- {next_state, StateName,
- next_packet(State#state{decoded_data_buffer = DecData,
- encoded_data_buffer = EncData,
- undecoded_packet_length =
- RemainingSshPacketLen})};
- false ->
- handle_ssh_packet(RemainingSshPacketLen, StateName,
- State#state{decoded_data_buffer = DecData,
- encoded_data_buffer = EncData})
-
- end.
-
-handle_ssh_packet(Length, StateName, #state{decoded_data_buffer = DecData0,
- encoded_data_buffer = EncData0,
- ssh_params = Ssh0,
- transport_protocol = _Protocol,
- socket = _Socket} = State0) ->
- try
- {Ssh1, DecData, EncData, Mac} =
- ssh_transport:unpack(EncData0, Length, Ssh0),
- SshPacket = <<DecData0/binary, DecData/binary>>,
- case ssh_transport:is_valid_mac(Mac, SshPacket, Ssh1) of
- true ->
- PacketData = ssh_transport:msg_data(SshPacket),
- {Ssh1, Msg} = ssh_transport:decompress(Ssh1, PacketData),
- generate_event(Msg, StateName,
- State0#state{ssh_params = Ssh1,
- %% Important to be set for
- %% next_packet
- decoded_data_buffer = <<>>},
- EncData);
- false ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad mac",
- language = "en"},
- handle_disconnect(DisconnectMsg, State0)
- end
- catch _:_ ->
- Disconnect =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad input",
- language = "en"},
- handle_disconnect(Disconnect, State0)
- end.
-
-
-handle_disconnect(DisconnectMsg, State) ->
- handle_disconnect(own, DisconnectMsg, State).
-
-handle_disconnect(#ssh_msg_disconnect{} = DisconnectMsg, State, Error) ->
- handle_disconnect(own, DisconnectMsg, State, Error);
-handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, role = Role} = State0) ->
- {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role),
- State = send_replies(disconnect_replies(Type, Msg, Replies), State0),
- {stop, {shutdown, Desc}, State#state{connection_state = Connection}}.
-
-handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0,
- role = Role} = State0, ErrorMsg) ->
- {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role),
- State = send_replies(disconnect_replies(Type, Msg, Replies), State0),
- {stop, {shutdown, {Desc, ErrorMsg}}, State#state{connection_state = Connection}}.
-
-disconnect_replies(own, Msg, Replies) ->
- [{connection_reply, Msg} | Replies];
-disconnect_replies(peer, _, Replies) ->
- Replies.
-
+ {Id, State#data{connection_state =
+ Connection#connection{channel_id_seed = Id + 1}}}.
+
+%%%----------------------------------------------------------------
+%% %%% This server/client has decided to disconnect via the state machine:
+disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) ->
+ State = send_msg(Msg, State0),
+ disconnect_fun(Description, State),
+timer:sleep(400),
+ {stop, {shutdown,Description}, State}.
+
+%%%----------------------------------------------------------------
counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) ->
Ssh#ssh{c_vsn = NumVsn , c_version = StrVsn};
counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) ->
Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}.
-opposite_role(client) ->
- server;
-opposite_role(server) ->
- client.
-connected_fun(User, PeerAddr, Method, Opts) ->
- case proplists:get_value(connectfun, Opts) of
- undefined ->
- ok;
- Fun ->
- catch Fun(User, PeerAddr, Method)
- end.
-
-retry_fun(_, _, undefined, _) ->
- ok;
-
-retry_fun(User, PeerAddr, {error, Reason}, Opts) ->
- case proplists:get_value(failfun, Opts) of
- undefined ->
- ok;
- Fun ->
- do_retry_fun(Fun, User, PeerAddr, Reason)
- end;
-
-retry_fun(User, PeerAddr, Reason, Opts) ->
- case proplists:get_value(infofun, Opts) of
- undefined ->
- ok;
- Fun ->
- do_retry_fun(Fun, User, PeerAddr, Reason)
- end.
-
-do_retry_fun(Fun, User, PeerAddr, Reason) ->
- case erlang:fun_info(Fun, arity) of
- {arity, 2} -> %% Backwards compatible
- catch Fun(User, Reason);
- {arity, 3} ->
- catch Fun(User, PeerAddr, Reason)
- end.
-
ssh_info([], _State, Acc) ->
Acc;
-ssh_info([client_version | Rest], #state{ssh_params = #ssh{c_vsn = IntVsn,
+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], #state{ssh_params =#ssh{s_vsn = IntVsn,
+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], #state{ssh_params = #ssh{peer = Peer}} = State, Acc) ->
+ssh_info([peer | Rest], #data{ssh_params = #ssh{peer = Peer}} = State, Acc) ->
ssh_info(Rest, State, [{peer, Peer} | Acc]);
-ssh_info([sockname | Rest], #state{socket = Socket} = State, 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], #state{auth_user = User} = State, 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;
@@ -1605,36 +1680,49 @@ ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize,
ssh_channel_info([ _ | Rest], Channel, Acc) ->
ssh_channel_info(Rest, Channel, Acc).
-log_error(Reason) ->
- Report = io_lib:format("Erlang ssh connection handler failed with reason: "
- "~p ~n, Stacktrace: ~p ~n",
- [Reason, erlang:get_stacktrace()]),
- error_logger:error_report(Report),
- "Internal error".
-send_replies([], State) ->
- State;
-send_replies([{connection_reply, Data} | Rest], #state{ssh_params = Ssh0} = State) ->
- {Packet, Ssh} = ssh_transport:ssh_packet(Data, Ssh0),
- send_msg(Packet, State),
- send_replies(Rest, State#state{ssh_params = Ssh});
-send_replies([Msg | Rest], State) ->
- catch send_reply(Msg),
- send_replies(Rest, State).
-
-send_reply({channel_data, Pid, Data}) ->
- Pid ! {ssh_cm, self(), Data};
-send_reply({channel_requst_reply, From, Data}) ->
- gen_fsm:reply(From, Data);
-send_reply({flow_control, Cache, Channel, From, Msg}) ->
+log_error(Reason) ->
+ Report = io_lib:format("Erlang ssh connection handler failed with reason:~n"
+ " ~p~n"
+ "Stacktrace:~n"
+ " ~p~n",
+ [Reason, erlang:get_stacktrace()]),
+ error_logger:error_report(Report).
+
+
+%%%----------------------------------------------------------------
+not_connected_filter({connection_reply, _Data}) -> true;
+not_connected_filter(_) -> false.
+
+%%%----------------------------------------------------------------
+send_replies(Repls, State) ->
+ lists:foldl(fun get_repl/2,
+ {[],State},
+ Repls).
+
+get_repl({connection_reply,Msg}, {CallRepls,S}) ->
+ {CallRepls, send_msg(Msg,S)};
+get_repl({channel_data,undefined,_Data}, Acc) ->
+ Acc;
+get_repl({channel_data,Pid,Data}, Acc) ->
+ Pid ! {ssh_cm, self(), Data},
+ Acc;
+get_repl({channel_request_reply,From,Data}, {CallRepls,S}) ->
+ {[{reply,From,Data}|CallRepls], S};
+get_repl({flow_control,Cache,Channel,From,Msg}, {CallRepls,S}) ->
ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}),
- gen_fsm:reply(From, Msg);
-send_reply({flow_control, From, Msg}) ->
- gen_fsm:reply(From, Msg).
+ {[{reply,From,Msg}|CallRepls], S};
+get_repl({flow_control,From,Msg}, {CallRepls,S}) ->
+ {[{reply,From,Msg}|CallRepls], S};
+get_repl(noreply, Acc) ->
+ Acc;
+get_repl(X, Acc) ->
+ exit({get_repl,X,Acc}).
-disconnect_fun(_, undefined) ->
- ok;
-disconnect_fun(Reason, Opts) ->
+%%%----------------------------------------------------------------
+disconnect_fun({disconnect,Msg}, D) ->
+ disconnect_fun(Msg, D);
+disconnect_fun(Reason, #data{opts=Opts}) ->
case proplists:get_value(disconnectfun, Opts) of
undefined ->
ok;
@@ -1642,41 +1730,137 @@ disconnect_fun(Reason, Opts) ->
catch Fun(Reason)
end.
-check_cache(#state{opts = Opts} = State, Cache) ->
- %% Check the number of entries in Cache
- case proplists:get_value(size, ets:info(Cache)) of
- 0 ->
- case proplists:get_value(idle_time, Opts, infinity) of
- infinity ->
- State;
- Time ->
- handle_idle_timer(Time, State)
- 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.
+
+
+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.
+
+
+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.
+
+retry_fun(_, undefined, _) ->
+ ok;
+retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts,
+ peer = {_,Peer}
+ }}) ->
+ {Tag,Info} =
+ case Reason of
+ {error, Error} ->
+ {failfun, Error};
+ _ ->
+ {infofun, Reason}
+ end,
+ Fun = proplists:get_value(Tag, Opts, fun(_,_)-> ok end),
+ try erlang:fun_info(Fun, arity)
+ of
+ {arity, 2} -> %% Backwards compatible
+ catch Fun(User, Info);
+ {arity, 3} ->
+ catch Fun(User, Peer, Info);
_ ->
- State
+ ok
+ catch
+ _:_ ->
+ ok
end.
-handle_idle_timer(Time, #state{idle_timer_ref = undefined} = State) ->
- TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}),
- State#state{idle_timer_ref=TimerRef};
-handle_idle_timer(_, State) ->
- State.
-
-remove_timer_ref(State) ->
- case State#state.idle_timer_ref of
- infinity -> %% If the timer is not activated
- State;
- undefined -> %% If we already has cancelled the timer
- State;
- TimerRef -> %% Timer is active
+%%%----------------------------------------------------------------
+%%% Cache idle timer that closes the connection if there are no
+%%% channels open for a while.
+
+cache_init_idle_timer(D) ->
+ case proplists:get_value(idle_time, D#data.opts, infinity) of
+ infinity ->
+ D#data{idle_timer_value = infinity,
+ idle_timer_ref = infinity % A flag used later...
+ };
+ IdleTime ->
+ %% We dont want to set the timeout on first connect
+ D#data{idle_timer_value = IdleTime}
+ end.
+
+
+cache_check_set_idle_timer(D = #data{idle_timer_ref = undefined,
+ idle_timer_value = IdleTime}) ->
+ %% No timer set - shall we set one?
+ case ssh_channel:cache_info(num_entries, cache(D)) of
+ 0 when IdleTime == infinity ->
+ %% No. Meaningless to set a timer that fires in an infinite time...
+ D;
+ 0 ->
+ %% Yes, we'll set one since the cache is empty and it should not
+ %% be that for a specified time
+ D#data{idle_timer_ref =
+ erlang:send_after(IdleTime, self(), {'EXIT',[],"Timeout"})};
+ _ ->
+ %% No - there are entries in the cache
+ D
+ end;
+cache_check_set_idle_timer(D) ->
+ %% There is already a timer set or the timeout time is infinite
+ D.
+
+
+cache_cancel_idle_timer(D) ->
+ case D#data.idle_timer_ref of
+ infinity ->
+ %% The timer is not activated
+ D;
+ undefined ->
+ %% The timer is already cancelled
+ D;
+ TimerRef ->
+ %% The timer is active
erlang:cancel_timer(TimerRef),
- State#state{idle_timer_ref = undefined}
+ D#data{idle_timer_ref = undefined}
end.
-socket_control(Socket, Pid, Transport) ->
- case Transport:controlling_process(Socket, Pid) of
+
+cache_request_idle_timer_check(D = #data{idle_timer_value = infinity}) ->
+ D;
+cache_request_idle_timer_check(D = #data{idle_timer_value = IdleTime}) ->
+ erlang:send_after(IdleTime, self(), check_cache),
+ D.
+
+%%%----------------------------------------------------------------
+start_channel_request_timer(_,_, infinity) ->
+ ok;
+start_channel_request_timer(Channel, From, Time) ->
+ erlang:send_after(Time, self(), {timeout, {Channel, From}}).
+
+%%%----------------------------------------------------------------
+%%% 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
ok ->
- send_event(Pid, socket_control);
+ gen_statem:cast(Pid, socket_control);
{error, Reason} ->
{error, Reason}
end.
@@ -1702,10 +1886,6 @@ handshake(Pid, Ref, Timeout) ->
{error, Reason}
after Timeout ->
stop(Pid),
- {error, Timeout}
+ {error, timeout}
end.
-start_timeout(_,_, infinity) ->
- ok;
-start_timeout(Channel, From, Time) ->
- erlang:send_after(Time, self(), {timeout, {Channel, From}}).
diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl
index c5abc8f23b..8c7628e909 100644
--- a/lib/ssh/src/ssh_connection_sup.erl
+++ b/lib/ssh/src/ssh_connection_sup.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_daemon_channel.erl b/lib/ssh/src/ssh_daemon_channel.erl
index ab3efbcaff..6ca93eff44 100644
--- a/lib/ssh/src/ssh_daemon_channel.erl
+++ b/lib/ssh/src/ssh_daemon_channel.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl
new file mode 100644
index 0000000000..fbf85cfcfc
--- /dev/null
+++ b/lib/ssh/src/ssh_dbg.erl
@@ -0,0 +1,140 @@
+%%
+%% %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%
+%%
+
+%%
+
+-module(ssh_dbg).
+
+-export([messages/0,
+ messages/1
+ ]).
+
+-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(Write) when is_function(Write,2) ->
+ catch dbg:start(),
+
+ Handler = fun msg_formater/2,
+ InitialData = #data{writer = Write},
+ {ok,_} = dbg:tracer(process, {Handler, InitialData}),
+
+ dbg:p(new,c),
+ dbg:tp(ssh_message,encode,1, x),
+ dbg:tp(ssh_message,decode,1, x),
+ dbg:tpl(ssh_transport,select_algorithm,3, x).
+
+%%%================================================================
+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,decode,1},Msg}, D) ->
+ fmt("~nRECV ~p ~s~n", [Pid,wr_record(shrink_bin(Msg))], 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(_, D) ->
+ D.
+
+
+fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) ->
+ D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}.
+
+%%%----------------------------------------------------------------
+shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN',size(B),element(1,split_binary(B,20)),'***'};
+shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L);
+shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T)));
+shrink_bin(X) -> X.
+
+%%%----------------------------------------------------------------
+-define(wr_record(N,BlackList), wr_record(R=#N{}) -> wr_record(R, record_info(fields,N), BlackList)).
+
+-define(wr_record(N), ?wr_record(N, [])).
+
+
+?wr_record(alg);
+
+?wr_record(ssh_msg_disconnect);
+?wr_record(ssh_msg_ignore);
+?wr_record(ssh_msg_unimplemented);
+?wr_record(ssh_msg_debug);
+?wr_record(ssh_msg_service_request);
+?wr_record(ssh_msg_service_accept);
+?wr_record(ssh_msg_kexinit);
+?wr_record(ssh_msg_kexdh_init);
+?wr_record(ssh_msg_kexdh_reply);
+?wr_record(ssh_msg_newkeys);
+?wr_record(ssh_msg_kex_dh_gex_request);
+?wr_record(ssh_msg_kex_dh_gex_request_old);
+?wr_record(ssh_msg_kex_dh_gex_group);
+?wr_record(ssh_msg_kex_dh_gex_init);
+?wr_record(ssh_msg_kex_dh_gex_reply);
+?wr_record(ssh_msg_kex_ecdh_init);
+?wr_record(ssh_msg_kex_ecdh_reply);
+
+?wr_record(ssh_msg_userauth_request);
+?wr_record(ssh_msg_userauth_failure);
+?wr_record(ssh_msg_userauth_success);
+?wr_record(ssh_msg_userauth_banner);
+?wr_record(ssh_msg_userauth_passwd_changereq);
+?wr_record(ssh_msg_userauth_pk_ok);
+?wr_record(ssh_msg_userauth_info_request);
+?wr_record(ssh_msg_userauth_info_response);
+
+?wr_record(ssh_msg_global_request);
+?wr_record(ssh_msg_request_success);
+?wr_record(ssh_msg_request_failure);
+?wr_record(ssh_msg_channel_open);
+?wr_record(ssh_msg_channel_open_confirmation);
+?wr_record(ssh_msg_channel_open_failure);
+?wr_record(ssh_msg_channel_window_adjust);
+?wr_record(ssh_msg_channel_data);
+?wr_record(ssh_msg_channel_extended_data);
+?wr_record(ssh_msg_channel_eof);
+?wr_record(ssh_msg_channel_close);
+?wr_record(ssh_msg_channel_request);
+?wr_record(ssh_msg_channel_success);
+?wr_record(ssh_msg_channel_failure);
+
+wr_record(R) -> io_lib:format('~p~n',[R]).
+
+
+wr_record(T, Fs, BL) when is_tuple(T) ->
+ wr_record(tuple_to_list(T), Fs, BL);
+wr_record([Name|Values], Fields, BlackL) ->
+ W = case Fields of
+ [] -> 0;
+ _ -> lists:max([length(atom_to_list(F)) || F<-Fields])
+ end,
+ [io_lib:format("~p:~n",[string:to_upper(atom_to_list(Name))])
+ | [io_lib:format(" ~*p: ~p~n",[W,Tag,Value]) || {Tag,Value} <- lists:zip(Fields,Values),
+ not lists:member(Tag,BlackL)
+ ]
+ ].
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index 5692138a8a..4486d36fe4 100644
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -51,8 +52,20 @@ host_key(Algorithm, Opts) ->
%% so probably we could hardcod Password = ignore, but
%% we keep it as an undocumented option for now.
Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore),
- decode(File, Password).
-
+ 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
+ end.
is_auth_key(Key, User,Opts) ->
case lookup_user_key(Key, User, Opts) of
@@ -80,16 +93,15 @@ user_key(Algorithm, Opts) ->
%% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-file_base_name('ssh-rsa') ->
- "ssh_host_rsa_key";
-file_base_name('ssh-dss') ->
- "ssh_host_dsa_key";
-file_base_name(_) ->
- "ssh_host_key".
+file_base_name('ssh-rsa' ) -> "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";
+file_base_name('ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key";
+file_base_name(_ ) -> "ssh_host_key".
decode(File, Password) ->
- try
- {ok, decode_ssh_file(read_ssh_file(File), Password)}
+ try {ok, decode_ssh_file(read_ssh_file(File), Password)}
catch
throw:Reason ->
{error, Reason};
@@ -209,29 +221,32 @@ do_lookup_host_key(KeyToMatch, Host, Alg, Opts) ->
{ok, Fd} ->
Res = lookup_host_key_fd(Fd, KeyToMatch, Host, Alg),
file:close(Fd),
- {ok, Res};
- {error, enoent} -> {error, not_found};
- Error -> Error
+ Res;
+ {error, enoent} ->
+ {error, not_found};
+ Error ->
+ Error
end.
-identity_key_filename('ssh-dss') ->
- "id_dsa";
-identity_key_filename('ssh-rsa') ->
- "id_rsa".
-
-identity_pass_phrase("ssh-dss") ->
- dsa_pass_phrase;
-identity_pass_phrase('ssh-dss') ->
- dsa_pass_phrase;
-identity_pass_phrase('ssh-rsa') ->
- rsa_pass_phrase;
-identity_pass_phrase("ssh-rsa") ->
- rsa_pass_phrase.
-
+identity_key_filename('ssh-dss' ) -> "id_dsa";
+identity_key_filename('ssh-rsa' ) -> "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("ecdsa-sha2-"++_) -> ecdsa_pass_phrase;
+identity_pass_phrase(P) when is_atom(P) ->
+ identity_pass_phrase(atom_to_list(P)).
+
lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) ->
case io:get_line(Fd, '') of
eof ->
{error, not_found};
+ {error,Error} ->
+ %% Rare... For example NFS errors
+ {error,Error};
Line ->
case ssh_decode_line(Line, known_hosts) of
[{Key, Attributes}] ->
@@ -252,7 +267,7 @@ handle_host(Fd, KeyToMatch, Host, HostList, Key, KeyType) ->
Host1 = host_name(Host),
case lists:member(Host1, HostList) andalso key_match(Key, KeyType) of
true when KeyToMatch == Key ->
- Key;
+ {ok,Key};
_ ->
lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType)
end.
@@ -266,6 +281,13 @@ key_match(#'RSAPublicKey'{}, 'ssh-rsa') ->
true;
key_match({_, #'Dss-Parms'{}}, 'ssh-dss') ->
true;
+key_match({#'ECPoint'{},{namedCurve,Curve}}, Alg) ->
+ case atom_to_list(Alg) of
+ "ecdsa-sha2-"++IdS ->
+ Curve == public_key:ssh_curvename2oid(list_to_binary(IdS));
+ _ ->
+ false
+ end;
key_match(_, _) ->
false.
@@ -292,6 +314,9 @@ lookup_user_key_fd(Fd, Key) ->
case io:get_line(Fd, '') of
eof ->
{error, not_found};
+ {error,Error} ->
+ %% Rare... For example NFS errors
+ {error,Error};
Line ->
case ssh_decode_line(Line, auth_keys) of
[{AuthKey, _}] ->
@@ -311,8 +336,18 @@ is_auth_key(Key, Key) ->
is_auth_key(_,_) ->
false.
-default_user_dir()->
- {ok,[[Home|_]]} = init:get_argument(home),
+
+default_user_dir() ->
+ try
+ default_user_dir(os:getenv("HOME"))
+ catch
+ _:_ ->
+ default_user_dir(init:get_argument(home))
+ end.
+
+default_user_dir({ok,[[Home|_]]}) ->
+ default_user_dir(Home);
+default_user_dir(Home) when is_list(Home) ->
UserDir = filename:join(Home, ".ssh"),
ok = filelib:ensure_dir(filename:join(UserDir, "dummy")),
{ok,Info} = file:read_file_info(UserDir),
diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl
index 9ed598b3ab..0c24c09887 100644
--- a/lib/ssh/src/ssh_info.erl
+++ b/lib/ssh/src/ssh_info.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -24,123 +25,174 @@
-module(ssh_info).
--compile(export_all).
+-export([print/0,
+ print/1,
+ string/0,
+ collect_pids/0
+ ]).
+
+-include("ssh_connect.hrl").
print() ->
+ io:format("~s", [string()]).
+
+print(File) when is_list(File) ->
+ {ok,D} = file:open(File, [write]),
+ print(D),
+ file:close(D);
+print(D) ->
+ io:format(D, "~s", [string()]).
+
+string() ->
try supervisor:which_children(ssh_sup)
of
_ ->
- io:nl(),
- print_general(),
- io:nl(),
- underline("Client part", $=),
- print_clients(),
- io:nl(),
- underline("Server part", $=),
- print_servers(),
- io:nl(),
- %% case os:type() of
- %% {unix,_} ->
- %% io:nl(),
- %% underline("Linux part", $=),
- %% underline("Listening"),
- %% catch io:format(os:cmd("netstat -tpln")),
- %% io:nl(),
- %% underline("Other"),
- %% catch io:format(os:cmd("netstat -tpn"));
- %% _ -> ok
- %% end,
- underline("Supervisors", $=),
- walk_sups(ssh_sup),
- io:nl()
+ [io_lib:nl(),
+ print_general(),
+ io_lib:nl(),
+ underline("Client part", $=),
+ print_clients(),
+ io_lib:nl(),
+ underline("Server part", $=),
+ print_servers(),
+ io_lib:nl(),
+ underline("Supervisors", $=),
+ walk_sups(ssh_sup),
+ io_lib:nl()]
catch
_:_ ->
- io:format("Ssh not found~n",[])
+ io_lib:format("Ssh not found~n",[])
end.
+
%%%================================================================
+-define(INDENT, " ").
+
print_general() ->
{_Name, Slogan, Ver} = lists:keyfind(ssh,1,application:which_applications()),
- underline(io_lib:format("~s ~s", [Slogan, Ver]), $=),
- io:format('This printout is generated ~s. ~n',[datetime()]).
+ [underline(io_lib:format("~s ~s", [Slogan, Ver]), $=),
+ io_lib:format('This printout is generated ~s. ~n',[datetime()])
+ ].
-%%%================================================================
print_clients() ->
try
- lists:foreach(fun print_client/1, supervisor:which_children(sshc_sup))
+ lists:map(fun print_client/1,
+ supervisor:which_children(sshc_sup))
catch
C:E ->
- io:format('***FAILED: ~p:~p~n',[C,E])
+ io_lib:format('***print_clients FAILED: ~p:~p~n',[C,E])
end.
print_client({undefined,Pid,supervisor,[ssh_connection_handler]}) ->
{{Local,Remote},_Str} = ssh_connection_handler:get_print_info(Pid),
- io:format(" Local=~s Remote=~s~n",[fmt_host_port(Local),fmt_host_port(Remote)]);
+ [io_lib:format(?INDENT"Local: ~s Remote: ~s ConnectionRef = ~p~n",
+ [fmt_host_port(Local), fmt_host_port(Remote), Pid]),
+ case channels(Pid) of
+ {ok,Channels=[_|_]} ->
+ [print_ch(ChPid) || #channel{user=ChPid} <- Channels];
+ _ ->
+ io_lib:format(?INDENT?INDENT?INDENT"No channels~n",[])
+ end];
+
print_client(Other) ->
- io:format(" [[Other 1: ~p]]~n",[Other]).
+ io_lib:format(" [[Other 1: ~p]]~n",[Other]).
%%%================================================================
print_servers() ->
try
- lists:foreach(fun print_server/1, supervisor:which_children(sshd_sup))
+ lists:map(fun print_server/1,
+ supervisor:which_children(sshd_sup))
catch
C:E ->
- io:format('***FAILED: ~p:~p~n',[C,E])
+ io_lib:format('***print_servers FAILED: ~p:~p~n',[C,E])
end.
-print_server({{server,ssh_system_sup,LocalHost,LocalPort},Pid,supervisor,[ssh_system_sup]}) when is_pid(Pid) ->
- io:format('Local=~s (~p children)~n',[fmt_host_port({LocalHost,LocalPort}),
- ssh_acceptor:number_of_connections(Pid)]),
- lists:foreach(fun print_system_sup/1, supervisor:which_children(Pid));
-print_server(Other) ->
- io:format(" [[Other 2: ~p]]~n",[Other]).
-
+
+print_server({{server,ssh_system_sup,LocalHost,LocalPort,Profile},Pid,supervisor,[ssh_system_sup]}) when is_pid(Pid) ->
+ Children = supervisor:which_children(Pid),
+ [io_lib:format(?INDENT"Listen: ~s (~p children) Profile ~p",[fmt_host_port({LocalHost,LocalPort}),
+ ssh_acceptor:number_of_connections(Pid),
+ Profile]),
+ case [AccPid
+ || {{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, AccPid, supervisor, [ssh_acceptor_sup]}
+ <- Children] of
+ AcceptorPids = [_|_] ->
+ [io_lib:format(" [Acceptor Pid", []),
+ [io_lib:format(" ~p",[AccPid]) || AccPid <- AcceptorPids],
+ io_lib:format("]~n", [])
+ ];
+ [] ->
+ io_lib:nl()
+ end,
+ lists:map(fun print_system_sup/1,
+ supervisor:which_children(Pid))
+ ].
+
+
print_system_sup({Ref,Pid,supervisor,[ssh_subsystem_sup]}) when is_reference(Ref),
is_pid(Pid) ->
- lists:foreach(fun print_channels/1, supervisor:which_children(Pid));
-print_system_sup({{ssh_acceptor_sup,LocalHost,LocalPort}, Pid,supervisor, [ssh_acceptor_sup]}) when is_pid(Pid) ->
- io:format(" [Acceptor for ~s]~n",[fmt_host_port({LocalHost,LocalPort})]);
-print_system_sup(Other) ->
- io:format(" [[Other 3: ~p]]~n",[Other]).
+ lists:map(fun print_channels/1,
+ supervisor:which_children(Pid));
+
+print_system_sup({{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, Pid, supervisor, [ssh_acceptor_sup]}) when is_pid(Pid) ->
+ [].
+
+
print_channels({{server,ssh_channel_sup,_,_},Pid,supervisor,[ssh_channel_sup]}) when is_pid(Pid) ->
- lists:foreach(fun print_channel/1, supervisor:which_children(Pid));
-print_channels(Other) ->
- io:format(" [[Other 4: ~p]]~n",[Other]).
-
-
-print_channel({Ref,Pid,worker,[ssh_channel]}) when is_reference(Ref),
- is_pid(Pid) ->
- {{ConnManager,ChannelID}, Str} = ssh_channel:get_print_info(Pid),
- {{Local,Remote},StrM} = ssh_connection_handler:get_print_info(ConnManager),
- io:format(' ch ~p: ~s ~s',[ChannelID, StrM, Str]),
- io:format(" Local=~s Remote=~s~n",[fmt_host_port(Local),fmt_host_port(Remote)]);
-print_channel(Other) ->
- io:format(" [[Other 5: ~p]]~n",[Other]).
-
+ Children = supervisor:which_children(Pid),
+ ChannelPids = [P || {R,P,worker,[ssh_channel]} <- Children,
+ is_pid(P),
+ is_reference(R)],
+ case ChannelPids of
+ [] -> io_lib:format(?INDENT?INDENT"No channels~n",[]);
+ [Ch1Pid|_] ->
+ {{ConnManager,_}, _Str} = ssh_channel:get_print_info(Ch1Pid),
+ {{_,Remote},_} = ssh_connection_handler:get_print_info(ConnManager),
+ [io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p~n",[fmt_host_port(Remote),ConnManager]),
+ lists:map(fun print_ch/1, ChannelPids)
+ ]
+ end;
+print_channels({{server,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_sup]}) when is_pid(Pid) ->
+ []. % The supervisor of the connections socket owning process
+
+print_ch(Pid) ->
+ try
+ {{ConnManager,ChannelID}, Str} = ssh_channel:get_print_info(Pid),
+ {_LocalRemote,StrM} = ssh_connection_handler:get_print_info(ConnManager),
+ io_lib:format(?INDENT?INDENT?INDENT"ch ~p ~p: ~s ~s~n",[ChannelID, Pid, StrM, Str])
+ catch
+ C:E ->
+ io_lib:format('****print_ch FAILED for ChanPid ~p: ~p:~p~n',[Pid, C, E])
+ end.
+
+
%%%================================================================
-define(inc(N), (N+4)).
walk_sups(StartPid) ->
- io:format("Start at ~p, ~s.~n",[StartPid,dead_or_alive(StartPid)]),
+ io_lib:format("Start at ~p, ~s.~n",[StartPid,dead_or_alive(StartPid)]),
walk_sups(children(StartPid), _Indent=?inc(0)).
-walk_sups([H={_,Pid,SupOrWorker,_}|T], Indent) ->
- indent(Indent), io:format('~200p ~p is ~s~n',[H,Pid,dead_or_alive(Pid)]),
- case SupOrWorker of
- supervisor -> walk_sups(children(Pid), ?inc(Indent));
- _ -> ok
- end,
- walk_sups(T, Indent);
+walk_sups([H={_,Pid,_,_}|T], Indent) ->
+ [indent(Indent),
+ io_lib:format('~200p ~p is ~s~n',[H,Pid,dead_or_alive(Pid)]),
+ case H of
+ {_,_,supervisor,[ssh_connection_handler]} -> "";
+ {_,Pid,supervisor,_} -> walk_sups(children(Pid), ?inc(Indent));
+ _ -> ""
+ end,
+ walk_sups(T, Indent)
+ ];
walk_sups([], _) ->
- ok.
+ "".
dead_or_alive(Name) when is_atom(Name) ->
case whereis(Name) of
- undefined ->
+ undefined ->
"**UNDEFINED**";
- Pid ->
+ Pid ->
dead_or_alive(Pid)
end;
dead_or_alive(Pid) when is_pid(Pid) ->
@@ -149,7 +201,8 @@ dead_or_alive(Pid) when is_pid(Pid) ->
_ -> "alive"
end.
-indent(I) -> io:format('~*c',[I,$ ]).
+indent(I) -> io_lib:format('~*c',[I,$ ]).
+
children(Pid) ->
Parent = self(),
@@ -160,34 +213,124 @@ children(Pid) ->
{Helper,L} when is_list(L) ->
L
after
- 2000 ->
+ 2000 ->
catch exit(Helper, kill),
[]
end.
-%%%================================================================
-underline(Str) ->
- underline(Str, $-).
+is_connection_handler(Pid) ->
+ try
+ {ssh_connection_handler,init,_} =
+ proplists:get_value(
+ '$initial_call',
+ proplists:get_value(
+ dictionary,
+ process_info(Pid, [dictionary])))
+ of
+ _ -> true
+
+ catch
+ _:_ ->
+ false
+ end.
+
+channels(Pid) ->
+ case is_connection_handler(Pid) of
+ true ->
+ ssh_connection_handler:info(Pid,all);
+ false ->
+ false
+ end.
+%%%================================================================
underline(Str, LineChar) ->
- Len = lists:flatlength(Str),
- io:format('~s~n',[Str]),
- line(Len,LineChar).
+ io_lib:format('~s~n~*c~n',[Str, lists:flatlength(Str), LineChar]).
-line(Len, Char) ->
- io:format('~*c~n', [Len,Char]).
-
datetime() ->
- {{YYYY,MM,DD}, {H,M,S}} = calendar:now_to_universal_time(now()),
+ {{YYYY,MM,DD}, {H,M,S}} = calendar:now_to_universal_time(erlang:timestamp()),
lists:flatten(io_lib:format('~4w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w UTC',[YYYY,MM,DD, H,M,S])).
fmt_host_port({{A,B,C,D},Port}) -> io_lib:format('~p.~p.~p.~p:~p',[A,B,C,D,Port]);
fmt_host_port({Host,Port}) -> io_lib:format('~s:~p',[Host,Port]).
+%%%################################################################
+collect_pids() -> collect_pids(ssh_sup).
+
+collect_pids(P) ->
+ Collector = pcollect_pids(P, spawn(fun init_collector/0)),
+ Collector ! {get_values,self()},
+ receive
+ {values,Values} ->
+ Values
+ end.
+
+%%%----------------
+pcollect_pids(undefined, Collector) ->
+ Collector;
+
+pcollect_pids(A, Collector) when is_atom(A) ->
+ pcollect_pids(whereis(A), Collector);
+
+pcollect_pids(Pid, Collector) when is_pid(Pid) ->
+ Collector ! {expect,Pid},
+ spawn(fun() ->
+ lists:foreach(
+ fun(P2) ->
+ pcollect_pids(P2,Collector)
+ end, children(Pid)),
+ Collector ! {value,Pid,Pid}
+ end),
+ Collector;
+pcollect_pids({Ref,Pid,supervisor,_}, Collector) when is_pid(Pid),
+ is_reference(Ref) ->
+ pcollect_pids(Pid, Collector);
-nyi() ->
- io:format('Not yet implemented~n',[]),
- nyi.
+pcollect_pids({sshc_sup,Pid,supervisor,_}, Collector) when is_pid(Pid) ->
+ pcollect_pids(Pid, Collector);
+
+pcollect_pids({sshd_sup,Pid,supervisor,_}, Collector) when is_pid(Pid) ->
+ pcollect_pids(Pid, Collector);
+
+pcollect_pids({{ssh_acceptor_sup,_,_,_},Pid,supervisor,_}, Collector) when is_pid(Pid) ->
+ pcollect_pids(Pid, Collector);
+
+pcollect_pids({{server,_,_,_},Pid,supervisor,_}, Collector) when is_pid(Pid) ->
+ pcollect_pids(Pid, Collector);
+
+pcollect_pids({{server,_,_,_,_},Pid,supervisor,_}, Collector) when is_pid(Pid) ->
+ pcollect_pids(Pid, Collector);
+
+pcollect_pids({undefined,Pid,supervisor,[ssh_connection_handler]}, Collector) ->
+ Collector ! {value,Pid,Pid},
+ case channels(Pid) of
+ {ok,L} ->
+ [Collector!{value,P,P} || #channel{user=P} <- L];
+ _ ->
+ ok
+ end,
+ Collector;
+
+pcollect_pids({_,Pid,_,_}, Collector) when is_pid(Pid) ->
+ Collector ! {value,Pid,Pid},
+ Collector;
+
+pcollect_pids(_, Collector) ->
+ Collector.
+
+%%%----------------
+init_collector() ->
+ loop_collector([],[]).
+
+loop_collector(Expects, Values) ->
+ receive
+ {expect, Ref} ->
+ loop_collector([Ref|Expects], Values);
+ {value, Ref, Val} ->
+ loop_collector(Expects--[Ref], [Val|Values]);
+ {get_values, From} when Expects==[] ->
+%% Values=/=[] ->
+ From ! {values,Values}
+ end.
diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl
index 97e2dee27a..026d0f6151 100644
--- a/lib/ssh/src/ssh_io.erl
+++ b/lib/ssh/src/ssh_io.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_math.erl b/lib/ssh/src/ssh_math.erl
deleted file mode 100644
index 569c1cb58d..0000000000
--- a/lib/ssh/src/ssh_math.erl
+++ /dev/null
@@ -1,41 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-
-%%% Description: SSH math utilities
-
--module(ssh_math).
-
--export([ipow/3]).
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-%% INTEGER utils
-%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% calculate A^B mod M
-ipow(A, B, M) when M > 0, B >= 0 ->
- crypto:bytes_to_integer(crypto:mod_pow(A, B, M)).
-
-
-
-
-
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 66e7717095..db80d4c9e3 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -29,18 +30,40 @@
-include("ssh_auth.hrl").
-include("ssh_transport.hrl").
--export([encode/1, decode/1, encode_host_key/1, decode_keyboard_interactive_prompts/2]).
+-export([encode/1, decode/1, decode_keyboard_interactive_prompts/2]).
+
+-define('2bin'(X), (if is_binary(X) -> X;
+ is_list(X) -> list_to_binary(X);
+ X==undefined -> <<>>
+ end) ).
+
+-define('E...'(X), ?'2bin'(X)/binary ).
+-define(Eboolean(X), ?BOOLEAN(case X of
+ true -> ?TRUE;
+ false -> ?FALSE
+ end) ).
+-define(Ebyte(X), ?BYTE(X) ).
+-define(Euint32(X), ?UINT32(X) ).
+-define(Estring(X), ?STRING(?'2bin'(X)) ).
+-define(Estring_utf8(X), ?string_utf8(X)/binary ).
+-define(Ename_list(X), ?STRING(ssh_bits:name_list(X)) ).
+-define(Empint(X), (ssh_bits:mpint(X))/binary ).
+-define(Ebinary(X), ?STRING(X) ).
+
+-define(unicode_list(B), unicode:characters_to_list(B)).
encode(#ssh_msg_global_request{
name = Name,
want_reply = Bool,
data = Data}) ->
- ssh_bits:encode([?SSH_MSG_GLOBAL_REQUEST,
- Name, Bool, Data], [byte, string, boolean, '...']);
+ <<?Ebyte(?SSH_MSG_GLOBAL_REQUEST), ?Estring(Name), ?Eboolean(Bool), ?'E...'(Data)>>;
+
encode(#ssh_msg_request_success{data = Data}) ->
- <<?BYTE(?SSH_MSG_REQUEST_SUCCESS), Data/binary>>;
+ <<?Ebyte(?SSH_MSG_REQUEST_SUCCESS), Data/binary>>;
+
encode(#ssh_msg_request_failure{}) ->
- <<?BYTE(?SSH_MSG_REQUEST_FAILURE)>>;
+ <<?Ebyte(?SSH_MSG_REQUEST_FAILURE)>>;
+
encode(#ssh_msg_channel_open{
channel_type = Type,
sender_channel = Sender,
@@ -48,9 +71,8 @@ encode(#ssh_msg_channel_open{
maximum_packet_size = Max,
data = Data
}) ->
- ssh_bits:encode([?SSH_MSG_CHANNEL_OPEN,
- Type, Sender, Window, Max, Data], [byte, string, uint32,
- uint32, uint32, '...']);
+ <<?Ebyte(?SSH_MSG_CHANNEL_OPEN), ?Estring(Type), ?Euint32(Sender), ?Euint32(Window), ?Euint32(Max), ?'E...'(Data)>>;
+
encode(#ssh_msg_channel_open_confirmation{
recipient_channel = Recipient,
sender_channel = Sender,
@@ -58,60 +80,63 @@ encode(#ssh_msg_channel_open_confirmation{
maximum_packet_size = MaxPacketSize,
data = Data
}) ->
- ssh_bits:encode([?SSH_MSG_CHANNEL_OPEN_CONFIRMATION, Recipient,
- Sender, InitWindowSize, MaxPacketSize, Data],
- [byte, uint32, uint32, uint32, uint32, '...']);
+ <<?Ebyte(?SSH_MSG_CHANNEL_OPEN_CONFIRMATION),
+ ?Euint32(Recipient), ?Euint32(Sender), ?Euint32(InitWindowSize), ?Euint32(MaxPacketSize),
+ ?'E...'(Data)>>;
+
encode(#ssh_msg_channel_open_failure{
recipient_channel = Recipient,
reason = Reason,
description = Desc,
lang = Lang
}) ->
- ssh_bits:encode([?SSH_MSG_CHANNEL_OPEN_FAILURE, Recipient,
- Reason, Desc, Lang], [byte, uint32, uint32, string, string]);
+ <<?Ebyte(?SSH_MSG_CHANNEL_OPEN_FAILURE), ?Euint32(Recipient),?Euint32(Reason), ?Estring(Desc), ?Estring(Lang)>>;
+
encode(#ssh_msg_channel_window_adjust{
recipient_channel = Recipient,
bytes_to_add = Bytes
}) ->
- ssh_bits:encode([?SSH_MSG_CHANNEL_WINDOW_ADJUST, Recipient, Bytes],
- [byte, uint32, uint32]);
+ <<?Ebyte(?SSH_MSG_CHANNEL_WINDOW_ADJUST), ?Euint32(Recipient), ?Euint32(Bytes)>>;
+
encode(#ssh_msg_channel_data{
recipient_channel = Recipient,
data = Data
}) ->
- ssh_bits:encode([?SSH_MSG_CHANNEL_DATA, Recipient, Data], [byte, uint32, binary]);
+ <<?Ebyte(?SSH_MSG_CHANNEL_DATA), ?Euint32(Recipient), ?Ebinary(Data)>>;
encode(#ssh_msg_channel_extended_data{
recipient_channel = Recipient,
data_type_code = DataType,
data = Data
}) ->
- ssh_bits:encode([?SSH_MSG_CHANNEL_EXTENDED_DATA, Recipient,
- DataType, Data], [byte, uint32, uint32, binary]);
+ <<?Ebyte(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?Euint32(Recipient), ?Euint32(DataType), ?Ebinary(Data)>>;
encode(#ssh_msg_channel_eof{recipient_channel = Recipient
}) ->
- <<?BYTE(?SSH_MSG_CHANNEL_EOF), ?UINT32(Recipient)>>;
+ <<?Ebyte(?SSH_MSG_CHANNEL_EOF), ?Euint32(Recipient)>>;
+
encode(#ssh_msg_channel_close{
recipient_channel = Recipient
}) ->
- <<?BYTE(?SSH_MSG_CHANNEL_CLOSE), ?UINT32(Recipient)>>;
+ <<?Ebyte(?SSH_MSG_CHANNEL_CLOSE), ?Euint32(Recipient)>>;
+
encode(#ssh_msg_channel_request{
recipient_channel = Recipient,
request_type = Type,
want_reply = Bool,
data = Data
}) ->
- ssh_bits:encode([?SSH_MSG_CHANNEL_REQUEST, Recipient, Type, Bool, Data],
- [byte, uint32, string, boolean, '...']);
+ <<?Ebyte(?SSH_MSG_CHANNEL_REQUEST), ?Euint32(Recipient), ?Estring(Type), ?Eboolean(Bool), ?'E...'(Data)>>;
+
encode(#ssh_msg_channel_success{
recipient_channel = Recipient
}) ->
- <<?BYTE(?SSH_MSG_CHANNEL_SUCCESS), ?UINT32(Recipient)>>;
+ <<?Ebyte(?SSH_MSG_CHANNEL_SUCCESS), ?Euint32(Recipient)>>;
+
encode(#ssh_msg_channel_failure{
recipient_channel = Recipient
}) ->
- <<?BYTE(?SSH_MSG_CHANNEL_FAILURE), ?UINT32(Recipient)>>;
+ <<?Ebyte(?SSH_MSG_CHANNEL_FAILURE), ?Euint32(Recipient)>>;
encode(#ssh_msg_userauth_request{
user = User,
@@ -119,36 +144,33 @@ encode(#ssh_msg_userauth_request{
method = Method,
data = Data
}) ->
- ssh_bits:encode([?SSH_MSG_USERAUTH_REQUEST, User, Service, Method, Data],
- [byte, string_utf8, string, string, '...']);
+ <<?Ebyte(?SSH_MSG_USERAUTH_REQUEST), ?Estring_utf8(User), ?Estring(Service), ?Estring(Method), ?'E...'(Data)>>;
+
encode(#ssh_msg_userauth_failure{
authentications = Auths,
partial_success = Bool
}) ->
- ssh_bits:encode([?SSH_MSG_USERAUTH_FAILURE, Auths, Bool],
- [byte, string, boolean]);
+ <<?Ebyte(?SSH_MSG_USERAUTH_FAILURE), ?Estring(Auths), ?Eboolean(Bool)>>;
+
encode(#ssh_msg_userauth_success{}) ->
- <<?BYTE(?SSH_MSG_USERAUTH_SUCCESS)>>;
+ <<?Ebyte(?SSH_MSG_USERAUTH_SUCCESS)>>;
encode(#ssh_msg_userauth_banner{
message = Banner,
language = Lang
}) ->
- ssh_bits:encode([?SSH_MSG_USERAUTH_BANNER, Banner, Lang],
- [byte, string_utf8, string]);
+ <<?Ebyte(?SSH_MSG_USERAUTH_BANNER), ?Estring_utf8(Banner), ?Estring(Lang)>>;
encode(#ssh_msg_userauth_pk_ok{
algorithm_name = Alg,
key_blob = KeyBlob
}) ->
- ssh_bits:encode([?SSH_MSG_USERAUTH_PK_OK, Alg, KeyBlob],
- [byte, string, binary]);
+ <<?Ebyte(?SSH_MSG_USERAUTH_PK_OK), ?Estring(Alg), ?Ebinary(KeyBlob)>>;
encode(#ssh_msg_userauth_passwd_changereq{prompt = Prompt,
languge = Lang
})->
- ssh_bits:encode([?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, Prompt, Lang],
- [byte, string, string]);
+ <<?Ebyte(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?Estring_utf8(Prompt), ?Estring(Lang)>>;
encode(#ssh_msg_userauth_info_request{
name = Name,
@@ -156,41 +178,37 @@ encode(#ssh_msg_userauth_info_request{
language_tag = Lang,
num_prompts = NumPromtps,
data = Data}) ->
- ssh_bits:encode([?SSH_MSG_USERAUTH_INFO_REQUEST, Name, Inst, Lang, NumPromtps, Data],
- [byte, string, string, string, uint32, '...']);
+ <<?Ebyte(?SSH_MSG_USERAUTH_INFO_REQUEST), ?Estring_utf8(Name), ?Estring_utf8(Inst), ?Estring(Lang),
+ ?Euint32(NumPromtps), ?'E...'(Data)>>;
encode(#ssh_msg_userauth_info_response{
num_responses = Num,
data = Data}) ->
- Responses = lists:map(fun("") ->
- <<>>;
- (Response) ->
- ssh_bits:encode([Response], [string])
- end, Data),
- Start = ssh_bits:encode([?SSH_MSG_USERAUTH_INFO_RESPONSE, Num],
- [byte, uint32]),
- iolist_to_binary([Start, Responses]);
+ lists:foldl(fun %%("", Acc) -> Acc; % commented out since it seem wrong
+ (Response, Acc) -> <<Acc/binary, ?Estring_utf8(Response)>>
+ end,
+ <<?Ebyte(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?Euint32(Num)>>,
+ Data);
encode(#ssh_msg_disconnect{
code = Code,
description = Desc,
language = Lang
}) ->
- ssh_bits:encode([?SSH_MSG_DISCONNECT, Code, Desc, Lang],
- [byte, uint32, string, string]);
+ <<?Ebyte(?SSH_MSG_DISCONNECT), ?Euint32(Code), ?Estring_utf8(Desc), ?Estring(Lang)>>;
encode(#ssh_msg_service_request{
name = Service
}) ->
- ssh_bits:encode([?SSH_MSG_SERVICE_REQUEST, Service], [byte, string]);
+ <<?Ebyte(?SSH_MSG_SERVICE_REQUEST), ?Estring_utf8(Service)>>;
encode(#ssh_msg_service_accept{
name = Service
}) ->
- ssh_bits:encode([?SSH_MSG_SERVICE_ACCEPT, Service], [byte, string]);
+ <<?Ebyte(?SSH_MSG_SERVICE_ACCEPT), ?Estring_utf8(Service)>>;
encode(#ssh_msg_newkeys{}) ->
- <<?BYTE(?SSH_MSG_NEWKEYS)>>;
+ <<?Ebyte(?SSH_MSG_NEWKEYS)>>;
encode(#ssh_msg_kexinit{
cookie = Cookie,
@@ -207,46 +225,38 @@ encode(#ssh_msg_kexinit{
first_kex_packet_follows = Bool,
reserved = Reserved
}) ->
- ssh_bits:encode([?SSH_MSG_KEXINIT, Cookie, KeyAlgs, HostKeyAlgs, EncAlgC2S, EncAlgS2C,
- MacAlgC2S, MacAlgS2C, CompAlgS2C, CompAlgC2S, LangC2S, LangS2C, Bool,
- Reserved],
- [byte, cookie,
- name_list, name_list,
- name_list, name_list,
- name_list, name_list,
- name_list, name_list,
- name_list, name_list,
- boolean, uint32]);
+ <<?Ebyte(?SSH_MSG_KEXINIT), Cookie/binary,
+ ?Ename_list(KeyAlgs), ?Ename_list(HostKeyAlgs), ?Ename_list(EncAlgC2S), ?Ename_list(EncAlgS2C), ?Ename_list(MacAlgC2S),
+ ?Ename_list(MacAlgS2C), ?Ename_list(CompAlgS2C), ?Ename_list(CompAlgC2S), ?Ename_list(LangC2S), ?Ename_list(LangS2C),
+ ?Eboolean(Bool), ?Euint32(Reserved)>>;
encode(#ssh_msg_kexdh_init{e = E}) ->
- ssh_bits:encode([?SSH_MSG_KEXDH_INIT, E], [byte, mpint]);
+ <<?Ebyte(?SSH_MSG_KEXDH_INIT), ?Empint(E)>>;
encode(#ssh_msg_kexdh_reply{
public_host_key = Key,
f = F,
h_sig = Signature
}) ->
- EncKey = encode_host_key(Key),
- EncSign = encode_sign(Key, Signature),
- ssh_bits:encode([?SSH_MSG_KEXDH_REPLY, EncKey, F, EncSign], [byte, binary, mpint, binary]);
+ EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncSign = encode_signature(Key, Signature),
+ <<?Ebyte(?SSH_MSG_KEXDH_REPLY), ?Ebinary(EncKey), ?Empint(F), ?Ebinary(EncSign)>>;
encode(#ssh_msg_kex_dh_gex_request{
min = Min,
n = N,
max = Max
}) ->
- ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_REQUEST, Min, N, Max],
- [byte, uint32, uint32, uint32, uint32]);
+ <<?Ebyte(?SSH_MSG_KEX_DH_GEX_REQUEST), ?Euint32(Min), ?Euint32(N), ?Euint32(Max)>>;
+
encode(#ssh_msg_kex_dh_gex_request_old{n = N}) ->
- ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_REQUEST_OLD, N],
- [byte, uint32]);
+ <<?Ebyte(?SSH_MSG_KEX_DH_GEX_REQUEST_OLD), ?Euint32(N)>>;
encode(#ssh_msg_kex_dh_gex_group{p = Prime, g = Generator}) ->
- ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_GROUP, Prime, Generator],
- [byte, mpint, mpint]);
+ <<?Ebyte(?SSH_MSG_KEX_DH_GEX_GROUP), ?Empint(Prime), ?Empint(Generator)>>;
encode(#ssh_msg_kex_dh_gex_init{e = Public}) ->
- ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_INIT, Public], [byte, mpint]);
+ <<?Ebyte(?SSH_MSG_KEX_DH_GEX_INIT), ?Empint(Public)>>;
encode(#ssh_msg_kex_dh_gex_reply{
%% Will be private key encode_host_key extracts only the public part!
@@ -254,25 +264,32 @@ encode(#ssh_msg_kex_dh_gex_reply{
f = F,
h_sig = Signature
}) ->
- EncKey = encode_host_key(Key),
- EncSign = encode_sign(Key, Signature),
- ssh_bits:encode([?SSH_MSG_KEXDH_REPLY, EncKey, F, EncSign], [byte, binary, mpint, binary]);
+ EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncSign = encode_signature(Key, Signature),
+ <<?Ebyte(?SSH_MSG_KEX_DH_GEX_REPLY), ?Ebinary(EncKey), ?Empint(F), ?Ebinary(EncSign)>>;
+
+encode(#ssh_msg_kex_ecdh_init{q_c = Q_c}) ->
+ <<?Ebyte(?SSH_MSG_KEX_ECDH_INIT), ?Empint(Q_c)>>;
+
+encode(#ssh_msg_kex_ecdh_reply{public_host_key = Key, q_s = Q_s, h_sig = Sign}) ->
+ EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncSign = encode_signature(Key, Sign),
+ <<?Ebyte(?SSH_MSG_KEX_ECDH_REPLY), ?Ebinary(EncKey), ?Empint(Q_s), ?Ebinary(EncSign)>>;
encode(#ssh_msg_ignore{data = Data}) ->
- ssh_bits:encode([?SSH_MSG_IGNORE, Data], [byte, string]);
+ <<?Ebyte(?SSH_MSG_IGNORE), ?Estring_utf8(Data)>>;
encode(#ssh_msg_unimplemented{sequence = Seq}) ->
- ssh_bits:encode([?SSH_MSG_UNIMPLEMENTED, Seq], [byte, uint32]);
+ <<?Ebyte(?SSH_MSG_UNIMPLEMENTED), ?Euint32(Seq)>>;
encode(#ssh_msg_debug{always_display = Bool,
message = Msg,
language = Lang}) ->
- ssh_bits:encode([?SSH_MSG_DEBUG, Bool, Msg, Lang], [byte, boolean, string, string]).
+ <<?Ebyte(?SSH_MSG_DEBUG), ?Eboolean(Bool), ?Estring_utf8(Msg), ?Estring(Lang)>>.
%% Connection Messages
-decode(<<?BYTE(?SSH_MSG_GLOBAL_REQUEST), ?UINT32(Len), Name:Len/binary,
- ?BYTE(Bool), Data/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_GLOBAL_REQUEST), ?DEC_BIN(Name,__0), ?BYTE(Bool), Data/binary>>) ->
#ssh_msg_global_request{
name = Name,
want_reply = erl_boolean(Bool),
@@ -283,8 +300,7 @@ decode(<<?BYTE(?SSH_MSG_REQUEST_SUCCESS), Data/binary>>) ->
decode(<<?BYTE(?SSH_MSG_REQUEST_FAILURE)>>) ->
#ssh_msg_request_failure{};
decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN),
- ?UINT32(Len), Type:Len/binary,
- ?UINT32(Sender), ?UINT32(Window), ?UINT32(Max),
+ ?DEC_BIN(Type,__0), ?UINT32(Sender), ?UINT32(Window), ?UINT32(Max),
Data/binary>>) ->
#ssh_msg_channel_open{
channel_type = binary_to_list(Type),
@@ -304,11 +320,11 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN_CONFIRMATION), ?UINT32(Recipient), ?UINT32(
data = Data
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN_FAILURE), ?UINT32(Recipient), ?UINT32(Reason),
- ?UINT32(Len0), Desc:Len0/binary, ?UINT32(Len1), Lang:Len1/binary >>) ->
+ ?DEC_BIN(Desc,__0), ?DEC_BIN(Lang,__1) >> ) ->
#ssh_msg_channel_open_failure{
recipient_channel = Recipient,
reason = Reason,
- description = unicode:characters_to_list(Desc),
+ description = ?unicode_list(Desc),
lang = Lang
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_WINDOW_ADJUST), ?UINT32(Recipient), ?UINT32(Bytes)>>) ->
@@ -317,13 +333,13 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_WINDOW_ADJUST), ?UINT32(Recipient), ?UINT32(Byte
bytes_to_add = Bytes
};
-decode(<<?BYTE(?SSH_MSG_CHANNEL_DATA), ?UINT32(Recipient), ?UINT32(Len), Data:Len/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_CHANNEL_DATA), ?UINT32(Recipient), ?DEC_BIN(Data,__0)>>) ->
#ssh_msg_channel_data{
recipient_channel = Recipient,
data = Data
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient),
- ?UINT32(DataType), ?UINT32(Len), Data:Len/binary>>) ->
+ ?UINT32(DataType), ?DEC_BIN(Data,__0)>>) ->
#ssh_msg_channel_extended_data{
recipient_channel = Recipient,
data_type_code = DataType,
@@ -338,11 +354,10 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_CLOSE), ?UINT32(Recipient)>>) ->
recipient_channel = Recipient
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_REQUEST), ?UINT32(Recipient),
- ?UINT32(Len), RequestType:Len/binary,
- ?BYTE(Bool), Data/binary>>) ->
+ ?DEC_BIN(RequestType,__0), ?BYTE(Bool), Data/binary>>) ->
#ssh_msg_channel_request{
recipient_channel = Recipient,
- request_type = unicode:characters_to_list(RequestType),
+ request_type = ?unicode_list(RequestType),
want_reply = erl_boolean(Bool),
data = Data
};
@@ -357,38 +372,34 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_FAILURE), ?UINT32(Recipient)>>) ->
%%% Auth Messages
decode(<<?BYTE(?SSH_MSG_USERAUTH_REQUEST),
- ?UINT32(Len0), User:Len0/binary,
- ?UINT32(Len1), Service:Len1/binary,
- ?UINT32(Len2), Method:Len2/binary,
+ ?DEC_BIN(User,__0), ?DEC_BIN(Service,__1), ?DEC_BIN(Method,__2),
Data/binary>>) ->
#ssh_msg_userauth_request{
- user = unicode:characters_to_list(User),
- service = unicode:characters_to_list(Service),
- method = unicode:characters_to_list(Method),
+ user = ?unicode_list(User),
+ service = ?unicode_list(Service),
+ method = ?unicode_list(Method),
data = Data
};
decode(<<?BYTE(?SSH_MSG_USERAUTH_FAILURE),
- ?UINT32(Len0), Auths:Len0/binary,
+ ?DEC_BIN(Auths,__0),
?BYTE(Bool)>>) ->
#ssh_msg_userauth_failure {
- authentications = unicode:characters_to_list(Auths),
+ authentications = ?unicode_list(Auths),
partial_success = erl_boolean(Bool)
};
decode(<<?BYTE(?SSH_MSG_USERAUTH_SUCCESS)>>) ->
#ssh_msg_userauth_success{};
-decode(<<?BYTE(?SSH_MSG_USERAUTH_BANNER),
- ?UINT32(Len0), Banner:Len0/binary,
- ?UINT32(Len1), Lang:Len1/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_USERAUTH_BANNER), ?DEC_BIN(Banner,__0), ?DEC_BIN(Lang,__1) >>) ->
#ssh_msg_userauth_banner{
message = Banner,
language = Lang
};
-decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary,
- ?UINT32(Len1), Inst:Len1/binary, ?UINT32(Len2), Lang:Len2/binary,
+decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST),
+ ?DEC_BIN(Name,__0), ?DEC_BIN(Inst,__1), ?DEC_BIN(Lang,__2),
?UINT32(NumPromtps), Data/binary>>) ->
#ssh_msg_userauth_info_request{
name = Name,
@@ -398,15 +409,14 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary,
data = Data};
%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST:
-decode(<<?BYTE(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?UINT32(Len0), Prompt:Len0/binary,
- ?UINT32(Len1), Lang:Len1/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?DEC_BIN(Prompt,__0), ?DEC_BIN(Lang,__1) >>) ->
#ssh_msg_userauth_passwd_changereq{
prompt = Prompt,
languge = Lang
};
%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST:
-decode(<<?BYTE(?SSH_MSG_USERAUTH_PK_OK), ?UINT32(Len), Alg:Len/binary, KeyBlob/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_USERAUTH_PK_OK), ?DEC_BIN(Alg,__0), KeyBlob/binary>>) ->
#ssh_msg_userauth_pk_ok{
algorithm_name = Alg,
key_blob = KeyBlob
@@ -421,80 +431,107 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) ->
decode(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) ->
decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10);
-decode(<<?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/binary>>) ->
- #ssh_msg_kexdh_init{e = erlint(Len, E)
+decode(<<"dh",?BYTE(?SSH_MSG_KEXDH_INIT), ?DEC_MPINT(E,__0)>>) ->
+ #ssh_msg_kexdh_init{e = E
};
+
+decode(<<"dh", ?BYTE(?SSH_MSG_KEXDH_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) ->
+ #ssh_msg_kexdh_reply{
+ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
+ f = F,
+ h_sig = decode_signature(Hashsign)
+ };
+
decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST), ?UINT32(Min), ?UINT32(N), ?UINT32(Max)>>) ->
#ssh_msg_kex_dh_gex_request{
min = Min,
n = N,
max = Max
};
-decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST_OLD), ?UINT32(N)>>) ->
+
+decode(<<"dh_gex",?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST_OLD), ?UINT32(N)>>) ->
#ssh_msg_kex_dh_gex_request_old{
n = N
};
-decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP),
- ?UINT32(Len0), Prime:Len0/big-signed-integer-unit:8,
- ?UINT32(Len1), Generator:Len1/big-signed-integer-unit:8>>) ->
+
+decode(<<"dh_gex",?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), ?DEC_MPINT(Prime,__0), ?DEC_MPINT(Generator,__1) >>) ->
#ssh_msg_kex_dh_gex_group{
p = Prime,
g = Generator
};
-decode(<<?BYTE(?SSH_MSG_KEXDH_REPLY), ?UINT32(Len0), Key:Len0/binary,
- ?UINT32(Len1), F:Len1/binary,
- ?UINT32(Len2), Hashsign:Len2/binary>>) ->
- #ssh_msg_kexdh_reply{
- public_host_key = decode_host_key(Key),
- f = erlint(Len1, F),
- h_sig = decode_sign(Hashsign)
+
+decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_INIT), ?DEC_MPINT(E,__0)>>) ->
+ #ssh_msg_kex_dh_gex_init{
+ e = E
};
-decode(<<?SSH_MSG_SERVICE_REQUEST, ?UINT32(Len0), Service:Len0/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) ->
+ #ssh_msg_kex_dh_gex_reply{
+ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
+ f = F,
+ h_sig = decode_signature(Hashsign)
+ };
+
+decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT), ?DEC_MPINT(Q_c,__0)>>) ->
+ #ssh_msg_kex_ecdh_init{
+ q_c = Q_c
+ };
+
+decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_REPLY),
+ ?DEC_BIN(Key,__1), ?DEC_MPINT(Q_s,__2), ?DEC_BIN(Sig,__3)>>) ->
+ #ssh_msg_kex_ecdh_reply{
+ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
+ q_s = Q_s,
+ h_sig = decode_signature(Sig)
+ };
+
+decode(<<?SSH_MSG_SERVICE_REQUEST, ?DEC_BIN(Service,__0)>>) ->
#ssh_msg_service_request{
- name = unicode:characters_to_list(Service)
+ name = ?unicode_list(Service)
};
-decode(<<?SSH_MSG_SERVICE_ACCEPT, ?UINT32(Len0), Service:Len0/binary>>) ->
+decode(<<?SSH_MSG_SERVICE_ACCEPT, ?DEC_BIN(Service,__0)>>) ->
#ssh_msg_service_accept{
- name = unicode:characters_to_list(Service)
+ name = ?unicode_list(Service)
};
-decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code),
- ?UINT32(Len0), Desc:Len0/binary, ?UINT32(Len1), Lang:Len1/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code), ?DEC_BIN(Desc,__0), ?DEC_BIN(Lang,__1)>>) ->
#ssh_msg_disconnect{
code = Code,
- description = unicode:characters_to_list(Desc),
+ description = ?unicode_list(Desc),
language = Lang
};
%% Accept bad disconnects from ancient openssh clients that doesn't send language tag. Use english as a work-around.
-decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code),
- ?UINT32(Len0), Desc:Len0/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code), ?DEC_BIN(Desc,__0)>>) ->
#ssh_msg_disconnect{
code = Code,
- description = unicode:characters_to_list(Desc),
+ description = ?unicode_list(Desc),
language = <<"en">>
};
decode(<<?SSH_MSG_NEWKEYS>>) ->
#ssh_msg_newkeys{};
-decode(<<?BYTE(?SSH_MSG_IGNORE), ?UINT32(Len), Data:Len/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_IGNORE), ?DEC_BIN(Data,__0)>>) ->
#ssh_msg_ignore{data = Data};
decode(<<?BYTE(?SSH_MSG_UNIMPLEMENTED), ?UINT32(Seq)>>) ->
#ssh_msg_unimplemented{sequence = Seq};
-decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?UINT32(Len0), Msg:Len0/binary,
- ?UINT32(Len1), Lang:Len1/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?DEC_BIN(Msg,__0), ?DEC_BIN(Lang,__1)>>) ->
#ssh_msg_debug{always_display = erl_boolean(Bool),
message = Msg,
language = Lang}.
+%%%================================================================
+%%%
+%%% Helper functions
+%%%
+
decode_keyboard_interactive_prompts(<<>>, Acc) ->
lists:reverse(Acc);
-decode_keyboard_interactive_prompts(<<?UINT32(Len), Prompt:Len/binary, ?BYTE(Bool), Bin/binary>>,
+decode_keyboard_interactive_prompts(<<?DEC_BIN(Prompt,__0), ?BYTE(Bool), Bin/binary>>,
Acc) ->
decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]).
@@ -510,45 +547,25 @@ decode_kex_init(<<?BYTE(Bool)>>, Acc, 0) ->
%% See rfc 4253 7.1
X = 0,
list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc]));
-decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) ->
- Names = string:tokens(unicode:characters_to_list(Data), ","),
+decode_kex_init(<<?DEC_BIN(Data,__0), Rest/binary>>, Acc, N) ->
+ Names = string:tokens(?unicode_list(Data), ","),
decode_kex_init(Rest, [Names | Acc], N -1).
-erlint(MPIntSize, MPIntValue) ->
- Bits = MPIntSize * 8,
- <<Integer:Bits/integer>> = MPIntValue,
- Integer.
-decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) ->
+%%%================================================================
+%%%
+%%% Signature decode/encode
+%%%
+
+decode_signature(<<?DEC_BIN(_Alg,__0), ?UINT32(_), Signature/binary>>) ->
Signature.
-decode_host_key(<<?UINT32(Len), Alg:Len/binary, Rest/binary>>) ->
- decode_host_key(Alg, Rest).
-
-decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/binary,
- ?UINT32(Len1), N:Len1/binary>>) ->
- #'RSAPublicKey'{publicExponent = erlint(Len0, E),
- modulus = erlint(Len1, N)};
-
-decode_host_key(<<"ssh-dss">>,
- <<?UINT32(Len0), P:Len0/binary,
- ?UINT32(Len1), Q:Len1/binary,
- ?UINT32(Len2), G:Len2/binary,
- ?UINT32(Len3), Y:Len3/binary>>) ->
- {erlint(Len3, Y), #'Dss-Parms'{p = erlint(Len0, P), q = erlint(Len1, Q),
- g = erlint(Len2, G)}}.
-
-encode_host_key(#'RSAPublicKey'{modulus = N, publicExponent = E}) ->
- ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]);
-encode_host_key({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) ->
- ssh_bits:encode(["ssh-dss", P, Q, G, Y],
- [string, mpint, mpint, mpint, mpint]);
-encode_host_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) ->
- ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]);
-encode_host_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) ->
- ssh_bits:encode(["ssh-dss", P, Q, G, Y],
- [string, mpint, mpint, mpint, mpint]).
-encode_sign(#'RSAPrivateKey'{}, Signature) ->
- ssh_bits:encode(["ssh-rsa", Signature],[string, binary]);
-encode_sign(#'DSAPrivateKey'{}, Signature) ->
- ssh_bits:encode(["ssh-dss", Signature],[string, binary]).
+
+encode_signature(#'RSAPublicKey'{}, Signature) ->
+ <<?Ebinary(<<"ssh-rsa">>), ?Ebinary(Signature)>>;
+encode_signature({_, #'Dss-Parms'{}}, Signature) ->
+ <<?Ebinary(<<"ssh-dss">>), ?Ebinary(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_no_io.erl b/lib/ssh/src/ssh_no_io.erl
index 825a0d4af5..1da257ed99 100644
--- a/lib/ssh/src/ssh_no_io.erl
+++ b/lib/ssh/src/ssh_no_io.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -26,27 +27,39 @@
-export([yes_no/2, read_password/2, read_line/2, format/2]).
+
+-spec yes_no(any(), any()) -> no_return().
+
yes_no(_, _) ->
- throw({{no_io_allowed, yes_no},
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
- description = "User interaction is not allowed",
- language = "en"}}).
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "User interaction is not allowed"},
+ {no_io_allowed, yes_no}).
+
+
+-spec read_password(any(), any()) -> no_return().
read_password(_, _) ->
- throw({{no_io_allowed, read_password},
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
- description = "User interaction is not allowed",
- language = "en"}}).
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "User interaction is not allowed"},
+ {no_io_allowed, read_password}).
+
+
+-spec read_line(any(), any()) -> no_return().
read_line(_, _) ->
- throw({{no_io_allowed, read_line},
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
- description = "User interaction is not allowed",
- language = "en"}} ).
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "User interaction is not allowed"},
+ {no_io_allowed, read_line}).
+
+
+-spec format(any(), any()) -> no_return().
format(_, _) ->
- throw({{no_io_allowed, format},
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
- description = "User interaction is not allowed",
- language = "en"}}).
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "User interaction is not allowed"},
+ {no_io_allowed, format}).
diff --git a/lib/ssh/src/ssh_server_key.erl b/lib/ssh/src/ssh_server_key.erl
index 8140114990..2ce0c7e3fe 100644
--- a/lib/ssh/src/ssh_server_key.erl
+++ b/lib/ssh/src/ssh_server_key.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_server_key_api.erl b/lib/ssh/src/ssh_server_key_api.erl
index 4fd660ecb5..c1d43a486c 100644
--- a/lib/ssh/src/ssh_server_key_api.erl
+++ b/lib/ssh/src/ssh_server_key_api.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl
index 613f8f25b2..b03652a136 100644
--- a/lib/ssh/src/ssh_sftp.erl
+++ b/lib/ssh/src/ssh_sftp.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -98,8 +99,8 @@ start_channel(Host) when is_list(Host) ->
start_channel(Host, []).
start_channel(Cm, Opts) when is_pid(Cm) ->
Timeout = proplists:get_value(timeout, Opts, infinity),
- {_, SftpOpts} = handle_options(Opts, [], []),
- case ssh_xfer:attach(Cm, []) of
+ {_, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []),
+ case ssh_xfer:attach(Cm, [], ChanOpts) of
{ok, ChannelId, Cm} ->
case ssh_channel:start(Cm, ChannelId,
?MODULE, [Cm, ChannelId, SftpOpts]) of
@@ -111,7 +112,7 @@ start_channel(Cm, Opts) when is_pid(Cm) ->
TimeOut
end;
{error, Reason} ->
- {error, Reason};
+ {error, format_channel_start_error(Reason)};
ignore ->
{error, ignore}
end;
@@ -122,9 +123,9 @@ start_channel(Cm, Opts) when is_pid(Cm) ->
start_channel(Host, Opts) ->
start_channel(Host, 22, Opts).
start_channel(Host, Port, Opts) ->
- {SshOpts, SftpOpts} = handle_options(Opts, [], []),
+ {SshOpts, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []),
Timeout = proplists:get_value(timeout, SftpOpts, infinity),
- case ssh_xfer:connect(Host, Port, SshOpts, Timeout) of
+ case ssh_xfer:connect(Host, Port, SshOpts, ChanOpts, Timeout) of
{ok, ChannelId, Cm} ->
case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,
ChannelId, SftpOpts]) of
@@ -136,7 +137,7 @@ start_channel(Host, Port, Opts) ->
TimeOut
end;
{error, Reason} ->
- {error, Reason};
+ {error, format_channel_start_error(Reason)};
ignore ->
{error, ignore}
end;
@@ -438,7 +439,7 @@ write_file(Pid, Name, List) ->
write_file(Pid, Name, List, ?FILEOP_TIMEOUT).
write_file(Pid, Name, List, FileOpTimeout) when is_list(List) ->
- write_file(Pid, Name, unicode:characters_to_binary(List), FileOpTimeout);
+ write_file(Pid, Name, list_to_binary(List), FileOpTimeout);
write_file(Pid, Name, Bin, FileOpTimeout) ->
case open(Pid, Name, [write, binary], FileOpTimeout) of
{ok, Handle} ->
@@ -491,9 +492,9 @@ init([Cm, ChannelId, Options]) ->
inf = new_inf(),
opts = Options}};
failure ->
- {stop, "server failed to start sftp subsystem"};
+ {stop, {shutdown, "server failed to start sftp subsystem"}};
Error ->
- {stop, Error}
+ {stop, {shutdown, Error}}
end.
%%--------------------------------------------------------------------
@@ -508,12 +509,12 @@ init([Cm, ChannelId, Options]) ->
%%--------------------------------------------------------------------
handle_call({{timeout, infinity}, wait_for_version_negotiation}, From,
#state{xf = #ssh_xfer{vsn = undefined} = Xf} = State) ->
- {noreply, State#state{xf = Xf#ssh_xfer{vsn = From}}};
+ {noreply, State#state{xf = Xf#ssh_xfer{vsn = {wait, From, undefined}}}};
handle_call({{timeout, Timeout}, wait_for_version_negotiation}, From,
#state{xf = #ssh_xfer{vsn = undefined} = Xf} = State) ->
- timer:send_after(Timeout, {timeout, undefined, From}),
- {noreply, State#state{xf = Xf#ssh_xfer{vsn = From}}};
+ TRef = erlang:send_after(Timeout, self(), {timeout, undefined, From}),
+ {noreply, State#state{xf = Xf#ssh_xfer{vsn = {wait, From, TRef}}}};
handle_call({_, wait_for_version_negotiation}, _, State) ->
{reply, ok, State};
@@ -610,8 +611,7 @@ do_handle_call({pread,Async,Handle,At,Length}, From, State) ->
fun({ok,Data}, State2) ->
case get_mode(Handle, State2) of
binary -> {{ok,Data}, State2};
- text ->
- {{ok,unicode:characters_to_list(Data)}, State2}
+ text -> {{ok,binary_to_list(Data)}, State2}
end;
(Rep, State2) ->
{Rep, State2}
@@ -842,14 +842,18 @@ terminate(_Reason, State) ->
%%====================================================================
%% Internal functions
%%====================================================================
-handle_options([], Sftp, Ssh) ->
- {Ssh, Sftp};
-handle_options([{timeout, _} = Opt | Rest], Sftp, Ssh) ->
- handle_options(Rest, [Opt | Sftp], Ssh);
-handle_options([{sftp_vsn, _} = Opt| Rest], Sftp, Ssh) ->
- handle_options(Rest, [Opt | Sftp], Ssh);
-handle_options([Opt | Rest], Sftp, Ssh) ->
- handle_options(Rest, Sftp, [Opt | Ssh]).
+handle_options([], Sftp, Chan, Ssh) ->
+ {Ssh, Chan, Sftp};
+handle_options([{timeout, _} = Opt | Rest], Sftp, Chan, Ssh) ->
+ handle_options(Rest, [Opt|Sftp], Chan, Ssh);
+handle_options([{sftp_vsn, _} = Opt| Rest], Sftp, Chan, Ssh) ->
+ handle_options(Rest, [Opt|Sftp], Chan, Ssh);
+handle_options([{window_size, _} = Opt| Rest], Sftp, Chan, Ssh) ->
+ handle_options(Rest, Sftp, [Opt|Chan], Ssh);
+handle_options([{packet_size, _} = Opt| Rest], Sftp, Chan, Ssh) ->
+ handle_options(Rest, Sftp, [Opt|Chan], Ssh);
+handle_options([Opt|Rest], Sftp, Chan, Ssh) ->
+ handle_options(Rest, Sftp, Chan, [Opt|Ssh]).
call(Pid, Msg, TimeOut) ->
ssh_channel:call(Pid, {{timeout, TimeOut}, Msg}, infinity).
@@ -865,7 +869,12 @@ do_handle_reply(#state{xf = Xf} = State,
case Xf#ssh_xfer.vsn of
undefined ->
ok;
- From ->
+ {wait, From, TRef} ->
+ if is_reference(TRef) ->
+ erlang:cancel_timer(TRef);
+ true ->
+ ok
+ end,
ssh_channel:reply(From, ok)
end,
State#state{xf = Xf#ssh_xfer{vsn = Version, ext = Ext}, rep_buf = Rest};
@@ -1412,3 +1421,8 @@ open_buf1(Pid, BufInfo0, FileOpTimeout, CryptoState, ChunkSize) ->
BufHandle = make_ref(),
call(Pid, {put_bufinf,BufHandle,BufInfo}, FileOpTimeout),
{ok,BufHandle}.
+
+format_channel_start_error({shutdown, Reason}) ->
+ Reason;
+format_channel_start_error(Reason) ->
+ Reason.
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 52665635f0..819cba697e 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -29,6 +30,7 @@
-include("ssh.hrl").
-include("ssh_xfer.hrl").
+-include("ssh_connect.hrl"). %% For ?DEFAULT_PACKET_SIZE and ?DEFAULT_WINDOW_SIZE
%%--------------------------------------------------------------------
%% External exports
@@ -46,6 +48,7 @@
file_handler, % atom() - callback module
file_state, % state for the file callback module
max_files, % integer >= 0 max no files sent during READDIR
+ options, % from the subsystem declaration
handles % list of open handles
%% handle is either {<int>, directory, {Path, unread|eof}} or
%% {<int>, file, {Path, IoDevice}}
@@ -120,6 +123,7 @@ init(Options) ->
MaxLength = proplists:get_value(max_files, Options, 0),
Vsn = proplists:get_value(sftpd_vsn, Options, 5),
{ok, State#state{cwd = CWD, root = Root, max_files = MaxLength,
+ options = Options,
handles = [], pending = <<>>,
xf = #ssh_xfer{vsn = Vsn, ext = []}}}.
@@ -163,7 +167,9 @@ handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
%% Description: Handles other messages
%%--------------------------------------------------------------------
handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
- #state{xf =Xf} = State) ->
+ #state{xf = Xf,
+ options = Options} = State) ->
+ maybe_increase_recv_window(ConnectionManager, ChannelId, Options),
{ok, State#state{xf = Xf#ssh_xfer{cm = ConnectionManager,
channel = ChannelId}}}.
@@ -559,56 +565,73 @@ stat(ReqId, RelPath, State0=#state{file_handler=FileMod,
send_status({error, E}, ReqId, State1)
end.
-decode_4_open_flag(create_new) ->
- [write];
-decode_4_open_flag(create_truncate) ->
- [write];
-decode_4_open_flag(truncate_existing) ->
- [write];
-decode_4_open_flag(open_existing) ->
- [read].
-
-decode_4_flags([OpenFlag | Flags]) ->
- decode_4_flags(Flags, decode_4_open_flag(OpenFlag)).
-
-decode_4_flags([], Flags) ->
- Flags;
-decode_4_flags([append_data|R], _Flags) ->
- decode_4_flags(R, [append]);
-decode_4_flags([append_data_atomic|R], _Flags) ->
- decode_4_flags(R, [append]);
-decode_4_flags([_|R], Flags) ->
- decode_4_flags(R, Flags).
-
-decode_4_access_flag(read_data) ->
- [read];
-decode_4_access_flag(list_directory) ->
- [read];
-decode_4_access_flag(write_data) ->
- [write];
-decode_4_access_flag(add_file) ->
- [write];
-decode_4_access_flag(add_subdirectory) ->
- [read];
-decode_4_access_flag(append_data) ->
- [append];
-decode_4_access_flag(write_attributes) ->
- [write];
-decode_4_access_flag(_) ->
- [read].
-
-decode_4_acess([_ | _] = Flags) ->
+sftp_to_erlang_flag(read, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ read;
+sftp_to_erlang_flag(write, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ write;
+sftp_to_erlang_flag(append, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ append;
+sftp_to_erlang_flag(creat, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ write;
+sftp_to_erlang_flag(trunc, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ write;
+sftp_to_erlang_flag(excl, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ read;
+sftp_to_erlang_flag(create_new, Vsn) when Vsn > 4 ->
+ write;
+sftp_to_erlang_flag(create_truncate, Vsn) when Vsn > 4 ->
+ write;
+sftp_to_erlang_flag(open_existing, Vsn) when Vsn > 4 ->
+ read;
+sftp_to_erlang_flag(open_or_create, Vsn) when Vsn > 4 ->
+ write;
+sftp_to_erlang_flag(truncate_existing, Vsn) when Vsn > 4 ->
+ write;
+sftp_to_erlang_flag(append_data, Vsn) when Vsn > 4 ->
+ append;
+sftp_to_erlang_flag(append_data_atomic, Vsn) when Vsn > 4 ->
+ append;
+sftp_to_erlang_flag(_, _) ->
+ read.
+
+sftp_to_erlang_flags(Flags, Vsn) ->
+ lists:map(fun(Flag) ->
+ sftp_to_erlang_flag(Flag, Vsn)
+ end, Flags).
+
+sftp_to_erlang_access_flag(read_data, _) ->
+ read;
+sftp_to_erlang_access_flag(list_directory, _) ->
+ read;
+sftp_to_erlang_access_flag(write_data, _) ->
+ write;
+sftp_to_erlang_access_flag(append_data, _) ->
+ append;
+sftp_to_erlang_access_flag(add_subdirectory, _) ->
+ read;
+sftp_to_erlang_access_flag(add_file, _) ->
+ write;
+sftp_to_erlang_access_flag(write_attributes, _) ->
+ write;
+sftp_to_erlang_access_flag(_, _) ->
+ read.
+sftp_to_erlang_access_flags(Flags, Vsn) ->
lists:map(fun(Flag) ->
- [decode_4_access_flag(Flag)]
- end, Flags);
-decode_4_acess([]) ->
- [].
+ sftp_to_erlang_access_flag(Flag, Vsn)
+ end, Flags).
open(Vsn, ReqId, Data, State) when Vsn =< 3 ->
<<?UINT32(BLen), BPath:BLen/binary, ?UINT32(PFlags),
_Attrs/binary>> = Data,
Path = unicode:characters_to_list(BPath),
- Flags = ssh_xfer:decode_open_flags(Vsn, PFlags),
+ FlagBits = ssh_xfer:decode_open_flags(Vsn, PFlags),
+ Flags = lists:usort(sftp_to_erlang_flags(FlagBits, Vsn)),
do_open(ReqId, State, Path, Flags);
open(Vsn, ReqId, Data, State) when Vsn >= 4 ->
<<?UINT32(BLen), BPath:BLen/binary, ?UINT32(Access),
@@ -616,15 +639,12 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 ->
Path = unicode:characters_to_list(BPath),
FlagBits = ssh_xfer:decode_open_flags(Vsn, PFlags),
AcessBits = ssh_xfer:decode_ace_mask(Access),
- %% TODO: This is to make sure the Access flags are not ignored
- %% but this should be thought through better. This solution should
- %% be considered a hack in order to buy some time. At least
- %% it works better than when the Access flags where totally ignored.
- %% A better solution may need some code refactoring that we do
- %% not have time for right now.
- AcessFlags = decode_4_acess(AcessBits),
- Flags = lists:append(lists:umerge(
- [[decode_4_flags(FlagBits)] | AcessFlags])),
+ %% TODO: There are still flags that are not
+ %% fully handled as SSH_FXF_ACCESS_TEXT_MODE and
+ %% a lot a ACE flags, the later we may not need
+ %% to understand as they are NFS flags
+ AcessFlags = sftp_to_erlang_access_flags(AcessBits, Vsn),
+ Flags = lists:usort(sftp_to_erlang_flags(FlagBits, Vsn) ++ AcessFlags),
do_open(ReqId, State, Path, Flags).
do_open(ReqId, State0, Path, Flags) ->
@@ -919,3 +939,18 @@ rename(Path, Path2, ReqId, State0) ->
{Status, FS1} = FileMod:rename(Path, Path2, FS0),
State1 = State0#state{file_state = FS1},
send_status(Status, ReqId, State1).
+
+
+maybe_increase_recv_window(ConnectionManager, ChannelId, Options) ->
+ WantedRecvWindowSize =
+ proplists:get_value(recv_window_size, Options, 1000000),
+ NumPkts = WantedRecvWindowSize div ?DEFAULT_PACKET_SIZE,
+ Increment = NumPkts*?DEFAULT_PACKET_SIZE - ?DEFAULT_WINDOW_SIZE,
+
+ if
+ Increment > 0 ->
+ ssh_connection:adjust_window(ConnectionManager, ChannelId,
+ Increment);
+ Increment =< 0 ->
+ do_nothing
+ end.
diff --git a/lib/ssh/src/ssh_sftpd_file.erl b/lib/ssh/src/ssh_sftpd_file.erl
index 91ba228e38..311cdc1a3d 100644
--- a/lib/ssh/src/ssh_sftpd_file.erl
+++ b/lib/ssh/src/ssh_sftpd_file.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl
index 83d90907f5..78f452df67 100644
--- a/lib/ssh/src/ssh_sftpd_file_api.erl
+++ b/lib/ssh/src/ssh_sftpd_file_api.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2012. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% Copyright Ericsson AB 2007-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%
%%
diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl
index 8031450617..d31d5a297d 100644
--- a/lib/ssh/src/ssh_shell.erl
+++ b/lib/ssh/src/ssh_shell.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl
index e8855b09ac..11e02491c4 100644
--- a/lib/ssh/src/ssh_subsystem_sup.erl
+++ b/lib/ssh/src/ssh_subsystem_sup.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl
index 6d2b9c107d..f827594717 100644
--- a/lib/ssh/src/ssh_sup.erl
+++ b/lib/ssh/src/ssh_sup.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index 660fe8bb65..9a9786a914 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -28,13 +29,15 @@
-behaviour(supervisor).
+-include("ssh.hrl").
+
-export([start_link/1, stop_listener/1,
- stop_listener/2, stop_system/1,
- stop_system/2, system_supervisor/2,
+ 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/2,
- restart_acceptor/2, stop_subsystem/2]).
+ acceptor_supervisor/1, start_subsystem/2, restart_subsystem/3,
+ restart_acceptor/3, stop_subsystem/2]).
%% Supervisor callback
-export([init/1]).
@@ -45,14 +48,15 @@
start_link(ServerOpts) ->
Address = proplists:get_value(address, ServerOpts),
Port = proplists:get_value(port, ServerOpts),
- Name = make_name(Address, Port),
+ Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ Name = make_name(Address, Port, Profile),
supervisor:start_link({local, Name}, ?MODULE, [ServerOpts]).
stop_listener(SysSup) ->
stop_acceptor(SysSup).
-stop_listener(Address, Port) ->
- Name = make_name(Address, Port),
+stop_listener(Address, Port, Profile) ->
+ Name = make_name(Address, Port, Profile),
stop_acceptor(whereis(Name)).
stop_system(SysSup) ->
@@ -60,12 +64,12 @@ stop_system(SysSup) ->
spawn(fun() -> sshd_sup:stop_child(Name) end),
ok.
-stop_system(Address, Port) ->
- spawn(fun() -> sshd_sup:stop_child(Address, Port) end),
+stop_system(Address, Port, Profile) ->
+ spawn(fun() -> sshd_sup:stop_child(Address, Port, Profile) end),
ok.
-system_supervisor(Address, Port) ->
- Name = make_name(Address, Port),
+system_supervisor(Address, Port, Profile) ->
+ Name = make_name(Address, Port, Profile),
whereis(Name).
subsystem_supervisor(SystemSup) ->
@@ -103,9 +107,9 @@ stop_subsystem(SystemSup, SubSys) ->
end.
-restart_subsystem(Address, Port) ->
- SysSupName = make_name(Address, Port),
- SubSysName = id(ssh_subsystem_sup, Address, Port),
+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);
@@ -113,9 +117,9 @@ restart_subsystem(Address, Port) ->
Error
end.
-restart_acceptor(Address, Port) ->
- SysSupName = make_name(Address, Port),
- AcceptorName = id(ssh_acceptor_sup, Address, Port),
+restart_acceptor(Address, Port, Profile) ->
+ SysSupName = make_name(Address, Port, Profile),
+ AcceptorName = id(ssh_acceptor_sup, Address, Port, Profile),
supervisor:restart_child(SysSupName, AcceptorName).
%%%=========================================================================
@@ -137,7 +141,8 @@ child_specs(ServerOpts) ->
ssh_acceptor_child_spec(ServerOpts) ->
Address = proplists:get_value(address, ServerOpts),
Port = proplists:get_value(port, ServerOpts),
- Name = id(ssh_acceptor_sup, Address, Port),
+ 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,
@@ -155,12 +160,23 @@ ssh_subsystem_child_spec(ServerOpts) ->
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-id(Sup, Address, Port) ->
- {Sup, Address, Port}.
-
-make_name(Address, Port) ->
- list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_sup",
- [Address, Port]))).
+id(Sup, Address, Port, Profile) ->
+ case is_list(Address) of
+ true ->
+ {Sup, any, Port, Profile};
+ false ->
+ {Sup, Address, Port, Profile}
+ end.
+
+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.
ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) ->
Child;
@@ -176,5 +192,9 @@ stop_acceptor(Sup) ->
[{Name, AcceptorSup}] =
[{SupName, ASup} || {SupName, ASup, _, [ssh_acceptor_sup]} <-
supervisor:which_children(Sup)],
- supervisor:terminate_child(AcceptorSup, Name).
-
+ case supervisor:terminate_child(AcceptorSup, Name) of
+ ok ->
+ supervisor:delete_child(AcceptorSup, Name);
+ Error ->
+ Error
+ end.
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 76fa776113..7cb3b75ac0 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -30,26 +31,147 @@
-include("ssh.hrl").
-export([versions/2, hello_version_msg/1]).
--export([next_seqnum/1, decrypt_first_block/2, decrypt_blocks/3,
- is_valid_mac/3,
+-export([next_seqnum/1,
+ supported_algorithms/0, supported_algorithms/1,
+ default_algorithms/0, default_algorithms/1,
+ handle_packet_part/4,
handle_hello_version/1,
key_exchange_init_msg/1,
key_init/3, new_keys_message/1,
handle_kexinit_msg/3, handle_kexdh_init/2,
- handle_kex_dh_gex_group/2, handle_kex_dh_gex_reply/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,
- unpack/3, decompress/2, ssh_packet/2, pack/2, msg_data/1,
+ handle_kex_ecdh_init/2,
+ handle_kex_ecdh_reply/2,
+ extract_public_key/1,
+ ssh_packet/2, pack/2,
sign/3, verify/4]).
+%%% For test suites
+-export([pack/3]).
+-export([decompress/2, decrypt_blocks/3, is_valid_mac/3 ]). % FIXME: remove
+
+-define(Estring(X), ?STRING((if is_binary(X) -> X;
+ is_list(X) -> list_to_binary(X);
+ X==undefined -> <<>>
+ end))).
+-define(Empint(X), (ssh_bits:mpint(X))/binary ).
+-define(Ebinary(X), ?STRING(X) ).
+-define(Euint32(X), ?UINT32(X) ).
+
+%%%----------------------------------------------------------------------------
+%%%
+%%% There is a difference between supported and default algorithms. The
+%%% SUPPORTED algorithms can be handled (maybe untested...). The DEFAULT ones
+%%% are announced in ssh_msg_kexinit and in ssh:default_algorithms/0 to the
+%%% user.
+%%%
+%%% A supported algorithm can be requested in the option 'preferred_algorithms',
+%%% but may give unexpected results before being promoted to default.
+%%%
+%%% This makes it possible to add experimental algorithms (in supported_algorithms)
+%%% and test them without letting the default users know about them.
+%%%
+
+default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()].
+
+algo_classes() -> [kex, public_key, cipher, mac, compression].
+
+
+default_algorithms(cipher) ->
+ supported_algorithms(cipher, same(['AEAD_AES_128_GCM',
+ 'AEAD_AES_256_GCM']));
+default_algorithms(mac) ->
+ supported_algorithms(mac, same(['AEAD_AES_128_GCM',
+ 'AEAD_AES_256_GCM']));
+default_algorithms(Alg) ->
+ supported_algorithms(Alg, []).
+
+
+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}]},
+ {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]},
+ {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]},
+ {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]},
+ {'diffie-hellman-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}]},
+ {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]},
+ {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]}
+ ]);
+
+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,128}}]},
+ {'[email protected]', [{ciphers,{aes_gcm,256}}]},
+ {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
+ {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]},
+ {'3des-cbc', [{ciphers,des3_cbc}]}
+ ]
+ ));
+supported_algorithms(mac) ->
+ same(
+ select_crypto_supported(
+ [{'hmac-sha2-256', [{hashs,sha256}]},
+ {'hmac-sha2-512', [{hashs,sha512}]},
+ {'hmac-sha1', [{hashs,sha}]},
+ {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
+ {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]}
+ ]
+ ));
+supported_algorithms(compression) ->
+ same(['none',
+ 'zlib'
+ ]).
+
+%%%----------------------------------------------------------------------------
versions(client, Options)->
Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION),
- Version = format_version(Vsn),
- {Vsn, Version};
+ {Vsn, format_version(Vsn, software_version(Options))};
versions(server, Options) ->
Vsn = proplists:get_value(vsn, Options, ?DEFAULT_SERVER_VERSION),
- Version = format_version(Vsn),
- {Vsn, Version}.
+ {Vsn, format_version(Vsn, software_version(Options))}.
+
+software_version(Options) ->
+ case proplists:get_value(id_string, Options) of
+ undefined ->
+ "Erlang"++ssh_vsn();
+ {random,Nlo,Nup} ->
+ random_id(Nlo,Nup);
+ ID ->
+ ID
+ end.
+
+ssh_vsn() ->
+ try {ok,L} = application:get_all_key(ssh),
+ proplists:get_value(vsn,L,"")
+ of
+ "" -> "";
+ VSN when is_list(VSN) -> "/" ++ VSN;
+ _ -> ""
+ catch
+ _:_ -> ""
+ end.
+
+random_id(Nlo, Nup) ->
+ [crypto:rand_uniform($a,$z+1) || _<- lists:duplicate(crypto:rand_uniform(Nlo,Nup+1),x) ].
hello_version_msg(Data) ->
[Data,"\r\n"].
@@ -57,12 +179,6 @@ hello_version_msg(Data) ->
next_seqnum(SeqNum) ->
(SeqNum + 1) band 16#ffffffff.
-decrypt_first_block(Bin, #ssh{decrypt_block_size = BlockSize} = Ssh0) ->
- <<EncBlock:BlockSize/binary, EncData/binary>> = Bin,
- {Ssh, <<?UINT32(PacketLen), _/binary>> = DecData} =
- decrypt(Ssh0, EncBlock),
- {Ssh, PacketLen, DecData, EncData}.
-
decrypt_blocks(Bin, Length, Ssh0) ->
<<EncBlocks:Length/binary, EncData/binary>> = Bin,
{Ssh, DecData} = decrypt(Ssh0, EncBlocks),
@@ -77,9 +193,9 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm,
yes_no(Ssh, Prompt) ->
(Ssh#ssh.io_cb):yes_no(Prompt, Ssh).
-format_version({Major,Minor}) ->
+format_version({Major,Minor}, SoftwareVersion) ->
"SSH-" ++ integer_to_list(Major) ++ "." ++
- integer_to_list(Minor) ++ "-Erlang".
+ integer_to_list(Minor) ++ "-" ++ SoftwareVersion.
handle_hello_version(Version) ->
try
@@ -106,67 +222,51 @@ key_exchange_init_msg(Ssh0) ->
kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) ->
Random = ssh_bits:random(16),
- Compression = case proplists:get_value(compression, Opts, none) of
- openssh_zlib -> ["[email protected]", "none"];
- zlib -> ["zlib", "none"];
- none -> ["none", "zlib"]
- end,
- kexinit_messsage(Role, Random, Compression, HostKeyAlgs).
+ PrefAlgs =
+ case proplists:get_value(preferred_algorithms,Opts) of
+ undefined ->
+ default_algorithms();
+ Algs0 ->
+ Algs0
+ end,
+ kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs).
key_init(client, Ssh, Value) ->
Ssh#ssh{c_keyinit = Value};
key_init(server, Ssh, Value) ->
Ssh#ssh{s_keyinit = Value}.
-available_ssh_algos() ->
- Supports = crypto:supports(),
- CipherAlgos = [{aes_ctr, "aes128-ctr"}, {aes_cbc128, "aes128-cbc"}, {des3_cbc, "3des-cbc"}],
- Ciphers = [SshAlgo ||
- {CryptoAlgo, SshAlgo} <- CipherAlgos,
- lists:member(CryptoAlgo, proplists:get_value(ciphers, Supports, []))],
- HashAlgos = [{sha256, "hmac-sha2-256"}, {sha, "hmac-sha1"}],
- Hashs = [SshAlgo ||
- {CryptoAlgo, SshAlgo} <- HashAlgos,
- lists:member(CryptoAlgo, proplists:get_value(hashs, Supports, []))],
- {Ciphers, Hashs}.
-
-kexinit_messsage(client, Random, Compression, HostKeyAlgs) ->
- {CipherAlgs, HashAlgs} = available_ssh_algos(),
- #ssh_msg_kexinit{
- cookie = Random,
- kex_algorithms = ["diffie-hellman-group1-sha1"],
- server_host_key_algorithms = HostKeyAlgs,
- encryption_algorithms_client_to_server = CipherAlgs,
- encryption_algorithms_server_to_client = CipherAlgs,
- mac_algorithms_client_to_server = HashAlgs,
- mac_algorithms_server_to_client = HashAlgs,
- compression_algorithms_client_to_server = Compression,
- compression_algorithms_server_to_client = Compression,
- languages_client_to_server = [],
- languages_server_to_client = []
- };
-kexinit_messsage(server, Random, Compression, HostKeyAlgs) ->
- {CipherAlgs, HashAlgs} = available_ssh_algos(),
+kexinit_message(_Role, Random, Algs, HostKeyAlgs) ->
#ssh_msg_kexinit{
cookie = Random,
- kex_algorithms = ["diffie-hellman-group1-sha1"],
+ kex_algorithms = to_strings( get_algs(kex,Algs) ),
server_host_key_algorithms = HostKeyAlgs,
- encryption_algorithms_client_to_server = CipherAlgs,
- encryption_algorithms_server_to_client = CipherAlgs,
- mac_algorithms_client_to_server = HashAlgs,
- mac_algorithms_server_to_client = HashAlgs,
- compression_algorithms_client_to_server = Compression,
- compression_algorithms_server_to_client = Compression,
+ encryption_algorithms_client_to_server = c2s(cipher,Algs),
+ encryption_algorithms_server_to_client = s2c(cipher,Algs),
+ mac_algorithms_client_to_server = c2s(mac,Algs),
+ mac_algorithms_server_to_client = s2c(mac,Algs),
+ compression_algorithms_client_to_server = c2s(compression,Algs),
+ compression_algorithms_server_to_client = s2c(compression,Algs),
languages_client_to_server = [],
languages_server_to_client = []
}.
+c2s(Key, Algs) -> x2y(client2server, Key, Algs).
+s2c(Key, Algs) -> x2y(server2client, Key, Algs).
+
+x2y(DirectionKey, Key, Algs) -> to_strings(proplists:get_value(DirectionKey, get_algs(Key,Algs))).
+
+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),
{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),
@@ -176,135 +276,406 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
Ssh0#ssh{algorithms = Algoritms});
_ ->
%% TODO: Correct code?
- throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Selection of key exchange"
- " algorithm failed",
- language = "en"})
+ 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),
- {ok, Ssh#ssh{algorithms = Algoritms}}.
+ 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"
+ })
+ end.
%% TODO: diffie-hellman-group14-sha1 should also be supported.
%% Maybe check more things ...
-verify_algorithm(#alg{kex = 'diffie-hellman-group1-sha1'}) ->
- true;
-verify_algorithm(#alg{kex = 'diffie-hellman-group-exchange-sha1'}) ->
- true;
-verify_algorithm(_) ->
- false.
-key_exchange_first_msg('diffie-hellman-group1-sha1', Ssh0) ->
- {G, P} = dh_group1(),
- {Private, Public} = dh_gen_key(G, P, 1024),
+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)).
+
+%%%----------------------------------------------------------------
+%%%
+%%% Key exchange initialization
+%%%
+key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ;
+ Kex == 'diffie-hellman-group14-sha1' ->
+ {G, P} = dh_group(Kex),
+ {Public, Private} = generate_key(dh, [P,G]),
{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('diffie-hellman-group-exchange-sha1', Ssh0) ->
- Min = ?DEFAULT_DH_GROUP_MIN,
- NBits = ?DEFAULT_DH_GROUP_NBITS,
- Max = ?DEFAULT_DH_GROUP_MAX,
+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}),
{SshPacket, Ssh1} =
ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min,
- n = NBits, max = Max},
+ n = NBits,
+ max = Max},
Ssh0),
{ok, SshPacket,
- Ssh1#ssh{keyex_info = {Min, Max, NBits}}}.
-
-
-handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0) ->
- {G, P} = dh_group1(),
- {Private, Public} = dh_gen_key(G, P, 1024),
- K = ssh_math:ipow(E, Private, P),
- Key = get_host_key(Ssh0),
- H = kex_h(Ssh0, Key, E, Public, K),
- H_SIG = sign_host_key(Ssh0, Key, H),
- {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_reply{public_host_key = Key,
- f = Public,
- h_sig = H_SIG
- }, Ssh0),
-
- {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}},
- shared_secret = K,
- exchanged_hash = H,
- session_id = sid(Ssh1, H)}}.
-
-handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) ->
- {Private, Public} = dh_gen_key(G,P,1024),
- {SshPacket, Ssh1} =
- ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0),
+ Ssh1#ssh{keyex_info = {Min, Max, NBits}}};
+
+key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ;
+ Kex == 'ecdh-sha2-nistp384' ;
+ Kex == 'ecdh-sha2-nistp521' ->
+ Curve = ecdh_curve(Kex),
+ {Public, Private} = generate_key(ecdh, Curve),
+ {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_ecdh_init{q_c=Public}, Ssh0),
{ok, SshPacket,
- Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}.
-
-handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
- try install_alg(Ssh0) of
- #ssh{} = Ssh ->
- {ok, Ssh}
- catch
- error:_Error -> %% TODO: Throw earlier ....
- throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Install alg failed",
- language = "en"})
- end.
+ Ssh1#ssh{keyex_key = {{Public,Private},Curve}}}.
+
+%%%----------------------------------------------------------------
+%%%
+%%% diffie-hellman-group1-sha1
+%%% diffie-hellman-group14-sha1
+%%%
+handle_kexdh_init(#ssh_msg_kexdh_init{e = E},
+ Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) ->
+ %% server
+ {G, P} = dh_group(Kex),
+ if
+ 1=<E, E=<(P-1) ->
+ {Public, Private} = generate_key(dh, [P,G]),
+ K = compute_key(dh, E, Private, [P,G]),
+ MyPrivHostKey = get_host_key(Ssh0),
+ MyPubHostKey = extract_public_key(MyPrivHostKey),
+ H = kex_h(Ssh0, MyPubHostKey, E, Public, K),
+ H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ {SshPacket, Ssh1} =
+ ssh_packet(#ssh_msg_kexdh_reply{public_host_key = MyPubHostKey,
+ f = Public,
+ h_sig = H_SIG
+ }, Ssh0),
+ {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}},
+ shared_secret = K,
+ exchanged_hash = H,
+ session_id = sid(Ssh1, H)}};
+ true ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, 'e' out of bounds"},
+ {error,bad_e_from_peer}
+ )
+ end.
-%% %% Select algorithms
-handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey, f = F,
+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) ->
- K = ssh_math:ipow(F, Private, P),
- H = kex_h(Ssh0, HostKey, Public, F, K),
-
- case verify_host_key(Ssh0, HostKey, 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)}};
- Error ->
- Disconnect = #ssh_msg_disconnect{
- code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- description = "Key exchange failed",
- language = "en"},
- throw({Error, Disconnect})
+ #ssh{keyex_key = {{Private, Public}, {G, P}}} = Ssh0) ->
+ %% client
+ if
+ 1=<F, F=<(P-1)->
+ K = compute_key(dh, F, Private, [P,G]),
+ H = kex_h(Ssh0, PeerPubHostKey, 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)}};
+ Error ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed"},
+ Error)
+ end;
+
+ true ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, 'f' out of bounds"},
+ bad_f_from_peer
+ )
end.
-handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = _Min,
- n = _NBits,
- max = _Max}, Ssh0) ->
- {G,P} = dh_group1(), %% TODO real imp this seems to be a hack?!
- {Private, Public} = dh_gen_key(G, P, 1024),
- {SshPacket, Ssh} =
- ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0),
+
+%%%----------------------------------------------------------------
+%%%
+%%% diffie-hellman-group-exchange-sha1
+%%%
+handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0,
+ n = NBits,
+ max = Max0},
+ Ssh0=#ssh{opts=Opts}) when Min0=<NBits, NBits=<Max0 ->
+ %% 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]),
+ {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}
+ }};
+ {error,_} ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "No possible diffie-hellman-group-exchange group found"
+ })
+ end;
+
+handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits},
+ Ssh0=#ssh{opts=Opts}) ->
+ %% server
+ %%
+ %% 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
+ %% 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".
+ %% Further, it says that "Servers and clients SHOULD support groups
+ %% with a modulus length of k bits, where 1024 <= k <= 8192."
+ %%
+ Min0 = 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]),
+ {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
+ }};
+ {error,_} ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "No possible diffie-hellman-group-exchange group found"
+ })
+ end;
+
+handle_kex_dh_gex_request(_, _) ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, bad values in ssh_msg_kex_dh_gex_request"},
+ bad_ssh_msg_kex_dh_gex_request).
+
+
+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
+ 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]),
+ {SshPacket, Ssh1} =
+ ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0), % Pub = G^Priv mod P (def)
+
{ok, SshPacket,
- Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}}.
+ Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}.
-handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey,
+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) ->
+ %% 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),
+ 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),
+ {SshPacket, Ssh} =
+ ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey,
+ f = Public,
+ h_sig = H_SIG}, Ssh0),
+ {ok, SshPacket, Ssh#ssh{shared_secret = K,
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)
+ }};
+ true ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, 'K' out of bounds"},
+ bad_K)
+ end;
+ true ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, 'e' out of bounds"},
+ bad_e_from_peer)
+ end.
+
+handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostKey,
f = F,
h_sig = H_SIG},
#ssh{keyex_key = {{Private, Public}, {G, P}},
keyex_info = {Min, Max, NBits}} =
- Ssh0) ->
- K = ssh_math:ipow(F, Private, P),
- H = kex_h(Ssh0, HostKey, Min, NBits, Max, P, G, Public, F, K),
-
- case verify_host_key(Ssh0, HostKey, 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)}};
- _Error ->
- Disconnect = #ssh_msg_disconnect{
- code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- description = "Key exchange failed",
- language = "en"},
- throw(Disconnect)
- end.
+ Ssh0) ->
+ %% client
+ if
+ 1=<F, F=<(P-1)->
+ 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),
+
+ 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)}};
+ _Error ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed"
+ })
+ end;
+
+ true ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, 'K' out of bounds"},
+ bad_K)
+ end;
+ true ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, 'f' out of bounds"},
+ bad_f_from_peer
+ )
+ end.
+
+%%%----------------------------------------------------------------
+%%%
+%%% diffie-hellman-ecdh-sha2-*
+%%%
+handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
+ Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) ->
+ %% at server
+ Curve = ecdh_curve(Kex),
+ {MyPublic, MyPrivate} = generate_key(ecdh, Curve),
+ try
+ compute_key(ecdh, PeerPublic, MyPrivate, Curve)
+ of
+ K ->
+ MyPrivHostKey = get_host_key(Ssh0),
+ MyPubHostKey = extract_public_key(MyPrivHostKey),
+ H = kex_h(Ssh0, Curve, MyPubHostKey, PeerPublic, MyPublic, K),
+ H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ {SshPacket, Ssh1} =
+ ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = MyPubHostKey,
+ q_s = MyPublic,
+ h_sig = H_SIG},
+ Ssh0),
+ {ok, SshPacket, Ssh1#ssh{keyex_key = {{MyPublic,MyPrivate},Curve},
+ shared_secret = K,
+ exchanged_hash = H,
+ session_id = sid(Ssh1, H)}}
+ catch
+ _:_ ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Peer ECDH public key is invalid"},
+ invalid_peer_public_key)
+ end.
+
+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
+ ) ->
+ %% at client
+ try
+ compute_key(ecdh, PeerPublic, MyPrivate, Curve)
+ of
+ K ->
+ H = kex_h(Ssh0, Curve, PeerPubHostKey, 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)}};
+ Error ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed"},
+ Error)
+ end
+ catch
+ _:_ ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Peer ECDH public key is invalid"},
+ invalid_peer_public_key)
+ end.
+
+
+%%%----------------------------------------------------------------
+handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
+ try install_alg(Ssh0) of
+ #ssh{} = Ssh ->
+ {ok, Ssh}
+ catch
+ _C:_Error -> %% TODO: Throw earlier ....
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Install alg failed"
+ })
+ end.
%% select session id
sid(#ssh{session_id = undefined}, H) ->
@@ -319,33 +690,49 @@ 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, #'RSAPrivateKey'{} = Key} -> Key;
+ {ok, #'DSAPrivateKey'{} = Key} -> Key;
+ {ok, #'ECPrivateKey'{} = Key} -> Key;
Result ->
exit({error, {Result, unsupported_key_type}})
end.
-sign_host_key(_Ssh, #'RSAPrivateKey'{} = Private, H) ->
- Hash = sha, %% Option ?!
- _Signature = sign(H, Hash, Private);
-sign_host_key(_Ssh, #'DSAPrivateKey'{} = Private, H) ->
- Hash = sha, %% Option ?!
- _RawSignature = sign(H, Hash, Private).
+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}) ->
+ {Y, #'Dss-Parms'{p=P, q=Q, g=G}};
+extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
+ publicKey = Q}) ->
+ {#'ECPoint'{point=Q}, {namedCurve,OID}}.
+
verify_host_key(SSH, PublicKey, Digest, Signature) ->
- case verify(Digest, sha, Signature, PublicKey) of
+ case verify(Digest, host_key_sha(PublicKey), Signature, PublicKey) of
false ->
{error, bad_signature};
true ->
known_host_key(SSH, PublicKey, public_algo(PublicKey))
end.
-public_algo(#'RSAPublicKey'{}) ->
- 'ssh-rsa';
-public_algo({_, #'Dss-Parms'{}}) ->
- 'ssh-dss'.
+
+host_key_sha(#'RSAPublicKey'{}) -> sha;
+host_key_sha({_, #'Dss-Parms'{}}) -> sha;
+host_key_sha({#'ECPoint'{},{namedCurve,OID}}) -> sha(OID).
+
+public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa';
+public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
+public_algo({#'ECPoint'{},{namedCurve,OID}}) ->
+ Curve = public_key:oid2ssh_curvename(OID),
+ list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)).
+
accepted_host(Ssh, PeerName, Opts) ->
case proplists:get_value(silently_accept_hosts, Opts, false) of
@@ -378,8 +765,12 @@ 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) ->
- {Encrypt, Decrypt} = select_encrypt_decrypt(Role, Client, Server),
- {SendMac, RecvMac} = select_send_recv_mac(Role, Client, Server),
+ {Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server),
+ {SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server),
+
+ {Encrypt, SendMac} = aead_gcm_simultan(Encrypt0, SendMac0),
+ {Decrypt, RecvMac} = aead_gcm_simultan(Decrypt0, RecvMac0),
+
{Compression, Decompression} =
select_compression_decompression(Role, Client, Server),
@@ -410,6 +801,38 @@ select_algorithm(Role, Client, Server) ->
s_lng = S_Lng},
{ok, Alg}.
+
+%%% It is an agreed problem with RFC 5674 that if the selection is
+%%% Cipher = AEAD_AES_x_GCM and
+%%% Mac = AEAD_AES_y_GCM (where x =/= y)
+%%% then it is undefined what length should be selected.
+%%%
+%%% If only one of the two lengths (128,256) is available, I claim that
+%%% there is no such ambiguity.
+
+%%% From https://anongit.mindrot.org/openssh.git/plain/PROTOCOL
+%%% (read Nov 20, 2015)
+%%% 1.6 transport: AES-GCM
+%%%
+%%% OpenSSH supports the AES-GCM algorithm as specified in RFC 5647.
+%%% Because of problems with the specification of the key exchange
+%%% the behaviour of OpenSSH differs from the RFC as follows:
+%%%
+%%% AES-GCM is only negotiated as the cipher algorithms
+%%% "[email protected]" or "[email protected]" and never as
+%%% an MAC algorithm. Additionally, if AES-GCM is selected as the cipher
+%%% the exchanged MAC algorithms are ignored and there doesn't have to be
+%%% a matching MAC.
+
+aead_gcm_simultan('[email protected]', _) -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'};
+aead_gcm_simultan('[email protected]', _) -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'};
+aead_gcm_simultan('AEAD_AES_128_GCM', _) -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'};
+aead_gcm_simultan('AEAD_AES_256_GCM', _) -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'};
+aead_gcm_simultan(_, 'AEAD_AES_128_GCM') -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'};
+aead_gcm_simultan(_, 'AEAD_AES_256_GCM') -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'};
+aead_gcm_simultan(Cipher, Mac) -> {Cipher,Mac}.
+
+
select_encrypt_decrypt(client, Client, Server) ->
Encrypt =
select(Client#ssh_msg_kexinit.encryption_algorithms_client_to_server,
@@ -444,18 +867,18 @@ select_compression_decompression(client, Client, Server) ->
Compression =
select(Client#ssh_msg_kexinit.compression_algorithms_client_to_server,
Server#ssh_msg_kexinit.compression_algorithms_client_to_server),
- Decomprssion =
+ Decompression =
select(Client#ssh_msg_kexinit.compression_algorithms_server_to_client,
Server#ssh_msg_kexinit.compression_algorithms_server_to_client),
- {Compression, Decomprssion};
+ {Compression, Decompression};
select_compression_decompression(server, Client, Server) ->
- Decomprssion =
+ Decompression =
select(Client#ssh_msg_kexinit.compression_algorithms_client_to_server,
Server#ssh_msg_kexinit.compression_algorithms_client_to_server),
Compression =
select(Client#ssh_msg_kexinit.compression_algorithms_server_to_client,
Server#ssh_msg_kexinit.compression_algorithms_server_to_client),
- {Compression, Decomprssion}.
+ {Compression, Decompression}.
install_alg(SSH) ->
SSH1 = alg_final(SSH),
@@ -497,10 +920,16 @@ alg_final(SSH0) ->
{ok,SSH6} = decompress_final(SSH5),
SSH6.
-select_all(CL, SL) ->
+select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS ->
A = CL -- SL, %% algortihms only used by client
%% algorithms used by client and server (client pref)
- lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)).
+ lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A));
+select_all(CL, SL) ->
+ Err = lists:concat(["Received too many algorithms (",length(CL),"+",length(SL)," >= ",?MAX_NUM_ALGORITHMS,")."]),
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = Err}).
+
select([], []) ->
none;
@@ -520,58 +949,147 @@ ssh_packet(Msg, Ssh) ->
BinMsg = ssh_message:encode(Msg),
pack(BinMsg, Ssh).
-pack(Data0, #ssh{encrypt_block_size = BlockSize,
- send_sequence = SeqNum, send_mac = MacAlg,
- send_mac_key = MacKey}
- = Ssh0) when is_binary(Data0) ->
- {Ssh1, Data} = compress(Ssh0, Data0),
- PL = (BlockSize - ((4 + 1 + size(Data)) rem BlockSize)) rem BlockSize,
- PaddingLen = if PL < 4 -> PL + BlockSize;
- true -> PL
- end,
- Padding = ssh_bits:random(PaddingLen),
- PacketLen = 1 + PaddingLen + size(Data),
- PacketData = <<?UINT32(PacketLen),?BYTE(PaddingLen),
- Data/binary, Padding/binary>>,
- {Ssh2, EncPacket} = encrypt(Ssh1, PacketData),
- MAC = mac(MacAlg, MacKey, SeqNum, PacketData),
- Packet = [EncPacket, MAC],
- Ssh = Ssh2#ssh{send_sequence = (SeqNum+1) band 16#ffffffff},
- {Packet, Ssh}.
-
-unpack(EncodedSoFar, ReminingLenght, #ssh{recv_mac_size = MacSize} = Ssh0) ->
- SshLength = ReminingLenght - MacSize,
- {NoMac, Mac, Rest} = case MacSize of
- 0 ->
- <<NoMac0:SshLength/binary,
- Rest0/binary>> = EncodedSoFar,
- {NoMac0, <<>>, Rest0};
- _ ->
- <<NoMac0:SshLength/binary,
- Mac0:MacSize/binary,
- Rest0/binary>> = EncodedSoFar,
- {NoMac0, Mac0, Rest0}
- end,
- {Ssh1, DecData, <<>>} =
- case SshLength of
- 0 ->
- {Ssh0, <<>>, <<>>};
- _ ->
- decrypt_blocks(NoMac, SshLength, Ssh0)
+pack(Data, Ssh=#ssh{}) ->
+ pack(Data, Ssh, 0).
+
+%%% Note: pack/3 is only to be called from tests that wants
+%%% to deliberetly send packets with wrong PacketLength!
+%%% Use pack/2 for all other purposes!
+pack(PlainText,
+ #ssh{send_sequence = SeqNum,
+ send_mac = MacAlg,
+ send_mac_key = MacKey,
+ encrypt = CryptoAlg} = Ssh0, PacketLenDeviationForTests) when is_binary(PlainText) ->
+
+ {Ssh1, CompressedPlainText} = compress(Ssh0, PlainText),
+ {EcryptedPacket, MAC, Ssh3} =
+ case pkt_type(CryptoAlg) of
+ common ->
+ PaddingLen = padding_length(4+1+size(CompressedPlainText), Ssh0),
+ Padding = ssh_bits:random(PaddingLen),
+ PlainPacketLen = 1 + PaddingLen + size(CompressedPlainText) + PacketLenDeviationForTests,
+ PlainPacketData = <<?UINT32(PlainPacketLen),?BYTE(PaddingLen), CompressedPlainText/binary, Padding/binary>>,
+ {Ssh2, EcryptedPacket0} = encrypt(Ssh1, PlainPacketData),
+ MAC0 = mac(MacAlg, MacKey, SeqNum, PlainPacketData),
+ {EcryptedPacket0, MAC0, Ssh2};
+ aead ->
+ PaddingLen = padding_length(1+size(CompressedPlainText), Ssh0),
+ Padding = ssh_bits:random(PaddingLen),
+ PlainPacketLen = 1 + PaddingLen + size(CompressedPlainText) + PacketLenDeviationForTests,
+ PlainPacketData = <<?BYTE(PaddingLen), CompressedPlainText/binary, Padding/binary>>,
+ {Ssh2, {EcryptedPacket0,MAC0}} = encrypt(Ssh1, {<<?UINT32(PlainPacketLen)>>,PlainPacketData}),
+ {<<?UINT32(PlainPacketLen),EcryptedPacket0/binary>>, MAC0, Ssh2}
end,
- {Ssh1, DecData, Rest, Mac}.
+ FinalPacket = [EcryptedPacket, MAC],
+ Ssh = Ssh3#ssh{send_sequence = (SeqNum+1) band 16#ffffffff},
+ {FinalPacket, Ssh}.
+
+
+padding_length(Size, #ssh{encrypt_block_size = BlockSize,
+ random_length_padding = RandomLengthPadding}) ->
+ PL = (BlockSize - (Size rem BlockSize)) rem BlockSize,
+ MinPaddingLen = if PL < 4 -> PL + BlockSize;
+ true -> PL
+ end,
+ PadBlockSize = max(BlockSize,4),
+ MaxExtraBlocks = (max(RandomLengthPadding,MinPaddingLen) - MinPaddingLen) div PadBlockSize,
+ ExtraPaddingLen = try crypto:rand_uniform(0,MaxExtraBlocks)*PadBlockSize
+ catch _:_ -> 0
+ end,
+ MinPaddingLen + ExtraPaddingLen.
+
+
+
+handle_packet_part(<<>>, Encrypted0, undefined, #ssh{decrypt = CryptoAlg} = Ssh0) ->
+ %% New ssh packet
+ case get_length(pkt_type(CryptoAlg), Encrypted0, Ssh0) of
+ get_more ->
+ %% too short to get the length
+ {get_more, <<>>, Encrypted0, undefined, Ssh0};
+
+ {ok, PacketLen, _, _, _} when PacketLen > ?SSH_MAX_PACKET_SIZE ->
+ %% far too long message than expected
+ {error, {exceeds_max_size,PacketLen}};
+
+ {ok, PacketLen, Decrypted, Encrypted1,
+ #ssh{recv_mac_size = MacSize} = Ssh1} ->
+ %% enough bytes so we got the length and can calculate how many
+ %% more bytes to expect for a full packet
+ TotalNeeded = (4 + PacketLen + MacSize),
+ handle_packet_part(Decrypted, Encrypted1, TotalNeeded, Ssh1)
+ end;
-msg_data(PacketData) ->
- <<Len:32, PaddingLen:8, _/binary>> = PacketData,
- DataLen = Len - PaddingLen - 1,
- <<_:32, _:8, Data:DataLen/binary,
- _:PaddingLen/binary>> = PacketData,
- Data.
+handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, Ssh0)
+ when (size(DecryptedPfx)+size(EncryptedBuffer)) < TotalNeeded ->
+ %% need more bytes to finalize the packet
+ {get_more, DecryptedPfx, EncryptedBuffer, TotalNeeded, Ssh0};
+
+handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded,
+ #ssh{recv_mac_size = MacSize,
+ decrypt = CryptoAlg} = Ssh0) ->
+ %% enough bytes to decode the packet.
+ DecryptLen = TotalNeeded - size(DecryptedPfx) - MacSize,
+ <<EncryptedSfx:DecryptLen/binary, Mac:MacSize/binary, NextPacketBytes/binary>> = EncryptedBuffer,
+ case pkt_type(CryptoAlg) of
+ common ->
+ {Ssh1, DecryptedSfx} = decrypt(Ssh0, EncryptedSfx),
+ DecryptedPacket = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
+ case is_valid_mac(Mac, DecryptedPacket, Ssh1) of
+ false ->
+ {bad_mac, Ssh1};
+ true ->
+ {Ssh, DecompressedPayload} = decompress(Ssh1, payload(DecryptedPacket)),
+ {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh}
+ end;
+ aead ->
+ PacketLenBin = DecryptedPfx,
+ case decrypt(Ssh0, {PacketLenBin,EncryptedSfx,Mac}) of
+ {Ssh1, error} ->
+ {bad_mac, Ssh1};
+ {Ssh1, DecryptedSfx} ->
+ DecryptedPacket = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
+ {Ssh, DecompressedPayload} = decompress(Ssh1, payload(DecryptedPacket)),
+ {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh}
+ end
+ end.
+
+
+get_length(common, EncryptedBuffer, #ssh{decrypt_block_size = BlockSize} = Ssh0) ->
+ case size(EncryptedBuffer) >= erlang:max(8, BlockSize) of
+ true ->
+ <<EncBlock:BlockSize/binary, EncryptedRest/binary>> = EncryptedBuffer,
+ {Ssh,
+ <<?UINT32(PacketLen),_/binary>> = Decrypted} = decrypt(Ssh0, EncBlock),
+ {ok, PacketLen, Decrypted, EncryptedRest, Ssh};
+ false ->
+ get_more
+ end;
+get_length(aead, EncryptedBuffer, Ssh) ->
+ case size(EncryptedBuffer) >= 4 of
+ true ->
+ <<?UINT32(PacketLen), EncryptedRest/binary>> = EncryptedBuffer,
+ {ok, PacketLen, <<?UINT32(PacketLen)>>, EncryptedRest, Ssh};
+ false ->
+ get_more
+ end.
+
+pkt_type('AEAD_AES_128_GCM') -> aead;
+pkt_type('AEAD_AES_256_GCM') -> aead;
+pkt_type(_) -> common.
+
+payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) ->
+ PayloadLen = PacketLen - PaddingLen - 1,
+ <<Payload:PayloadLen/binary, _/binary>> = PayloadAndPadding,
+ Payload.
sign(SigData, Hash, #'DSAPrivateKey'{} = Key) ->
DerSignature = public_key:sign(SigData, Hash, 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),
+ #'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).
@@ -579,59 +1097,48 @@ 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).
-%% public key algorithms
-%%
-%% ssh-dss REQUIRED sign Raw DSS Key
-%% ssh-rsa RECOMMENDED sign Raw RSA Key
-%% x509v3-sign-rsa OPTIONAL sign X.509 certificates (RSA key)
-%% x509v3-sign-dss OPTIONAL sign X.509 certificates (DSS key)
-%% spki-sign-rsa OPTIONAL sign SPKI certificates (RSA key)
-%% spki-sign-dss OPTIONAL sign SPKI certificates (DSS key)
-%% pgp-sign-rsa OPTIONAL sign OpenPGP certificates (RSA key)
-%% pgp-sign-dss OPTIONAL sign OpenPGP certificates (DSS key)
-%%
-
-%% key exchange
-%%
-%% diffie-hellman-group1-sha1 REQUIRED
-%%
-%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Encryption
-%%
-%% chiphers
%%
-%% 3des-cbc REQUIRED
-%% three-key 3DES in CBC mode
-%% blowfish-cbc OPTIONAL Blowfish in CBC mode
-%% twofish256-cbc OPTIONAL Twofish in CBC mode,
-%% with 256-bit key
-%% twofish-cbc OPTIONAL alias for "twofish256-cbc" (this
-%% is being retained for
-%% historical reasons)
-%% twofish192-cbc OPTIONAL Twofish with 192-bit key
-%% twofish128-cbc OPTIONAL Twofish with 128-bit key
-%% aes256-cbc OPTIONAL AES in CBC mode,
-%% with 256-bit key
-%% aes192-cbc OPTIONAL AES with 192-bit key
-%% aes128-cbc RECOMMENDED AES with 128-bit key
-%% serpent256-cbc OPTIONAL Serpent in CBC mode, with
-%% 256-bit key
-%% serpent192-cbc OPTIONAL Serpent with 192-bit key
-%% serpent128-cbc OPTIONAL Serpent with 128-bit key
-%% arcfour OPTIONAL the ARCFOUR stream cipher
-%% idea-cbc OPTIONAL IDEA in CBC mode
-%% cast128-cbc OPTIONAL CAST-128 in CBC mode
-%% none OPTIONAL no encryption; NOT RECOMMENDED
+%% Encryption
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
encrypt_init(#ssh{encrypt = none} = Ssh) ->
{ok, Ssh};
+encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) ->
+ IV = hash(Ssh, "A", 12*8),
+ <<K:16/binary>> = hash(Ssh, "C", 128),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = server} = Ssh) ->
+ IV = hash(Ssh, "B", 12*8),
+ <<K:16/binary>> = hash(Ssh, "D", 128),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = 'AEAD_AES_256_GCM', role = client} = Ssh) ->
+ IV = hash(Ssh, "A", 12*8),
+ <<K:32/binary>> = hash(Ssh, "C", 256),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = 'AEAD_AES_256_GCM', role = server} = Ssh) ->
+ IV = hash(Ssh, "B", 12*8),
+ <<K:32/binary>> = hash(Ssh, "D", 256),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
encrypt_init(#ssh{encrypt = '3des-cbc', role = client} = Ssh) ->
IV = hash(Ssh, "A", 64),
<<K1:8/binary, K2:8/binary, K3:8/binary>> = hash(Ssh, "C", 192),
@@ -657,18 +1164,46 @@ encrypt_init(#ssh{encrypt = 'aes128-cbc', role = server} = Ssh) ->
encrypt_block_size = 16,
encrypt_ctx = IV}};
encrypt_init(#ssh{encrypt = 'aes128-ctr', role = client} = Ssh) ->
- IV = hash(Ssh, "A", 128),
+ IV = hash(Ssh, "A", 128),
<<K:16/binary>> = hash(Ssh, "C", 128),
State = crypto:stream_init(aes_ctr, K, IV),
{ok, Ssh#ssh{encrypt_keys = K,
encrypt_block_size = 16,
encrypt_ctx = State}};
+encrypt_init(#ssh{encrypt = 'aes192-ctr', role = client} = Ssh) ->
+ IV = hash(Ssh, "A", 128),
+ <<K:24/binary>> = hash(Ssh, "C", 192),
+ State = crypto:stream_init(aes_ctr, K, IV),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = State}};
+encrypt_init(#ssh{encrypt = 'aes256-ctr', role = client} = Ssh) ->
+ IV = hash(Ssh, "A", 128),
+ <<K:32/binary>> = hash(Ssh, "C", 256),
+ State = crypto:stream_init(aes_ctr, K, IV),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = State}};
encrypt_init(#ssh{encrypt = 'aes128-ctr', role = server} = Ssh) ->
- IV = hash(Ssh, "B", 128),
+ IV = hash(Ssh, "B", 128),
<<K:16/binary>> = hash(Ssh, "D", 128),
State = crypto:stream_init(aes_ctr, K, IV),
{ok, Ssh#ssh{encrypt_keys = K,
encrypt_block_size = 16,
+ encrypt_ctx = State}};
+encrypt_init(#ssh{encrypt = 'aes192-ctr', role = server} = Ssh) ->
+ IV = hash(Ssh, "B", 128),
+ <<K:24/binary>> = hash(Ssh, "D", 192),
+ State = crypto:stream_init(aes_ctr, K, IV),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = State}};
+encrypt_init(#ssh{encrypt = 'aes256-ctr', role = server} = Ssh) ->
+ IV = hash(Ssh, "B", 128),
+ <<K:32/binary>> = hash(Ssh, "D", 256),
+ State = crypto:stream_init(aes_ctr, K, IV),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
encrypt_ctx = State}}.
encrypt_final(Ssh) ->
@@ -680,6 +1215,18 @@ encrypt_final(Ssh) ->
encrypt(#ssh{encrypt = none} = Ssh, Data) ->
{Ssh, Data};
+encrypt(#ssh{encrypt = 'AEAD_AES_128_GCM',
+ encrypt_keys = K,
+ encrypt_ctx = IV0} = Ssh, Data={_AAD,_Ptext}) ->
+ Enc = {_Ctext,_Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, Data),
+ IV = next_gcm_iv(IV0),
+ {Ssh#ssh{encrypt_ctx = IV}, Enc};
+encrypt(#ssh{encrypt = 'AEAD_AES_256_GCM',
+ encrypt_keys = K,
+ encrypt_ctx = IV0} = Ssh, Data={_AAD,_Ptext}) ->
+ Enc = {_Ctext,_Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, Data),
+ IV = next_gcm_iv(IV0),
+ {Ssh#ssh{encrypt_ctx = IV}, Enc};
encrypt(#ssh{encrypt = '3des-cbc',
encrypt_keys = {K1,K2,K3},
encrypt_ctx = IV0} = Ssh, Data) ->
@@ -695,6 +1242,14 @@ encrypt(#ssh{encrypt = 'aes128-cbc',
encrypt(#ssh{encrypt = 'aes128-ctr',
encrypt_ctx = State0} = Ssh, Data) ->
{State, Enc} = crypto:stream_encrypt(State0,Data),
+ {Ssh#ssh{encrypt_ctx = State}, Enc};
+encrypt(#ssh{encrypt = 'aes192-ctr',
+ encrypt_ctx = State0} = Ssh, Data) ->
+ {State, Enc} = crypto:stream_encrypt(State0,Data),
+ {Ssh#ssh{encrypt_ctx = State}, Enc};
+encrypt(#ssh{encrypt = 'aes256-ctr',
+ encrypt_ctx = State0} = Ssh, Data) ->
+ {State, Enc} = crypto:stream_encrypt(State0,Data),
{Ssh#ssh{encrypt_ctx = State}, Enc}.
@@ -704,6 +1259,30 @@ encrypt(#ssh{encrypt = 'aes128-ctr',
decrypt_init(#ssh{decrypt = none} = Ssh) ->
{ok, Ssh};
+decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) ->
+ IV = hash(Ssh, "B", 12*8),
+ <<K:16/binary>> = hash(Ssh, "D", 128),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = IV}};
+decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = server} = Ssh) ->
+ IV = hash(Ssh, "A", 12*8),
+ <<K:16/binary>> = hash(Ssh, "C", 128),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = IV}};
+decrypt_init(#ssh{decrypt = 'AEAD_AES_256_GCM', role = client} = Ssh) ->
+ IV = hash(Ssh, "B", 12*8),
+ <<K:32/binary>> = hash(Ssh, "D", 256),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = IV}};
+decrypt_init(#ssh{decrypt = 'AEAD_AES_256_GCM', role = server} = Ssh) ->
+ IV = hash(Ssh, "A", 12*8),
+ <<K:32/binary>> = hash(Ssh, "C", 256),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = IV}};
decrypt_init(#ssh{decrypt = '3des-cbc', role = client} = Ssh) ->
{IV, KD} = {hash(Ssh, "B", 64),
hash(Ssh, "D", 192)},
@@ -735,12 +1314,40 @@ decrypt_init(#ssh{decrypt = 'aes128-ctr', role = client} = Ssh) ->
{ok, Ssh#ssh{decrypt_keys = K,
decrypt_block_size = 16,
decrypt_ctx = State}};
+decrypt_init(#ssh{decrypt = 'aes192-ctr', role = client} = Ssh) ->
+ IV = hash(Ssh, "B", 128),
+ <<K:24/binary>> = hash(Ssh, "D", 192),
+ State = crypto:stream_init(aes_ctr, K, IV),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = State}};
+decrypt_init(#ssh{decrypt = 'aes256-ctr', role = client} = Ssh) ->
+ IV = hash(Ssh, "B", 128),
+ <<K:32/binary>> = hash(Ssh, "D", 256),
+ State = crypto:stream_init(aes_ctr, K, IV),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = State}};
decrypt_init(#ssh{decrypt = 'aes128-ctr', role = server} = Ssh) ->
IV = hash(Ssh, "A", 128),
<<K:16/binary>> = hash(Ssh, "C", 128),
State = crypto:stream_init(aes_ctr, K, IV),
{ok, Ssh#ssh{decrypt_keys = K,
decrypt_block_size = 16,
+ decrypt_ctx = State}};
+decrypt_init(#ssh{decrypt = 'aes192-ctr', role = server} = Ssh) ->
+ IV = hash(Ssh, "A", 128),
+ <<K:24/binary>> = hash(Ssh, "C", 192),
+ State = crypto:stream_init(aes_ctr, K, IV),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = State}};
+decrypt_init(#ssh{decrypt = 'aes256-ctr', role = server} = Ssh) ->
+ IV = hash(Ssh, "A", 128),
+ <<K:32/binary>> = hash(Ssh, "C", 256),
+ State = crypto:stream_init(aes_ctr, K, IV),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
decrypt_ctx = State}}.
@@ -750,8 +1357,22 @@ decrypt_final(Ssh) ->
decrypt_ctx = undefined,
decrypt_block_size = 8}}.
+decrypt(Ssh, <<>>) ->
+ {Ssh, <<>>};
decrypt(#ssh{decrypt = none} = Ssh, Data) ->
{Ssh, Data};
+decrypt(#ssh{decrypt = 'AEAD_AES_128_GCM',
+ decrypt_keys = K,
+ decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) ->
+ Dec = crypto:block_decrypt(aes_gcm, K, IV0, Data), % Dec = PlainText | error
+ IV = next_gcm_iv(IV0),
+ {Ssh#ssh{decrypt_ctx = IV}, Dec};
+decrypt(#ssh{decrypt = 'AEAD_AES_256_GCM',
+ decrypt_keys = K,
+ decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) ->
+ Dec = crypto:block_decrypt(aes_gcm, K, IV0, Data), % Dec = PlainText | error
+ IV = next_gcm_iv(IV0),
+ {Ssh#ssh{decrypt_ctx = IV}, Dec};
decrypt(#ssh{decrypt = '3des-cbc', decrypt_keys = Keys,
decrypt_ctx = IV0} = Ssh, Data) ->
{K1, K2, K3} = Keys,
@@ -766,8 +1387,20 @@ decrypt(#ssh{decrypt = 'aes128-cbc', decrypt_keys = Key,
decrypt(#ssh{decrypt = 'aes128-ctr',
decrypt_ctx = State0} = Ssh, Data) ->
{State, Enc} = crypto:stream_decrypt(State0,Data),
+ {Ssh#ssh{decrypt_ctx = State}, Enc};
+decrypt(#ssh{decrypt = 'aes192-ctr',
+ decrypt_ctx = State0} = Ssh, Data) ->
+ {State, Enc} = crypto:stream_decrypt(State0,Data),
+ {Ssh#ssh{decrypt_ctx = State}, Enc};
+decrypt(#ssh{decrypt = 'aes256-ctr',
+ decrypt_ctx = State0} = Ssh, Data) ->
+ {State, Enc} = crypto:stream_decrypt(State0,Data),
{Ssh#ssh{decrypt_ctx = State}, Enc}.
+
+next_gcm_iv(<<Fixed:32, InvCtr:64>>) -> <<Fixed:32, (InvCtr+1):64>>.
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Compression
%%
@@ -850,49 +1483,54 @@ decompress(#ssh{decompress = '[email protected]', decompress_ctx = Context, authe
{Ssh, list_to_binary(Decompressed)}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% MAC calculation
%%
-%% hmac-sha1 REQUIRED HMAC-SHA1 (digest length = key
-%% length = 20)
-%% hmac-sha1-96 RECOMMENDED first 96 bits of HMAC-SHA1 (digest
-%% length = 12, key length = 20)
-%% hmac-md5 OPTIONAL HMAC-MD5 (digest length = key
-%% length = 16)
-%% hmac-md5-96 OPTIONAL first 96 bits of HMAC-MD5 (digest
-%% length = 12, key length = 16)
-%% none OPTIONAL no MAC; NOT RECOMMENDED
+%% MAC calculation
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
send_mac_init(SSH) ->
- case SSH#ssh.role of
- client ->
- KeySize =mac_key_size(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),
- Key = hash(SSH, "F", KeySize),
- {ok, SSH#ssh { send_mac_key = Key }}
+ case pkt_type(SSH#ssh.send_mac) of
+ common ->
+ case SSH#ssh.role of
+ client ->
+ KeySize = mac_key_size(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),
+ Key = hash(SSH, "F", KeySize),
+ {ok, SSH#ssh { send_mac_key = Key }}
+ end;
+ aead ->
+ %% Not applicable
+ {ok, SSH}
end.
send_mac_final(SSH) ->
- {ok, SSH#ssh { send_mac = none, send_mac_key = undefined }}.
+ {ok, SSH#ssh {send_mac = none,
+ send_mac_key = undefined }}.
+
recv_mac_init(SSH) ->
- case SSH#ssh.role of
- client ->
- Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)),
- {ok, SSH#ssh { recv_mac_key = Key }};
- server ->
- Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)),
- {ok, SSH#ssh { recv_mac_key = Key }}
+ case pkt_type(SSH#ssh.recv_mac) of
+ common ->
+ case SSH#ssh.role of
+ client ->
+ Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)),
+ {ok, SSH#ssh { recv_mac_key = Key }};
+ server ->
+ Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)),
+ {ok, SSH#ssh { recv_mac_key = Key }}
+ end;
+ aead ->
+ %% Not applicable
+ {ok, SSH}
end.
recv_mac_final(SSH) ->
{ok, SSH#ssh { recv_mac = none, recv_mac_key = undefined }}.
-mac(none, _ , _, _) ->
+mac(none, _ , _, _) ->
<<>>;
mac('hmac-sha1', Key, SeqNum, Data) ->
crypto:hmac(sha, Key, [<<?UINT32(SeqNum)>>, Data]);
@@ -903,7 +1541,9 @@ mac('hmac-md5', Key, SeqNum, Data) ->
mac('hmac-md5-96', Key, SeqNum, Data) ->
crypto:hmac(md5, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-md5-96'));
mac('hmac-sha2-256', Key, SeqNum, Data) ->
- crypto:hmac(sha256, Key, [<<?UINT32(SeqNum)>>, Data]).
+ crypto:hmac(sha256, Key, [<<?UINT32(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) ->
@@ -911,8 +1551,20 @@ hash(SSH, Char, Bits) ->
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,
@@ -936,39 +1588,55 @@ hash(K, H, Ki, N, HASH) ->
hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HASH).
kex_h(SSH, Key, E, F, K) ->
- L = ssh_bits:encode([SSH#ssh.c_version, SSH#ssh.s_version,
- SSH#ssh.c_keyinit, SSH#ssh.s_keyinit,
- ssh_message:encode_host_key(Key), E,F,K],
- [string,string,binary,binary,binary,
- mpint,mpint,mpint]),
- crypto:hash(sha,L).
-
+ 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 ->
- Ts = [string,string,binary,binary,binary,
- uint32,
- mpint,mpint,mpint,mpint,mpint],
- ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version,
- SSH#ssh.c_keyinit,SSH#ssh.s_keyinit,
- ssh_message:encode_host_key(Key), NBits, Prime, Gen, E,F,K],
- Ts);
+ %% 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 ->
- Ts = [string,string,binary,binary,binary,
- uint32,uint32,uint32,
- mpint,mpint,mpint,mpint,mpint],
- ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version,
- SSH#ssh.c_keyinit,SSH#ssh.s_keyinit,
- ssh_message:encode_host_key(Key), Min, NBits, Max,
- Prime, Gen, E,F,K], Ts)
+ <<?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,L).
+ crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L).
+
+sha(secp256r1) -> sha256;
+sha(secp384r1) -> sha384;
+sha(secp521r1) -> sha512;
+sha('diffie-hellman-group1-sha1') -> sha;
+sha('diffie-hellman-group14-sha1') -> sha;
+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.
mac_digest_size('hmac-sha1') -> 20;
@@ -976,6 +1644,9 @@ mac_digest_size('hmac-sha1-96') -> 12;
mac_digest_size('hmac-md5') -> 20;
mac_digest_size('hmac-md5-96') -> 12;
mac_digest_size('hmac-sha2-256') -> 32;
+mac_digest_size('hmac-sha2-512') -> 64;
+mac_digest_size('AEAD_AES_128_GCM') -> 16;
+mac_digest_size('AEAD_AES_256_GCM') -> 16;
mac_digest_size(none) -> 0.
peer_name({Host, _}) ->
@@ -987,12 +1658,91 @@ peer_name({Host, _}) ->
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-dh_group1() ->
- {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}.
+dh_group('diffie-hellman-group1-sha1') -> ?dh_group1;
+dh_group('diffie-hellman-group14-sha1') -> ?dh_group14.
+
+%%%----------------------------------------------------------------
+generate_key(Algorithm, Args) ->
+ {Public,Private} = crypto:generate_key(Algorithm, Args),
+ {crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}.
-dh_gen_key(G, P, _) ->
- {Public, Private} = crypto:generate_key(dh, [P, G]),
- {crypto:bytes_to_integer(Private), crypto:bytes_to_integer(Public)}.
+
+compute_key(Algorithm, OthersPublic, MyPrivate, Args) ->
+ Shared = crypto:compute_key(Algorithm, OthersPublic, MyPrivate, Args),
+ crypto:bytes_to_integer(Shared).
+
+
+ecdh_curve('ecdh-sha2-nistp256') -> secp256r1;
+ecdh_curve('ecdh-sha2-nistp384') -> secp384r1;
+ecdh_curve('ecdh-sha2-nistp521') -> secp521r1.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Utils for default_algorithms/1 and supported_algorithms/1
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) ->
+ [{client2server,As1},{server2client,As2}] = supported_algorithms(Key),
+ [{client2server,As1--BL1},{server2client,As2--BL2}];
+supported_algorithms(Key, BlackList) ->
+ supported_algorithms(Key) -- BlackList.
+
+
+select_crypto_supported(L) ->
+ Sup = [{ec_curve,crypto_supported_curves()} | crypto:supports()],
+ [Name || {Name,CryptoRequires} <- L,
+ crypto_supported(CryptoRequires, Sup)].
+
+crypto_supported_curves() ->
+ try crypto:ec_curves()
+ catch _:_ -> []
+ end.
+
+crypto_supported(Conditions, Supported) ->
+ lists:all( fun({Tag,CryptoName}) when is_atom(CryptoName) ->
+ crypto_name_supported(Tag,CryptoName,Supported);
+ ({Tag,{Name,Len}}) when is_integer(Len) ->
+ crypto_name_supported(Tag,Name,Supported) andalso
+ len_supported(Name,Len)
+ end, Conditions).
+
+crypto_name_supported(Tag, CryptoName, Supported) ->
+ lists:member(CryptoName, proplists:get_value(Tag,Supported,[])).
+
+len_supported(Name, Len) ->
+ try
+ case Name of
+ aes_ctr ->
+ {_, <<_/binary>>} =
+ %% Test encryption
+ crypto:stream_encrypt(crypto:stream_init(Name, <<0:Len>>, <<0:128>>), <<"">>);
+ aes_gcm ->
+ {<<_/binary>>, <<_/binary>>} =
+ crypto:block_encrypt(Name,
+ _Key = <<0:Len>>,
+ _IV = <<0:12/unsigned-unit:8>>,
+ {<<"AAD">>,"PT"})
+ end
+ of
+ _ -> true
+ catch
+ _:_ -> false
+ end.
+
+
+same(Algs) -> [{client2server,Algs}, {server2client,Algs}].
+
+
+%% default_algorithms(kex) -> % Example of how to disable an algorithm
+%% supported_algorithms(kex, ['ecdh-sha2-nistp521']);
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Other utils
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
trim_tail(Str) ->
lists:reverse(trim_head(lists:reverse(Str))).
diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
index 27d3e32355..f91cb1dd63 100644
--- a/lib/ssh/src/ssh_transport.hrl
+++ b/lib/ssh/src/ssh_transport.hrl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -28,9 +29,12 @@
-define(DEFAULT_CLIENT_VERSION, {2, 0}).
-define(DEFAULT_SERVER_VERSION, {2, 0}).
--define(DEFAULT_DH_GROUP_MIN, 512).
--define(DEFAULT_DH_GROUP_NBITS, 1024).
--define(DEFAULT_DH_GROUP_MAX, 4096).
+
+-define(MAX_NUM_ALGORITHMS, 200).
+
+-define(DEFAULT_DH_GROUP_MIN, 1024).
+-define(DEFAULT_DH_GROUP_NBITS, 2048).
+-define(DEFAULT_DH_GROUP_MAX, 8192).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
@@ -108,8 +112,9 @@
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% diffie-hellman-group1-sha1
--define(SSH_MSG_KEXDH_INIT, 30).
+%% diffie-hellman-group1-sha1 | diffie-hellman-group14-sha1
+
+-define(SSH_MSG_KEXDH_INIT, 30).
-define(SSH_MSG_KEXDH_REPLY, 31).
-record(ssh_msg_kexdh_init,
@@ -133,7 +138,7 @@
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% diffie-hellman-group-exchange-sha1
+%% diffie-hellman-group-exchange-sha1 | diffie-hellman-group-exchange-sha256
-define(SSH_MSG_KEX_DH_GEX_REQUEST_OLD, 30).
-define(SSH_MSG_KEX_DH_GEX_REQUEST, 34).
-define(SSH_MSG_KEX_DH_GEX_GROUP, 31).
@@ -170,7 +175,36 @@
h_sig
}).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% KEY ECDH messages
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% ecdh-sha2-nistp256 | ecdh-sha2-nistp384 | ecdh-sha2-nistp521
+
+-define(SSH_MSG_KEX_ECDH_INIT, 30).
+-define(SSH_MSG_KEX_ECDH_REPLY, 31).
+
+-record(ssh_msg_kex_ecdh_init,
+ {
+ q_c % string (client's ephemeral public key octet string)
+ }).
+
+-record(ssh_msg_kex_ecdh_reply,
+ {
+ public_host_key, % string (server's public host key) (k_s)
+ q_s, % string (server's ephemeral public key octet string)
+ h_sig % string (the signature on the exchange hash)
+ }).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
%% error codes
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
-define(SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, 1).
-define(SSH_DISCONNECT_PROTOCOL_ERROR, 2).
-define(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 3).
@@ -188,48 +222,20 @@
-define(SSH_DISCONNECT_ILLEGAL_USER_NAME, 15).
-%%%----------------------------------------------------------------------
-%%% # DH_14_xxx
-%%% Description: Oakley group 14 prime numbers and generator. Used in
-%%% diffie-hellman-group1-sha1 key exchange method.
-%%%----------------------------------------------------------------------
-%%%----------------------------------------------------------------------
-%%% # DH_14_P
-%%% Description: Prime for this group
-%%%----------------------------------------------------------------------
-
--define(DH_14_P,
- <<000,000,000,129,000,255,255,255,255,255,255,255,255,201,015,218,
- 162,033,104,194,052,196,198,098,139,128,220,028,209,041,002,078,
- 008,138,103,204,116,002,011,190,166,059,019,155,034,081,074,008,
- 121,142,052,004,221,239,149,025,179,205,058,067,027,048,043,010,
- 109,242,095,020,055,079,225,053,109,109,081,194,069,228,133,181,
- 118,098,094,126,198,244,076,066,233,166,055,237,107,011,255,092,
- 182,244,006,183,237,238,056,107,251,090,137,159,165,174,159,036,
- 017,124,075,031,230,073,040,102,081,236,230,083,129,255,255,255,
- 255,255,255,255,255>>).
-
-%%%----------------------------------------------------------------------
-%%% # DH_14_G
-%%% Description: Generator for DH_14_P.
-%%%----------------------------------------------------------------------
-
--define(DH_14_G, <<0,0,0,1,2>>).
-
-%%%----------------------------------------------------------------------
-%%% # DH_14_Q
-%%% Description: Group order (DH_14_P - 1) / 2.
-%%%----------------------------------------------------------------------
-
--define(DH_14_Q,
- <<000,000,000,128,127,255,255,255,255,255,255,255,228,135,237,081,
- 016,180,097,026,098,099,049,069,192,110,014,104,148,129,039,004,
- 069,051,230,058,001,005,223,083,029,137,205,145,040,165,004,060,
- 199,026,002,110,247,202,140,217,230,157,033,141,152,021,133,054,
- 249,047,138,027,167,240,154,182,182,168,225,034,242,066,218,187,
- 049,047,063,099,122,038,033,116,211,027,246,181,133,255,174,091,
- 122,003,091,246,247,028,053,253,173,068,207,210,215,079,146,008,
- 190,037,143,243,036,148,051,040,246,115,041,192,255,255,255,255,
- 255,255,255,255>>).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% groups
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% rfc 2489, ch 6.2
+%%% Size 1024
+-define(dh_group1,
+ {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}).
+
+%%% rfc 3526, ch3
+%%% Size 2048
+-define(dh_group14,
+ {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF}).
-endif. % -ifdef(ssh_transport).
diff --git a/lib/ssh/src/ssh_userauth.hrl b/lib/ssh/src/ssh_userauth.hrl
index 7c38719d92..2cfc1f0f83 100644
--- a/lib/ssh/src/ssh_userauth.hrl
+++ b/lib/ssh/src/ssh_userauth.hrl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl
index 2743b704f1..e1680c120e 100644
--- a/lib/ssh/src/ssh_xfer.erl
+++ b/lib/ssh/src/ssh_xfer.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -23,7 +24,7 @@
-module(ssh_xfer).
--export([attach/2, connect/3, connect/4]).
+-export([attach/2, attach/3, connect/3, connect/4, connect/5]).
-export([open/6, opendir/3, readdir/3, close/3, read/5, write/5,
rename/5, remove/3, mkdir/4, rmdir/3, realpath/3, extended/4,
stat/4, fstat/4, lstat/4, setstat/4,
@@ -46,28 +47,38 @@
-define(is_set(F, Bits),
((F) band (Bits)) == (F)).
--define(XFER_PACKET_SIZE, 32768).
--define(XFER_WINDOW_SIZE, 4*?XFER_PACKET_SIZE).
+-define(XFER_PACKET_SIZE, 65536).
+-define(XFER_WINDOW_SIZE, 20*?XFER_PACKET_SIZE).
attach(CM, Opts) ->
- open_xfer(CM, Opts).
+ open_xfer(CM, Opts, []).
+
+attach(CM, Opts, ChanOpts) ->
+ open_xfer(CM, Opts, ChanOpts).
+
connect(Host, Port, Opts) ->
case ssh:connect(Host, Port, Opts) of
- {ok, CM} -> open_xfer(CM, Opts);
+ {ok, CM} -> open_xfer(CM, Opts, []);
Error -> Error
end.
connect(Host, Port, Opts, Timeout) ->
+ connect(Host, Port, Opts, [], Timeout).
+
+connect(Host, Port, Opts, ChanOpts, Timeout) ->
case ssh:connect(Host, Port, Opts, Timeout) of
- {ok, CM} -> open_xfer(CM, [{timeout, Timeout}|Opts]);
+ {ok, CM} -> open_xfer(CM, [{timeout, Timeout}|Opts], ChanOpts);
{error, Timeout} -> {error, timeout};
Error -> Error
end.
-open_xfer(CM, Opts) ->
+
+open_xfer(CM, Opts, ChanOpts) ->
TMO = proplists:get_value(timeout, Opts, infinity),
- case ssh_connection:session_channel(CM, ?XFER_WINDOW_SIZE, ?XFER_PACKET_SIZE, TMO) of
+ WindowSize = proplists:get_value(window_size, ChanOpts, ?XFER_WINDOW_SIZE),
+ PacketSize = proplists:get_value(packet_size, ChanOpts, ?XFER_PACKET_SIZE),
+ case ssh_connection:session_channel(CM, WindowSize, PacketSize, TMO) of
{ok, ChannelId} ->
{ok, ChannelId, CM};
Error ->
diff --git a/lib/ssh/src/ssh_xfer.hrl b/lib/ssh/src/ssh_xfer.hrl
index 8dc9a40f92..a76e9151db 100644
--- a/lib/ssh/src/ssh_xfer.hrl
+++ b/lib/ssh/src/ssh_xfer.hrl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl
index e6b4b681a4..71b5c2c46a 100644
--- a/lib/ssh/src/sshc_sup.erl
+++ b/lib/ssh/src/sshc_sup.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -63,7 +64,7 @@ child_spec(_) ->
Name = undefined, % As simple_one_for_one is used.
StartFunc = {ssh_connection_handler, start_link, []},
Restart = temporary,
- Shutdown = infinity,
+ Shutdown = 4000,
Modules = [ssh_connection_handler],
- Type = supervisor,
+ Type = worker,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl
index 60222f5172..ac9e232b3a 100644
--- a/lib/ssh/src/sshd_sup.erl
+++ b/lib/ssh/src/sshd_sup.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -26,8 +27,10 @@
-behaviour(supervisor).
+-include("ssh.hrl").
+
-export([start_link/1, start_child/1, stop_child/1,
- stop_child/2, system_name/1]).
+ stop_child/3, system_name/1]).
%% Supervisor callback
-export([init/1]).
@@ -40,13 +43,14 @@ start_link(Servers) ->
start_child(ServerOpts) ->
Address = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- case ssh_system_sup:system_supervisor(Address, Port) of
+ Port = proplists:get_value(port, ServerOpts),
+ Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ 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),
+ Name = id(Address, Port, Profile),
supervisor:delete_child(?MODULE, Name),
supervisor:start_child(?MODULE, Spec);
Reply ->
@@ -60,8 +64,8 @@ start_child(ServerOpts) ->
stop_child(Name) ->
supervisor:terminate_child(?MODULE, Name).
-stop_child(Address, Port) ->
- Name = id(Address, Port),
+stop_child(Address, Port, Profile) ->
+ Name = id(Address, Port, Profile),
stop_child(Name).
system_name(SysSup) ->
@@ -87,7 +91,8 @@ init([Servers]) ->
%%% Internal functions
%%%=========================================================================
child_spec(Address, Port, ServerOpts) ->
- Name = id(Address, Port),
+ 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,
@@ -95,8 +100,13 @@ child_spec(Address, Port, ServerOpts) ->
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-id(Address, Port) ->
- {server, ssh_system_sup, Address, Port}.
+id(Address, Port, Profile) ->
+ case is_list(Address) of
+ true ->
+ {server, ssh_system_sup, any, Port, Profile};
+ false ->
+ {server, ssh_system_sup, Address, Port, Profile}
+ end.
system_name([], _ ) ->
undefined;