diff --git a/MANIFEST.in b/MANIFEST.in index 1368ae2..2c65a02 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ -include ChangeLog LICENSE test.py demo.py demo_simple.py demo_server.py demo_dss_key demo_rsa_key +include ChangeLog LICENSE test.py demo.py demo_simple.py demo_server.py demo_dss_key demo_rsa_key forward.py recursive-include docs * recursive-include tests *.py diff --git a/Makefile b/Makefile index a4e2c02..873fbb1 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ # doduo (04jan04) - 0.9 # eevee (08mar04) # fearow (23apr04) +# gyarados (31may04) release: python ./setup.py sdist --formats=zip diff --git a/README b/README index 4be8bcc..41bf6d3 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ paramiko 0.9 -"fearow" release, 23 apr 2004 +"gyarados" release, 31 may 2004 Copyright (c) 2003-2004 Robey Pointer @@ -117,6 +117,15 @@ the best and easiest examples of how to use the SFTP class. highlights of what's new in each release: +v0.9 GYARADOS +* Transport.open_channel() -- supports local & remote port forwarding now +* now imports UTF-8 encodings explicitly as a hint to "freeze" utilities +* no longer rejects older SFTP servers +* default packet size bumped to 8kB +* fixed deadlock in closing a channel +* Transport.connect() -- fixed bug where it would always fail when given a + host key to verify + v0.9 FEAROW * Transport.send_ignore() -- send random ignored bytes * RSAKey/DSSKey added from_private_key_file() as a factory constructor; diff --git a/forward.py b/forward.py new file mode 100755 index 0000000..0f5eacf --- /dev/null +++ b/forward.py @@ -0,0 +1,210 @@ +#!/usr/bin/python + +""" +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. + +It uses SocketServer and select, so may not work on Windows. +""" + +import sys +import os +import socket +import select +import SocketServer +import getpass +import base64 +from optparse import OptionParser + +import paramiko + +DEFAULT_PORT = 4000 +SSH_PORT = 22 +VERBOSE = True +READPASS = False + + +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, e: + verbose('Incoming request to %s:%d failed: %s' % (self.chain_host, + self.chain_port, + repr(e))) + return + + verbose('Connected! Tunnel open.') + while True: + 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) + chan.close() + self.request.close() + verbose('Tunnel closed.') + + +def forward_tunnel(local_port, remote_host, remote_port, 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 + ForwardServer(('', local_port), SubHander).serve_forever() + +def load_host_keys(): + filename = os.path.expanduser('~/.ssh/known_hosts') + keys = {} + try: + f = open(filename, 'r') + except Exception, e: + print '*** Unable to open host keys file (%s)' % filename + return + for line in f: + keylist = line.split(' ') + if len(keylist) != 3: + continue + hostlist, keytype, key = keylist + hosts = hostlist.split(',') + for host in hosts: + if not keys.has_key(host): + keys[host] = {} + keys[host][keytype] = base64.decodestring(key) + f.close() + return keys + +def find_default_key_file(): + filename = os.path.expanduser('~/.ssh/id_rsa') + if os.access(filename, os.R_OK): + return filename + filename = os.path.expanduser('~/.ssh/id_dsa') + if os.access(filename, os.R_OK): + return filename + return '' + +def verbose(s): + if VERBOSE: + print s + + +##### + + +parser = OptionParser(usage='usage: %prog [options] :', + version='%prog 1.0') +parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=VERBOSE, + help='squelch all informational output') +parser.add_option('-l', '--local-port', action='store', type='int', dest='port', + default=DEFAULT_PORT, + help='local port to forward (default: %d)' % DEFAULT_PORT) +parser.add_option('-r', '--host', action='store', type='string', dest='ssh_host', + help='SSH host to tunnel through (required)') +parser.add_option('-p', '--port', action='store', type='int', dest='ssh_port', default=SSH_PORT, + help='SSH port to tunnel through (default: %d)' % SSH_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=find_default_key_file(), + help='private key file to use for SSH authentication') +parser.add_option('', '--no-key', action='store_false', dest='use_key', default=True, + help='don\'t look for or use a private key file') +parser.add_option('-P', '--password', action='store_true', dest='readpass', default=READPASS, + help='read password (for key or password auth) from stdin') +options, args = parser.parse_args() + +VERBOSE = options.verbose +READPASS = options.readpass + + +if len(args) != 1: + parser.error('Incorrect number of arguments.') +remote_host = args[0] +if ':' not in remote_host: + parser.error('Remote port missing.') +remote_host, remote_port = remote_host.split(':', 1) +try: + remote_port = int(remote_port) +except: + parser.error('Remote port must be a number.') + +if not options.ssh_host: + parser.error('SSH host is required.') +if ':' in options.ssh_host: + options.ssh_host, options.ssh_port = options.ssh_host.split(':', 1) + try: + options.ssh_port = int(options.ssh_port) + except: + parser.error('SSH port must be a number.') + +host_keys = load_host_keys() +if not host_keys.has_key(options.ssh_host): + print '*** Warning: no host key for %s' % options.ssh_host + expected_host_key_type = None + expected_host_key = None +else: + expected_host_key_type = host_keys[options.ssh_host].keys()[0] + expected_host_key = host_keys[options.ssh_host][expected_host_key_type] + +key = None +password = None +if options.use_key: + try: + key = paramiko.RSAKey.from_private_key_file(options.keyfile) + except paramiko.PasswordRequiredException: + if not READPASS: + print '*** Password needed for keyfile (use -P): %s' % options.keyfile + sys.exit(1) + key_password = raw_input() + try: + key = paramiko.RSAKey.from_private_key_file(options.keyfile, key_password) + except: + print '*** Unable to read keyfile: %s' % options.keyfile + sys.exit(1) + except: + pass + +if key is None: + # try reading a password then + if not READPASS: + print '*** Either a valid private key or password is required (use -K or -P).' + sys.exit(1) + password = raw_input() + +verbose('Connecting to ssh host %s:%d ...' % (options.ssh_host, options.ssh_port)) + +transport = paramiko.Transport((options.ssh_host, options.ssh_port)) +transport.connect(hostkeytype=expected_host_key_type, + hostkey=expected_host_key, + username=options.user, + password=password, + pkey=key) + +verbose('Now forwarding port %d to %s:%d ...' % (options.port, remote_host, remote_port)) + +try: + forward_tunnel(options.port, remote_host, remote_port, transport) +except KeyboardInterrupt: + print 'Port forwarding stopped.' + sys.exit(0) diff --git a/paramiko/__init__.py b/paramiko/__init__.py index d39f762..2450369 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -48,7 +48,7 @@ released under the GNU Lesser General Public License (LGPL). Website: U{http://www.lag.net/~robey/paramiko/} -@version: 0.9 (fearow) +@version: 0.9 (gyarados) @author: Robey Pointer @contact: robey@lag.net @license: GNU Lesser General Public License (LGPL) @@ -61,9 +61,8 @@ if sys.version_info < (2, 2): __author__ = "Robey Pointer " -__date__ = "23 Apr 2004" -__version__ = "0.9-fearow" -#__credits__ = "Huzzah!" +__date__ = "31 May 2004" +__version__ = "0.9-gyarados" __license__ = "GNU Lesser General Public License (LGPL)" diff --git a/setup.py b/setup.py index a5ce8ec..62a6da4 100644 --- a/setup.py +++ b/setup.py @@ -13,13 +13,13 @@ Required packages: ''' setup(name = "paramiko", - version = "0.9-fearow", + version = "0.9-gyarados", description = "SSH2 protocol library", author = "Robey Pointer", author_email = "robey@lag.net", url = "http://www.lag.net/~robey/paramiko/", packages = [ 'paramiko' ], - download_url = 'http://www.lag.net/~robey/paramiko/paramiko-0.9-fearow.zip', + download_url = 'http://www.lag.net/~robey/paramiko/paramiko-0.9-gyarados.zip', license = 'LGPL', platforms = 'Posix; MacOS X; Windows', classifiers = [ 'Development Status :: 3 - Alpha',