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

openSUSE Build Service is sponsored by