#!/usr/bin/env python
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
#
# This file is part of paramiko.
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
Sample script showing how to do local port forwarding over paramiko.
This script connects to the requested SSH server and sets up local port
forwarding (the openssh -L option) from a local port through a tunneled
connection to a destination reachable from the SSH server machine.
"""
import getpass
import os
import socket
import select
try:
import SocketServer
except ImportError:
import socketserver as SocketServer
import sys
from optparse import OptionParser
import paramiko
SSH_PORT = 22
DEFAULT_PORT = 4000
g_verbose = False
theServer = None # serve_forever, shutdown, serve_close
SendStatusFunction = None
SelectLoopFlag = True
Transport = None
class ForwardServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
class Handler(SocketServer.BaseRequestHandler):
def handle(self):
try:
chan = self.ssh_transport.open_channel(
"direct-tcpip",
(self.chain_host, self.chain_port),
self.request.getpeername(),
)
except Exception as e:
verbose(
"Incoming request to %s:%d failed: %s"
% (self.chain_host, self.chain_port, repr(e))
)
return
if chan is None:
verbose(
"Incoming request to %s:%d was rejected by the SSH server."
% (self.chain_host, self.chain_port)
)
return
verbose(
"Connected! Tunnel open %r -> %r -> %r"
% (
self.request.getpeername(),
chan.getpeername(),
(self.chain_host, self.chain_port),
)
)
while SelectLoopFlag:
print("# select loop")
r, w, x = select.select([self.request, chan], [], [])
if self.request in r:
data = self.request.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
self.request.send(data)
peername = self.request.getpeername()
chan.close()
self.request.close()
verbose("Tunnel closed from %r" % (peername,))
def forward_tunnel(local_port, remote_host, remote_port, transport):
global theServer, Transport
Transport = transport
# this is a little convoluted, but lets me configure things for the Handler
# object. (SocketServer doesn't give Handlers any way to access the outer
# server normally.)
class SubHander(Handler):
chain_host = remote_host
chain_port = remote_port
ssh_transport = transport
try:
theServer = ForwardServer(("", local_port), SubHander) # .serve_forever()
theServer.serve_forever()
except Exception as e:
raise
def shutdown():
# https://docs.python.org/2/library/socketserver.html
verbose("Called server shutdown")
global theServer, SelectLoopFlag, Transport
verbose("Server is going shutdown")
SelectLoopFlag = False
if Transport:
Transport.close()
if theServer:
theServer.shutdown() # Tell the serve_forever() loop to stop and wait until it does.
theServer.server_close() # Clean up the server. May be overridden.
else:
verbose("theServer is None")
theServer = None
Transport = None
verbose("Shutdown")
def isShutdown():
global Transport
if Transport:
return False
else:
return True
def verbose(s):
global g_verbose
if g_verbose:
print(s)
sendStatus(s)
def sendStatus(status):
global SendStatusFunction
if not SendStatusFunction:
return
SendStatusFunction(status)
HELP = """\
Set up a forward tunnel across an SSH server, using paramiko. A local port
(given with -p) is forwarded across an SSH session to an address:port from
the SSH server. This is similar to the openssh -L option.
"""
def get_host_port(spec, default_port):
"parse 'hostname:22' into a host and port, with the port optional"
args = (spec.split(":", 1) + [default_port])[:2]
args[1] = int(args[1])
return args[0], args[1]
def parse_options():
global g_verbose, SSH_PORT, SSH_PORT
parser = OptionParser(
usage="usage: %prog [options] <ssh-server>[:<server-port>]",
version="%prog 1.0",
description=HELP,
)
parser.add_option(
"-q",
"--quiet",
action="store_false",
dest="verbose",
default=True,
help="squelch all informational output",
)
parser.add_option(
"-p",
"--local-port",
action="store",
type="int",
dest="port",
default=DEFAULT_PORT,
help="local port to forward (default: %d)" % DEFAULT_PORT,
)
parser.add_option(
"-u",
"--user",
action="store",
type="string",
dest="user",
default=getpass.getuser(),
help="username for SSH authentication (default: %s)"
% getpass.getuser(),
)
parser.add_option(
"-K",
"--key",
action="store",
type="string",
dest="keyfile",
default=None,
help="private key file to use for SSH authentication",
)
parser.add_option(
"",
"--no-key",
action="store_false",
dest="look_for_keys",
default=True,
help="don't look for or use a private key file",
)
parser.add_option(
"-P",
"--password",
action="store_true",
dest="readpass",
default=False,
help="read password (for key or password auth) from stdin",
)
parser.add_option(
"-r",
"--remote",
action="store",
type="string",
dest="remote",
default=None,
metavar="host:port",
help="remote host and port to forward to",
)
options, args = parser.parse_args()
if len(args) != 1:
parser.error("Incorrect number of arguments.")
if options.remote is None:
parser.error("Remote address required (-r).")
g_verbose = options.verbose
server_host, server_port = get_host_port(args[0], SSH_PORT)
remote_host, remote_port = get_host_port(options.remote, SSH_PORT)
return options, (server_host, server_port), (remote_host, remote_port)
"""
{
'options': {
'verbose': True,
'port': 8000,
'readpass': True,
'look_for_keys': True,
'user': '',
'keyfile': None, # '/home/user/.ssh/id_rsa'
'password': '', # optional
},
'server': {
'address': '192.168.11.102',
'port': 22,
},
'remote': {
'address': 'localhost',
'port': 8080,
},
}
"""
def main(opt, sendStatusFunction = None):
# options, server, remote = parse_options()
# print(options)
# print(server)
# print(remote)
global SelectLoopFlag, SendStatusFunction
if sendStatusFunction:
SendStatusFunction = sendStatusFunction
SelectLoopFlag = True
password = None
if opt['options']['readpass']:
password = getpass.getpass("Enter SSH password: ")
elif 'password' in opt['options']:
password = opt['options']['password']
client = paramiko.SSHClient()
client.load_system_host_keys()
# client.set_missing_host_key_policy(paramiko.WarningPolicy())
# client.set_missing_host_key_policy(paramiko.RejectPolicy())
client.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy())
verbose("Connecting to ssh host %s:%d ..." % (opt['server']['address'], opt['server']['port']))
try:
client.connect(
opt['server']['address'],
opt['server']['port'],
username=opt['options']['user'],
key_filename=opt['options']['keyfile'],
look_for_keys=opt['options']['look_for_keys'],
password=password,
)
except Exception as e:
message = "*** Failed to connect to %s:%d: %r" % (opt['server']['address'], opt['server']['port'], e)
verbose(message)
# sys.exit(1)
return 1
verbose(
"Now forwarding port %d to %s:%d ..."
% (opt['options']['port'], opt['remote']['address'], opt['remote']['port'])
)
try:
forward_tunnel(
opt['options']['port'], opt['remote']['address'], opt['remote']['port'], client.get_transport()
)
except KeyboardInterrupt:
message = "C-c: Port forwarding stopped."
print(message)
verbose(message)
# sys.exit(0)
return 2
except Exception as e:
if len(e.args) > 1:
verbose(e.args[1])
else:
verbose(str(e))
# print('Exception:')
# print(' type : ' + str(type(e)))
# print(' args : ' + str(e.args))
# print(' message: ' + e.message)
# print(' e : ' + str(e))
# sys.exit(1)
return 3
return 0 # shutdown
if __name__ == "__main__":
options, server, remote = parse_options()
opt = {
'options': options.__dict__,
'server': {
'address': server[0],
'port': server[1],
},
'remote': {
'address': remote[0],
'port': remote[1],
},
}
print(opt)
main(opt)