summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog42
-rw-r--r--Makefile7
-rw-r--r--NOTES13
-rw-r--r--README43
-rwxr-xr-xdemo.py1
-rw-r--r--demo_rsa_key (renamed from demo_host_key)0
-rwxr-xr-xdemo_server.py3
-rwxr-xr-xdemo_simple.py136
-rw-r--r--paramiko.py24
-rw-r--r--paramiko/__init__.py37
-rw-r--r--paramiko/auth_transport.py42
-rw-r--r--paramiko/channel.py553
-rw-r--r--paramiko/dsskey.py14
-rw-r--r--paramiko/kex_gex.py28
-rw-r--r--paramiko/kex_group1.py16
-rw-r--r--paramiko/pkey.py112
-rw-r--r--paramiko/rsakey.py23
-rw-r--r--paramiko/transport.py353
-rw-r--r--setup.py2
19 files changed, 989 insertions, 460 deletions
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 00000000..c151d251
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,42 @@
+
+2003-08-24:
+ * implemented the other hashes: all 4 from the draft are working now
+ * added 'aes128-cbc' and '3des-cbc' cipher support
+ * fixed channel eof/close semantics
+2003-09-12: version "aerodactyl"
+ * implemented group-exchange kex ("kex-gex")
+ * implemented RSA/DSA private key auth
+2003-09-13:
+ * fixed inflate_long and deflate_long to handle negatives, even though
+ they're never used in the current ssh protocol
+2003-09-14:
+ * fixed session_id handling: re-keying works now
+ * added the ability for a Channel to have a fileno() for select/poll
+ purposes, although this will cause worse window performance if the
+ client app isn't careful
+2003-09-16: version "bulbasaur"
+ * fixed pipe (fileno) method to be nonblocking and it seems to work now
+ * fixed silly bug that caused large blocks to be truncated
+2003-10-08:
+ * patch to fix Channel.invoke_subsystem and add Channel.exec_command
+ [vaclav dvorak]
+ * patch to add Channel.sendall [vaclav dvorak]
+ * patch to add Channel.shutdown [vaclav dvorak]
+ * patch to add Channel.makefile and a ChannelFile class which emulates
+ a python file object [vaclav dvorak]
+2003-10-26:
+ * thread creation no longer happens during construction -- use the new
+ method "start_client(event)" to get things rolling
+ * re-keying now takes place after 1GB of data or 1 billion packets
+ (these limits can be easily changed per-session if needed)
+2003-11-06:
+ * added a demo server and host key
+2003-11-09:
+ * lots of changes to server mode
+ * ChannelFile supports universal newline mode; fixed readline
+ * fixed a bug with parsing the remote banner
+2003-11-10: version "charmander"
+ * renamed SSHException -> SecshException
+ * cleaned up server mode and the demo server
+
+*** for all subsequent changes, please see 'tla changelog'.
diff --git a/Makefile b/Makefile
index a2b28aef..c4d38736 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
# releases:
# aerodactyl (13sep03)
-# bulbasaur
+# bulbasaur (18sep03)
# charmander (10nov03)
RELEASE=charmander
@@ -8,8 +8,11 @@ RELEASE=charmander
release:
python ./setup.py sdist --formats=zip
+docs:
+ epydoc -o docs/ paramiko
+
# places where the version number is stored:
#
# setup.py
-# secsh.py
+# __init__.py
# README
diff --git a/NOTES b/NOTES
index 7fbcc733..8a72a913 100644
--- a/NOTES
+++ b/NOTES
@@ -15,19 +15,6 @@ SSHOutputStream --> ssh2 chan --> ssh2 transport --> SOS [no thread]
exported API...
-from BaseTransport:
- start_client
- start_server
- add_server_key
- get_server_key
- close
- get_remote_server_key
-* is_active
- open_session
- open_channel
- renegotiate_keys
- check_channel_request
-
from Transport:
* is_authenticated
auth_key
diff --git a/README b/README
index 693d5b0a..a67b889e 100644
--- a/README
+++ b/README
@@ -130,49 +130,6 @@ are still running (and you'll have to kill -9 from another shell window).
[fixme: add info about server mode]
-*** CHANGELOG
-
-2003-08-24:
- * implemented the other hashes: all 4 from the draft are working now
- * added 'aes128-cbc' and '3des-cbc' cipher support
- * fixed channel eof/close semantics
-2003-09-12: version "aerodactyl"
- * implemented group-exchange kex ("kex-gex")
- * implemented RSA/DSA private key auth
-2003-09-13:
- * fixed inflate_long and deflate_long to handle negatives, even though
- they're never used in the current ssh protocol
-2003-09-14:
- * fixed session_id handling: re-keying works now
- * added the ability for a Channel to have a fileno() for select/poll
- purposes, although this will cause worse window performance if the
- client app isn't careful
-2003-09-16: version "bulbasaur"
- * fixed pipe (fileno) method to be nonblocking and it seems to work now
- * fixed silly bug that caused large blocks to be truncated
-2003-10-08:
- * patch to fix Channel.invoke_subsystem and add Channel.exec_command
- [vaclav dvorak]
- * patch to add Channel.sendall [vaclav dvorak]
- * patch to add Channel.shutdown [vaclav dvorak]
- * patch to add Channel.makefile and a ChannelFile class which emulates
- a python file object [vaclav dvorak]
-2003-10-26:
- * thread creation no longer happens during construction -- use the new
- method "start_client(event)" to get things rolling
- * re-keying now takes place after 1GB of data or 1 billion packets
- (these limits can be easily changed per-session if needed)
-2003-11-06:
- * added a demo server and host key
-2003-11-09:
- * lots of changes to server mode
- * ChannelFile supports universal newline mode; fixed readline
- * fixed a bug with parsing the remote banner
-2003-11-10: version "charmander"
- * renamed SSHException -> SecshException
- * cleaned up server mode and the demo server
-
-
*** MISSING LINKS
* ctr forms of ciphers are missing (blowfish-ctr, aes128-ctr, aes256-ctr)
diff --git a/demo.py b/demo.py
index 875c2043..963f7d83 100755
--- a/demo.py
+++ b/demo.py
@@ -66,7 +66,6 @@ except Exception, e:
try:
event = threading.Event()
t = paramiko.Transport(sock)
- t.ultra_debug = 0
t.start_client(event)
# print repr(t)
event.wait(15)
diff --git a/demo_host_key b/demo_rsa_key
index f50e9c53..f50e9c53 100644
--- a/demo_host_key
+++ b/demo_rsa_key
diff --git a/demo_server.py b/demo_server.py
index e04c8027..7fd25ad3 100755
--- a/demo_server.py
+++ b/demo_server.py
@@ -13,10 +13,11 @@ if len(l.handlers) == 0:
l.addHandler(lh)
#host_key = paramiko.RSAKey()
-#host_key.read_private_key_file('demo_host_key')
+#host_key.read_private_key_file('demo_rsa_key')
host_key = paramiko.DSSKey()
host_key.read_private_key_file('demo_dss_key')
+
print 'Read key: ' + paramiko.hexify(host_key.get_fingerprint())
diff --git a/demo_simple.py b/demo_simple.py
new file mode 100755
index 00000000..0bd877c2
--- /dev/null
+++ b/demo_simple.py
@@ -0,0 +1,136 @@
+#!/usr/bin/python
+
+import sys, os, base64, getpass, socket, logging, traceback, termios, tty, select
+import paramiko
+
+
+##### utility functions
+
+def load_host_keys():
+ filename = os.environ['HOME'] + '/.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
+
+
+# setup logging
+l = logging.getLogger("paramiko")
+l.setLevel(logging.DEBUG)
+if len(l.handlers) == 0:
+ f = open('demo.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)
+
+# get hostname
+username = ''
+if len(sys.argv) > 1:
+ hostname = sys.argv[1]
+ if hostname.find('@') >= 0:
+ username, hostname = hostname.split('@')
+else:
+ hostname = raw_input('Hostname: ')
+if len(hostname) == 0:
+ print '*** Hostname required.'
+ sys.exit(1)
+port = 22
+if hostname.find(':') >= 0:
+ hostname, portstr = hostname.split(':')
+ port = int(portstr)
+
+
+# get username
+if username == '':
+ default_username = getpass.getuser()
+ username = raw_input('Username [%s]: ' % default_username)
+ if len(username) == 0:
+ username = default_username
+password = getpass.getpass('Password for %s@%s: ' % (username, hostname))
+
+
+# get host key, if we know one
+hostkeytype = None
+hostkey = None
+hkeys = load_host_keys()
+if hkeys.has_key(hostname):
+ hostkeytype = hkeys[hostname].keys()[0]
+ hostkey = hkeys[hostname][hostkeytype]
+ print 'Using host key of type %s' % hostkeytype
+
+
+# now connect
+try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect((hostname, port))
+except Exception, e:
+ print '*** Connect failed: ' + str(e)
+ traceback.print_exc()
+ sys.exit(1)
+
+
+# finally, use paramiko Transport to negotiate SSH2 across the connection
+try:
+ t = paramiko.Transport(sock)
+ t.connect(username=username, password=password, hostkeytype=hostkeytype, hostkey=hostkey)
+ chan = t.open_session()
+ chan.get_pty()
+ chan.invoke_shell()
+ print '*** Here we go!'
+ print
+
+ try:
+ oldtty = termios.tcgetattr(sys.stdin)
+ tty.setraw(sys.stdin.fileno())
+ tty.setcbreak(sys.stdin.fileno())
+ chan.settimeout(0.0)
+
+ while 1:
+ r, w, e = select.select([chan, sys.stdin], [], [])
+ if chan in r:
+ try:
+ x = chan.recv(1024)
+ if len(x) == 0:
+ print '\r\n*** EOF\r\n',
+ break
+ sys.stdout.write(x)
+ sys.stdout.flush()
+ except socket.timeout:
+ pass
+ if sys.stdin in r:
+ # FIXME: reading 1 byte at a time is incredibly dumb.
+ x = sys.stdin.read(1)
+ if len(x) == 0:
+ print
+ print '*** Bye.\r\n',
+ break
+ chan.send(x)
+
+ finally:
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
+
+ chan.close()
+ t.close()
+
+except Exception, e:
+ print '*** Caught exception: ' + str(e.__class__) + ': ' + str(e)
+ traceback.print_exc()
+ try:
+ t.close()
+ except:
+ pass
+ sys.exit(1)
diff --git a/paramiko.py b/paramiko.py
deleted file mode 100644
index cc5fbfaa..00000000
--- a/paramiko.py
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/python
-
-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.')
-
-class SSHException(Exception):
- pass
-
-
-from auth_transport import Transport
-from channel import Channel
-from rsakey import RSAKey
-from dsskey import DSSKey
-
-from util import hexify
-
-
-__author__ = "Robey Pointer <robey@lag.net>"
-__date__ = "10 Nov 2003"
-__version__ = "0.1-charmander"
-__credits__ = "Huzzah!"
-
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 0e96a92a..81d41edb 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -11,10 +11,35 @@ __version__ = "0.1-charmander"
__credits__ = "Huzzah!"
-from auth_transport import Transport
-from channel import Channel
-from rsakey import RSAKey
-from dsskey import DSSKey
-from util import hexify
+import ssh_exception, transport, auth_transport, channel, rsakey, dsskey, util
-#__all__ = [ 'Transport', 'Channel', 'RSAKey', 'DSSKey', 'hexify' ]
+class SSHException (ssh_exception.SSHException):
+ pass
+
+class Transport (auth_transport.Transport):
+ """
+ An SSH Transport attaches to a stream (usually a socket), negotiates an
+ encrypted session, authenticates, and then creates stream tunnels, called
+ L{Channel}s, across the session. Multiple channels can be multiplexed
+ across a single session (and often are, in the case of port forwardings).
+ """
+ pass
+
+class Channel (channel.Channel):
+ """
+ A secure tunnel across an SSH L{Transport}. A Channel is meant to behave
+ like a socket, and has an API that should be indistinguishable from the
+ python socket API.
+ """
+ pass
+
+class RSAKey (rsakey.RSAKey):
+ pass
+
+class DSSKey (dsskey.DSSKey):
+ pass
+
+
+__all__ = [ 'Transport', 'Channel', 'RSAKey', 'DSSKey', 'transport',
+ 'auth_transport', 'channel', 'rsakey', 'ddskey', 'util',
+ 'SSHException' ]
diff --git a/paramiko/auth_transport.py b/paramiko/auth_transport.py
index c39c4630..34f11744 100644
--- a/paramiko/auth_transport.py
+++ b/paramiko/auth_transport.py
@@ -1,18 +1,18 @@
#!/usr/bin/python
from transport import BaseTransport
-from transport import MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, \
- MSG_USERAUTH_SUCCESS, MSG_USERAUTH_BANNER
+from transport import _MSG_SERVICE_REQUEST, _MSG_SERVICE_ACCEPT, _MSG_USERAUTH_REQUEST, _MSG_USERAUTH_FAILURE, \
+ _MSG_USERAUTH_SUCCESS, _MSG_USERAUTH_BANNER
from message import Message
from ssh_exception import SSHException
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
-DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \
- DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
+_DISCONNECT_SERVICE_NOT_AVAILABLE, _DISCONNECT_AUTH_CANCELLED_BY_USER, \
+ _DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
-class Transport(BaseTransport):
+class Transport (BaseTransport):
"BaseTransport with the auth framework hooked up"
AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3)
@@ -55,7 +55,7 @@ class Transport(BaseTransport):
def _request_auth(self):
m = Message()
- m.add_byte(chr(MSG_SERVICE_REQUEST))
+ m.add_byte(chr(_MSG_SERVICE_REQUEST))
m.add_string('ssh-userauth')
self._send_message(m)
@@ -90,8 +90,8 @@ class Transport(BaseTransport):
def disconnect_service_not_available(self):
m = Message()
- m.add_byte(chr(MSG_DISCONNECT))
- m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
+ m.add_byte(chr(_MSG_DISCONNECT))
+ m.add_int(_DISCONNECT_SERVICE_NOT_AVAILABLE)
m.add_string('Service not available')
m.add_string('en')
self._send_message(m)
@@ -99,8 +99,8 @@ class Transport(BaseTransport):
def disconnect_no_more_auth(self):
m = Message()
- m.add_byte(chr(MSG_DISCONNECT))
- m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
+ m.add_byte(chr(_MSG_DISCONNECT))
+ m.add_int(_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
m.add_string('No more auth methods available')
m.add_string('en')
self._send_message(m)
@@ -111,7 +111,7 @@ class Transport(BaseTransport):
if self.server_mode and (service == 'ssh-userauth'):
# accepted
m = Message()
- m.add_byte(chr(MSG_SERVICE_ACCEPT))
+ m.add_byte(chr(_MSG_SERVICE_ACCEPT))
m.add_string(service)
self._send_message(m)
return
@@ -123,7 +123,7 @@ class Transport(BaseTransport):
if service == 'ssh-userauth':
self._log(DEBUG, 'userauth is OK')
m = Message()
- m.add_byte(chr(MSG_USERAUTH_REQUEST))
+ m.add_byte(chr(_MSG_USERAUTH_REQUEST))
m.add_string(self.username)
m.add_string('ssh-connection')
m.add_string(self.auth_method)
@@ -161,7 +161,7 @@ class Transport(BaseTransport):
if not self.server_mode:
# er, uh... what?
m = Message()
- m.add_byte(chr(MSG_USERAUTH_FAILURE))
+ m.add_byte(chr(_MSG_USERAUTH_FAILURE))
m.add_string('none')
m.add_boolean(0)
self._send_message(m)
@@ -202,11 +202,11 @@ class Transport(BaseTransport):
m = Message()
if result == self.AUTH_SUCCESSFUL:
self._log(DEBUG, 'Auth granted.')
- m.add_byte(chr(MSG_USERAUTH_SUCCESS))
+ m.add_byte(chr(_MSG_USERAUTH_SUCCESS))
self.auth_complete = 1
else:
self._log(DEBUG, 'Auth rejected.')
- m.add_byte(chr(MSG_USERAUTH_FAILURE))
+ m.add_byte(chr(_MSG_USERAUTH_FAILURE))
m.add_string(self.get_allowed_auths(username))
if result == self.AUTH_PARTIALLY_SUCCESSFUL:
m.add_boolean(1)
@@ -245,11 +245,11 @@ class Transport(BaseTransport):
_handler_table = BaseTransport._handler_table.copy()
_handler_table.update({
- MSG_SERVICE_REQUEST: parse_service_request,
- MSG_SERVICE_ACCEPT: parse_service_accept,
- MSG_USERAUTH_REQUEST: parse_userauth_request,
- MSG_USERAUTH_SUCCESS: parse_userauth_success,
- MSG_USERAUTH_FAILURE: parse_userauth_failure,
- MSG_USERAUTH_BANNER: parse_userauth_banner,
+ _MSG_SERVICE_REQUEST: parse_service_request,
+ _MSG_SERVICE_ACCEPT: parse_service_accept,
+ _MSG_USERAUTH_REQUEST: parse_userauth_request,
+ _MSG_USERAUTH_SUCCESS: parse_userauth_success,
+ _MSG_USERAUTH_FAILURE: parse_userauth_failure,
+ _MSG_USERAUTH_BANNER: parse_userauth_banner,
})
diff --git a/paramiko/channel.py b/paramiko/channel.py
index 2ac0866b..78d8ef2c 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -1,14 +1,14 @@
from message import Message
from ssh_exception import SSHException
-from transport import MSG_CHANNEL_REQUEST, MSG_CHANNEL_CLOSE, MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, \
- MSG_CHANNEL_EOF, MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE
+from transport import _MSG_CHANNEL_REQUEST, _MSG_CHANNEL_CLOSE, _MSG_CHANNEL_WINDOW_ADJUST, _MSG_CHANNEL_DATA, \
+ _MSG_CHANNEL_EOF, _MSG_CHANNEL_SUCCESS, _MSG_CHANNEL_FAILURE
import time, threading, logging, socket, os
from logging import DEBUG
# this is ugly, and won't work on windows
-def set_nonblocking(fd):
+def _set_nonblocking(fd):
import fcntl
fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
@@ -17,7 +17,7 @@ class Channel(object):
"""
Abstraction for an SSH2 channel.
"""
-
+
def __init__(self, chanid):
self.chanid = chanid
self.transport = None
@@ -35,6 +35,11 @@ class Channel(object):
self.pipe_rfd = self.pipe_wfd = None
def __repr__(self):
+ """
+ Returns a string representation of this object, for debugging.
+
+ @rtype: string
+ """
out = '<paramiko.Channel %d' % self.chanid
if self.closed:
out += ' (closed)'
@@ -50,146 +55,23 @@ class Channel(object):
out += '>'
return out
- def _set_transport(self, transport):
- self.transport = transport
-
- def _log(self, level, msg):
- self.logger.log(level, msg)
-
- def set_window(self, window_size, max_packet_size):
- self.in_window_size = window_size
- self.in_max_packet_size = max_packet_size
- # threshold of bytes we receive before we bother to send a window update
- self.in_window_threshold = window_size // 10
- self.in_window_sofar = 0
-
- def set_remote_channel(self, chanid, window_size, max_packet_size):
- self.remote_chanid = chanid
- self.out_window_size = window_size
- self.out_max_packet_size = max_packet_size
- self.active = 1
-
- def request_success(self, m):
- self._log(DEBUG, 'Sesch channel %d request ok' % self.chanid)
- return
-
- def request_failed(self, m):
- self.close()
-
- def feed(self, m):
- s = m.get_string()
- try:
- self.lock.acquire()
- self._log(DEBUG, 'fed %d bytes' % len(s))
- if self.pipe_wfd != None:
- self.feed_pipe(s)
- else:
- self.in_buffer += s
- self.in_buffer_cv.notifyAll()
- self._log(DEBUG, '(out from feed)')
- finally:
- self.lock.release()
-
- def window_adjust(self, m):
- nbytes = m.get_int()
- try:
- self.lock.acquire()
- self._log(DEBUG, 'window up %d' % nbytes)
- self.out_window_size += nbytes
- self.out_buffer_cv.notifyAll()
- finally:
- self.lock.release()
-
- def check_pty_request(self, term, width, height, pixelwidth, pixelheight, modes):
- "override me! return True if a pty of the given dimensions (for shell access, usually) can be provided"
- return False
-
- def check_shell_request(self):
- "override me! return True if shell access will be provided"
- return False
-
- def check_subsystem_request(self, name):
- "override me! return True if the given subsystem can be provided"
- return False
-
- def check_window_change_request(self, width, height, pixelwidth, pixelheight):
- "override me! return True if the pty was resized"
- return False
-
- def handle_request(self, m):
- key = m.get_string()
- want_reply = m.get_boolean()
- ok = False
- if key == 'exit-status':
- self.exit_status = m.get_int()
- ok = True
- elif key == 'xon-xoff':
- # ignore
- ok = True
- elif key == 'pty-req':
- term = m.get_string()
- width = m.get_int()
- height = m.get_int()
- pixelwidth = m.get_int()
- pixelheight = m.get_int()
- modes = m.get_string()
- ok = self.check_pty_request(term, width, height, pixelwidth, pixelheight, modes)
- elif key == 'shell':
- ok = self.check_shell_request()
- elif key == 'subsystem':
- name = m.get_string()
- ok = self.check_subsystem_request(name)
- elif key == 'window-change':
- width = m.get_int()
- height = m.get_int()
- pixelwidth = m.get_int()
- pixelheight = m.get_int()
- ok = self.check_window_change_request(width, height, pixelwidth, pixelheight)
- else:
- self._log(DEBUG, 'Unhandled channel request "%s"' % key)
- ok = False
- if want_reply:
- m = Message()
- if ok:
- m.add_byte(chr(MSG_CHANNEL_SUCCESS))
- else:
- m.add_byte(chr(MSG_CHANNEL_FAILURE))
- m.add_int(self.remote_chanid)
- self.transport._send_message(m)
-
- def handle_eof(self, m):
- try:
- self.lock.acquire()
- if not self.eof_received:
- self.eof_received = 1
- self.in_buffer_cv.notifyAll()
- if self.pipe_wfd != None:
- os.close(self.pipe_wfd)
- self.pipe_wfd = None
- finally:
- self.lock.release()
- self._log(DEBUG, 'EOF received')
-
- def handle_close(self, m):
- self.close()
- try:
- self.lock.acquire()
- self.in_buffer_cv.notifyAll()
- self.out_buffer_cv.notifyAll()
- if self.pipe_wfd != None:
- os.close(self.pipe_wfd)
- self.pipe_wfd = None
- finally:
- self.lock.release()
-
-
- # API for external use
-
def get_pty(self, term='vt100', width=80, height=24):
+ """
+ Request a pseudo-terminal from the server. This is usually used right
+ after creating a client channel, to ask the server to provide some
+ basic terminal semantics for the next command you execute.
+
+ @param term: the terminal type to emulate (for example, C{'vt100'}).
+ @type term: string
+ @param width: width (in characters) of the terminal screen
+ @type width: int
+ @param height: height (in characters) of the terminal screen
+ @type height: int
+ """
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
m = Message()
- m.add_byte(chr(MSG_CHANNEL_REQUEST))
+ m.add_byte(chr(_MSG_CHANNEL_REQUEST))
m.add_int(self.remote_chanid)
m.add_string('pty-req')
m.add_boolean(0)
@@ -205,7 +87,7 @@ class Channel(object):
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
m = Message()
- m.add_byte(chr(MSG_CHANNEL_REQUEST))
+ m.add_byte(chr(_MSG_CHANNEL_REQUEST))
m.add_int(self.remote_chanid)
m.add_string('shell')
m.add_boolean(1)
@@ -215,7 +97,7 @@ class Channel(object):
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
m = Message()
- m.add_byte(chr(MSG_CHANNEL_REQUEST))
+ m.add_byte(chr(_MSG_CHANNEL_REQUEST))
m.add_int(self.remote_chanid)
m.add_string('exec')
m.add_boolean(1)
@@ -226,7 +108,7 @@ class Channel(object):
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
m = Message()
- m.add_byte(chr(MSG_CHANNEL_REQUEST))
+ m.add_byte(chr(_MSG_CHANNEL_REQUEST))
m.add_int(self.remote_chanid)
m.add_string('subsystem')
m.add_boolean(1)
@@ -234,10 +116,19 @@ class Channel(object):
self.transport._send_message(m)
def resize_pty(self, width=80, height=24):
+ """
+ Resize the pseudo-terminal. This can be used to change the width and
+ height of the terminal emulation created in a previous L{get_pty} call.
+
+ @param width: new width (in characters) of the terminal screen
+ @type width: int
+ @param height: new height (in characters) of the terminal screen
+ @type height: int
+ """
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
m = Message()
- m.add_byte(chr(MSG_CHANNEL_REQUEST))
+ m.add_byte(chr(_MSG_CHANNEL_REQUEST))
m.add_int(self.remote_chanid)
m.add_string('window-change')
m.add_boolean(0)
@@ -247,48 +138,103 @@ class Channel(object):
self.transport._send_message(m)
def get_transport(self):
+ """
+ Return the L{Transport} associated with this channel.
+
+ @return: the L{Transport} that was used to create this channel.
+ @rtype: L{Transport}
+ """
return self.transport
def set_name(self, name):
+ """
+ Set a name for this channel. Currently it's only used to set the name
+ of the log level used for debugging. The name can be fetched with the
+ L{get_name} method.
+
+ @param name: new channel name
+ @type name: string
+ """
self.name = name
self.logger = logging.getLogger('paramiko.chan.' + name)
def get_name(self):
- return self.name
+ """
+ Get the name of this channel that was previously set by L{set_name}.
- def send_eof(self):
- if self.eof_sent:
- return
- m = Message()
- m.add_byte(chr(MSG_CHANNEL_EOF))
- m.add_int(self.remote_chanid)
- self.transport._send_message(m)
- self.eof_sent = 1
- self._log(DEBUG, 'EOF sent')
- return
+ @return: the name of this channel
+ @rtype: string
+ """
+ return self.name
+
+ ### socket API
- # socket equivalency methods...
def settimeout(self, timeout):
+ """
+ Set a timeout on blocking read/write operations. The C{timeout}
+ argument can be a nonnegative float expressing seconds, or C{None}. If
+ a float is given, subsequent channel read/write operations will raise
+ a timeout exception if the timeout period value has elapsed before the
+ operation has completed. Setting a timeout of C{None} disables
+ timeouts on socket operations.
+
+ C{chan.settimeout(0.0)} is equivalent to C{chan.setblocking(0)};
+ C{chan.settimeout(None)} is equivalent to C{chan.setblocking(1)}.
+
+ @param timeout: seconds to wait for a pending read/write operation
+ before raising C{socket.timeout}, or C{None} for no timeout.
+ @type timeout: float
+ """
self.timeout = timeout
def gettimeout(self):
+ """
+ Returns the timeout in seconds (as a float) associated with socket
+ operations, or C{None} if no timeout is set. This reflects the last
+ call to L{setblocking} or L{settimeout}.
+
+ @return: timeout in seconds, or C{None}.
+ @rtype: float
+ """
return self.timeout
def setblocking(self, blocking):
+ """
+ Set blocking or non-blocking mode of the channel: if C{blocking} is 0,
+ the channel is set to non-blocking mode; otherwise it's set to blocking
+ mode. Initially all channels are in blocking mode.
+
+ In non-blocking mode, if a L{recv} call doesn't find any data, or if a
+ L{send} call can't immediately dispose of the data, an error exception
+ is raised. In blocking mode, the calls block until they can proceed.
+
+ C{chan.setblocking(0)} is equivalent to C{chan.settimeout(0)};
+ C{chan.setblocking(1)} is equivalent to C{chan.settimeout(None)}.
+
+ @param blocking: 0 to set non-blocking mode; non-0 to set blocking
+ mode.
+ @type blocking: int
+ """
if blocking:
self.settimeout(None)
else:
self.settimeout(0.0)
def close(self):
+ """
+ Close the channel. All future read/write operations on the channel
+ will fail. The remote end will receive no more data (after queued data
+ is flushed). Channels are automatically closed when they are garbage-
+ collected, or when their L{Transport} is closed.
+ """
try:
self.lock.acquire()
if self.active and not self.closed:
- self.send_eof()
+ self._send_eof()
m = Message()
- m.add_byte(chr(MSG_CHANNEL_CLOSE))
+ m.add_byte(chr(_MSG_CHANNEL_CLOSE))
m.add_int(self.remote_chanid)
self.transport._send_message(m)
self.closed = 1
@@ -297,22 +243,44 @@ class Channel(object):
self.lock.release()
def recv_ready(self):
- "doesn't work if you've called fileno()"
+ """
+ Returns true if data is ready to be read from this channel.
+
+ @return: C{True} if a L{recv} call on this channel would immediately
+ return at least one byte; C{False} otherwise.
+ @rtype: boolean
+
+ @note: This method doesn't work if you've called L{fileno}.
+ """
try:
self.lock.acquire()
if len(self.in_buffer) == 0:
- return 0
- return 1
+ return False
+ return True
finally:
self.lock.release()
def recv(self, nbytes):
+ """
+ Receive data from the channel. The return value is a string
+ representing the data received. The maximum amount of data to be
+ received at once is specified by C{nbytes}. If a string of length zero
+ is returned, the channel stream has closed.
+
+ @param nbytes: maximum number of bytes to read.
+ @type nbytes: int
+ @return: data
+ @rtype: string
+
+ @raise socket.timeout: if no data is ready before the timeout set by
+ L{settimeout}.
+ """
out = ''
try:
self.lock.acquire()
if self.pipe_rfd != None:
# use the pipe
- return self.read_pipe(nbytes)
+ return self._read_pipe(nbytes)
if len(self.in_buffer) == 0:
if self.closed or self.eof_received:
return out
@@ -335,12 +303,27 @@ class Channel(object):
else:
out = self.in_buffer[:nbytes]
self.in_buffer = self.in_buffer[nbytes:]
- self.check_add_window(len(out))
+ self._check_add_window(len(out))
finally:
self.lock.release()
return out
def send(self, s):
+ """
+ Send data to the channel. Returns the number of bytes sent, or 0 if
+ the channel stream is closed. Applications are responsible for
+ checking that all data has been sent: if only some of the data was
+ transmitted, the application needs to attempt delivery of the remaining
+ data.
+
+ @param s: data to send.
+ @type s: string
+ @return: number of bytes actually sent.
+ @rtype: int
+
+ @raise socket.timeout: if no data could be sent before the timeout set
+ by L{settimeout}.
+ """
size = 0
if self.closed or self.eof_sent:
return size
@@ -368,7 +351,7 @@ class Channel(object):
if self.out_max_packet_size < size:
size = self.out_max_packet_size
m = Message()
- m.add_byte(chr(MSG_CHANNEL_DATA))
+ m.add_byte(chr(_MSG_CHANNEL_DATA))
m.add_int(self.remote_chanid)
m.add_string(s[:size])
self.transport._send_message(m)
@@ -378,6 +361,23 @@ class Channel(object):
return size
def sendall(self, s):
+ """
+ Send data to the channel, without allowing partial results. Unlike
+ L{send}, this method continues to send data from the given string until
+ either all data has been sent or an error occurs. Nothing is returned.
+
+ @param s: data to send.
+ @type s: string
+
+ @raise socket.timeout: if sending stalled for longer than the timeout
+ set by L{settimeout}.
+ @raise socket.error: if an error occured before the entire string was
+ sent.
+
+ @note: If the channel is closed while only part of the data hase been
+ sent, there is no way to determine how much data (if any) was sent.
+ This is irritating, but identically follows python's API.
+ """
while s:
if self.closed:
# this doesn't seem useful, but it is the documented behavior of Socket
@@ -387,20 +387,37 @@ class Channel(object):
return None
def makefile(self, *params):
+ """
+ Return a file-like object associated with this channel, without the
+ non-portable side effects of L{fileno}. The optional C{mode} and
+ C{bufsize} arguments are interpreted the same way as by the built-in
+ C{file()} function in python.
+
+ @return: object which can be used for python file I/O.
+ @rtype: L{ChannelFile}
+ """
return ChannelFile(*([self] + list(params)))
def fileno(self):
"""
- returns an OS-level fd which can be used for polling and reading (but
- NOT for writing). this is primarily to allow python's \"select\" module
- to work. the first time this function is called, a pipe is created to
- simulate real OS-level fd behavior. because of this, two actual fds are
- created: one to return and one to feed. this may be inefficient if you
- plan to use many fds.
+ Returns an OS-level file descriptor which can be used for polling and
+ reading (but I{not} for writing). This is primaily to allow python's
+ C{select} module to work.
+
+ The first time C{fileno} is called on a channel, a pipe is created to
+ simulate real OS-level file descriptor (FD) behavior. Because of this,
+ two actual FDs are created -- this may be inefficient if you plan to
+ use many channels.
- the channel's receive window will be updated as data comes in, not as
- you read it, so if you fail to poll the channel often enough, it may
- block ALL channels across the transport.
+ @return: a small integer file descriptor
+ @rtype: int
+
+ @warning: This method causes several aspects of the channel to change
+ behavior. It is always more efficient to avoid using this method.
+
+ @bug: This does not work on Windows. The problem is that pipes are
+ used to simulate an open FD, but I haven't figured out how to make
+ pipes enter non-blocking mode on Windows yet.
"""
try:
self.lock.acquire()
@@ -408,27 +425,189 @@ class Channel(object):
return self.pipe_rfd
# create the pipe and feed in any existing data
self.pipe_rfd, self.pipe_wfd = os.pipe()
- set_nonblocking(self.pipe_wfd)
- set_nonblocking(self.pipe_rfd)
+ _set_nonblocking(self.pipe_wfd)
+ _set_nonblocking(self.pipe_rfd)
if len(self.in_buffer) > 0:
x = self.in_buffer
self.in_buffer = ''
- self.feed_pipe(x)
+ self._feed_pipe(x)
return self.pipe_rfd
finally:
self.lock.release()
def shutdown(self, how):
+ """
+ Shut down one or both halves of the connection. If C{how} is 0,
+ further receives are disallowed. If C{how} is 1, further sends
+ are disallowed. If C{how} is 2, further sends and receives are
+ disallowed. This closes the stream in one or both directions.
+
+ @param how: 0 (stop receiving), 1 (stop sending), or 2 (stop
+ receiving and sending).
+ @type how: int
+ """
if (how == 0) or (how == 2):
# feign "read" shutdown
self.eof_received = 1
if (how == 1) or (how == 2):
- self.send_eof()
+ self._send_eof()
+
+
+ ### overrides
+
+
+ def check_pty_request(self, term, width, height, pixelwidth, pixelheight, modes):
+ "override me! return True if a pty of the given dimensions (for shell access, usually) can be provided"
+ return False
+
+ def check_shell_request(self):
+ "override me! return True if shell access will be provided"
+ return False
+ def check_subsystem_request(self, name):
+ "override me! return True if the given subsystem can be provided"
+ return False
- # internal use...
+ def check_window_change_request(self, width, height, pixelwidth, pixelheight):
+ "override me! return True if the pty was resized"
+ return False
+
+
+ ### calls from Transport
+
+
+ def _set_transport(self, transport):
+ self.transport = transport
+
+ def _set_window(self, window_size, max_packet_size):
+ self.in_window_size = window_size
+ self.in_max_packet_size = max_packet_size
+ # threshold of bytes we receive before we bother to send a window update
+ self.in_window_threshold = window_size // 10
+ self.in_window_sofar = 0
+
+ def _set_remote_channel(self, chanid, window_size, max_packet_size):
+ self.remote_chanid = chanid
+ self.out_window_size = window_size
+ self.out_max_packet_size = max_packet_size
+ self.active = 1
+
+ def _request_success(self, m):
+ self._log(DEBUG, 'Sesch channel %d request ok' % self.chanid)
+ return
+
+ def _request_failed(self, m):
+ self.close()
+
+ def _feed(self, m):
+ s = m.get_string()
+ try:
+ self.lock.acquire()
+ self._log(DEBUG, 'fed %d bytes' % len(s))
+ if self.pipe_wfd != None:
+ self._feed_pipe(s)
+ else:
+ self.in_buffer += s
+ self.in_buffer_cv.notifyAll()
+ self._log(DEBUG, '(out from feed)')
+ finally:
+ self.lock.release()
+
+ def _window_adjust(self, m):
+ nbytes = m.get_int()
+ try:
+ self.lock.acquire()
+ self._log(DEBUG, 'window up %d' % nbytes)
+ self.out_window_size += nbytes
+ self.out_buffer_cv.notifyAll()
+ finally:
+ self.lock.release()
+
+ def _handle_request(self, m):
+ key = m.get_string()
+ want_reply = m.get_boolean()
+ ok = False
+ if key == 'exit-status':
+ self.exit_status = m.get_int()
+ ok = True
+ elif key == 'xon-xoff':
+ # ignore
+ ok = True
+ elif key == 'pty-req':
+ term = m.get_string()
+ width = m.get_int()
+ height = m.get_int()
+ pixelwidth = m.get_int()
+ pixelheight = m.get_int()
+ modes = m.get_string()
+ ok = self.check_pty_request(term, width, height, pixelwidth, pixelheight, modes)
+ elif key == 'shell':
+ ok = self.check_shell_request()
+ elif key == 'subsystem':
+ name = m.get_string()
+ ok = self.check_subsystem_request(name)
+ elif key == 'window-change':
+ width = m.get_int()
+ height = m.get_int()
+ pixelwidth = m.get_int()
+ pixelheight = m.get_int()
+ ok = self.check_window_change_request(width, height, pixelwidth, pixelheight)
+ else:
+ self._log(DEBUG, 'Unhandled channel request "%s"' % key)
+ ok = False
+ if want_reply:
+ m = Message()
+ if ok:
+ m.add_byte(chr(_MSG_CHANNEL_SUCCESS))
+ else:
+ m.add_byte(chr(_MSG_CHANNEL_FAILURE))
+ m.add_int(self.remote_chanid)
+ self.transport._send_message(m)
+
+ def _handle_eof(self, m):
+ try:
+ self.lock.acquire()
+ if not self.eof_received:
+ self.eof_received = 1
+ self.in_buffer_cv.notifyAll()
+ if self.pipe_wfd != None:
+ os.close(self.pipe_wfd)
+ self.pipe_wfd = None
+ finally:
+ self.lock.release()
+ self._log(DEBUG, 'EOF received')
+
+ def _handle_close(self, m):
+ self.close()
+ try:
+ self.lock.acquire()
+ self.in_buffer_cv.notifyAll()
+ self.out_buffer_cv.notifyAll()
+ if self.pipe_wfd != None:
+ os.close(self.pipe_wfd)
+ self.pipe_wfd = None
+ finally:
+ self.lock.release()
+
+
+ ### internals...
+
+
+ def _log(self, level, msg):
+ self.logger.log(level, msg)
+
+ def _send_eof(self):
+ if self.eof_sent:
+ return
+ m = Message()
+ m.add_byte(chr(_MSG_CHANNEL_EOF))
+ m.add_int(self.remote_chanid)
+ self.transport._send_message(m)
+ self.eof_sent = 1
+ self._log(DEBUG, 'EOF sent')
+ return
- def feed_pipe(self, data):
+ def _feed_pipe(self, data):
"you are already holding the lock"
if len(self.in_buffer) > 0:
self.in_buffer += data
@@ -439,7 +618,7 @@ class Channel(object):
# at least on linux, this will never happen, as the writes are
# considered atomic... but just in case.
self.in_buffer = data[n:]
- self.check_add_window(n)
+ self._check_add_window(n)
self.in_buffer_cv.notifyAll()
return
except OSError, e:
@@ -451,7 +630,7 @@ class Channel(object):
try:
os.write(self.pipe_wfd, x)
self.in_buffer = data
- self.check_add_window(1)
+ self._check_add_window(1)
self.in_buffer_cv.notifyAll()
return
except OSError, e:
@@ -460,12 +639,12 @@ class Channel(object):
self.in_buffer = data
self.in_buffer_cv.notifyAll()
- def read_pipe(self, nbytes):
+ def _read_pipe(self, nbytes):
"you are already holding the lock"
try:
x = os.read(self.pipe_rfd, nbytes)
if len(x) > 0:
- self.push_pipe(len(x))
+ self._push_pipe(len(x))
return x
except OSError, e:
pass
@@ -487,13 +666,13 @@ class Channel(object):
try:
x = os.read(self.pipe_rfd, nbytes)
if len(x) > 0:
- self.push_pipe(len(x))
+ self._push_pipe(len(x))
return x
except OSError, e:
pass
pass
- def push_pipe(self, nbytes):
+ def _push_pipe(self, nbytes):
# successfully read N bytes from the pipe, now re-feed the pipe if necessary
# (assumption: the pipe can hold as many bytes as were read out)
if len(self.in_buffer) == 0:
@@ -512,7 +691,7 @@ class Channel(object):
self.closed = 1
self.transport._unlink_channel(self.chanid)
- def check_add_window(self, n):
+ def _check_add_window(self, n):
# already holding the lock!
if self.closed or self.eof_received or not self.active:
return
@@ -521,7 +700,7 @@ class Channel(object):
if self.in_window_sofar > self.in_window_threshold:
self._log(DEBUG, 'addwindow send %d' % self.in_window_sofar)
m = Message()
- m.add_byte(chr(MSG_CHANNEL_WINDOW_ADJUST))
+ m.add_byte(chr(_MSG_CHANNEL_WINDOW_ADJUST))
m.add_int(self.remote_chanid)
m.add_int(self.in_window_sofar)
self.transport._send_message(m)
diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py
index 35d8a8d1..3eca8589 100644
--- a/paramiko/dsskey.py
+++ b/paramiko/dsskey.py
@@ -3,16 +3,16 @@
import base64
from ssh_exception import SSHException
from message import Message
-from transport import MSG_USERAUTH_REQUEST
+from transport import _MSG_USERAUTH_REQUEST
from util import inflate_long, deflate_long
from Crypto.PublicKey import DSA
-from Crypto.Hash import SHA, MD5
+from Crypto.Hash import SHA
from ber import BER
+from pkey import PKey
from util import format_binary
-
-class DSSKey(object):
+class DSSKey (PKey):
def __init__(self, msg=None):
self.valid = 0
@@ -39,9 +39,6 @@ class DSSKey(object):
def get_name(self):
return 'ssh-dss'
- def get_fingerprint(self):
- return MD5.new(str(self)).digest()
-
def verify_ssh_sig(self, data, msg):
if not self.valid:
return 0
@@ -78,7 +75,6 @@ class DSSKey(object):
return str(m)
def read_private_key_file(self, filename):
- "throws a file exception, or SSHException (on invalid key, or base64 decoding exception"
# private key file contains:
# DSAPrivateKey = { version = 0, p, q, g, y, x }
self.valid = 0
@@ -102,7 +98,7 @@ class DSSKey(object):
def sign_ssh_session(self, randpool, sid, username):
m = Message()
m.add_string(sid)
- m.add_byte(chr(MSG_USERAUTH_REQUEST))
+ m.add_byte(chr(_MSG_USERAUTH_REQUEST))
m.add_string(username)
m.add_string('ssh-connection')
m.add_string('publickey')
diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py
index 5e02bab7..f14c7864 100644
--- a/paramiko/kex_gex.py
+++ b/paramiko/kex_gex.py
@@ -7,12 +7,12 @@
from message import Message
from util import inflate_long, deflate_long, bit_length
from ssh_exception import SSHException
-from transport import MSG_NEWKEYS
+from transport import _MSG_NEWKEYS
from Crypto.Hash import SHA
from Crypto.Util import number
from logging import DEBUG
-MSG_KEXDH_GEX_GROUP, MSG_KEXDH_GEX_INIT, MSG_KEXDH_GEX_REPLY, MSG_KEXDH_GEX_REQUEST = range(31, 35)
+_MSG_KEXDH_GEX_GROUP, _MSG_KEXDH_GEX_INIT, _MSG_KEXDH_GEX_REPLY, _MSG_KEXDH_GEX_REQUEST = range(31, 35)
class KexGex(object):
@@ -27,27 +27,27 @@ class KexGex(object):
def start_kex(self):
if self.transport.server_mode:
- self.transport._expect_packet(MSG_KEXDH_GEX_REQUEST)
+ self.transport._expect_packet(_MSG_KEXDH_GEX_REQUEST)
return
# request a bit range: we accept (min_bits) to (max_bits), but prefer
# (preferred_bits). according to the spec, we shouldn't pull the
# minimum up above 1024.
m = Message()
- m.add_byte(chr(MSG_KEXDH_GEX_REQUEST))
+ m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST))
m.add_int(self.min_bits)
m.add_int(self.preferred_bits)
m.add_int(self.max_bits)
self.transport._send_message(m)
- self.transport._expect_packet(MSG_KEXDH_GEX_GROUP)
+ self.transport._expect_packet(_MSG_KEXDH_GEX_GROUP)
def parse_next(self, ptype, m):
- if ptype == MSG_KEXDH_GEX_REQUEST:
+ if ptype == _MSG_KEXDH_GEX_REQUEST:
return self._parse_kexdh_gex_request(m)
- elif ptype == MSG_KEXDH_GEX_GROUP:
+ elif ptype == _MSG_KEXDH_GEX_GROUP:
return self._parse_kexdh_gex_group(m)
- elif ptype == MSG_KEXDH_GEX_INIT:
+ elif ptype == _MSG_KEXDH_GEX_INIT:
return self._parse_kexdh_gex_init(m)
- elif ptype == MSG_KEXDH_GEX_REPLY:
+ elif ptype == _MSG_KEXDH_GEX_REPLY:
return self._parse_kexdh_gex_reply(m)
raise SSHException('KexGex asked to handle packet type %d' % ptype)
@@ -96,11 +96,11 @@ class KexGex(object):
raise SSHException('Can\'t do server-side gex with no modulus pack')
self.g, self.p = pack.get_modulus(min, preferred, max)
m = Message()
- m.add_byte(chr(MSG_KEXDH_GEX_GROUP))
+ m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
m.add_mpint(self.p)
m.add_mpint(self.g)
self.transport._send_message(m)
- self.transport._expect_packet(MSG_KEXDH_GEX_INIT)
+ self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
def _parse_kexdh_gex_group(self, m):
self.p = m.get_mpint()
@@ -114,10 +114,10 @@ class KexGex(object):
# now compute e = g^x mod p
self.e = pow(self.g, self.x, self.p)
m = Message()
- m.add_byte(chr(MSG_KEXDH_GEX_INIT))
+ m.add_byte(chr(_MSG_KEXDH_GEX_INIT))
m.add_mpint(self.e)
self.transport._send_message(m)
- self.transport._expect_packet(MSG_KEXDH_GEX_REPLY)
+ self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY)
def _parse_kexdh_gex_init(self, m):
self.e = m.get_mpint()
@@ -142,7 +142,7 @@ class KexGex(object):
sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H)
# send reply
m = Message()
- m.add_byte(chr(MSG_KEXDH_GEX_REPLY))
+ m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
m.add_string(key)
m.add_mpint(self.f)
m.add_string(sig)
diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py
index de1a2546..31123269 100644
--- a/paramiko/kex_group1.py
+++ b/paramiko/kex_group1.py
@@ -6,11 +6,11 @@
from message import Message, inflate_long
from ssh_exception import SSHException
-from transport import MSG_NEWKEYS
+from transport import _MSG_NEWKEYS
from Crypto.Hash import SHA
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
-MSG_KEXDH_INIT, MSG_KEXDH_REPLY = range(30, 32)
+_MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32)
# draft-ietf-secsh-transport-09.txt, page 17
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFFL
@@ -44,20 +44,20 @@ class KexGroup1(object):
if self.transport.server_mode:
# compute f = g^x mod p, but don't send it yet
self.f = pow(G, self.x, P)
- self.transport._expect_packet(MSG_KEXDH_INIT)
+ self.transport._expect_packet(_MSG_KEXDH_INIT)
return
# compute e = g^x mod p (where g=2), and send it
self.e = pow(G, self.x, P)
m = Message()
- m.add_byte(chr(MSG_KEXDH_INIT))
+ m.add_byte(chr(_MSG_KEXDH_INIT))
m.add_mpint(self.e)
self.transport._send_message(m)
- self.transport._expect_packet(MSG_KEXDH_REPLY)
+ self.transport._expect_packet(_MSG_KEXDH_REPLY)
def parse_next(self, ptype, m):
- if self.transport.server_mode and (ptype == MSG_KEXDH_INIT):
+ if self.transport.server_mode and (ptype == _MSG_KEXDH_INIT):
return self.parse_kexdh_init(m)
- elif not self.transport.server_mode and (ptype == MSG_KEXDH_REPLY):
+ elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY):
return self.parse_kexdh_reply(m)
raise SSHException('KexGroup1 asked to handle packet type %d' % ptype)
@@ -94,7 +94,7 @@ class KexGroup1(object):
sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H)
# send reply
m = Message()
- m.add_byte(chr(MSG_KEXDH_REPLY))
+ m.add_byte(chr(_MSG_KEXDH_REPLY))
m.add_string(key)
m.add_mpint(self.f)
m.add_string(sig)
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
new file mode 100644
index 00000000..c4c7599d
--- /dev/null
+++ b/paramiko/pkey.py
@@ -0,0 +1,112 @@
+
+from Crypto.Hash import MD5
+from message import Message
+
+class PKey (object):
+ """
+ Base class for public keys.
+ """
+
+ def __init__(self, msg=None):
+ """
+ Create a new instance of this public key type. If C{msg} is not
+ C{None}, the key's public part(s) will be filled in from the
+ message.
+
+ @param msg: an optional SSH L{Message} containing a public key of this
+ type.
+ @type msg: L{Message}
+ """
+ pass
+
+ def __str__(self):
+ """
+ Return a string of an SSH L{Message} made up of the public part(s) of
+ this key.
+
+ @return: string representation of an SSH key message.
+ @rtype: string
+ """
+ return ''
+
+ def get_name(self):
+ """
+ Return the name of this private key implementation.
+
+ @return: name of this private key type, in SSH terminology (for
+ example, C{"ssh-rsa"}).
+ @rtype: string
+ """
+ return ''
+
+ def get_fingerprint(self):
+ """
+ Return an MD5 fingerprint of the public part of this key. Nothing
+ secret is revealed.
+
+ @return: a 16-byte string (binary) of the MD5 fingerprint, in SSH
+ format.
+ @rtype: string
+ """
+ return MD5.new(str(self)).digest()
+
+ def verify_ssh_sig(self, data, msg):
+ """
+ Given a blob of data, and an SSH message representing a signature of
+ that data, verify that it was signed with this key.
+
+ @param data: the data that was signed.
+ @type data: string
+ @param msg: an SSH signature message
+ @type msg: L{Message}
+ @return: C{True} if the signature verifies correctly; C{False}
+ otherwise.
+ @rtype: boolean
+ """
+ return False
+
+ def sign_ssh_data(self, randpool, data):
+ """
+ Sign a blob of data with this private key, and return a string
+ representing an SSH signature message.
+
+ @bug: It would be cleaner for this method to return a L{Message}
+ object, so it would be complementary to L{verify_ssh_sig}. FIXME.
+
+ @param randpool: a secure random number generator.
+ @type randpool: L{Crypto.Util.randpool.RandomPool}
+ @param data: the data to sign.
+ @type data: string
+ @return: string representation of an SSH signature message.
+ @rtype: string
+ """
+ return ''
+
+ def read_private_key_file(self, filename):
+ """
+ Read private key contents from a file into this object.
+
+ @param filename: name of the file to read.
+ @type filename: string
+
+ @raise IOError: if there was an error reading the file.
+ @raise SSHException: if the key file is invalid
+ @raise binascii.Error: on base64 decoding error
+ """
+ pass
+
+ def sign_ssh_session(self, randpool, sid, username):
+ """
+ Sign an SSH authentication request.
+
+ @bug: Same as L{sign_ssh_data}
+
+ @param randpool: a secure random number generator.
+ @type randpool: L{Crypto.Util.randpool.RandomPool}
+ @param sid: the session ID given by the server
+ @type sid: string
+ @param username: the username to use in the authentication request
+ @type username: string
+ @return: string representation of an SSH signature message.
+ @rtype: string
+ """
diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py
index 74502aa2..e0935d5d 100644
--- a/paramiko/rsakey.py
+++ b/paramiko/rsakey.py
@@ -1,14 +1,15 @@
#!/usr/bin/python
from message import Message
-from transport import MSG_USERAUTH_REQUEST
+from transport import _MSG_USERAUTH_REQUEST
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA
from ber import BER
from util import format_binary, inflate_long, deflate_long
+from pkey import PKey
import base64
-class RSAKey(object):
+class RSAKey (PKey):
def __init__(self, msg=None):
self.valid = 0
@@ -31,10 +32,7 @@ class RSAKey(object):
def get_name(self):
return 'ssh-rsa'
- def get_fingerprint(self):
- return MD5.new(str(self)).digest()
-
- def pkcs1imify(self, data):
+ def _pkcs1imify(self, data):
"""
turn a 20-byte SHA1 hash into a blob of data as large as the key's N,
using PKCS1's \"emsa-pkcs1-v1_5\" encoding. totally bizarre.
@@ -45,26 +43,25 @@ class RSAKey(object):
def verify_ssh_sig(self, data, msg):
if (not self.valid) or (msg.get_string() != 'ssh-rsa'):
- return 0
+ return False
sig = inflate_long(msg.get_string(), 1)
# verify the signature by SHA'ing the data and encrypting it using the
# public key. some wackiness ensues where we "pkcs1imify" the 20-byte
# hash into a string as long as the RSA key.
- hash = inflate_long(self.pkcs1imify(SHA.new(data).digest()), 1)
+ hash = inflate_long(self._pkcs1imify(SHA.new(data).digest()), 1)
rsa = RSA.construct((long(self.n), long(self.e)))
return rsa.verify(hash, (sig,))
def sign_ssh_data(self, randpool, data):
hash = SHA.new(data).digest()
rsa = RSA.construct((long(self.n), long(self.e), long(self.d)))
- sig = deflate_long(rsa.sign(self.pkcs1imify(hash), '')[0], 0)
+ sig = deflate_long(rsa.sign(self._pkcs1imify(hash), '')[0], 0)
m = Message()
m.add_string('ssh-rsa')
m.add_string(sig)
return str(m)
def read_private_key_file(self, filename):
- "throws a file exception, or SSHException (on invalid key), or base64 decoding exception"
# private key file contains:
# RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p }
self.valid = 0
@@ -72,11 +69,11 @@ class RSAKey(object):
lines = f.readlines()
f.close()
if lines[0].strip() != '-----BEGIN RSA PRIVATE KEY-----':
- raise SSHException('not a valid DSA private key file')
+ raise SSHException('not a valid RSA private key file')
data = base64.decodestring(''.join(lines[1:-1]))
keylist = BER(data).decode()
if (type(keylist) != type([])) or (len(keylist) < 4) or (keylist[0] != 0):
- raise SSHException('not a valid DSA private key file (bad ber encoding)')
+ raise SSHException('not a valid RSA private key file (bad ber encoding)')
self.n = keylist[1]
self.e = keylist[2]
self.d = keylist[3]
@@ -89,7 +86,7 @@ class RSAKey(object):
def sign_ssh_session(self, randpool, sid, username):
m = Message()
m.add_string(sid)
- m.add_byte(chr(MSG_USERAUTH_REQUEST))
+ m.add_byte(chr(_MSG_USERAUTH_REQUEST))
m.add_string(username)
m.add_string('ssh-connection')
m.add_string('publickey')
diff --git a/paramiko/transport.py b/paramiko/transport.py
index f982d78f..1a42fb76 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -1,15 +1,15 @@
#!/usr/bin/python
-MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, MSG_SERVICE_REQUEST, \
- MSG_SERVICE_ACCEPT = range(1, 7)
-MSG_KEXINIT, MSG_NEWKEYS = range(20, 22)
-MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, MSG_USERAUTH_SUCCESS, \
- MSG_USERAUTH_BANNER = range(50, 54)
-MSG_USERAUTH_PK_OK = 60
-MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
- MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \
- MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST, \
- MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101)
+_MSG_DISCONNECT, _MSG_IGNORE, _MSG_UNIMPLEMENTED, _MSG_DEBUG, _MSG_SERVICE_REQUEST, \
+ _MSG_SERVICE_ACCEPT = range(1, 7)
+_MSG_KEXINIT, _MSG_NEWKEYS = range(20, 22)
+_MSG_USERAUTH_REQUEST, _MSG_USERAUTH_FAILURE, _MSG_USERAUTH_SUCCESS, \
+ _MSG_USERAUTH_BANNER = range(50, 54)
+_MSG_USERAUTH_PK_OK = 60
+_MSG_CHANNEL_OPEN, _MSG_CHANNEL_OPEN_SUCCESS, _MSG_CHANNEL_OPEN_FAILURE, \
+ _MSG_CHANNEL_WINDOW_ADJUST, _MSG_CHANNEL_DATA, _MSG_CHANNEL_EXTENDED_DATA, \
+ _MSG_CHANNEL_EOF, _MSG_CHANNEL_CLOSE, _MSG_CHANNEL_REQUEST, \
+ _MSG_CHANNEL_SUCCESS, _MSG_CHANNEL_FAILURE = range(90, 101)
import sys, os, string, threading, socket, logging, struct
from ssh_exception import SSHException
@@ -36,7 +36,7 @@ from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
# channel request failed reasons:
-CONNECTION_FAILED_CODE = {
+_CONNECTION_FAILED_CODE = {
1: 'Administratively prohibited',
2: 'Connect failed',
3: 'Unknown channel type',
@@ -54,29 +54,14 @@ except:
randpool.randomize()
-
-class BaseTransport(threading.Thread):
- '''
- An SSH Transport attaches to a stream (usually a socket), negotiates an
- encrypted session, authenticates, and then creates stream tunnels, called
- "channels", across the session. Multiple channels can be multiplexed
- across a single session (and often are, in the case of port forwardings).
-
- Transport expects to receive a "socket-like object" to talk to the SSH
- server. This means it has a method "settimeout" which sets a timeout for
- read/write calls, and a method "send()" to write bytes and "recv()" to
- read bytes. "recv" returns from 1 to n bytes, or 0 if the stream has been
- closed. EOFError may also be raised on a closed stream. (A return value
- of 0 is converted to an EOFError internally.) "send(s)" writes from 1 to
- len(s) bytes, and returns the number of bytes written, or returns 0 if the
- stream has been closed. As with instream, EOFError may be raised instead
- of returning 0.
-
- FIXME: Describe events here.
- '''
-
- PROTO_ID = '2.0'
- CLIENT_ID = 'pyssh_1.1'
+class BaseTransport (threading.Thread):
+ """
+ Handles protocol negotiation, key exchange, encryption, and the creation
+ of channels across an SSH session. Basically everything but authentication
+ is done here.
+ """
+ _PROTO_ID = '2.0'
+ _CLIENT_ID = 'pyssh_1.1'
preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ]
preferred_macs = [ 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ]
@@ -108,13 +93,34 @@ class BaseTransport(threading.Thread):
OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, \
OPEN_FAILED_RESOURCE_SHORTAGE = range(1, 5)
+ _modulus_pack = None
+
def __init__(self, sock):
+ """
+ Create a new SSH session over an existing socket, or socket-like
+ object. This only creates the Transport object; it doesn't begin the
+ SSH session yet. Use L{connect} or L{start_client} to begin a client
+ session, or L{start_server} to begin a server session.
+
+ 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.
+ - C{recv(int)}: Reads from 1 to C{int} bytes and returns them as a
+ string. Returns 0 or raises C{EOFError} if the stream has been
+ closed.
+
+ @param sock: a socket or socket-like object to create the session over.
+ @type sock: socket
+ """
threading.Thread.__init__(self, target=self._run)
self.randpool = randpool
self.sock = sock
self.sock.settimeout(0.1)
# 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.block_size_out = self.block_size_in = 8
self.local_mac_len = self.remote_mac_len = 0
@@ -136,7 +142,7 @@ class BaseTransport(threading.Thread):
self.window_size = 65536
self.max_packet_size = 2048
self.ultra_debug = 0
- self.modulus_pack = None
+ self.saved_exception = None
# used for noticing when to re-key:
self.received_bytes = 0
self.received_packets = 0
@@ -159,6 +165,17 @@ class BaseTransport(threading.Thread):
self.start()
def add_server_key(self, key):
+ """
+ Add a host key to the list of keys used for server mode. When behaving
+ as a server, the host key is used to sign certain packets during the
+ SSH2 negotiation, so that the client can trust that we are who we say
+ we are. Because this is used for signing, the key must contain private
+ key info, not just the public half.
+
+ @param key: the host key to add, usually an L{RSAKey <rsakey.RSAKey>} or
+ L{DSSKey <dsskey.DSSKey>}.
+ @type key: L{PKey <pkey.PKey>}
+ """
self.server_key_dict[key.get_name()] = key
def get_server_key(self):
@@ -167,7 +184,7 @@ class BaseTransport(threading.Thread):
except KeyError:
return None
- def load_server_moduli(self, filename=None):
+ def load_server_moduli(filename=None):
"""
I{(optional)}
Load a file of prime moduli for use in doing group-exchange key
@@ -195,26 +212,32 @@ class BaseTransport(threading.Thread):
@note: This has no effect when used in client mode.
"""
- self.modulus_pack = ModulusPack(self.randpool)
+ BaseTransport._modulus_pack = ModulusPack(randpool)
# places to look for the openssh "moduli" file
file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
if filename is not None:
file_list.insert(0, filename)
for fn in file_list:
try:
- self.modulus_pack.read_file(fn)
+ BaseTransport._modulus_pack.read_file(fn)
return True
except IOError:
pass
# none succeeded
- self.modulus_pack = None
+ BaseTransport._modulus_pack = None
return False
+ load_server_moduli = staticmethod(load_server_moduli)
def _get_modulus_pack(self):
"used by KexGex to find primes for group exchange"
- return self.modulus_pack
+ return self._modulus_pack
def __repr__(self):
+ """
+ Returns a string representation of this object, for debugging.
+
+ @rtype: string
+ """
if not self.active:
return '<paramiko.BaseTransport (unconnected)>'
out = '<paramiko.BaseTransport'
@@ -285,7 +308,7 @@ class BaseTransport(threading.Thread):
chanid = self.channel_counter
self.channel_counter += 1
m = Message()
- m.add_byte(chr(MSG_CHANNEL_OPEN))
+ m.add_byte(chr(_MSG_CHANNEL_OPEN))
m.add_string(kind)
m.add_int(chanid)
m.add_int(self.window_size)
@@ -293,7 +316,7 @@ class BaseTransport(threading.Thread):
self.channels[chanid] = chan = Channel(chanid)
self.channel_events[chanid] = event = threading.Event()
chan._set_transport(self)
- chan.set_window(self.window_size, self.max_packet_size)
+ chan._set_window(self.window_size, self.max_packet_size)
self._send_message(m)
finally:
self.lock.release()
@@ -310,7 +333,141 @@ class BaseTransport(threading.Thread):
finally:
self.lock.release()
return chan
+
+ def renegotiate_keys(self):
+ """
+ Force this session to switch to new keys. Normally this is done
+ automatically after the session hits a certain number of packets or
+ bytes sent or received, but this method gives you the option of forcing
+ new keys whenever you want. Negotiating new keys causes a pause in
+ traffic both ways as the two sides swap keys and do computations. This
+ method returns when the session has switched to new keys, or the
+ session has died mid-negotiation.
+
+ @return: True if the renegotiation was successful, and the link is
+ using new keys; False if the session dropped during renegotiation.
+ @rtype: boolean
+ """
+ self.completion_event = threading.Event()
+ self._send_kex_init()
+ while 1:
+ self.completion_event.wait(0.1);
+ if not self.active:
+ return False
+ if self.completion_event.isSet():
+ break
+ return True
+
+ def check_channel_request(self, kind, chanid):
+ "override me! return object descended from Channel to allow, or None to reject"
+ return None
+
+ def accept(self, timeout=None):
+ try:
+ self.lock.acquire()
+ if len(self.server_accepts) > 0:
+ chan = self.server_accepts.pop(0)
+ else:
+ self.server_accept_cv.wait(timeout)
+ if len(self.server_accepts) > 0:
+ chan = self.server_accepts.pop(0)
+ else:
+ # timeout
+ chan = None
+ finally:
+ self.lock.release()
+ return chan
+
+ def connect(self, hostkeytype=None, hostkey=None, username='', password=None, pkey=None):
+ """
+ Negotiate an SSH2 session, and optionally verify the server's host key
+ and authenticate using a password or private key. This is a shortcut
+ for L{start_client}, L{get_remote_server_key}, and
+ L{Transport.auth_password} or L{Transport.auth_key}. Use those methods
+ if you want more control.
+
+ You can use this method immediately after creating a Transport to
+ negotiate encryption with a server. If it fails, an exception will be
+ thrown. On success, the method will return cleanly, and an encrypted
+ session exists. You may immediately call L{open_channel} or
+ L{open_session} to get a L{Channel} object, which is used for data
+ transfer.
+
+ @note: If you fail to supply a password or private key, this method may
+ succeed, but a subsequent L{open_channel} or L{open_session} call may
+ fail because you haven't authenticated yet.
+
+ @param hostkeytype: the type of host key expected from the server
+ (usually C{"ssh-rsa"} or C{"ssh-dss"}), or C{None} if you don't want
+ to do host key verification.
+ @type hostkeytype: string
+ @param hostkey: the host key expected from the server, or C{None} if
+ you don't want to do host key verification.
+ @type hostkey: string
+ @param username: the username to authenticate as.
+ @type username: string
+ @param password: a password to use for authentication, if you want to
+ use password authentication; otherwise C{None}.
+ @type password: string
+ @param pkey: a private key to use for authentication, if you want to
+ use private key authentication; otherwise C{None}.
+ @type pkey: L{PKey<pkey.PKey>}
+
+ @raise SSHException: if the SSH2 negotiation fails, the host key
+ supplied by the server is incorrect, or authentication fails.
+ """
+ if hostkeytype is not None:
+ self.preferred_keys = [ hostkeytype ]
+ event = threading.Event()
+ self.start_client(event)
+ while 1:
+ event.wait(0.1)
+ if not self.active:
+ e = self.saved_exception
+ self.saved_exception = None
+ if e is not None:
+ raise e
+ raise SSHException('Negotiation failed.')
+ if event.isSet():
+ break
+
+ # check host key if we were given one
+ if (hostkeytype is not None) and (hostkey is not None):
+ type, key = self.get_remote_server_key()
+ if (type != hostkeytype) or (key != hostkey):
+ print repr(type) + ' - ' + repr(hostkeytype)
+ print repr(key) + ' - ' + repr(hostkey)
+ raise SSHException('Bad host key from server')
+ self._log(DEBUG, 'Host key verified (%s)' % hostkeytype)
+
+ if (pkey is not None) or (password is not None):
+ event.clear()
+ if password is not None:
+ self._log(DEBUG, 'Attempting password auth...')
+ self.auth_password(username, password, event)
+ else:
+ self._log(DEBUG, 'Attempting password auth...')
+ self.auth_key(username, pkey, event)
+ while 1:
+ event.wait(0.1)
+ if not self.active:
+ e = self.saved_exception
+ self.saved_exception = None
+ if e is not None:
+ raise e
+ raise SSHException('Authentication failed.')
+ if event.isSet():
+ break
+ if not self.is_authenticated():
+ raise SSHException('Authentication failed.')
+
+ return
+
+
+ ### internals...
+
+
def _unlink_channel(self, chanid):
"used by a Channel to remove itself from the active channel list"
try:
@@ -405,7 +562,8 @@ class BaseTransport(threading.Thread):
padding = ord(packet[0])
payload = packet[1:packet_size - padding + 1]
randpool.add_event(packet[packet_size - padding + 1])
- #self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding))
+ if self.ultra_debug:
+ self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding))
msg = Message(payload[1:])
msg.seqno = self.sequence_number_in
self.sequence_number_in = (self.sequence_number_in + 1) & 0xffffffffL
@@ -482,17 +640,17 @@ class BaseTransport(threading.Thread):
self._write_all(self.local_version + '\r\n')
self._check_banner()
self._send_kex_init()
- self.expected_packet = MSG_KEXINIT
+ self.expected_packet = _MSG_KEXINIT
while self.active:
ptype, m = self._read_message()
- if ptype == MSG_IGNORE:
+ if ptype == _MSG_IGNORE:
continue
- elif ptype == MSG_DISCONNECT:
+ elif ptype == _MSG_DISCONNECT:
self._parse_disconnect(m)
self.active = False
break
- elif ptype == MSG_DEBUG:
+ elif ptype == _MSG_DEBUG:
self._parse_debug(m)
continue
if self.expected_packet != 0:
@@ -512,53 +670,34 @@ class BaseTransport(threading.Thread):
else:
self._log(WARNING, 'Oops, unhandled type %d' % ptype)
msg = Message()
- msg.add_byte(chr(MSG_UNIMPLEMENTED))
+ msg.add_byte(chr(_MSG_UNIMPLEMENTED))
msg.add_int(m.seqno)
self._send_message(msg)
except SSHException, e:
self._log(DEBUG, 'Exception: ' + str(e))
self._log(DEBUG, tb_strings())
+ self.saved_exception = e
except EOFError, e:
self._log(DEBUG, 'EOF')
self._log(DEBUG, tb_strings())
+ self.saved_exception = e
except Exception, e:
self._log(DEBUG, 'Unknown exception: ' + str(e))
self._log(DEBUG, tb_strings())
+ self.saved_exception = e
if self.active:
self.active = False
if self.completion_event != None:
self.completion_event.set()
if self.auth_event != None:
self.auth_event.set()
- for e in self.channel_events.values():
- e.set()
+ for event in self.channel_events.values():
+ event.set()
self.sock.close()
- ### protocol stages
- def renegotiate_keys(self):
- """
- Force this session to switch to new keys. Normally this is done
- automatically after the session hits a certain number of packets or
- bytes sent or received, but this method gives you the option of forcing
- new keys whenever you want. Negotiating new keys causes a pause in
- traffic both ways as the two sides swap keys and do computations. This
- method returns when the session has switched to new keys, or the
- session has died mid-negotiation.
+ ### protocol stages
- @return: True if the renegotiation was successful, and the link is
- using new keys; False if the session dropped during renegotiation.
- @rtype: boolean
- """
- self.completion_event = threading.Event()
- self._send_kex_init()
- while 1:
- self.completion_event.wait(0.1);
- if not self.active:
- return False
- if self.completion_event.isSet():
- break
- return True
def _negotiate_keys(self, m):
# throws SSHException on anything unusual
@@ -615,7 +754,7 @@ class BaseTransport(threading.Thread):
available_server_keys = self.preferred_keys
m = Message()
- m.add_byte(chr(MSG_KEXINIT))
+ m.add_byte(chr(_MSG_KEXINIT))
m.add_bytes(randpool.get_bytes(16))
m.add(','.join(self.preferred_kex))
m.add(','.join(available_server_keys))
@@ -726,7 +865,7 @@ class BaseTransport(threading.Thread):
# actually some extra bytes (one NUL byte in openssh's case) added to
# the end of the packet but not parsed. turns out we need to throw
# away those bytes because they aren't part of the hash.
- self.remote_kex_init = chr(MSG_KEXINIT) + m.get_so_far()
+ self.remote_kex_init = chr(_MSG_KEXINIT) + m.get_so_far()
def _activate_inbound(self):
"switch on newly negotiated encryption parameters for inbound traffic"
@@ -750,7 +889,7 @@ class BaseTransport(threading.Thread):
def _activate_outbound(self):
"switch on newly negotiated encryption parameters for outbound traffic"
m = Message()
- m.add_byte(chr(MSG_NEWKEYS))
+ m.add_byte(chr(_MSG_NEWKEYS))
self._send_message(m)
self.block_size_out = self._cipher_info[self.local_cipher]['block-size']
if self.server_mode:
@@ -769,7 +908,7 @@ class BaseTransport(threading.Thread):
else:
self.mac_key_out = self._compute_key('E', self.local_mac_engine.digest_size)
# we always expect to receive NEWKEYS now
- self.expected_packet = MSG_NEWKEYS
+ self.expected_packet = _MSG_NEWKEYS
def _parse_newkeys(self, m):
self._log(DEBUG, 'Switch to new keys ...')
@@ -801,7 +940,7 @@ class BaseTransport(threading.Thread):
try:
self.lock.acquire()
chan = self.channels[chanid]
- chan.set_remote_channel(server_chanid, server_window_size, server_max_packet_size)
+ chan._set_remote_channel(server_chanid, server_window_size, server_max_packet_size)
self._log(INFO, 'Secsh channel %d opened.' % chanid)
if self.channel_events.has_key(chanid):
self.channel_events[chanid].set()
@@ -815,8 +954,8 @@ class BaseTransport(threading.Thread):
reason = m.get_int()
reason_str = m.get_string()
lang = m.get_string()
- if CONNECTION_FAILED_CODE.has_key(reason):
- reason_text = CONNECTION_FAILED_CODE[reason]
+ if _CONNECTION_FAILED_CODE.has_key(reason):
+ reason_text = _CONNECTION_FAILED_CODE[reason]
else:
reason_text = '(unknown code)'
self._log(INFO, 'Secsh channel %d open FAILED: %s: %s' % (chanid, reason_str, reason_text))
@@ -831,10 +970,6 @@ class BaseTransport(threading.Thread):
self.lock.release()
return
- def check_channel_request(self, kind, chanid):
- "override me! return object descended from Channel to allow, or None to reject"
- return None
-
def _parse_channel_open(self, m):
kind = m.get_string()
chanid = m.get_int()
@@ -862,7 +997,7 @@ class BaseTransport(threading.Thread):
reason = self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
if reject:
msg = Message()
- msg.add_byte(chr(MSG_CHANNEL_OPEN_FAILURE))
+ msg.add_byte(chr(_MSG_CHANNEL_OPEN_FAILURE))
msg.add_int(chanid)
msg.add_int(reason)
msg.add_string('')
@@ -873,12 +1008,12 @@ class BaseTransport(threading.Thread):
self.lock.acquire()
self.channels[my_chanid] = chan
chan._set_transport(self)
- chan.set_window(self.window_size, self.max_packet_size)
- chan.set_remote_channel(chanid, initial_window_size, max_packet_size)
+ chan._set_window(self.window_size, self.max_packet_size)
+ chan._set_remote_channel(chanid, initial_window_size, max_packet_size)
finally:
self.lock.release()
m = Message()
- m.add_byte(chr(MSG_CHANNEL_OPEN_SUCCESS))
+ m.add_byte(chr(_MSG_CHANNEL_OPEN_SUCCESS))
m.add_int(chanid)
m.add_int(my_chanid)
m.add_int(self.window_size)
@@ -892,22 +1027,6 @@ class BaseTransport(threading.Thread):
finally:
self.lock.release()
- def accept(self, timeout=None):
- try:
- self.lock.acquire()
- if len(self.server_accepts) > 0:
- chan = self.server_accepts.pop(0)
- else:
- self.server_accept_cv.wait(timeout)
- if len(self.server_accepts) > 0:
- chan = self.server_accepts.pop(0)
- else:
- # timeout
- chan = None
- finally:
- self.lock.release()
- return chan
-
def _parse_debug(self, m):
always_display = m.get_boolean()
msg = m.get_string()
@@ -915,19 +1034,19 @@ class BaseTransport(threading.Thread):
self._log(DEBUG, 'Debug msg: ' + safe_string(msg))
_handler_table = {
- MSG_NEWKEYS: _parse_newkeys,
- MSG_CHANNEL_OPEN_SUCCESS: _parse_channel_open_success,
- MSG_CHANNEL_OPEN_FAILURE: _parse_channel_open_failure,
- MSG_CHANNEL_OPEN: _parse_channel_open,
- MSG_KEXINIT: _negotiate_keys,
+ _MSG_NEWKEYS: _parse_newkeys,
+ _MSG_CHANNEL_OPEN_SUCCESS: _parse_channel_open_success,
+ _MSG_CHANNEL_OPEN_FAILURE: _parse_channel_open_failure,
+ _MSG_CHANNEL_OPEN: _parse_channel_open,
+ _MSG_KEXINIT: _negotiate_keys,
}
_channel_handler_table = {
- MSG_CHANNEL_SUCCESS: Channel.request_success,
- MSG_CHANNEL_FAILURE: Channel.request_failed,
- MSG_CHANNEL_DATA: Channel.feed,
- MSG_CHANNEL_WINDOW_ADJUST: Channel.window_adjust,
- MSG_CHANNEL_REQUEST: Channel.handle_request,
- MSG_CHANNEL_EOF: Channel.handle_eof,
- MSG_CHANNEL_CLOSE: Channel.handle_close,
+ _MSG_CHANNEL_SUCCESS: Channel._request_success,
+ _MSG_CHANNEL_FAILURE: Channel._request_failed,
+ _MSG_CHANNEL_DATA: Channel._feed,
+ _MSG_CHANNEL_WINDOW_ADJUST: Channel._window_adjust,
+ _MSG_CHANNEL_REQUEST: Channel._handle_request,
+ _MSG_CHANNEL_EOF: Channel._handle_eof,
+ _MSG_CHANNEL_CLOSE: Channel._handle_close,
}
diff --git a/setup.py b/setup.py
index 1d49611a..656a2982 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ from distutils.core import setup
longdesc = '''
This is a library for making SSH2 connections (client or server).
Emphasis is on using SSH2 as an alternative to SSL for making secure
-connections between pyton scripts. All major ciphers and hash methods
+connections between python scripts. All major ciphers and hash methods
are supported.
(Previous name: secsh)