summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2004-04-06 08:16:02 +0000
committerRobey Pointer <robey@lag.net>2004-04-06 08:16:02 +0000
commit945a41dd3d2cf7f3d37012c588d8eb07bcc296b2 (patch)
tree112de2889851b2515994cbaab42bf3f868d0939e
parented72847ad1e392af6bb8920176c30548c68ddb23 (diff)
[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.
-rw-r--r--README12
-rwxr-xr-xdemo.py2
-rwxr-xr-xdemo_server.py10
-rwxr-xr-xdemo_simple.py2
-rw-r--r--paramiko/__init__.py4
-rw-r--r--paramiko/auth_transport.py2
-rw-r--r--paramiko/channel.py6
-rw-r--r--paramiko/common.py15
-rw-r--r--paramiko/kex_gex.py9
-rw-r--r--paramiko/kex_group1.py6
-rw-r--r--paramiko/logging22.py62
-rw-r--r--paramiko/message.py6
-rw-r--r--paramiko/sftp.py10
-rw-r--r--paramiko/transport.py44
-rw-r--r--paramiko/util.py18
-rwxr-xr-xtests/test_sftp.py26
16 files changed, 175 insertions, 59 deletions
diff --git a/README b/README
index b7284cc2..ab9b2102 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 <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.
diff --git a/demo.py b/demo.py
index c9dd5014..dfb7231f 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 6447d4a0..5d6cbb5c 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 6a216b28..0bc46bf3 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 80cae55c..4e5f4aa2 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 <robey@lag.net>"
diff --git a/paramiko/auth_transport.py b/paramiko/auth_transport.py
index ccb71499..b8468f0d 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 cf2ff633..44672d90 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 4aa2c34e..e5aaaf8a 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 c7b07a3e..7db93d6a 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 77857f8c..c528f29a 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 00000000..b59aacf0
--- /dev/null
+++ b/paramiko/logging22.py
@@ -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()
diff --git a/paramiko/message.py b/paramiko/message.py
index bb79d609..0ce0c707 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 928a1342..9a26298b 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 391f2a04..bc3338cf 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 57c9b981..191f8454 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 798853f6..5085d7a1 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: