diff --git a/README b/README
index b7284cc..ab9b210 100644
--- a/README
+++ b/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
+ (python 2.2 may work with some pain)
pyCrypt
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.
diff --git a/demo.py b/demo.py
index c9dd501..dfb7231 100755
--- a/demo.py
+++ b/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
diff --git a/demo_server.py b/demo_server.py
index 6447d4a..5d6cbb5 100755
--- a/demo_server.py
+++ b/demo_server.py
@@ -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')
diff --git a/demo_simple.py b/demo_simple.py
index 6a216b2..0bc46bf 100755
--- a/demo_simple.py
+++ b/demo_simple.py
@@ -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
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 80cae55..4e5f4aa 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -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 "
diff --git a/paramiko/auth_transport.py b/paramiko/auth_transport.py
index ccb7149..b8468f0 100644
--- a/paramiko/auth_transport.py
+++ b/paramiko/auth_transport.py
@@ -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):
diff --git a/paramiko/channel.py b/paramiko/channel.py
index cf2ff63..44672d9 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -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):
diff --git a/paramiko/common.py b/paramiko/common.py
index 4aa2c34..e5aaaf8 100644
--- a/paramiko/common.py
+++ b/paramiko/common.py
@@ -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
diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py
index c7b07a3..7db93d6 100644
--- a/paramiko/kex_gex.py
+++ b/paramiko/kex_gex.py
@@ -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)
diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py
index 77857f8..c528f29 100644
--- a/paramiko/kex_group1.py
+++ b/paramiko/kex_group1.py
@@ -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()
diff --git a/paramiko/logging22.py b/paramiko/logging22.py
new file mode 100644
index 0000000..b59aacf
--- /dev/null
+++ b/paramiko/logging22.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+
+# Copyright (C) 2003-2004 Robey Pointer
+#
+# 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()
diff --git a/paramiko/message.py b/paramiko/message.py
index bb79d60..0ce0c70 100644
--- a/paramiko/message.py
+++ b/paramiko/message.py
@@ -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):
diff --git a/paramiko/sftp.py b/paramiko/sftp.py
index 928a134..9a26298 100644
--- a/paramiko/sftp.py
+++ b/paramiko/sftp.py
@@ -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, ''
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 391f2a0..bc3338c 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -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,
diff --git a/paramiko/util.py b/paramiko/util.py
index 57c9b98..191f845 100644
--- a/paramiko/util.py
+++ b/paramiko/util.py
@@ -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:
diff --git a/tests/test_sftp.py b/tests/test_sftp.py
index 798853f..5085d7a 100755
--- a/tests/test_sftp.py
+++ b/tests/test_sftp.py
@@ -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: