File 2461-ssh-tcpip_tunnel_in-callback-function.patch of Package erlang
From e491a32f825d666e8cae84a94c1a0c24722fbd0e Mon Sep 17 00:00:00 2001
From: Stefan Grundmann <sg2342@googlemail.com>
Date: Tue, 11 Mar 2025 03:34:22 +0000
Subject: [PATCH] ssh: tcpip_tunnel_in callback function
allow/deny/log "direct-tcpip" channel open in daemon
---
lib/ssh/src/ssh.hrl | 4 +-
lib/ssh/src/ssh_connection.erl | 14 ++++++-
lib/ssh/src/ssh_options.erl | 2 +-
lib/ssh/test/ssh_to_openssh_SUITE.erl | 57 +++++++++++++++++++++++++++
4 files changed, 73 insertions(+), 4 deletions(-)
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index de0396d434..c2a41bd7a5 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -353,7 +353,7 @@
-type ssh_cli_daemon_option() :: {ssh_cli, mod_args() | no_cli }.
-type tcpip_tunnel_out_daemon_option() :: {tcpip_tunnel_out, boolean()} .
--type tcpip_tunnel_in_daemon_option() :: {tcpip_tunnel_in, boolean()} .
+-type tcpip_tunnel_in_daemon_option() :: {tcpip_tunnel_in, boolean() | Callback::fun((HostName::string(), inet:port_number()) -> boolean() | denied)} .
-type send_ext_info_daemon_option() :: {send_ext_info, boolean()} .
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index cc722ee059..08451de880 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -946,9 +946,19 @@ handle_msg(#ssh_msg_channel_open{channel_type = "direct-tcpip",
connection_supervisor = ConnectionSup
} = C,
server, _SSH) ->
+ Allowed = case ?GET_OPT(tcpip_tunnel_in, Options) of
+ T when is_boolean(T) -> T;
+ AllowedFun when is_function(AllowedFun, 2) ->
+ AllowedFun(binary_to_list(HostToConnect), PortToConnect)
+ end,
{ReplyMsg, NextChId} =
- case ?GET_OPT(tcpip_tunnel_in, Options) of
- %% May add more to the option, like allowed ip/port pairs to connect to
+ case Allowed of
+ denied ->
+ {channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
+ "Not allowed", "en"),
+ ChId};
+
false ->
{channel_open_failure_msg(RemoteId,
?SSH_OPEN_CONNECT_FAILED,
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index 6a55954bd0..62913c7bfc 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -454,7 +454,7 @@ default(server) ->
tcpip_tunnel_in =>
#{default => false,
- chk => fun(V) -> erlang:is_boolean(V) end,
+ chk => fun(V) -> check_function2(V) orelse erlang:is_boolean(V) end,
class => user_option
},
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index 183034f985..ac5ed4892d 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -45,6 +45,8 @@
exec_direct_with_io_in_sshc/1,
exec_with_io_in_sshc/1,
tunnel_in_erlclient_erlserver/1,
+ tunnel_in_erlclient_erlserver_allowed/1,
+ tunnel_in_erlclient_erlserver_denied/1,
tunnel_in_erlclient_openssh_server/1,
tunnel_in_non_erlclient_erlserver/1,
tunnel_out_erlclient_erlserver/1,
@@ -74,6 +76,8 @@ all() ->
groups() ->
[{erlang_client, [], [tunnel_in_erlclient_erlserver,
+ tunnel_in_erlclient_erlserver_allowed,
+ tunnel_in_erlclient_erlserver_denied,
tunnel_out_erlclient_erlserver,
{group, tunnel_distro_server},
erlang_shell_client_openssh_server,
@@ -414,6 +418,59 @@ tunnel_in_erlclient_erlserver(Config) ->
test_tunneling(ToSock, ListenHost, ListenPort).
+%%--------------------------------------------------------------------
+tunnel_in_erlclient_erlserver_allowed(Config) ->
+ SystemDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+ {ToSock, ToHost, ToPort} = tunneling_listner(),
+ Self = self(),
+ AllowedFun = fun(HostToConnect, PortToConnect) ->
+ Self ! {allowed, {HostToConnect, PortToConnect}},
+ true
+ end,
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, AllowedFun},
+ {system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords, [{"foo", "bar"}]},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user,"foo"},{password,"bar"},
+ {user_interaction, false}]),
+
+ ListenHost = inet:ntoa({127,0,0,1}),
+ {ok,ListenPort} = ssh:tcpip_tunnel_to_server(C, ListenHost,0, ToHost, ToPort, 2000),
+ test_tunneling(ToSock, ListenHost, ListenPort),
+ {allowed, {ListenHost, ToPort}} = receive X -> X after 500 -> timeout end,
+ {allowed, {ListenHost, ToPort}} = receive Y -> Y after 500 -> timeout end.
+
+%%--------------------------------------------------------------------
+tunnel_in_erlclient_erlserver_denied(Config) ->
+ SystemDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+ {ToSock, ToHost, ToPort} = tunneling_listner(),
+ Self = self(),
+ DeniedFun = fun(HostToConnect, PortToConnect) ->
+ Self ! {denied, {HostToConnect, PortToConnect}},
+ denied
+ end,
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, DeniedFun},
+ {system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords, [{"foo", "bar"}]},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user,"foo"},{password,"bar"},
+ {user_interaction, false}]),
+
+ ListenHost = inet:ntoa({127,0,0,1}),
+ {ok,ListenPort} = ssh:tcpip_tunnel_to_server(C, ListenHost,0, ToHost, ToPort, 2000),
+ {ok, Sock} = gen_tcp:connect(ListenHost, ListenPort, [{active, false}]),
+ {denied, {ListenHost, ToPort}} = receive Y -> Y after 500 -> timeout end,
+ {error, timeout} = gen_tcp:accept(ToSock, 2000),
+ {error, closed} = gen_tcp:recv(Sock, 0, 5000).
+
%%--------------------------------------------------------------------
tunnel_in_erlclient_openssh_server(_Config) ->
C = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
--
2.43.0