[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:
parent
ed72847ad1
commit
945a41dd3d
12
README
12
README
|
@ -9,7 +9,7 @@ http://www.lag.net/~robey/paramiko/
|
|||
*** WHAT
|
||||
|
||||
"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
|
||||
TLS), SSH2 protocol does not require heirarchical certificates signed by a
|
||||
powerful central authority. you may know SSH2 as the protocol that replaced
|
||||
|
@ -27,6 +27,7 @@ should have come with this archive.
|
|||
*** REQUIREMENTS
|
||||
|
||||
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 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
|
||||
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
|
||||
|
||||
|
@ -81,7 +89,7 @@ which actually motivated me to write more documentation than i ever would
|
|||
have before.
|
||||
|
||||
there are also unit tests here:
|
||||
$ python2 ./test.py
|
||||
$ python ./test.py
|
||||
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
|
||||
the best and easiest examples of how to use the SFTP class.
|
||||
|
|
2
demo.py
2
demo.py
|
@ -1,6 +1,6 @@
|
|||
#!/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
|
||||
|
||||
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import sys, os, socket, threading, logging, traceback, base64
|
||||
import sys, os, socket, threading, traceback, base64
|
||||
import paramiko
|
||||
|
||||
# setup logging
|
||||
l = logging.getLogger("paramiko")
|
||||
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)
|
||||
paramiko.util.log_to_file('demo_server.log')
|
||||
|
||||
#host_key = paramiko.RSAKey()
|
||||
#host_key.read_private_key_file('demo_rsa_key')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/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
|
||||
|
||||
|
||||
|
|
|
@ -56,8 +56,8 @@ Website: U{http://www.lag.net/~robey/paramiko/}
|
|||
|
||||
import sys
|
||||
|
||||
if (sys.version_info[0] < 2) or ((sys.version_info[0] == 2) and (sys.version_info[1] < 3)):
|
||||
raise RuntimeError('You need python 2.3 for this module.')
|
||||
if sys.version_info < (2, 2):
|
||||
raise RuntimeError('You need python 2.2 for this module.')
|
||||
|
||||
|
||||
__author__ = "Robey Pointer <robey@lag.net>"
|
||||
|
|
|
@ -24,10 +24,10 @@ This separation keeps either class file from being too unwieldy.
|
|||
"""
|
||||
|
||||
from common import *
|
||||
import util
|
||||
from transport import BaseTransport
|
||||
from message import Message
|
||||
from ssh_exception import SSHException
|
||||
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
|
||||
class Transport (BaseTransport):
|
||||
|
|
|
@ -22,14 +22,14 @@
|
|||
Abstraction for an SSH2 channel.
|
||||
"""
|
||||
|
||||
import time, threading, socket, os
|
||||
|
||||
from common import *
|
||||
import util
|
||||
from message import Message
|
||||
from ssh_exception import SSHException
|
||||
from file import BufferedFile
|
||||
|
||||
import time, threading, logging, socket, os
|
||||
from logging import DEBUG
|
||||
|
||||
|
||||
# this is ugly, and won't work on windows
|
||||
def _set_nonblocking(fd):
|
||||
|
|
|
@ -59,3 +59,18 @@ except:
|
|||
|
||||
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
|
||||
|
|
|
@ -26,11 +26,10 @@ client side, and a B{lot} more on the server side.
|
|||
|
||||
from Crypto.Hash import SHA
|
||||
from Crypto.Util import number
|
||||
from logging import DEBUG
|
||||
|
||||
from common import *
|
||||
from message import Message
|
||||
from util import inflate_long, deflate_long, bit_length
|
||||
import util
|
||||
from ssh_exception import SSHException
|
||||
|
||||
|
||||
|
@ -76,7 +75,7 @@ class KexGex (object):
|
|||
def _generate_x(self):
|
||||
# generate an "x" (1 < x < (p-1)/2).
|
||||
q = (self.p - 1) // 2
|
||||
qnorm = deflate_long(q, 0)
|
||||
qnorm = util.deflate_long(q, 0)
|
||||
qhbyte = ord(qnorm[0])
|
||||
bytes = len(qnorm)
|
||||
qmask = 0xff
|
||||
|
@ -87,7 +86,7 @@ class KexGex (object):
|
|||
self.transport.randpool.stir()
|
||||
x_bytes = self.transport.randpool.get_bytes(bytes)
|
||||
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):
|
||||
break
|
||||
self.x = x
|
||||
|
@ -128,7 +127,7 @@ class KexGex (object):
|
|||
self.p = m.get_mpint()
|
||||
self.g = m.get_mpint()
|
||||
# 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):
|
||||
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)
|
||||
|
|
|
@ -24,10 +24,10 @@ Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
|
|||
"""
|
||||
|
||||
from Crypto.Hash import SHA
|
||||
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
from common import *
|
||||
from message import Message, inflate_long
|
||||
import util
|
||||
from message import Message
|
||||
from ssh_exception import SSHException
|
||||
|
||||
_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 \
|
||||
(x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'):
|
||||
break
|
||||
self.x = inflate_long(x_bytes)
|
||||
self.x = util.inflate_long(x_bytes)
|
||||
|
||||
def start_kex(self):
|
||||
self.generate_x()
|
||||
|
|
|
@ -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()
|
|
@ -23,7 +23,7 @@ Implementation of an SSH2 "message".
|
|||
"""
|
||||
|
||||
import string, types, struct
|
||||
from util import inflate_long, deflate_long
|
||||
import util
|
||||
|
||||
|
||||
class Message (object):
|
||||
|
@ -158,7 +158,7 @@ class Message (object):
|
|||
@return: an arbitrary-length integer.
|
||||
@rtype: long
|
||||
"""
|
||||
return inflate_long(self.get_string())
|
||||
return util.inflate_long(self.get_string())
|
||||
|
||||
def get_string(self):
|
||||
"""
|
||||
|
@ -219,7 +219,7 @@ class Message (object):
|
|||
|
||||
def add_mpint(self, z):
|
||||
"this only works on positive numbers"
|
||||
self.add_string(deflate_long(z))
|
||||
self.add_string(util.deflate_long(z))
|
||||
return self
|
||||
|
||||
def add_string(self, s):
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
# along with Foobar; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
import struct, logging, socket
|
||||
from util import format_binary, tb_strings
|
||||
import struct, socket
|
||||
from common import *
|
||||
import util
|
||||
from channel import Channel
|
||||
from message import Message
|
||||
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
from file import BufferedFile
|
||||
|
||||
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):
|
||||
out = struct.pack('>I', len(packet) + 1) + chr(t) + packet
|
||||
if self.ultra_debug:
|
||||
self._log(DEBUG, format_binary(out, 'OUT: '))
|
||||
self._log(DEBUG, util.format_binary(out, 'OUT: '))
|
||||
self._write_all(out)
|
||||
|
||||
def _read_packet(self):
|
||||
size = struct.unpack('>I', self._read_all(4))[0]
|
||||
data = self._read_all(size)
|
||||
if self.ultra_debug:
|
||||
self._log(DEBUG, format_binary(data, 'IN: '));
|
||||
self._log(DEBUG, util.format_binary(data, 'IN: '));
|
||||
if size > 0:
|
||||
return ord(data[0]), data[1:]
|
||||
return 0, ''
|
||||
|
|
|
@ -22,13 +22,13 @@
|
|||
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 ssh_exception import SSHException
|
||||
from message import Message
|
||||
from channel import Channel
|
||||
from util import format_binary, safe_string, inflate_long, deflate_long, tb_strings
|
||||
import util
|
||||
from rsakey import RSAKey
|
||||
from dsskey import DSSKey
|
||||
from kex_group1 import KexGroup1
|
||||
|
@ -43,8 +43,6 @@ from primes import ModulusPack
|
|||
from Crypto.Cipher import Blowfish, AES, DES3
|
||||
from Crypto.Hash import SHA, MD5, HMAC
|
||||
|
||||
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
|
||||
# for thread cleanup
|
||||
_active_threads = []
|
||||
|
@ -105,7 +103,6 @@ class BaseTransport (threading.Thread):
|
|||
|
||||
If the object is not actually a socket, it must have the following
|
||||
methods:
|
||||
- C{settimeout(float)}: Sets a timeout for read & write calls.
|
||||
- C{send(string)}: Writes from 1 to C{len(string)} bytes, and
|
||||
returns an int representing the number of bytes written. Returns
|
||||
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)
|
||||
self.randpool = randpool
|
||||
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
|
||||
self.local_version = 'SSH-' + self._PROTO_ID + '-' + self._CLIENT_ID
|
||||
self.remote_version = ''
|
||||
|
@ -689,7 +690,24 @@ class BaseTransport (threading.Thread):
|
|||
finally:
|
||||
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):
|
||||
if PY22:
|
||||
return self._py22_read_all(n)
|
||||
out = ''
|
||||
while n > 0:
|
||||
try:
|
||||
|
@ -728,7 +746,7 @@ class BaseTransport (threading.Thread):
|
|||
# encrypt this sucka
|
||||
packet = self._build_packet(str(data))
|
||||
if self.ultra_debug:
|
||||
self._log(DEBUG, format_binary(packet, 'OUT: '))
|
||||
self._log(DEBUG, util.format_binary(packet, 'OUT: '))
|
||||
if self.engine_out != None:
|
||||
out = self.engine_out.encrypt(packet)
|
||||
else:
|
||||
|
@ -751,7 +769,7 @@ class BaseTransport (threading.Thread):
|
|||
if self.engine_in != None:
|
||||
header = self.engine_in.decrypt(header)
|
||||
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]
|
||||
# leftover contains decrypted bytes from the first block (after the length field)
|
||||
leftover = header[4:]
|
||||
|
@ -763,7 +781,7 @@ class BaseTransport (threading.Thread):
|
|||
if self.engine_in != None:
|
||||
packet = self.engine_in.decrypt(packet)
|
||||
if self.ultra_debug:
|
||||
self._log(DEBUG, format_binary(packet, 'IN: '));
|
||||
self._log(DEBUG, util.format_binary(packet, 'IN: '));
|
||||
packet = leftover + packet
|
||||
if self.remote_mac_len > 0:
|
||||
mac = post_packet[:self.remote_mac_len]
|
||||
|
@ -891,15 +909,15 @@ class BaseTransport (threading.Thread):
|
|||
self._send_message(msg)
|
||||
except SSHException, e:
|
||||
self._log(DEBUG, 'Exception: ' + str(e))
|
||||
self._log(DEBUG, tb_strings())
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
self.saved_exception = e
|
||||
except EOFError, e:
|
||||
self._log(DEBUG, 'EOF')
|
||||
self._log(DEBUG, tb_strings())
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
self.saved_exception = e
|
||||
except Exception, e:
|
||||
self._log(DEBUG, 'Unknown exception: ' + str(e))
|
||||
self._log(DEBUG, tb_strings())
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
self.saved_exception = e
|
||||
_active_threads.remove(self)
|
||||
if self.active:
|
||||
|
@ -1276,7 +1294,7 @@ class BaseTransport (threading.Thread):
|
|||
always_display = m.get_boolean()
|
||||
msg = 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 = {
|
||||
MSG_NEWKEYS: _parse_newkeys,
|
||||
|
|
|
@ -18,11 +18,25 @@
|
|||
# along with Foobar; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
from __future__ import generators
|
||||
|
||||
"""
|
||||
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):
|
||||
"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
|
||||
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"
|
||||
l = logging.getLogger("paramiko")
|
||||
if len(l.handlers) > 0:
|
||||
|
|
|
@ -31,9 +31,10 @@ import sys, os
|
|||
HOST = os.environ.get('TEST_HOST', 'localhost')
|
||||
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_PASSWD = os.environ.get('TEST_PKEY_PASSWD', None)
|
||||
FOLDER = os.environ.get('TEST_FOLDER', 'temp-testing')
|
||||
|
||||
import paramiko, logging, unittest
|
||||
import paramiko, unittest
|
||||
|
||||
ARTICLE = '''
|
||||
Insulin sensitivity and liver insulin receptor structure in ducks from two
|
||||
|
@ -64,16 +65,21 @@ decreased compared with chicken.
|
|||
|
||||
|
||||
# setup logging
|
||||
l = logging.getLogger('paramiko')
|
||||
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)
|
||||
paramiko.util.log_to_file('test.log')
|
||||
|
||||
t = paramiko.Transport(HOST)
|
||||
key = paramiko.RSAKey()
|
||||
key.read_private_key_file(PKEY)
|
||||
try:
|
||||
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:
|
||||
t.connect(username=USER, pkey=key)
|
||||
except paramiko.SSHException:
|
||||
|
|
Loading…
Reference in New Issue