summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2007-12-30 21:29:50 -0800
committerRobey Pointer <robey@lag.net>2007-12-30 21:29:50 -0800
commitba1fd0d61bc5c103004d496473681b2a4dce0f3b (patch)
tree21afc0f9e68df6d45a520304dc4b3080eeef10a5
parent06d3471b463ffe798d42e7d24e294bbc616d8358 (diff)
[project @ robey@lag.net-20071231052950-8h599bnez3sgbf2e]
patch from david guerizec for direct-tcpip forwarding support, and a unit test added by yours truly.
-rw-r--r--paramiko/server.py41
-rw-r--r--paramiko/transport.py12
-rw-r--r--tests/test_transport.py39
3 files changed, 88 insertions, 4 deletions
diff --git a/paramiko/server.py b/paramiko/server.py
index de20f391..bcaa4be4 100644
--- a/paramiko/server.py
+++ b/paramiko/server.py
@@ -492,6 +492,47 @@ class ServerInterface (object):
@rtype: bool
"""
return False
+
+ def check_channel_direct_tcpip_request(self, chanid, origin, destination):
+ """
+ Determine if a local port forwarding channel will be granted, and
+ return C{OPEN_SUCCEEDED} or an error code. This method is
+ called in server mode when the client requests a channel, after
+ authentication is complete.
+
+ The C{chanid} parameter is a small number that uniquely identifies the
+ channel within a L{Transport}. A L{Channel} object is not created
+ unless this method returns C{OPEN_SUCCEEDED} -- once a
+ L{Channel} object is created, you can call L{Channel.get_id} to
+ retrieve the channel ID.
+
+ The origin and destination parameters are (ip_address, port) tuples
+ that correspond to both ends of the TCP connection in the forwarding
+ tunnel.
+
+ The return value should either be C{OPEN_SUCCEEDED} (or
+ C{0}) to allow the channel request, or one of the following error
+ codes to reject it:
+ - C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}
+ - C{OPEN_FAILED_CONNECT_FAILED}
+ - C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE}
+ - C{OPEN_FAILED_RESOURCE_SHORTAGE}
+
+ The default implementation always returns
+ C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}.
+
+ @param chanid: ID of the channel
+ @type chanid: int
+ @param origin: 2-tuple containing the IP address and port of the
+ originator (client side)
+ @type origin: tuple
+ @param destination: 2-tuple containing the IP address and port of the
+ destination (server side)
+ @type destination: tuple
+ @return: a success or failure code (listed above)
+ @rtype: int
+ """
+ return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
class SubsystemHandler (threading.Thread):
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 9e7d3b15..a4c77205 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -1942,7 +1942,17 @@ class Transport (threading.Thread):
my_chanid = self._next_channel()
finally:
self.lock.release()
- reason = self.server_object.check_channel_request(kind, my_chanid)
+ if kind == 'direct-tcpip':
+ # handle direct-tcpip requests comming from the client
+ dest_addr = m.get_string()
+ dest_port = m.get_int()
+ origin_addr = m.get_string()
+ origin_port = m.get_int()
+ reason = self.server_object.check_channel_direct_tcpip_request(
+ my_chanid, (origin_addr, origin_port),
+ (dest_addr, dest_port))
+ else:
+ reason = self.server_object.check_channel_request(kind, my_chanid)
if reason != OPEN_SUCCEEDED:
self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind)
reject = True
diff --git a/tests/test_transport.py b/tests/test_transport.py
index d9ac1780..187fee90 100644
--- a/tests/test_transport.py
+++ b/tests/test_transport.py
@@ -119,6 +119,10 @@ class NullServer (ServerInterface):
self._listen.close()
self._listen = None
+ def check_channel_direct_tcpip_request(self, chanid, origin, destination):
+ self._tcpip_dest = destination
+ return OPEN_SUCCEEDED
+
class TransportTest (unittest.TestCase):
@@ -625,7 +629,7 @@ class TransportTest (unittest.TestCase):
cs = socket.socket()
cs.connect(('', port))
ss, _ = self.server._listen.accept()
- sch = self.ts.open_forwarded_tcpip_channel(ss.getpeername(), ss.getsockname())
+ sch = self.ts.open_forwarded_tcpip_channel(ss.getsockname(), ss.getpeername())
cch = self.tc.accept()
sch.send('hello')
@@ -639,7 +643,36 @@ class TransportTest (unittest.TestCase):
self.tc.cancel_port_forward('', port)
self.assertTrue(self.server._listen is None)
- def test_K_stderr_select(self):
+ def test_K_port_forwarding(self):
+ """
+ verify that a client can forward new connections from a locally-
+ forwarded port.
+ """
+ self.setup_test_server()
+ chan = self.tc.open_session()
+ chan.exec_command('yes')
+ schan = self.ts.accept(1.0)
+
+ # open a port on the "server" that the client will ask to forward to.
+ greeting_server = socket.socket()
+ greeting_server.listen(1)
+ greeting_port = greeting_server.getsockname()[1]
+
+ cs = self.tc.open_channel('direct-tcpip', ('', greeting_port), ('', 9000))
+ sch = self.ts.accept(1.0)
+ cch = socket.socket()
+ cch.connect(self.server._tcpip_dest)
+
+ ss, _ = greeting_server.accept()
+ ss.send('Hello!\n')
+ ss.close()
+ sch.send(cch.recv(8192))
+ sch.close()
+
+ self.assertEquals('Hello!\n', cs.recv(7))
+ cs.close()
+
+ def test_L_stderr_select(self):
"""
verify that select() on a channel works even if only stderr is
receiving data.
@@ -678,7 +711,7 @@ class TransportTest (unittest.TestCase):
schan.close()
chan.close()
- def test_L_send_ready(self):
+ def test_M_send_ready(self):
"""
verify that send_ready() indicates when a send would not block.
"""