aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--OTP_VERSION2
-rw-r--r--lib/ssh/doc/src/notes.xml36
-rw-r--r--lib/ssh/doc/src/ssh.xml12
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl5
-rw-r--r--lib/ssh/src/ssh_channel_sup.erl11
-rw-r--r--lib/ssh/src/ssh_connection.erl28
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl7
-rw-r--r--lib/ssh/src/ssh_connection_sup.erl5
-rw-r--r--lib/ssh/src/ssh_options.erl6
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl8
-rw-r--r--lib/ssh/src/ssh_sup.erl15
-rw-r--r--lib/ssh/src/ssh_system_sup.erl9
-rw-r--r--lib/ssh/src/ssh_transport.erl5
-rw-r--r--lib/ssh/src/sshc_sup.erl5
-rw-r--r--lib/ssh/src/sshd_sup.erl6
-rw-r--r--lib/ssh/test/ssh_options_SUITE.erl39
-rw-r--r--lib/ssh/test/ssh_sup_SUITE.erl96
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--otp_versions.table1
19 files changed, 231 insertions, 67 deletions
diff --git a/OTP_VERSION b/OTP_VERSION
index 21bfb1c028..1a2b175f89 100644
--- a/OTP_VERSION
+++ b/OTP_VERSION
@@ -1 +1 @@
-20.2.3
+20.2.4
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 3a2f55a487..c559da911b 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,42 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.6.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Adjusted supervisor timeouts</p>
+ <p>
+ Own Id: OTP-14907</p>
+ </item>
+ <item>
+ <p>
+ Remove ERROR messages for slow process exits</p>
+ <p>
+ Own Id: OTP-14930</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add option <c>save_accepted_host</c> to
+ <c>ssh:connection</c>. This option, if set to false,
+ inhibits saving host keys to e.g the file
+ <c>known_hosts</c>.</p>
+ <p>
+ Own Id: OTP-14935</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.6.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 337f4094cc..acf94ff6af 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -227,6 +227,18 @@
</item>
</list>
</item>
+
+ <tag><c><![CDATA[{save_accepted_host, boolean()}]]></c></tag>
+ <item>
+ <p>If <c>true</c>, the client saves an accepted host key to avoid the
+ accept question the next time the same host is connected. If the option
+ <c>key_cb</c> is not present, the key is saved in the file "known_hosts".
+ </p>
+ <p>If <c>false</c>, the key is not saved and the key will still be unknown
+ at the next access of the same host.
+ </p>
+ </item>
+
<tag><c><![CDATA[{user_interaction, boolean()}]]></c></tag>
<item>
<p>If <c>false</c>, disables the client to connect to the server
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index a24664793b..fc564a359b 100644
--- a/lib/ssh/src/ssh_acceptor_sup.erl
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -86,10 +86,7 @@ child_spec(Address, Port, Profile, Options) ->
Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT),
#{id => id(Address, Port, Profile),
start => {ssh_acceptor, start_link, [Port, Address, Options, Timeout]},
- restart => transient,
- shutdown => 5500, %brutal_kill,
- type => worker,
- modules => [ssh_acceptor]
+ restart => transient % because a crashed listener could be replaced by a new one
}.
id(Address, Port, Profile) ->
diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl
index 6b01dc334d..8444533fd1 100644
--- a/lib/ssh/src/ssh_channel_sup.erl
+++ b/lib/ssh/src/ssh_channel_sup.erl
@@ -26,7 +26,7 @@
-behaviour(supervisor).
--export([start_link/1, start_child/2]).
+-export([start_link/1, start_child/5]).
%% Supervisor callback
-export([init/1]).
@@ -37,7 +37,14 @@
start_link(Args) ->
supervisor:start_link(?MODULE, [Args]).
-start_child(Sup, ChildSpec) ->
+start_child(Sup, Callback, Id, Args, Exec) ->
+ ChildSpec =
+ #{id => make_ref(),
+ start => {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]},
+ restart => temporary,
+ type => worker,
+ modules => [ssh_channel]
+ },
supervisor:start_child(Sup, ChildSpec).
%%%=========================================================================
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 7e9ee78fd2..946ae2967b 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -812,22 +812,20 @@ start_channel(Cb, Id, Args, SubSysSup, Opts) ->
start_channel(Cb, Id, Args, SubSysSup, undefined, Opts).
start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) ->
- ChildSpec = child_spec(Cb, Id, Args, Exec),
ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup),
- assert_limit_num_channels_not_exceeded(ChannelSup, Opts),
- ssh_channel_sup:start_child(ChannelSup, ChildSpec).
+ case max_num_channels_not_exceeded(ChannelSup, Opts) of
+ true ->
+ ssh_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec);
+ false ->
+ throw(max_num_channels_exceeded)
+ end.
-assert_limit_num_channels_not_exceeded(ChannelSup, Opts) ->
+max_num_channels_not_exceeded(ChannelSup, Opts) ->
MaxNumChannels = ?GET_OPT(max_channels, Opts),
NumChannels = length([x || {_,_,worker,[ssh_channel]} <-
supervisor:which_children(ChannelSup)]),
- if
- %% Note that NumChannels is BEFORE starting a new one
- NumChannels < MaxNumChannels ->
- ok;
- true ->
- throw(max_num_channels_exceeded)
- end.
+ %% Note that NumChannels is BEFORE starting a new one
+ NumChannels < MaxNumChannels.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -874,14 +872,6 @@ check_subsystem(SsName, Options) ->
Value
end.
-child_spec(Callback, Id, Args, Exec) ->
- Name = make_ref(),
- StartFunc = {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]},
- Restart = temporary,
- Shutdown = 3600,
- Type = worker,
- {Name, StartFunc, Restart, Shutdown, Type, [ssh_channel]}.
-
start_cli(#connection{cli_spec = no_cli}, _) ->
{error, cli_disabled};
start_cli(#connection{options = Options,
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 0ca960ef96..c8ac3a9c04 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -1460,13 +1460,12 @@ terminate(shutdown, StateName, State0) ->
State0),
finalize_termination(StateName, State);
-%% terminate({shutdown,Msg}, StateName, State0) when is_record(Msg,ssh_msg_disconnect)->
-%% State = send_msg(Msg, State0),
-%% finalize_termination(StateName, Msg, State);
-
terminate({shutdown,_R}, StateName, State) ->
finalize_termination(StateName, State);
+terminate(kill, StateName, State) ->
+ finalize_termination(StateName, State);
+
terminate(Reason, StateName, State0) ->
%% Others, e.g undef, {badmatch,_}
log_error(Reason),
diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl
index 60ee8b7c73..2e8450090a 100644
--- a/lib/ssh/src/ssh_connection_sup.erl
+++ b/lib/ssh/src/ssh_connection_sup.erl
@@ -52,10 +52,7 @@ init(_) ->
},
ChildSpecs = [#{id => undefined, % As simple_one_for_one is used.
start => {ssh_connection_handler, start_link, []},
- restart => temporary,
- shutdown => 4000,
- type => worker,
- modules => [ssh_connection_handler]
+ restart => temporary % because there is no way to restart a crashed connection
}
],
{ok, {SupFlags,ChildSpecs}}.
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index 68c99743ee..cf1534bd78 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -439,6 +439,12 @@ default(client) ->
class => user_options
},
+ {save_accepted_host, def} =>
+ #{default => true,
+ chk => fun erlang:is_boolean/1,
+ class => user_options
+ },
+
{pref_public_key_algs, def} =>
#{default => ssh_transport:default_algorithms(public_key),
chk => fun check_pref_public_key_algs/1,
diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl
index 8db051095c..77da240a66 100644
--- a/lib/ssh/src/ssh_subsystem_sup.erl
+++ b/lib/ssh/src/ssh_subsystem_sup.erl
@@ -74,18 +74,14 @@ ssh_connection_child_spec(Role, Address, Port, _Profile, Options) ->
#{id => id(Role, ssh_connection_sup, Address, Port),
start => {ssh_connection_sup, start_link, [Options]},
restart => temporary,
- shutdown => 5000,
- type => supervisor,
- modules => [ssh_connection_sup]
+ type => supervisor
}.
ssh_channel_child_spec(Role, Address, Port, _Profile, Options) ->
#{id => id(Role, ssh_channel_sup, Address, Port),
start => {ssh_channel_sup, start_link, [Options]},
restart => temporary,
- shutdown => infinity,
- type => supervisor,
- modules => [ssh_channel_sup]
+ type => supervisor
}.
id(Role, Sup, Address, Port) ->
diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl
index eaec7a54e4..8183016ba5 100644
--- a/lib/ssh/src/ssh_sup.erl
+++ b/lib/ssh/src/ssh_sup.erl
@@ -36,15 +36,14 @@ init(_) ->
intensity => 10,
period => 3600
},
- ChildSpecs = [#{id => Module,
- start => {Module, start_link, []},
- restart => permanent,
- shutdown => 4000, %brutal_kill,
- type => supervisor,
- modules => [Module]
+ ChildSpecs = [#{id => sshd_sup,
+ start => {sshd_sup, start_link, []},
+ type => supervisor
+ },
+ #{id => sshc_sup,
+ start => {sshc_sup, start_link, []},
+ type => supervisor
}
- || Module <- [sshd_sup,
- sshc_sup]
],
{ok, {SupFlags,ChildSpecs}}.
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index e70abf59c2..17f990c5d8 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -63,9 +63,7 @@ init([Address, Port, Profile, Options]) ->
[#{id => id(ssh_acceptor_sup, Address, Port, Profile),
start => {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]},
restart => transient,
- shutdown => infinity,
- type => supervisor,
- modules => [ssh_acceptor_sup]
+ type => supervisor
}];
_ ->
[]
@@ -124,9 +122,8 @@ start_subsystem(SystemSup, Role, Address, Port, Profile, Options) ->
#{id => make_ref(),
start => {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]},
restart => temporary,
- shutdown => infinity,
- type => supervisor,
- modules => [ssh_subsystem_sup]},
+ type => supervisor
+ },
supervisor:start_child(SystemSup, SubsystemSpec).
stop_subsystem(SystemSup, SubSys) ->
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index ad9efc4755..975053d301 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -889,10 +889,13 @@ known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}
{_,true} ->
ok;
{_,false} ->
+ DoAdd = ?GET_OPT(save_accepted_host, Opts),
case accepted_host(Ssh, PeerName, Public, Opts) of
- true ->
+ true when DoAdd == true ->
{_,R} = add_host_key(KeyCb, PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]),
R;
+ true when DoAdd == false ->
+ ok;
false ->
{error, rejected_by_user};
{error,E} ->
diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl
index 133b2c6450..fd4d8a3c07 100644
--- a/lib/ssh/src/sshc_sup.erl
+++ b/lib/ssh/src/sshc_sup.erl
@@ -60,10 +60,7 @@ init(_) ->
},
ChildSpecs = [#{id => undefined, % As simple_one_for_one is used.
start => {ssh_connection_handler, start_link, []},
- restart => temporary,
- shutdown => 4000,
- type => worker,
- modules => [ssh_connection_handler]
+ restart => temporary % because there is no way to restart a crashed connection
}
],
{ok, {SupFlags,ChildSpecs}}.
diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl
index c23e65d955..779a861a54 100644
--- a/lib/ssh/src/sshd_sup.erl
+++ b/lib/ssh/src/sshd_sup.erl
@@ -90,10 +90,8 @@ init(_) ->
child_spec(Address, Port, Profile, Options) ->
#{id => id(Address, Port, Profile),
start => {ssh_system_sup, start_link, [Address, Port, Profile, Options]},
- restart => temporary,
- shutdown => infinity,
- type => supervisor,
- modules => [ssh_system_sup]
+ restart => temporary,
+ type => supervisor
}.
id(Address, Port, Profile) ->
diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl
index 144ec7f8fd..bb09ca4c8b 100644
--- a/lib/ssh/test/ssh_options_SUITE.erl
+++ b/lib/ssh/test/ssh_options_SUITE.erl
@@ -70,7 +70,8 @@
hostkey_fingerprint_check_sha256/1,
hostkey_fingerprint_check_sha384/1,
hostkey_fingerprint_check_sha512/1,
- hostkey_fingerprint_check_list/1
+ hostkey_fingerprint_check_list/1,
+ save_accepted_host_option/1
]).
%%% Common test callbacks
@@ -124,6 +125,7 @@ all() ->
id_string_own_string_server,
id_string_own_string_server_trail_space,
id_string_random_server,
+ save_accepted_host_option,
{group, hardening_tests}
].
@@ -211,7 +213,8 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(TestCase, Config) when TestCase == server_password_option;
TestCase == server_userpassword_option;
TestCase == server_pwdfun_option;
- TestCase == server_pwdfun_4_option ->
+ TestCase == server_pwdfun_4_option ;
+ TestCase == save_accepted_host_option ->
UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
ssh_test_lib:del_dirs(UserDir),
end_per_testcase(Config);
@@ -219,7 +222,9 @@ end_per_testcase(_TestCase, Config) ->
end_per_testcase(Config).
end_per_testcase(_Config) ->
+ ct:log("~p: Before ssh:stop()",[?FUNCTION_NAME]),
ssh:stop(),
+ ct:log("~p: After ssh:stop()",[?FUNCTION_NAME]),
ok.
%%--------------------------------------------------------------------
@@ -1314,6 +1319,36 @@ try_to_connect(Connect, Host, Port, Pid, Tref, N) ->
end.
%%--------------------------------------------------------------------
+save_accepted_host_option(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ KnownHosts = filename:join(UserDir, "known_hosts"),
+ file:make_dir(UserDir),
+ file:delete(KnownHosts),
+ SysDir = proplists:get_value(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {user_passwords, [{"vego", "morot"}]}
+ ]),
+ {error,enoent} = file:read_file(KnownHosts),
+
+ {ok,_C1} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "vego"},
+ {password, "morot"},
+ {user_interaction, false},
+ {save_accepted_host, false},
+ {user_dir, UserDir}]),
+ {error,enoent} = file:read_file(KnownHosts),
+
+ {ok,_C2} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "vego"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ {ok,_} = file:read_file(KnownHosts),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl
index 3920a1c592..d453a2e143 100644
--- a/lib/ssh/test/ssh_sup_SUITE.erl
+++ b/lib/ssh/test/ssh_sup_SUITE.erl
@@ -42,7 +42,9 @@ suite() ->
all() ->
[default_tree, sshc_subtree, sshd_subtree, sshd_subtree_profile,
- killed_acceptor_restarts].
+ killed_acceptor_restarts,
+ shell_channel_tree
+ ].
groups() ->
[].
@@ -246,6 +248,98 @@ killed_acceptor_restarts(Config) ->
{error,closed} = ssh:connection_info(C2,[client_version]).
%%-------------------------------------------------------------------------
+shell_channel_tree(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = proplists:get_value(data_dir, Config),
+ TimeoutShell =
+ fun() ->
+ io:format("TimeoutShell started!~n",[]),
+ timer:sleep(5000),
+ ct:pal("~p TIMEOUT!",[self()])
+ end,
+ {Daemon, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {shell, fun(_User) ->
+ spawn(TimeoutShell)
+ end
+ }
+ ]),
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, true},
+ {user_dir, UserDir}]),
+
+ [ChannelSup|_] = Sups0 = chk_empty_con_daemon(Daemon),
+
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ ok = ssh_connection:shell(ConnectionRef,ChannelId0),
+
+ ?wait_match([{_, GroupPid,worker,[ssh_channel]}],
+ supervisor:which_children(ChannelSup),
+ [GroupPid]),
+ {links,GroupLinks} = erlang:process_info(GroupPid, links),
+ [ShellPid] = GroupLinks--[ChannelSup],
+ ct:pal("GroupPid = ~p, ShellPid = ~p",[GroupPid,ShellPid]),
+
+ receive
+ {ssh_cm,ConnectionRef, {data, ChannelId0, 0, <<"TimeoutShell started!\r\n">>}} ->
+ receive
+ %%---- wait for the subsystem to terminate
+ {ssh_cm,ConnectionRef,{closed,ChannelId0}} ->
+ ct:pal("Subsystem terminated",[]),
+ case {chk_empty_con_daemon(Daemon),
+ process_info(GroupPid),
+ process_info(ShellPid)} of
+ {Sups0, undefined, undefined} ->
+ %% SUCCESS
+ ssh:stop_daemon(Daemon);
+ {Sups0, _, undefined} ->
+ ssh:stop_daemon(Daemon),
+ ct:fail("Group proc lives!");
+ {Sups0, undefined, _} ->
+ ssh:stop_daemon(Daemon),
+ ct:fail("Shell proc lives!");
+ _ ->
+ ssh:stop_daemon(Daemon),
+ ct:fail("Sup tree changed!")
+ end
+ after 10000 ->
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Daemon),
+ ct:fail("CLI Timeout")
+ end
+ after 10000 ->
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Daemon),
+ ct:fail("CLI Timeout")
+ end.
+
+
+chk_empty_con_daemon(Daemon) ->
+ ?wait_match([{_,SubSysSup, supervisor,[ssh_subsystem_sup]},
+ {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}],
+ supervisor:which_children(Daemon),
+ [SubSysSup,AccSup]),
+ ?wait_match([{{server,ssh_connection_sup, _,_},
+ ConnectionSup, supervisor,
+ [ssh_connection_sup]},
+ {{server,ssh_channel_sup,_ ,_},
+ ChannelSup,supervisor,
+ [ssh_channel_sup]}],
+ supervisor:which_children(SubSysSup),
+ [ConnectionSup,ChannelSup]),
+ ?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}],
+ supervisor:which_children(AccSup)),
+ ?wait_match([{_, _, worker,[ssh_connection_handler]}],
+ supervisor:which_children(ConnectionSup)),
+ ?wait_match([], supervisor:which_children(ChannelSup)),
+ [ChannelSup, ConnectionSup, SubSysSup, AccSup].
+
+%%-------------------------------------------------------------------------
%% Help functions
%%-------------------------------------------------------------------------
check_sshd_system_tree(Daemon, Config) ->
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 004db6e3a2..6aaa22a6b4 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,5 +1,5 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 4.6.4
+SSH_VSN = 4.6.5
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/otp_versions.table b/otp_versions.table
index 19aeb250fa..b25117e072 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,3 +1,4 @@
+OTP-20.2.4 : ssh-4.6.5 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.2.1 et-1.6.1 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 kernel-5.4.2 megaco-3.18.3 mnesia-4.15.3 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.4 sasl-3.1.1 snmp-5.2.9 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 :
OTP-20.2.3 : erts-9.2.1 kernel-5.4.2 runtime_tools-1.12.4 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 et-1.6.1 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 megaco-3.18.3 mnesia-4.15.3 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 sasl-3.1.1 snmp-5.2.9 ssh-4.6.4 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 :
OTP-20.2.2 : mnesia-4.15.3 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.2 et-1.6.1 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 kernel-5.4.1 megaco-3.18.3 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.3 sasl-3.1.1 snmp-5.2.9 ssh-4.6.4 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 :
OTP-20.2.1 : ssh-4.6.4 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.2 et-1.6.1 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 kernel-5.4.1 megaco-3.18.3 mnesia-4.15.2 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.3 sasl-3.1.1 snmp-5.2.9 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 :