[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-42]

support py22, more or less
add roger binns' patches for supporting python 2.2.  i hedged a bit on the
logging stuff and just added some trickery to let logging be stubbed out for
python 2.2.  this changed a lot of import statements but i managed to avoid
hacking at any of the existing logging.

socket timeouts are required for the threads to notice when they've been
deactivated.  worked around it by using the 'select' module on py22.

also fixed the sftp unit tests to cope with a password-protected private key.
This commit is contained in:
Robey Pointer 2004-04-06 08:16:02 +00:00
parent ed72847ad1
commit 945a41dd3d
16 changed files with 175 additions and 59 deletions

12
README
View File

@ -9,7 +9,7 @@ http://www.lag.net/~robey/paramiko/
*** WHAT *** WHAT
"paramiko" is a combination of the esperanto words for "paranoid" and "friend". "paramiko" is a combination of the esperanto words for "paranoid" and "friend".
it's a module for python 2.3 that implements the SSH2 protocol for secure it's a module for python 2.2+ that implements the SSH2 protocol for secure
(encrypted and authenticated) connections to remote machines. unlike SSL (aka (encrypted and authenticated) connections to remote machines. unlike SSL (aka
TLS), SSH2 protocol does not require heirarchical certificates signed by a TLS), SSH2 protocol does not require heirarchical certificates signed by a
powerful central authority. you may know SSH2 as the protocol that replaced powerful central authority. you may know SSH2 as the protocol that replaced
@ -27,6 +27,7 @@ should have come with this archive.
*** REQUIREMENTS *** REQUIREMENTS
python 2.3 <http://www.python.org/> python 2.3 <http://www.python.org/>
(python 2.2 may work with some pain)
pyCrypt <http://www.amk.ca/python/code/crypto.html> pyCrypt <http://www.amk.ca/python/code/crypto.html>
PyCrypt compiled for Win32 can be downloaded from the HashTar homepage: PyCrypt compiled for Win32 can be downloaded from the HashTar homepage:
@ -46,6 +47,13 @@ it changes behavior in some fundamental ways, and these ways require posix.
so don't call "fileno()" on a Channel on Windows. this is detailed in the so don't call "fileno()" on a Channel on Windows. this is detailed in the
documentation for the "fileno" method. documentation for the "fileno" method.
python 2.2 may work, thanks to some patches from Roger Binns. things to watch
out for:
* sockets in 2.2 don't support timeouts, so the 'select' module is imported
to do polling. this may not work on windows. (works fine on osx.)
* there is no logging, period.
you really should upgrade to python 2.3. laziness is no excuse!
*** DEMO *** DEMO
@ -81,7 +89,7 @@ which actually motivated me to write more documentation than i ever would
have before. have before.
there are also unit tests here: there are also unit tests here:
$ python2 ./test.py $ python ./test.py
which will verify that some of the core components are working correctly. which will verify that some of the core components are working correctly.
not much is tested yet, but it's a start. the tests for SFTP are probably not much is tested yet, but it's a start. the tests for SFTP are probably
the best and easiest examples of how to use the SFTP class. the best and easiest examples of how to use the SFTP class.

View File

@ -1,6 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
import sys, os, socket, threading, getpass, logging, time, base64, select, termios, tty, traceback import sys, os, socket, threading, getpass, time, base64, select, termios, tty, traceback
import paramiko import paramiko

View File

@ -1,16 +1,10 @@
#!/usr/bin/python #!/usr/bin/python
import sys, os, socket, threading, logging, traceback, base64 import sys, os, socket, threading, traceback, base64
import paramiko import paramiko
# setup logging # setup logging
l = logging.getLogger("paramiko") paramiko.util.log_to_file('demo_server.log')
l.setLevel(logging.DEBUG)
if len(l.handlers) == 0:
f = open('demo_server.log', 'w')
lh = logging.StreamHandler(f)
lh.setFormatter(logging.Formatter('%(levelname)-.3s [%(asctime)s] %(name)s: %(message)s', '%Y%m%d:%H%M%S'))
l.addHandler(lh)
#host_key = paramiko.RSAKey() #host_key = paramiko.RSAKey()
#host_key.read_private_key_file('demo_rsa_key') #host_key.read_private_key_file('demo_rsa_key')

View File

@ -1,6 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
import sys, os, base64, getpass, socket, logging, traceback, termios, tty, select import sys, os, base64, getpass, socket, traceback, termios, tty, select
import paramiko import paramiko

View File

@ -56,8 +56,8 @@ Website: U{http://www.lag.net/~robey/paramiko/}
import sys import sys
if (sys.version_info[0] < 2) or ((sys.version_info[0] == 2) and (sys.version_info[1] < 3)): if sys.version_info < (2, 2):
raise RuntimeError('You need python 2.3 for this module.') raise RuntimeError('You need python 2.2 for this module.')
__author__ = "Robey Pointer <robey@lag.net>" __author__ = "Robey Pointer <robey@lag.net>"

View File

@ -24,10 +24,10 @@ This separation keeps either class file from being too unwieldy.
""" """
from common import * from common import *
import util
from transport import BaseTransport from transport import BaseTransport
from message import Message from message import Message
from ssh_exception import SSHException from ssh_exception import SSHException
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
class Transport (BaseTransport): class Transport (BaseTransport):

View File

@ -22,14 +22,14 @@
Abstraction for an SSH2 channel. Abstraction for an SSH2 channel.
""" """
import time, threading, socket, os
from common import * from common import *
import util
from message import Message from message import Message
from ssh_exception import SSHException from ssh_exception import SSHException
from file import BufferedFile from file import BufferedFile
import time, threading, logging, socket, os
from logging import DEBUG
# this is ugly, and won't work on windows # this is ugly, and won't work on windows
def _set_nonblocking(fd): def _set_nonblocking(fd):

View File

@ -59,3 +59,18 @@ except:
randpool.randomize() randpool.randomize()
import sys
if sys.version_info < (2, 3):
import logging22 as logging
import select
PY22 = True
else:
import logging
PY22 = False
DEBUG = logging.DEBUG
INFO = logging.INFO
WARNING = logging.WARNING
ERROR = logging.ERROR
CRITICAL = logging.CRITICAL

View File

@ -26,11 +26,10 @@ client side, and a B{lot} more on the server side.
from Crypto.Hash import SHA from Crypto.Hash import SHA
from Crypto.Util import number from Crypto.Util import number
from logging import DEBUG
from common import * from common import *
from message import Message from message import Message
from util import inflate_long, deflate_long, bit_length import util
from ssh_exception import SSHException from ssh_exception import SSHException
@ -76,7 +75,7 @@ class KexGex (object):
def _generate_x(self): def _generate_x(self):
# generate an "x" (1 < x < (p-1)/2). # generate an "x" (1 < x < (p-1)/2).
q = (self.p - 1) // 2 q = (self.p - 1) // 2
qnorm = deflate_long(q, 0) qnorm = util.deflate_long(q, 0)
qhbyte = ord(qnorm[0]) qhbyte = ord(qnorm[0])
bytes = len(qnorm) bytes = len(qnorm)
qmask = 0xff qmask = 0xff
@ -87,7 +86,7 @@ class KexGex (object):
self.transport.randpool.stir() self.transport.randpool.stir()
x_bytes = self.transport.randpool.get_bytes(bytes) x_bytes = self.transport.randpool.get_bytes(bytes)
x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:] x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:]
x = inflate_long(x_bytes, 1) x = util.inflate_long(x_bytes, 1)
if (x > 1) and (x < q): if (x > 1) and (x < q):
break break
self.x = x self.x = x
@ -128,7 +127,7 @@ class KexGex (object):
self.p = m.get_mpint() self.p = m.get_mpint()
self.g = m.get_mpint() self.g = m.get_mpint()
# reject if p's bit length < 1024 or > 8192 # reject if p's bit length < 1024 or > 8192
bitlen = bit_length(self.p) bitlen = util.bit_length(self.p)
if (bitlen < 1024) or (bitlen > 8192): if (bitlen < 1024) or (bitlen > 8192):
raise SSHException('Server-generated gex p (don\'t ask) is out of range (%d bits)' % bitlen) raise SSHException('Server-generated gex p (don\'t ask) is out of range (%d bits)' % bitlen)
self.transport._log(DEBUG, 'Got server p (%d bits)' % bitlen) self.transport._log(DEBUG, 'Got server p (%d bits)' % bitlen)

View File

@ -24,10 +24,10 @@ Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
""" """
from Crypto.Hash import SHA from Crypto.Hash import SHA
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
from common import * from common import *
from message import Message, inflate_long import util
from message import Message
from ssh_exception import SSHException from ssh_exception import SSHException
_MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32) _MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32)
@ -57,7 +57,7 @@ class KexGroup1(object):
if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \ if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \
(x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'): (x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'):
break break
self.x = inflate_long(x_bytes) self.x = util.inflate_long(x_bytes)
def start_kex(self): def start_kex(self):
self.generate_x() self.generate_x()

62
paramiko/logging22.py Normal file
View File

@ -0,0 +1,62 @@
#!/usr/bin/python
# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net>
#
# 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 distrubuted 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 Foobar; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
Stub out logging on python < 2.3.
"""
DEBUG = 10
INFO = 20
WARNING = 30
ERROR = 40
CRITICAL = 50
def getLogger(name):
return _logger
class logger (object):
def __init__(self):
self.handlers = [ ]
self.level = ERROR
def setLevel(self, level):
self.level = level
def addHandler(self, h):
self.handlers.append(h)
def log(self, level, text):
if level >= self.level:
for h in self.handlers:
h.f.write(text + '\n')
h.f.flush()
class StreamHandler (object):
def __init__(self, f):
self.f = f
def setFormatter(self, f):
pass
class Formatter (object):
def __init__(self, x, y):
pass
_logger = logger()

View File

@ -23,7 +23,7 @@ Implementation of an SSH2 "message".
""" """
import string, types, struct import string, types, struct
from util import inflate_long, deflate_long import util
class Message (object): class Message (object):
@ -158,7 +158,7 @@ class Message (object):
@return: an arbitrary-length integer. @return: an arbitrary-length integer.
@rtype: long @rtype: long
""" """
return inflate_long(self.get_string()) return util.inflate_long(self.get_string())
def get_string(self): def get_string(self):
""" """
@ -219,7 +219,7 @@ class Message (object):
def add_mpint(self, z): def add_mpint(self, z):
"this only works on positive numbers" "this only works on positive numbers"
self.add_string(deflate_long(z)) self.add_string(util.deflate_long(z))
return self return self
def add_string(self, s): def add_string(self, s):

View File

@ -18,11 +18,11 @@
# along with Foobar; if not, write to the Free Software Foundation, Inc., # along with Foobar; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
import struct, logging, socket import struct, socket
from util import format_binary, tb_strings from common import *
import util
from channel import Channel from channel import Channel
from message import Message from message import Message
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
from file import BufferedFile from file import BufferedFile
CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, CMD_SETSTAT, \ CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, CMD_SETSTAT, \
@ -511,14 +511,14 @@ class SFTP (object):
def _send_packet(self, t, packet): def _send_packet(self, t, packet):
out = struct.pack('>I', len(packet) + 1) + chr(t) + packet out = struct.pack('>I', len(packet) + 1) + chr(t) + packet
if self.ultra_debug: if self.ultra_debug:
self._log(DEBUG, format_binary(out, 'OUT: ')) self._log(DEBUG, util.format_binary(out, 'OUT: '))
self._write_all(out) self._write_all(out)
def _read_packet(self): def _read_packet(self):
size = struct.unpack('>I', self._read_all(4))[0] size = struct.unpack('>I', self._read_all(4))[0]
data = self._read_all(size) data = self._read_all(size)
if self.ultra_debug: if self.ultra_debug:
self._log(DEBUG, format_binary(data, 'IN: ')); self._log(DEBUG, util.format_binary(data, 'IN: '));
if size > 0: if size > 0:
return ord(data[0]), data[1:] return ord(data[0]), data[1:]
return 0, '' return 0, ''

View File

@ -22,13 +22,13 @@
L{BaseTransport} handles the core SSH2 protocol. L{BaseTransport} handles the core SSH2 protocol.
""" """
import sys, os, string, threading, socket, logging, struct import sys, os, string, threading, socket, struct
from common import * from common import *
from ssh_exception import SSHException from ssh_exception import SSHException
from message import Message from message import Message
from channel import Channel from channel import Channel
from util import format_binary, safe_string, inflate_long, deflate_long, tb_strings import util
from rsakey import RSAKey from rsakey import RSAKey
from dsskey import DSSKey from dsskey import DSSKey
from kex_group1 import KexGroup1 from kex_group1 import KexGroup1
@ -43,8 +43,6 @@ from primes import ModulusPack
from Crypto.Cipher import Blowfish, AES, DES3 from Crypto.Cipher import Blowfish, AES, DES3
from Crypto.Hash import SHA, MD5, HMAC from Crypto.Hash import SHA, MD5, HMAC
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
# for thread cleanup # for thread cleanup
_active_threads = [] _active_threads = []
@ -105,7 +103,6 @@ class BaseTransport (threading.Thread):
If the object is not actually a socket, it must have the following If the object is not actually a socket, it must have the following
methods: methods:
- C{settimeout(float)}: Sets a timeout for read & write calls.
- C{send(string)}: Writes from 1 to C{len(string)} bytes, and - C{send(string)}: Writes from 1 to C{len(string)} bytes, and
returns an int representing the number of bytes written. Returns returns an int representing the number of bytes written. Returns
0 or raises C{EOFError} if the stream has been closed. 0 or raises C{EOFError} if the stream has been closed.
@ -139,7 +136,11 @@ class BaseTransport (threading.Thread):
threading.Thread.__init__(self, target=self._run) threading.Thread.__init__(self, target=self._run)
self.randpool = randpool self.randpool = randpool
self.sock = sock self.sock = sock
self.sock.settimeout(0.1) # Python < 2.3 doesn't have the settimeout method - RogerB
try:
self.sock.settimeout(0.1)
except AttributeError:
pass
# negotiated crypto parameters # negotiated crypto parameters
self.local_version = 'SSH-' + self._PROTO_ID + '-' + self._CLIENT_ID self.local_version = 'SSH-' + self._PROTO_ID + '-' + self._CLIENT_ID
self.remote_version = '' self.remote_version = ''
@ -689,7 +690,24 @@ class BaseTransport (threading.Thread):
finally: finally:
self.lock.release() self.lock.release()
def _py22_read_all(self, n):
out = ''
while n > 0:
r, w, e = select.select([self.sock], [], [], 0.1)
if self.sock not in r:
if not self.active:
raise EOFError()
else:
x = self.sock.recv(n)
if len(x) == 0:
raise EOFError()
out += x
n -= len(x)
return out
def _read_all(self, n): def _read_all(self, n):
if PY22:
return self._py22_read_all(n)
out = '' out = ''
while n > 0: while n > 0:
try: try:
@ -728,7 +746,7 @@ class BaseTransport (threading.Thread):
# encrypt this sucka # encrypt this sucka
packet = self._build_packet(str(data)) packet = self._build_packet(str(data))
if self.ultra_debug: if self.ultra_debug:
self._log(DEBUG, format_binary(packet, 'OUT: ')) self._log(DEBUG, util.format_binary(packet, 'OUT: '))
if self.engine_out != None: if self.engine_out != None:
out = self.engine_out.encrypt(packet) out = self.engine_out.encrypt(packet)
else: else:
@ -751,7 +769,7 @@ class BaseTransport (threading.Thread):
if self.engine_in != None: if self.engine_in != None:
header = self.engine_in.decrypt(header) header = self.engine_in.decrypt(header)
if self.ultra_debug: if self.ultra_debug:
self._log(DEBUG, format_binary(header, 'IN: ')); self._log(DEBUG, util.format_binary(header, 'IN: '));
packet_size = struct.unpack('>I', header[:4])[0] packet_size = struct.unpack('>I', header[:4])[0]
# leftover contains decrypted bytes from the first block (after the length field) # leftover contains decrypted bytes from the first block (after the length field)
leftover = header[4:] leftover = header[4:]
@ -763,7 +781,7 @@ class BaseTransport (threading.Thread):
if self.engine_in != None: if self.engine_in != None:
packet = self.engine_in.decrypt(packet) packet = self.engine_in.decrypt(packet)
if self.ultra_debug: if self.ultra_debug:
self._log(DEBUG, format_binary(packet, 'IN: ')); self._log(DEBUG, util.format_binary(packet, 'IN: '));
packet = leftover + packet packet = leftover + packet
if self.remote_mac_len > 0: if self.remote_mac_len > 0:
mac = post_packet[:self.remote_mac_len] mac = post_packet[:self.remote_mac_len]
@ -891,15 +909,15 @@ class BaseTransport (threading.Thread):
self._send_message(msg) self._send_message(msg)
except SSHException, e: except SSHException, e:
self._log(DEBUG, 'Exception: ' + str(e)) self._log(DEBUG, 'Exception: ' + str(e))
self._log(DEBUG, tb_strings()) self._log(DEBUG, util.tb_strings())
self.saved_exception = e self.saved_exception = e
except EOFError, e: except EOFError, e:
self._log(DEBUG, 'EOF') self._log(DEBUG, 'EOF')
self._log(DEBUG, tb_strings()) self._log(DEBUG, util.tb_strings())
self.saved_exception = e self.saved_exception = e
except Exception, e: except Exception, e:
self._log(DEBUG, 'Unknown exception: ' + str(e)) self._log(DEBUG, 'Unknown exception: ' + str(e))
self._log(DEBUG, tb_strings()) self._log(DEBUG, util.tb_strings())
self.saved_exception = e self.saved_exception = e
_active_threads.remove(self) _active_threads.remove(self)
if self.active: if self.active:
@ -1276,7 +1294,7 @@ class BaseTransport (threading.Thread):
always_display = m.get_boolean() always_display = m.get_boolean()
msg = m.get_string() msg = m.get_string()
lang = m.get_string() lang = m.get_string()
self._log(DEBUG, 'Debug msg: ' + safe_string(msg)) self._log(DEBUG, 'Debug msg: ' + util.safe_string(msg))
_handler_table = { _handler_table = {
MSG_NEWKEYS: _parse_newkeys, MSG_NEWKEYS: _parse_newkeys,

View File

@ -18,11 +18,25 @@
# along with Foobar; if not, write to the Free Software Foundation, Inc., # along with Foobar; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
from __future__ import generators
""" """
Useful functions used by the rest of paramiko. Useful functions used by the rest of paramiko.
""" """
import sys, struct, traceback, logging import sys, struct, traceback
from common import *
# Change by RogerB - python < 2.3 doesn't have enumerate so we implement it
if sys.version_info < (2,3):
class enumerate:
def __init__ (self, sequence):
self.sequence = sequence
def __iter__ (self):
count = 0
for item in self.sequence:
yield (count, item)
count += 1
def inflate_long(s, always_positive=False): def inflate_long(s, always_positive=False):
"turns a normalized byte string into a long-int (adapted from Crypto.Util.number)" "turns a normalized byte string into a long-int (adapted from Crypto.Util.number)"
@ -174,7 +188,7 @@ def mod_inverse(x, m):
u2 += m u2 += m
return u2 return u2
def log_to_file(filename, level=logging.DEBUG): def log_to_file(filename, level=DEBUG):
"send paramiko logs to a logfile, if they're not already going somewhere" "send paramiko logs to a logfile, if they're not already going somewhere"
l = logging.getLogger("paramiko") l = logging.getLogger("paramiko")
if len(l.handlers) > 0: if len(l.handlers) > 0:

View File

@ -31,9 +31,10 @@ import sys, os
HOST = os.environ.get('TEST_HOST', 'localhost') HOST = os.environ.get('TEST_HOST', 'localhost')
USER = os.environ.get('TEST_USER', os.environ.get('USER', 'nobody')) USER = os.environ.get('TEST_USER', os.environ.get('USER', 'nobody'))
PKEY = os.environ.get('TEST_PKEY', os.path.join(os.environ.get('HOME', '/'), '.ssh/id_rsa')) PKEY = os.environ.get('TEST_PKEY', os.path.join(os.environ.get('HOME', '/'), '.ssh/id_rsa'))
PKEY_PASSWD = os.environ.get('TEST_PKEY_PASSWD', None)
FOLDER = os.environ.get('TEST_FOLDER', 'temp-testing') FOLDER = os.environ.get('TEST_FOLDER', 'temp-testing')
import paramiko, logging, unittest import paramiko, unittest
ARTICLE = ''' ARTICLE = '''
Insulin sensitivity and liver insulin receptor structure in ducks from two Insulin sensitivity and liver insulin receptor structure in ducks from two
@ -64,16 +65,21 @@ decreased compared with chicken.
# setup logging # setup logging
l = logging.getLogger('paramiko') paramiko.util.log_to_file('test.log')
l.setLevel(logging.DEBUG)
if len(l.handlers) == 0:
f = open('test.log', 'w')
lh = logging.StreamHandler(f)
lh.setFormatter(logging.Formatter('%(levelname)-.3s [%(asctime)s] %(name)s: %(message)s', '%Y%m%d:%H%M%S'))
l.addHandler(lh)
t = paramiko.Transport(HOST) t = paramiko.Transport(HOST)
key = paramiko.RSAKey() try:
key.read_private_key_file(PKEY) key = paramiko.RSAKey.from_private_key_file(PKEY, PKEY_PASSWD)
except paramiko.PasswordRequiredException:
sys.stderr.write('\n\nparamiko.RSAKey.from_private_key_file REQUIRES PASSWORD.\n')
sys.stderr.write('You have two options:\n')
sys.stderr.write('* Change environment variable TEST_PKEY to point to a different\n')
sys.stderr.write(' (non-password-protected) private key file.\n')
sys.stderr.write('* Set environment variable TEST_PKEY_PASSWD to the password needed\n')
sys.stderr.write(' to unlock this private key.\n')
sys.stderr.write('\n')
sys.exit(1)
try: try:
t.connect(username=USER, pkey=key) t.connect(username=USER, pkey=key)
except paramiko.SSHException: except paramiko.SSHException: