From ba1fd0d61bc5c103004d496473681b2a4dce0f3b Mon Sep 17 00:00:00 2001 From: Robey Pointer Date: Sun, 30 Dec 2007 21:29:50 -0800 Subject: [project @ robey@lag.net-20071231052950-8h599bnez3sgbf2e] patch from david guerizec for direct-tcpip forwarding support, and a unit test added by yours truly. --- paramiko/server.py | 41 +++++++++++++++++++++++++++++++++++++++++ paramiko/transport.py | 12 +++++++++++- tests/test_transport.py | 39 ++++++++++++++++++++++++++++++++++++--- 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. """ -- cgit v1.2.3