[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-17]
lots more documentation, and added Transport.connect() renamed demo_host_key to demo_rsa_key. moved changelog to a separate file, and indicated that future changelog entries should be fetched from tla. tried to clean up "__all__" in a way that makes epydoc still work. added lots more documentation, and renamed many methods and vars to hide them as private non-exported API. Transport's ModulusPack is now a static member, so it only has to be loaded once, and can then be used by any future Transport object. added Transport.connect(), which tries to wrap all the SSH2 negotiation and authentication into one method. you should be able to create a Transport, call connect(), and then create channels.
This commit is contained in:
parent
36d6d95dc6
commit
48c7d888a2
|
@ -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'.
|
7
Makefile
7
Makefile
|
@ -1,6 +1,6 @@
|
||||||
# releases:
|
# releases:
|
||||||
# aerodactyl (13sep03)
|
# aerodactyl (13sep03)
|
||||||
# bulbasaur
|
# bulbasaur (18sep03)
|
||||||
# charmander (10nov03)
|
# charmander (10nov03)
|
||||||
|
|
||||||
RELEASE=charmander
|
RELEASE=charmander
|
||||||
|
@ -8,8 +8,11 @@ RELEASE=charmander
|
||||||
release:
|
release:
|
||||||
python ./setup.py sdist --formats=zip
|
python ./setup.py sdist --formats=zip
|
||||||
|
|
||||||
|
docs:
|
||||||
|
epydoc -o docs/ paramiko
|
||||||
|
|
||||||
# places where the version number is stored:
|
# places where the version number is stored:
|
||||||
#
|
#
|
||||||
# setup.py
|
# setup.py
|
||||||
# secsh.py
|
# __init__.py
|
||||||
# README
|
# README
|
||||||
|
|
13
NOTES
13
NOTES
|
@ -15,19 +15,6 @@ SSHOutputStream --> ssh2 chan --> ssh2 transport --> SOS [no thread]
|
||||||
|
|
||||||
exported API...
|
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:
|
from Transport:
|
||||||
* is_authenticated
|
* is_authenticated
|
||||||
auth_key
|
auth_key
|
||||||
|
|
43
README
43
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]
|
[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
|
*** MISSING LINKS
|
||||||
|
|
||||||
* ctr forms of ciphers are missing (blowfish-ctr, aes128-ctr, aes256-ctr)
|
* ctr forms of ciphers are missing (blowfish-ctr, aes128-ctr, aes256-ctr)
|
||||||
|
|
1
demo.py
1
demo.py
|
@ -66,7 +66,6 @@ except Exception, e:
|
||||||
try:
|
try:
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
t = paramiko.Transport(sock)
|
t = paramiko.Transport(sock)
|
||||||
t.ultra_debug = 0
|
|
||||||
t.start_client(event)
|
t.start_client(event)
|
||||||
# print repr(t)
|
# print repr(t)
|
||||||
event.wait(15)
|
event.wait(15)
|
||||||
|
|
|
@ -13,10 +13,11 @@ if len(l.handlers) == 0:
|
||||||
l.addHandler(lh)
|
l.addHandler(lh)
|
||||||
|
|
||||||
#host_key = paramiko.RSAKey()
|
#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 = paramiko.DSSKey()
|
||||||
host_key.read_private_key_file('demo_dss_key')
|
host_key.read_private_key_file('demo_dss_key')
|
||||||
|
|
||||||
print 'Read key: ' + paramiko.hexify(host_key.get_fingerprint())
|
print 'Read key: ' + paramiko.hexify(host_key.get_fingerprint())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
24
paramiko.py
24
paramiko.py
|
@ -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!"
|
|
||||||
|
|
|
@ -11,10 +11,35 @@ __version__ = "0.1-charmander"
|
||||||
__credits__ = "Huzzah!"
|
__credits__ = "Huzzah!"
|
||||||
|
|
||||||
|
|
||||||
from auth_transport import Transport
|
import ssh_exception, transport, auth_transport, channel, rsakey, dsskey, util
|
||||||
from channel import Channel
|
|
||||||
from rsakey import RSAKey
|
|
||||||
from dsskey import DSSKey
|
|
||||||
from util import hexify
|
|
||||||
|
|
||||||
#__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' ]
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
from transport import BaseTransport
|
from transport import BaseTransport
|
||||||
from transport import MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, \
|
from transport import _MSG_SERVICE_REQUEST, _MSG_SERVICE_ACCEPT, _MSG_USERAUTH_REQUEST, _MSG_USERAUTH_FAILURE, \
|
||||||
MSG_USERAUTH_SUCCESS, MSG_USERAUTH_BANNER
|
_MSG_USERAUTH_SUCCESS, _MSG_USERAUTH_BANNER
|
||||||
from message import Message
|
from message import Message
|
||||||
from ssh_exception import SSHException
|
from ssh_exception import SSHException
|
||||||
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||||
|
|
||||||
DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \
|
_DISCONNECT_SERVICE_NOT_AVAILABLE, _DISCONNECT_AUTH_CANCELLED_BY_USER, \
|
||||||
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
|
_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Transport(BaseTransport):
|
class Transport (BaseTransport):
|
||||||
"BaseTransport with the auth framework hooked up"
|
"BaseTransport with the auth framework hooked up"
|
||||||
|
|
||||||
AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3)
|
AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3)
|
||||||
|
@ -55,7 +55,7 @@ class Transport(BaseTransport):
|
||||||
|
|
||||||
def _request_auth(self):
|
def _request_auth(self):
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_SERVICE_REQUEST))
|
m.add_byte(chr(_MSG_SERVICE_REQUEST))
|
||||||
m.add_string('ssh-userauth')
|
m.add_string('ssh-userauth')
|
||||||
self._send_message(m)
|
self._send_message(m)
|
||||||
|
|
||||||
|
@ -90,8 +90,8 @@ class Transport(BaseTransport):
|
||||||
|
|
||||||
def disconnect_service_not_available(self):
|
def disconnect_service_not_available(self):
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_DISCONNECT))
|
m.add_byte(chr(_MSG_DISCONNECT))
|
||||||
m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
|
m.add_int(_DISCONNECT_SERVICE_NOT_AVAILABLE)
|
||||||
m.add_string('Service not available')
|
m.add_string('Service not available')
|
||||||
m.add_string('en')
|
m.add_string('en')
|
||||||
self._send_message(m)
|
self._send_message(m)
|
||||||
|
@ -99,8 +99,8 @@ class Transport(BaseTransport):
|
||||||
|
|
||||||
def disconnect_no_more_auth(self):
|
def disconnect_no_more_auth(self):
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_DISCONNECT))
|
m.add_byte(chr(_MSG_DISCONNECT))
|
||||||
m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
|
m.add_int(_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
|
||||||
m.add_string('No more auth methods available')
|
m.add_string('No more auth methods available')
|
||||||
m.add_string('en')
|
m.add_string('en')
|
||||||
self._send_message(m)
|
self._send_message(m)
|
||||||
|
@ -111,7 +111,7 @@ class Transport(BaseTransport):
|
||||||
if self.server_mode and (service == 'ssh-userauth'):
|
if self.server_mode and (service == 'ssh-userauth'):
|
||||||
# accepted
|
# accepted
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_SERVICE_ACCEPT))
|
m.add_byte(chr(_MSG_SERVICE_ACCEPT))
|
||||||
m.add_string(service)
|
m.add_string(service)
|
||||||
self._send_message(m)
|
self._send_message(m)
|
||||||
return
|
return
|
||||||
|
@ -123,7 +123,7 @@ class Transport(BaseTransport):
|
||||||
if service == 'ssh-userauth':
|
if service == 'ssh-userauth':
|
||||||
self._log(DEBUG, 'userauth is OK')
|
self._log(DEBUG, 'userauth is OK')
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_USERAUTH_REQUEST))
|
m.add_byte(chr(_MSG_USERAUTH_REQUEST))
|
||||||
m.add_string(self.username)
|
m.add_string(self.username)
|
||||||
m.add_string('ssh-connection')
|
m.add_string('ssh-connection')
|
||||||
m.add_string(self.auth_method)
|
m.add_string(self.auth_method)
|
||||||
|
@ -161,7 +161,7 @@ class Transport(BaseTransport):
|
||||||
if not self.server_mode:
|
if not self.server_mode:
|
||||||
# er, uh... what?
|
# er, uh... what?
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_USERAUTH_FAILURE))
|
m.add_byte(chr(_MSG_USERAUTH_FAILURE))
|
||||||
m.add_string('none')
|
m.add_string('none')
|
||||||
m.add_boolean(0)
|
m.add_boolean(0)
|
||||||
self._send_message(m)
|
self._send_message(m)
|
||||||
|
@ -202,11 +202,11 @@ class Transport(BaseTransport):
|
||||||
m = Message()
|
m = Message()
|
||||||
if result == self.AUTH_SUCCESSFUL:
|
if result == self.AUTH_SUCCESSFUL:
|
||||||
self._log(DEBUG, 'Auth granted.')
|
self._log(DEBUG, 'Auth granted.')
|
||||||
m.add_byte(chr(MSG_USERAUTH_SUCCESS))
|
m.add_byte(chr(_MSG_USERAUTH_SUCCESS))
|
||||||
self.auth_complete = 1
|
self.auth_complete = 1
|
||||||
else:
|
else:
|
||||||
self._log(DEBUG, 'Auth rejected.')
|
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))
|
m.add_string(self.get_allowed_auths(username))
|
||||||
if result == self.AUTH_PARTIALLY_SUCCESSFUL:
|
if result == self.AUTH_PARTIALLY_SUCCESSFUL:
|
||||||
m.add_boolean(1)
|
m.add_boolean(1)
|
||||||
|
@ -245,11 +245,11 @@ class Transport(BaseTransport):
|
||||||
|
|
||||||
_handler_table = BaseTransport._handler_table.copy()
|
_handler_table = BaseTransport._handler_table.copy()
|
||||||
_handler_table.update({
|
_handler_table.update({
|
||||||
MSG_SERVICE_REQUEST: parse_service_request,
|
_MSG_SERVICE_REQUEST: parse_service_request,
|
||||||
MSG_SERVICE_ACCEPT: parse_service_accept,
|
_MSG_SERVICE_ACCEPT: parse_service_accept,
|
||||||
MSG_USERAUTH_REQUEST: parse_userauth_request,
|
_MSG_USERAUTH_REQUEST: parse_userauth_request,
|
||||||
MSG_USERAUTH_SUCCESS: parse_userauth_success,
|
_MSG_USERAUTH_SUCCESS: parse_userauth_success,
|
||||||
MSG_USERAUTH_FAILURE: parse_userauth_failure,
|
_MSG_USERAUTH_FAILURE: parse_userauth_failure,
|
||||||
MSG_USERAUTH_BANNER: parse_userauth_banner,
|
_MSG_USERAUTH_BANNER: parse_userauth_banner,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
from message import Message
|
from message import Message
|
||||||
from ssh_exception import SSHException
|
from ssh_exception import SSHException
|
||||||
from transport import MSG_CHANNEL_REQUEST, MSG_CHANNEL_CLOSE, MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, \
|
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
|
_MSG_CHANNEL_EOF, _MSG_CHANNEL_SUCCESS, _MSG_CHANNEL_FAILURE
|
||||||
|
|
||||||
import time, threading, logging, socket, os
|
import time, threading, logging, socket, os
|
||||||
from logging import DEBUG
|
from logging import DEBUG
|
||||||
|
|
||||||
|
|
||||||
# this is ugly, and won't work on windows
|
# this is ugly, and won't work on windows
|
||||||
def set_nonblocking(fd):
|
def _set_nonblocking(fd):
|
||||||
import fcntl
|
import fcntl
|
||||||
fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
|
fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||||
|
|
||||||
|
@ -35,6 +35,11 @@ class Channel(object):
|
||||||
self.pipe_rfd = self.pipe_wfd = None
|
self.pipe_rfd = self.pipe_wfd = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""
|
||||||
|
Returns a string representation of this object, for debugging.
|
||||||
|
|
||||||
|
@rtype: string
|
||||||
|
"""
|
||||||
out = '<paramiko.Channel %d' % self.chanid
|
out = '<paramiko.Channel %d' % self.chanid
|
||||||
if self.closed:
|
if self.closed:
|
||||||
out += ' (closed)'
|
out += ' (closed)'
|
||||||
|
@ -50,55 +55,406 @@ class Channel(object):
|
||||||
out += '>'
|
out += '>'
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def _set_transport(self, transport):
|
def get_pty(self, term='vt100', width=80, height=24):
|
||||||
self.transport = transport
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
def _log(self, level, msg):
|
@param term: the terminal type to emulate (for example, C{'vt100'}).
|
||||||
self.logger.log(level, msg)
|
@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_int(self.remote_chanid)
|
||||||
|
m.add_string('pty-req')
|
||||||
|
m.add_boolean(0)
|
||||||
|
m.add_string(term)
|
||||||
|
m.add_int(width)
|
||||||
|
m.add_int(height)
|
||||||
|
# pixel height, width (usually useless)
|
||||||
|
m.add_int(0).add_int(0)
|
||||||
|
m.add_string('')
|
||||||
|
self.transport._send_message(m)
|
||||||
|
|
||||||
def set_window(self, window_size, max_packet_size):
|
def invoke_shell(self):
|
||||||
self.in_window_size = window_size
|
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
||||||
self.in_max_packet_size = max_packet_size
|
raise SSHException('Channel is not open')
|
||||||
# threshold of bytes we receive before we bother to send a window update
|
m = Message()
|
||||||
self.in_window_threshold = window_size // 10
|
m.add_byte(chr(_MSG_CHANNEL_REQUEST))
|
||||||
self.in_window_sofar = 0
|
m.add_int(self.remote_chanid)
|
||||||
|
m.add_string('shell')
|
||||||
|
m.add_boolean(1)
|
||||||
|
self.transport._send_message(m)
|
||||||
|
|
||||||
def set_remote_channel(self, chanid, window_size, max_packet_size):
|
def exec_command(self, command):
|
||||||
self.remote_chanid = chanid
|
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
||||||
self.out_window_size = window_size
|
raise SSHException('Channel is not open')
|
||||||
self.out_max_packet_size = max_packet_size
|
m = Message()
|
||||||
self.active = 1
|
m.add_byte(chr(_MSG_CHANNEL_REQUEST))
|
||||||
|
m.add_int(self.remote_chanid)
|
||||||
|
m.add_string('exec')
|
||||||
|
m.add_boolean(1)
|
||||||
|
m.add_string(command)
|
||||||
|
self.transport._send_message(m)
|
||||||
|
|
||||||
def request_success(self, m):
|
def invoke_subsystem(self, subsystem):
|
||||||
self._log(DEBUG, 'Sesch channel %d request ok' % self.chanid)
|
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
||||||
return
|
raise SSHException('Channel is not open')
|
||||||
|
m = Message()
|
||||||
|
m.add_byte(chr(_MSG_CHANNEL_REQUEST))
|
||||||
|
m.add_int(self.remote_chanid)
|
||||||
|
m.add_string('subsystem')
|
||||||
|
m.add_boolean(1)
|
||||||
|
m.add_string(subsystem)
|
||||||
|
self.transport._send_message(m)
|
||||||
|
|
||||||
def request_failed(self, m):
|
def resize_pty(self, width=80, height=24):
|
||||||
self.close()
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
def feed(self, m):
|
@param width: new width (in characters) of the terminal screen
|
||||||
s = m.get_string()
|
@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_int(self.remote_chanid)
|
||||||
|
m.add_string('window-change')
|
||||||
|
m.add_boolean(0)
|
||||||
|
m.add_int(width)
|
||||||
|
m.add_int(height)
|
||||||
|
m.add_int(0).add_int(0)
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Get the name of this channel that was previously set by L{set_name}.
|
||||||
|
|
||||||
|
@return: the name of this channel
|
||||||
|
@rtype: string
|
||||||
|
"""
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
### socket API
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
self._log(DEBUG, 'fed %d bytes' % len(s))
|
if self.active and not self.closed:
|
||||||
if self.pipe_wfd != None:
|
self._send_eof()
|
||||||
self.feed_pipe(s)
|
m = Message()
|
||||||
|
m.add_byte(chr(_MSG_CHANNEL_CLOSE))
|
||||||
|
m.add_int(self.remote_chanid)
|
||||||
|
self.transport._send_message(m)
|
||||||
|
self.closed = 1
|
||||||
|
self.transport._unlink_channel(self.chanid)
|
||||||
|
finally:
|
||||||
|
self.lock.release()
|
||||||
|
|
||||||
|
def recv_ready(self):
|
||||||
|
"""
|
||||||
|
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 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)
|
||||||
|
if len(self.in_buffer) == 0:
|
||||||
|
if self.closed or self.eof_received:
|
||||||
|
return out
|
||||||
|
# should we block?
|
||||||
|
if self.timeout == 0.0:
|
||||||
|
raise socket.timeout()
|
||||||
|
# loop here in case we get woken up but a different thread has grabbed everything in the buffer
|
||||||
|
timeout = self.timeout
|
||||||
|
while (len(self.in_buffer) == 0) and not self.closed and not self.eof_received:
|
||||||
|
then = time.time()
|
||||||
|
self.in_buffer_cv.wait(timeout)
|
||||||
|
if timeout != None:
|
||||||
|
timeout -= time.time() - then
|
||||||
|
if timeout <= 0.0:
|
||||||
|
raise socket.timeout()
|
||||||
|
# something in the buffer and we have the lock
|
||||||
|
if len(self.in_buffer) <= nbytes:
|
||||||
|
out = self.in_buffer
|
||||||
|
self.in_buffer = ''
|
||||||
else:
|
else:
|
||||||
self.in_buffer += s
|
out = self.in_buffer[:nbytes]
|
||||||
self.in_buffer_cv.notifyAll()
|
self.in_buffer = self.in_buffer[nbytes:]
|
||||||
self._log(DEBUG, '(out from feed)')
|
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
|
||||||
|
try:
|
||||||
|
self.lock.acquire()
|
||||||
|
if self.out_window_size == 0:
|
||||||
|
# should we block?
|
||||||
|
if self.timeout == 0.0:
|
||||||
|
raise socket.timeout()
|
||||||
|
# loop here in case we get woken up but a different thread has filled the buffer
|
||||||
|
timeout = self.timeout
|
||||||
|
while self.out_window_size == 0:
|
||||||
|
then = time.time()
|
||||||
|
self.out_buffer_cv.wait(timeout)
|
||||||
|
if timeout != None:
|
||||||
|
timeout -= time.time() - then
|
||||||
|
if timeout <= 0.0:
|
||||||
|
raise socket.timeout()
|
||||||
|
# we have some window to squeeze into
|
||||||
|
if self.closed:
|
||||||
|
return 0
|
||||||
|
size = len(s)
|
||||||
|
if self.out_window_size < size:
|
||||||
|
size = self.out_window_size
|
||||||
|
if self.out_max_packet_size < size:
|
||||||
|
size = self.out_max_packet_size
|
||||||
|
m = Message()
|
||||||
|
m.add_byte(chr(_MSG_CHANNEL_DATA))
|
||||||
|
m.add_int(self.remote_chanid)
|
||||||
|
m.add_string(s[:size])
|
||||||
|
self.transport._send_message(m)
|
||||||
|
self.out_window_size -= size
|
||||||
|
finally:
|
||||||
|
self.lock.release()
|
||||||
|
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
|
||||||
|
raise socket.error('Socket is closed')
|
||||||
|
sent = self.send(s)
|
||||||
|
s = s[sent:]
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
@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()
|
||||||
|
if self.pipe_rfd != None:
|
||||||
|
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)
|
||||||
|
if len(self.in_buffer) > 0:
|
||||||
|
x = self.in_buffer
|
||||||
|
self.in_buffer = ''
|
||||||
|
self._feed_pipe(x)
|
||||||
|
return self.pipe_rfd
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
def window_adjust(self, m):
|
def shutdown(self, how):
|
||||||
nbytes = m.get_int()
|
"""
|
||||||
try:
|
Shut down one or both halves of the connection. If C{how} is 0,
|
||||||
self.lock.acquire()
|
further receives are disallowed. If C{how} is 1, further sends
|
||||||
self._log(DEBUG, 'window up %d' % nbytes)
|
are disallowed. If C{how} is 2, further sends and receives are
|
||||||
self.out_window_size += nbytes
|
disallowed. This closes the stream in one or both directions.
|
||||||
self.out_buffer_cv.notifyAll()
|
|
||||||
finally:
|
@param how: 0 (stop receiving), 1 (stop sending), or 2 (stop
|
||||||
self.lock.release()
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
### overrides
|
||||||
|
|
||||||
|
|
||||||
def check_pty_request(self, term, width, height, pixelwidth, pixelheight, modes):
|
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"
|
"override me! return True if a pty of the given dimensions (for shell access, usually) can be provided"
|
||||||
|
@ -116,7 +472,58 @@ class Channel(object):
|
||||||
"override me! return True if the pty was resized"
|
"override me! return True if the pty was resized"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_request(self, m):
|
|
||||||
|
### 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()
|
key = m.get_string()
|
||||||
want_reply = m.get_boolean()
|
want_reply = m.get_boolean()
|
||||||
ok = False
|
ok = False
|
||||||
|
@ -151,13 +558,13 @@ class Channel(object):
|
||||||
if want_reply:
|
if want_reply:
|
||||||
m = Message()
|
m = Message()
|
||||||
if ok:
|
if ok:
|
||||||
m.add_byte(chr(MSG_CHANNEL_SUCCESS))
|
m.add_byte(chr(_MSG_CHANNEL_SUCCESS))
|
||||||
else:
|
else:
|
||||||
m.add_byte(chr(MSG_CHANNEL_FAILURE))
|
m.add_byte(chr(_MSG_CHANNEL_FAILURE))
|
||||||
m.add_int(self.remote_chanid)
|
m.add_int(self.remote_chanid)
|
||||||
self.transport._send_message(m)
|
self.transport._send_message(m)
|
||||||
|
|
||||||
def handle_eof(self, m):
|
def _handle_eof(self, m):
|
||||||
try:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
if not self.eof_received:
|
if not self.eof_received:
|
||||||
|
@ -170,7 +577,7 @@ class Channel(object):
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
self._log(DEBUG, 'EOF received')
|
self._log(DEBUG, 'EOF received')
|
||||||
|
|
||||||
def handle_close(self, m):
|
def _handle_close(self, m):
|
||||||
self.close()
|
self.close()
|
||||||
try:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
|
@ -183,252 +590,24 @@ class Channel(object):
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
|
|
||||||
# API for external use
|
### internals...
|
||||||
|
|
||||||
def get_pty(self, term='vt100', width=80, height=24):
|
|
||||||
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_int(self.remote_chanid)
|
|
||||||
m.add_string('pty-req')
|
|
||||||
m.add_boolean(0)
|
|
||||||
m.add_string(term)
|
|
||||||
m.add_int(width)
|
|
||||||
m.add_int(height)
|
|
||||||
# pixel height, width (usually useless)
|
|
||||||
m.add_int(0).add_int(0)
|
|
||||||
m.add_string('')
|
|
||||||
self.transport._send_message(m)
|
|
||||||
|
|
||||||
def invoke_shell(self):
|
def _log(self, level, msg):
|
||||||
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
self.logger.log(level, msg)
|
||||||
raise SSHException('Channel is not open')
|
|
||||||
m = Message()
|
|
||||||
m.add_byte(chr(MSG_CHANNEL_REQUEST))
|
|
||||||
m.add_int(self.remote_chanid)
|
|
||||||
m.add_string('shell')
|
|
||||||
m.add_boolean(1)
|
|
||||||
self.transport._send_message(m)
|
|
||||||
|
|
||||||
def exec_command(self, command):
|
def _send_eof(self):
|
||||||
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_int(self.remote_chanid)
|
|
||||||
m.add_string('exec')
|
|
||||||
m.add_boolean(1)
|
|
||||||
m.add_string(command)
|
|
||||||
self.transport._send_message(m)
|
|
||||||
|
|
||||||
def invoke_subsystem(self, subsystem):
|
|
||||||
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_int(self.remote_chanid)
|
|
||||||
m.add_string('subsystem')
|
|
||||||
m.add_boolean(1)
|
|
||||||
m.add_string(subsystem)
|
|
||||||
self.transport._send_message(m)
|
|
||||||
|
|
||||||
def resize_pty(self, width=80, height=24):
|
|
||||||
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_int(self.remote_chanid)
|
|
||||||
m.add_string('window-change')
|
|
||||||
m.add_boolean(0)
|
|
||||||
m.add_int(width)
|
|
||||||
m.add_int(height)
|
|
||||||
m.add_int(0).add_int(0)
|
|
||||||
self.transport._send_message(m)
|
|
||||||
|
|
||||||
def get_transport(self):
|
|
||||||
return self.transport
|
|
||||||
|
|
||||||
def set_name(self, name):
|
|
||||||
self.name = name
|
|
||||||
self.logger = logging.getLogger('paramiko.chan.' + name)
|
|
||||||
|
|
||||||
def get_name(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def send_eof(self):
|
|
||||||
if self.eof_sent:
|
if self.eof_sent:
|
||||||
return
|
return
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_CHANNEL_EOF))
|
m.add_byte(chr(_MSG_CHANNEL_EOF))
|
||||||
m.add_int(self.remote_chanid)
|
m.add_int(self.remote_chanid)
|
||||||
self.transport._send_message(m)
|
self.transport._send_message(m)
|
||||||
self.eof_sent = 1
|
self.eof_sent = 1
|
||||||
self._log(DEBUG, 'EOF sent')
|
self._log(DEBUG, 'EOF sent')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _feed_pipe(self, data):
|
||||||
# socket equivalency methods...
|
|
||||||
|
|
||||||
def settimeout(self, timeout):
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
def gettimeout(self):
|
|
||||||
return self.timeout
|
|
||||||
|
|
||||||
def setblocking(self, blocking):
|
|
||||||
if blocking:
|
|
||||||
self.settimeout(None)
|
|
||||||
else:
|
|
||||||
self.settimeout(0.0)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
try:
|
|
||||||
self.lock.acquire()
|
|
||||||
if self.active and not self.closed:
|
|
||||||
self.send_eof()
|
|
||||||
m = Message()
|
|
||||||
m.add_byte(chr(MSG_CHANNEL_CLOSE))
|
|
||||||
m.add_int(self.remote_chanid)
|
|
||||||
self.transport._send_message(m)
|
|
||||||
self.closed = 1
|
|
||||||
self.transport._unlink_channel(self.chanid)
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
def recv_ready(self):
|
|
||||||
"doesn't work if you've called fileno()"
|
|
||||||
try:
|
|
||||||
self.lock.acquire()
|
|
||||||
if len(self.in_buffer) == 0:
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
def recv(self, nbytes):
|
|
||||||
out = ''
|
|
||||||
try:
|
|
||||||
self.lock.acquire()
|
|
||||||
if self.pipe_rfd != None:
|
|
||||||
# use the pipe
|
|
||||||
return self.read_pipe(nbytes)
|
|
||||||
if len(self.in_buffer) == 0:
|
|
||||||
if self.closed or self.eof_received:
|
|
||||||
return out
|
|
||||||
# should we block?
|
|
||||||
if self.timeout == 0.0:
|
|
||||||
raise socket.timeout()
|
|
||||||
# loop here in case we get woken up but a different thread has grabbed everything in the buffer
|
|
||||||
timeout = self.timeout
|
|
||||||
while (len(self.in_buffer) == 0) and not self.closed and not self.eof_received:
|
|
||||||
then = time.time()
|
|
||||||
self.in_buffer_cv.wait(timeout)
|
|
||||||
if timeout != None:
|
|
||||||
timeout -= time.time() - then
|
|
||||||
if timeout <= 0.0:
|
|
||||||
raise socket.timeout()
|
|
||||||
# something in the buffer and we have the lock
|
|
||||||
if len(self.in_buffer) <= nbytes:
|
|
||||||
out = self.in_buffer
|
|
||||||
self.in_buffer = ''
|
|
||||||
else:
|
|
||||||
out = self.in_buffer[:nbytes]
|
|
||||||
self.in_buffer = self.in_buffer[nbytes:]
|
|
||||||
self.check_add_window(len(out))
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
return out
|
|
||||||
|
|
||||||
def send(self, s):
|
|
||||||
size = 0
|
|
||||||
if self.closed or self.eof_sent:
|
|
||||||
return size
|
|
||||||
try:
|
|
||||||
self.lock.acquire()
|
|
||||||
if self.out_window_size == 0:
|
|
||||||
# should we block?
|
|
||||||
if self.timeout == 0.0:
|
|
||||||
raise socket.timeout()
|
|
||||||
# loop here in case we get woken up but a different thread has filled the buffer
|
|
||||||
timeout = self.timeout
|
|
||||||
while self.out_window_size == 0:
|
|
||||||
then = time.time()
|
|
||||||
self.out_buffer_cv.wait(timeout)
|
|
||||||
if timeout != None:
|
|
||||||
timeout -= time.time() - then
|
|
||||||
if timeout <= 0.0:
|
|
||||||
raise socket.timeout()
|
|
||||||
# we have some window to squeeze into
|
|
||||||
if self.closed:
|
|
||||||
return 0
|
|
||||||
size = len(s)
|
|
||||||
if self.out_window_size < size:
|
|
||||||
size = self.out_window_size
|
|
||||||
if self.out_max_packet_size < size:
|
|
||||||
size = self.out_max_packet_size
|
|
||||||
m = Message()
|
|
||||||
m.add_byte(chr(MSG_CHANNEL_DATA))
|
|
||||||
m.add_int(self.remote_chanid)
|
|
||||||
m.add_string(s[:size])
|
|
||||||
self.transport._send_message(m)
|
|
||||||
self.out_window_size -= size
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
return size
|
|
||||||
|
|
||||||
def sendall(self, s):
|
|
||||||
while s:
|
|
||||||
if self.closed:
|
|
||||||
# this doesn't seem useful, but it is the documented behavior of Socket
|
|
||||||
raise socket.error('Socket is closed')
|
|
||||||
sent = self.send(s)
|
|
||||||
s = s[sent:]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def makefile(self, *params):
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.lock.acquire()
|
|
||||||
if self.pipe_rfd != None:
|
|
||||||
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)
|
|
||||||
if len(self.in_buffer) > 0:
|
|
||||||
x = self.in_buffer
|
|
||||||
self.in_buffer = ''
|
|
||||||
self.feed_pipe(x)
|
|
||||||
return self.pipe_rfd
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
def shutdown(self, how):
|
|
||||||
if (how == 0) or (how == 2):
|
|
||||||
# feign "read" shutdown
|
|
||||||
self.eof_received = 1
|
|
||||||
if (how == 1) or (how == 2):
|
|
||||||
self.send_eof()
|
|
||||||
|
|
||||||
|
|
||||||
# internal use...
|
|
||||||
|
|
||||||
def feed_pipe(self, data):
|
|
||||||
"you are already holding the lock"
|
"you are already holding the lock"
|
||||||
if len(self.in_buffer) > 0:
|
if len(self.in_buffer) > 0:
|
||||||
self.in_buffer += data
|
self.in_buffer += data
|
||||||
|
@ -439,7 +618,7 @@ class Channel(object):
|
||||||
# at least on linux, this will never happen, as the writes are
|
# at least on linux, this will never happen, as the writes are
|
||||||
# considered atomic... but just in case.
|
# considered atomic... but just in case.
|
||||||
self.in_buffer = data[n:]
|
self.in_buffer = data[n:]
|
||||||
self.check_add_window(n)
|
self._check_add_window(n)
|
||||||
self.in_buffer_cv.notifyAll()
|
self.in_buffer_cv.notifyAll()
|
||||||
return
|
return
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
|
@ -451,7 +630,7 @@ class Channel(object):
|
||||||
try:
|
try:
|
||||||
os.write(self.pipe_wfd, x)
|
os.write(self.pipe_wfd, x)
|
||||||
self.in_buffer = data
|
self.in_buffer = data
|
||||||
self.check_add_window(1)
|
self._check_add_window(1)
|
||||||
self.in_buffer_cv.notifyAll()
|
self.in_buffer_cv.notifyAll()
|
||||||
return
|
return
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
|
@ -460,12 +639,12 @@ class Channel(object):
|
||||||
self.in_buffer = data
|
self.in_buffer = data
|
||||||
self.in_buffer_cv.notifyAll()
|
self.in_buffer_cv.notifyAll()
|
||||||
|
|
||||||
def read_pipe(self, nbytes):
|
def _read_pipe(self, nbytes):
|
||||||
"you are already holding the lock"
|
"you are already holding the lock"
|
||||||
try:
|
try:
|
||||||
x = os.read(self.pipe_rfd, nbytes)
|
x = os.read(self.pipe_rfd, nbytes)
|
||||||
if len(x) > 0:
|
if len(x) > 0:
|
||||||
self.push_pipe(len(x))
|
self._push_pipe(len(x))
|
||||||
return x
|
return x
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
pass
|
pass
|
||||||
|
@ -487,13 +666,13 @@ class Channel(object):
|
||||||
try:
|
try:
|
||||||
x = os.read(self.pipe_rfd, nbytes)
|
x = os.read(self.pipe_rfd, nbytes)
|
||||||
if len(x) > 0:
|
if len(x) > 0:
|
||||||
self.push_pipe(len(x))
|
self._push_pipe(len(x))
|
||||||
return x
|
return x
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
pass
|
pass
|
||||||
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
|
# 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)
|
# (assumption: the pipe can hold as many bytes as were read out)
|
||||||
if len(self.in_buffer) == 0:
|
if len(self.in_buffer) == 0:
|
||||||
|
@ -512,7 +691,7 @@ class Channel(object):
|
||||||
self.closed = 1
|
self.closed = 1
|
||||||
self.transport._unlink_channel(self.chanid)
|
self.transport._unlink_channel(self.chanid)
|
||||||
|
|
||||||
def check_add_window(self, n):
|
def _check_add_window(self, n):
|
||||||
# already holding the lock!
|
# already holding the lock!
|
||||||
if self.closed or self.eof_received or not self.active:
|
if self.closed or self.eof_received or not self.active:
|
||||||
return
|
return
|
||||||
|
@ -521,7 +700,7 @@ class Channel(object):
|
||||||
if self.in_window_sofar > self.in_window_threshold:
|
if self.in_window_sofar > self.in_window_threshold:
|
||||||
self._log(DEBUG, 'addwindow send %d' % self.in_window_sofar)
|
self._log(DEBUG, 'addwindow send %d' % self.in_window_sofar)
|
||||||
m = Message()
|
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.remote_chanid)
|
||||||
m.add_int(self.in_window_sofar)
|
m.add_int(self.in_window_sofar)
|
||||||
self.transport._send_message(m)
|
self.transport._send_message(m)
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
import base64
|
import base64
|
||||||
from ssh_exception import SSHException
|
from ssh_exception import SSHException
|
||||||
from message import Message
|
from message import Message
|
||||||
from transport import MSG_USERAUTH_REQUEST
|
from transport import _MSG_USERAUTH_REQUEST
|
||||||
from util import inflate_long, deflate_long
|
from util import inflate_long, deflate_long
|
||||||
from Crypto.PublicKey import DSA
|
from Crypto.PublicKey import DSA
|
||||||
from Crypto.Hash import SHA, MD5
|
from Crypto.Hash import SHA
|
||||||
from ber import BER
|
from ber import BER
|
||||||
|
from pkey import PKey
|
||||||
|
|
||||||
from util import format_binary
|
from util import format_binary
|
||||||
|
|
||||||
|
class DSSKey (PKey):
|
||||||
class DSSKey(object):
|
|
||||||
|
|
||||||
def __init__(self, msg=None):
|
def __init__(self, msg=None):
|
||||||
self.valid = 0
|
self.valid = 0
|
||||||
|
@ -39,9 +39,6 @@ class DSSKey(object):
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return 'ssh-dss'
|
return 'ssh-dss'
|
||||||
|
|
||||||
def get_fingerprint(self):
|
|
||||||
return MD5.new(str(self)).digest()
|
|
||||||
|
|
||||||
def verify_ssh_sig(self, data, msg):
|
def verify_ssh_sig(self, data, msg):
|
||||||
if not self.valid:
|
if not self.valid:
|
||||||
return 0
|
return 0
|
||||||
|
@ -78,7 +75,6 @@ class DSSKey(object):
|
||||||
return str(m)
|
return str(m)
|
||||||
|
|
||||||
def read_private_key_file(self, filename):
|
def read_private_key_file(self, filename):
|
||||||
"throws a file exception, or SSHException (on invalid key, or base64 decoding exception"
|
|
||||||
# private key file contains:
|
# private key file contains:
|
||||||
# DSAPrivateKey = { version = 0, p, q, g, y, x }
|
# DSAPrivateKey = { version = 0, p, q, g, y, x }
|
||||||
self.valid = 0
|
self.valid = 0
|
||||||
|
@ -102,7 +98,7 @@ class DSSKey(object):
|
||||||
def sign_ssh_session(self, randpool, sid, username):
|
def sign_ssh_session(self, randpool, sid, username):
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_string(sid)
|
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(username)
|
||||||
m.add_string('ssh-connection')
|
m.add_string('ssh-connection')
|
||||||
m.add_string('publickey')
|
m.add_string('publickey')
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
from message import Message
|
from message import Message
|
||||||
from util import inflate_long, deflate_long, bit_length
|
from util import inflate_long, deflate_long, bit_length
|
||||||
from ssh_exception import SSHException
|
from ssh_exception import SSHException
|
||||||
from transport import MSG_NEWKEYS
|
from transport import _MSG_NEWKEYS
|
||||||
from Crypto.Hash import SHA
|
from Crypto.Hash import SHA
|
||||||
from Crypto.Util import number
|
from Crypto.Util import number
|
||||||
from logging import DEBUG
|
from 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):
|
class KexGex(object):
|
||||||
|
@ -27,27 +27,27 @@ class KexGex(object):
|
||||||
|
|
||||||
def start_kex(self):
|
def start_kex(self):
|
||||||
if self.transport.server_mode:
|
if self.transport.server_mode:
|
||||||
self.transport._expect_packet(MSG_KEXDH_GEX_REQUEST)
|
self.transport._expect_packet(_MSG_KEXDH_GEX_REQUEST)
|
||||||
return
|
return
|
||||||
# request a bit range: we accept (min_bits) to (max_bits), but prefer
|
# request a bit range: we accept (min_bits) to (max_bits), but prefer
|
||||||
# (preferred_bits). according to the spec, we shouldn't pull the
|
# (preferred_bits). according to the spec, we shouldn't pull the
|
||||||
# minimum up above 1024.
|
# minimum up above 1024.
|
||||||
m = Message()
|
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.min_bits)
|
||||||
m.add_int(self.preferred_bits)
|
m.add_int(self.preferred_bits)
|
||||||
m.add_int(self.max_bits)
|
m.add_int(self.max_bits)
|
||||||
self.transport._send_message(m)
|
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):
|
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)
|
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)
|
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)
|
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)
|
return self._parse_kexdh_gex_reply(m)
|
||||||
raise SSHException('KexGex asked to handle packet type %d' % ptype)
|
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')
|
raise SSHException('Can\'t do server-side gex with no modulus pack')
|
||||||
self.g, self.p = pack.get_modulus(min, preferred, max)
|
self.g, self.p = pack.get_modulus(min, preferred, max)
|
||||||
m = Message()
|
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.p)
|
||||||
m.add_mpint(self.g)
|
m.add_mpint(self.g)
|
||||||
self.transport._send_message(m)
|
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):
|
def _parse_kexdh_gex_group(self, m):
|
||||||
self.p = m.get_mpint()
|
self.p = m.get_mpint()
|
||||||
|
@ -114,10 +114,10 @@ class KexGex(object):
|
||||||
# now compute e = g^x mod p
|
# now compute e = g^x mod p
|
||||||
self.e = pow(self.g, self.x, self.p)
|
self.e = pow(self.g, self.x, self.p)
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_KEXDH_GEX_INIT))
|
m.add_byte(chr(_MSG_KEXDH_GEX_INIT))
|
||||||
m.add_mpint(self.e)
|
m.add_mpint(self.e)
|
||||||
self.transport._send_message(m)
|
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):
|
def _parse_kexdh_gex_init(self, m):
|
||||||
self.e = m.get_mpint()
|
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)
|
sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H)
|
||||||
# send reply
|
# send reply
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_KEXDH_GEX_REPLY))
|
m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
|
||||||
m.add_string(key)
|
m.add_string(key)
|
||||||
m.add_mpint(self.f)
|
m.add_mpint(self.f)
|
||||||
m.add_string(sig)
|
m.add_string(sig)
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
|
|
||||||
from message import Message, inflate_long
|
from message import Message, inflate_long
|
||||||
from ssh_exception import SSHException
|
from ssh_exception import SSHException
|
||||||
from transport import MSG_NEWKEYS
|
from transport import _MSG_NEWKEYS
|
||||||
from Crypto.Hash import SHA
|
from Crypto.Hash import SHA
|
||||||
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
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
|
# draft-ietf-secsh-transport-09.txt, page 17
|
||||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFFL
|
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFFL
|
||||||
|
@ -44,20 +44,20 @@ class KexGroup1(object):
|
||||||
if self.transport.server_mode:
|
if self.transport.server_mode:
|
||||||
# compute f = g^x mod p, but don't send it yet
|
# compute f = g^x mod p, but don't send it yet
|
||||||
self.f = pow(G, self.x, P)
|
self.f = pow(G, self.x, P)
|
||||||
self.transport._expect_packet(MSG_KEXDH_INIT)
|
self.transport._expect_packet(_MSG_KEXDH_INIT)
|
||||||
return
|
return
|
||||||
# compute e = g^x mod p (where g=2), and send it
|
# compute e = g^x mod p (where g=2), and send it
|
||||||
self.e = pow(G, self.x, P)
|
self.e = pow(G, self.x, P)
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_KEXDH_INIT))
|
m.add_byte(chr(_MSG_KEXDH_INIT))
|
||||||
m.add_mpint(self.e)
|
m.add_mpint(self.e)
|
||||||
self.transport._send_message(m)
|
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):
|
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)
|
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)
|
return self.parse_kexdh_reply(m)
|
||||||
raise SSHException('KexGroup1 asked to handle packet type %d' % ptype)
|
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)
|
sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H)
|
||||||
# send reply
|
# send reply
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_KEXDH_REPLY))
|
m.add_byte(chr(_MSG_KEXDH_REPLY))
|
||||||
m.add_string(key)
|
m.add_string(key)
|
||||||
m.add_mpint(self.f)
|
m.add_mpint(self.f)
|
||||||
m.add_string(sig)
|
m.add_string(sig)
|
||||||
|
|
|
@ -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
|
||||||
|
"""
|
|
@ -1,14 +1,15 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
from message import Message
|
from message import Message
|
||||||
from transport import MSG_USERAUTH_REQUEST
|
from transport import _MSG_USERAUTH_REQUEST
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
from Crypto.Hash import SHA
|
from Crypto.Hash import SHA
|
||||||
from ber import BER
|
from ber import BER
|
||||||
from util import format_binary, inflate_long, deflate_long
|
from util import format_binary, inflate_long, deflate_long
|
||||||
|
from pkey import PKey
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
class RSAKey(object):
|
class RSAKey (PKey):
|
||||||
|
|
||||||
def __init__(self, msg=None):
|
def __init__(self, msg=None):
|
||||||
self.valid = 0
|
self.valid = 0
|
||||||
|
@ -31,10 +32,7 @@ class RSAKey(object):
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return 'ssh-rsa'
|
return 'ssh-rsa'
|
||||||
|
|
||||||
def get_fingerprint(self):
|
def _pkcs1imify(self, data):
|
||||||
return MD5.new(str(self)).digest()
|
|
||||||
|
|
||||||
def pkcs1imify(self, data):
|
|
||||||
"""
|
"""
|
||||||
turn a 20-byte SHA1 hash into a blob of data as large as the key's N,
|
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.
|
using PKCS1's \"emsa-pkcs1-v1_5\" encoding. totally bizarre.
|
||||||
|
@ -45,26 +43,25 @@ class RSAKey(object):
|
||||||
|
|
||||||
def verify_ssh_sig(self, data, msg):
|
def verify_ssh_sig(self, data, msg):
|
||||||
if (not self.valid) or (msg.get_string() != 'ssh-rsa'):
|
if (not self.valid) or (msg.get_string() != 'ssh-rsa'):
|
||||||
return 0
|
return False
|
||||||
sig = inflate_long(msg.get_string(), 1)
|
sig = inflate_long(msg.get_string(), 1)
|
||||||
# verify the signature by SHA'ing the data and encrypting it using the
|
# verify the signature by SHA'ing the data and encrypting it using the
|
||||||
# public key. some wackiness ensues where we "pkcs1imify" the 20-byte
|
# public key. some wackiness ensues where we "pkcs1imify" the 20-byte
|
||||||
# hash into a string as long as the RSA key.
|
# 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)))
|
rsa = RSA.construct((long(self.n), long(self.e)))
|
||||||
return rsa.verify(hash, (sig,))
|
return rsa.verify(hash, (sig,))
|
||||||
|
|
||||||
def sign_ssh_data(self, randpool, data):
|
def sign_ssh_data(self, randpool, data):
|
||||||
hash = SHA.new(data).digest()
|
hash = SHA.new(data).digest()
|
||||||
rsa = RSA.construct((long(self.n), long(self.e), long(self.d)))
|
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 = Message()
|
||||||
m.add_string('ssh-rsa')
|
m.add_string('ssh-rsa')
|
||||||
m.add_string(sig)
|
m.add_string(sig)
|
||||||
return str(m)
|
return str(m)
|
||||||
|
|
||||||
def read_private_key_file(self, filename):
|
def read_private_key_file(self, filename):
|
||||||
"throws a file exception, or SSHException (on invalid key), or base64 decoding exception"
|
|
||||||
# private key file contains:
|
# private key file contains:
|
||||||
# RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p }
|
# RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p }
|
||||||
self.valid = 0
|
self.valid = 0
|
||||||
|
@ -72,11 +69,11 @@ class RSAKey(object):
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
f.close()
|
f.close()
|
||||||
if lines[0].strip() != '-----BEGIN RSA PRIVATE KEY-----':
|
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]))
|
data = base64.decodestring(''.join(lines[1:-1]))
|
||||||
keylist = BER(data).decode()
|
keylist = BER(data).decode()
|
||||||
if (type(keylist) != type([])) or (len(keylist) < 4) or (keylist[0] != 0):
|
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.n = keylist[1]
|
||||||
self.e = keylist[2]
|
self.e = keylist[2]
|
||||||
self.d = keylist[3]
|
self.d = keylist[3]
|
||||||
|
@ -89,7 +86,7 @@ class RSAKey(object):
|
||||||
def sign_ssh_session(self, randpool, sid, username):
|
def sign_ssh_session(self, randpool, sid, username):
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_string(sid)
|
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(username)
|
||||||
m.add_string('ssh-connection')
|
m.add_string('ssh-connection')
|
||||||
m.add_string('publickey')
|
m.add_string('publickey')
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, MSG_SERVICE_REQUEST, \
|
_MSG_DISCONNECT, _MSG_IGNORE, _MSG_UNIMPLEMENTED, _MSG_DEBUG, _MSG_SERVICE_REQUEST, \
|
||||||
MSG_SERVICE_ACCEPT = range(1, 7)
|
_MSG_SERVICE_ACCEPT = range(1, 7)
|
||||||
MSG_KEXINIT, MSG_NEWKEYS = range(20, 22)
|
_MSG_KEXINIT, _MSG_NEWKEYS = range(20, 22)
|
||||||
MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, MSG_USERAUTH_SUCCESS, \
|
_MSG_USERAUTH_REQUEST, _MSG_USERAUTH_FAILURE, _MSG_USERAUTH_SUCCESS, \
|
||||||
MSG_USERAUTH_BANNER = range(50, 54)
|
_MSG_USERAUTH_BANNER = range(50, 54)
|
||||||
MSG_USERAUTH_PK_OK = 60
|
_MSG_USERAUTH_PK_OK = 60
|
||||||
MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
|
_MSG_CHANNEL_OPEN, _MSG_CHANNEL_OPEN_SUCCESS, _MSG_CHANNEL_OPEN_FAILURE, \
|
||||||
MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \
|
_MSG_CHANNEL_WINDOW_ADJUST, _MSG_CHANNEL_DATA, _MSG_CHANNEL_EXTENDED_DATA, \
|
||||||
MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST, \
|
_MSG_CHANNEL_EOF, _MSG_CHANNEL_CLOSE, _MSG_CHANNEL_REQUEST, \
|
||||||
MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101)
|
_MSG_CHANNEL_SUCCESS, _MSG_CHANNEL_FAILURE = range(90, 101)
|
||||||
|
|
||||||
import sys, os, string, threading, socket, logging, struct
|
import sys, os, string, threading, socket, logging, struct
|
||||||
from ssh_exception import SSHException
|
from ssh_exception import SSHException
|
||||||
|
@ -36,7 +36,7 @@ from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||||
|
|
||||||
|
|
||||||
# channel request failed reasons:
|
# channel request failed reasons:
|
||||||
CONNECTION_FAILED_CODE = {
|
_CONNECTION_FAILED_CODE = {
|
||||||
1: 'Administratively prohibited',
|
1: 'Administratively prohibited',
|
||||||
2: 'Connect failed',
|
2: 'Connect failed',
|
||||||
3: 'Unknown channel type',
|
3: 'Unknown channel type',
|
||||||
|
@ -54,29 +54,14 @@ except:
|
||||||
randpool.randomize()
|
randpool.randomize()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTransport (threading.Thread):
|
||||||
class BaseTransport(threading.Thread):
|
"""
|
||||||
'''
|
Handles protocol negotiation, key exchange, encryption, and the creation
|
||||||
An SSH Transport attaches to a stream (usually a socket), negotiates an
|
of channels across an SSH session. Basically everything but authentication
|
||||||
encrypted session, authenticates, and then creates stream tunnels, called
|
is done here.
|
||||||
"channels", across the session. Multiple channels can be multiplexed
|
"""
|
||||||
across a single session (and often are, in the case of port forwardings).
|
_PROTO_ID = '2.0'
|
||||||
|
_CLIENT_ID = 'pyssh_1.1'
|
||||||
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'
|
|
||||||
|
|
||||||
preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ]
|
preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ]
|
||||||
preferred_macs = [ 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ]
|
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_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, \
|
||||||
OPEN_FAILED_RESOURCE_SHORTAGE = range(1, 5)
|
OPEN_FAILED_RESOURCE_SHORTAGE = range(1, 5)
|
||||||
|
|
||||||
|
_modulus_pack = None
|
||||||
|
|
||||||
def __init__(self, sock):
|
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)
|
threading.Thread.__init__(self, target=self._run)
|
||||||
self.randpool = randpool
|
self.randpool = randpool
|
||||||
self.sock = sock
|
self.sock = sock
|
||||||
self.sock.settimeout(0.1)
|
self.sock.settimeout(0.1)
|
||||||
# negotiated crypto parameters
|
# negotiated crypto parameters
|
||||||
self.local_version = 'SSH-' + self.PROTO_ID + '-' + self.CLIENT_ID
|
self.local_version = 'SSH-' + self._PROTO_ID + '-' + self._CLIENT_ID
|
||||||
self.remote_version = ''
|
self.remote_version = ''
|
||||||
self.block_size_out = self.block_size_in = 8
|
self.block_size_out = self.block_size_in = 8
|
||||||
self.local_mac_len = self.remote_mac_len = 0
|
self.local_mac_len = self.remote_mac_len = 0
|
||||||
|
@ -136,7 +142,7 @@ class BaseTransport(threading.Thread):
|
||||||
self.window_size = 65536
|
self.window_size = 65536
|
||||||
self.max_packet_size = 2048
|
self.max_packet_size = 2048
|
||||||
self.ultra_debug = 0
|
self.ultra_debug = 0
|
||||||
self.modulus_pack = None
|
self.saved_exception = None
|
||||||
# used for noticing when to re-key:
|
# used for noticing when to re-key:
|
||||||
self.received_bytes = 0
|
self.received_bytes = 0
|
||||||
self.received_packets = 0
|
self.received_packets = 0
|
||||||
|
@ -159,6 +165,17 @@ class BaseTransport(threading.Thread):
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def add_server_key(self, key):
|
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
|
self.server_key_dict[key.get_name()] = key
|
||||||
|
|
||||||
def get_server_key(self):
|
def get_server_key(self):
|
||||||
|
@ -167,7 +184,7 @@ class BaseTransport(threading.Thread):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def load_server_moduli(self, filename=None):
|
def load_server_moduli(filename=None):
|
||||||
"""
|
"""
|
||||||
I{(optional)}
|
I{(optional)}
|
||||||
Load a file of prime moduli for use in doing group-exchange key
|
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.
|
@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
|
# places to look for the openssh "moduli" file
|
||||||
file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
|
file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
file_list.insert(0, filename)
|
file_list.insert(0, filename)
|
||||||
for fn in file_list:
|
for fn in file_list:
|
||||||
try:
|
try:
|
||||||
self.modulus_pack.read_file(fn)
|
BaseTransport._modulus_pack.read_file(fn)
|
||||||
return True
|
return True
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
# none succeeded
|
# none succeeded
|
||||||
self.modulus_pack = None
|
BaseTransport._modulus_pack = None
|
||||||
return False
|
return False
|
||||||
|
load_server_moduli = staticmethod(load_server_moduli)
|
||||||
|
|
||||||
def _get_modulus_pack(self):
|
def _get_modulus_pack(self):
|
||||||
"used by KexGex to find primes for group exchange"
|
"used by KexGex to find primes for group exchange"
|
||||||
return self.modulus_pack
|
return self._modulus_pack
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""
|
||||||
|
Returns a string representation of this object, for debugging.
|
||||||
|
|
||||||
|
@rtype: string
|
||||||
|
"""
|
||||||
if not self.active:
|
if not self.active:
|
||||||
return '<paramiko.BaseTransport (unconnected)>'
|
return '<paramiko.BaseTransport (unconnected)>'
|
||||||
out = '<paramiko.BaseTransport'
|
out = '<paramiko.BaseTransport'
|
||||||
|
@ -285,7 +308,7 @@ class BaseTransport(threading.Thread):
|
||||||
chanid = self.channel_counter
|
chanid = self.channel_counter
|
||||||
self.channel_counter += 1
|
self.channel_counter += 1
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_CHANNEL_OPEN))
|
m.add_byte(chr(_MSG_CHANNEL_OPEN))
|
||||||
m.add_string(kind)
|
m.add_string(kind)
|
||||||
m.add_int(chanid)
|
m.add_int(chanid)
|
||||||
m.add_int(self.window_size)
|
m.add_int(self.window_size)
|
||||||
|
@ -293,7 +316,7 @@ class BaseTransport(threading.Thread):
|
||||||
self.channels[chanid] = chan = Channel(chanid)
|
self.channels[chanid] = chan = Channel(chanid)
|
||||||
self.channel_events[chanid] = event = threading.Event()
|
self.channel_events[chanid] = event = threading.Event()
|
||||||
chan._set_transport(self)
|
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)
|
self._send_message(m)
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
@ -311,6 +334,140 @@ class BaseTransport(threading.Thread):
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return chan
|
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):
|
def _unlink_channel(self, chanid):
|
||||||
"used by a Channel to remove itself from the active channel list"
|
"used by a Channel to remove itself from the active channel list"
|
||||||
try:
|
try:
|
||||||
|
@ -405,7 +562,8 @@ class BaseTransport(threading.Thread):
|
||||||
padding = ord(packet[0])
|
padding = ord(packet[0])
|
||||||
payload = packet[1:packet_size - padding + 1]
|
payload = packet[1:packet_size - padding + 1]
|
||||||
randpool.add_event(packet[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 = Message(payload[1:])
|
||||||
msg.seqno = self.sequence_number_in
|
msg.seqno = self.sequence_number_in
|
||||||
self.sequence_number_in = (self.sequence_number_in + 1) & 0xffffffffL
|
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._write_all(self.local_version + '\r\n')
|
||||||
self._check_banner()
|
self._check_banner()
|
||||||
self._send_kex_init()
|
self._send_kex_init()
|
||||||
self.expected_packet = MSG_KEXINIT
|
self.expected_packet = _MSG_KEXINIT
|
||||||
|
|
||||||
while self.active:
|
while self.active:
|
||||||
ptype, m = self._read_message()
|
ptype, m = self._read_message()
|
||||||
if ptype == MSG_IGNORE:
|
if ptype == _MSG_IGNORE:
|
||||||
continue
|
continue
|
||||||
elif ptype == MSG_DISCONNECT:
|
elif ptype == _MSG_DISCONNECT:
|
||||||
self._parse_disconnect(m)
|
self._parse_disconnect(m)
|
||||||
self.active = False
|
self.active = False
|
||||||
break
|
break
|
||||||
elif ptype == MSG_DEBUG:
|
elif ptype == _MSG_DEBUG:
|
||||||
self._parse_debug(m)
|
self._parse_debug(m)
|
||||||
continue
|
continue
|
||||||
if self.expected_packet != 0:
|
if self.expected_packet != 0:
|
||||||
|
@ -512,53 +670,34 @@ class BaseTransport(threading.Thread):
|
||||||
else:
|
else:
|
||||||
self._log(WARNING, 'Oops, unhandled type %d' % ptype)
|
self._log(WARNING, 'Oops, unhandled type %d' % ptype)
|
||||||
msg = Message()
|
msg = Message()
|
||||||
msg.add_byte(chr(MSG_UNIMPLEMENTED))
|
msg.add_byte(chr(_MSG_UNIMPLEMENTED))
|
||||||
msg.add_int(m.seqno)
|
msg.add_int(m.seqno)
|
||||||
self._send_message(msg)
|
self._send_message(msg)
|
||||||
except SSHException, e:
|
except SSHException, e:
|
||||||
self._log(DEBUG, 'Exception: ' + str(e))
|
self._log(DEBUG, 'Exception: ' + str(e))
|
||||||
self._log(DEBUG, tb_strings())
|
self._log(DEBUG, tb_strings())
|
||||||
|
self.saved_exception = e
|
||||||
except EOFError, e:
|
except EOFError, e:
|
||||||
self._log(DEBUG, 'EOF')
|
self._log(DEBUG, 'EOF')
|
||||||
self._log(DEBUG, tb_strings())
|
self._log(DEBUG, tb_strings())
|
||||||
|
self.saved_exception = e
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self._log(DEBUG, 'Unknown exception: ' + str(e))
|
self._log(DEBUG, 'Unknown exception: ' + str(e))
|
||||||
self._log(DEBUG, tb_strings())
|
self._log(DEBUG, tb_strings())
|
||||||
|
self.saved_exception = e
|
||||||
if self.active:
|
if self.active:
|
||||||
self.active = False
|
self.active = False
|
||||||
if self.completion_event != None:
|
if self.completion_event != None:
|
||||||
self.completion_event.set()
|
self.completion_event.set()
|
||||||
if self.auth_event != None:
|
if self.auth_event != None:
|
||||||
self.auth_event.set()
|
self.auth_event.set()
|
||||||
for e in self.channel_events.values():
|
for event in self.channel_events.values():
|
||||||
e.set()
|
event.set()
|
||||||
self.sock.close()
|
self.sock.close()
|
||||||
|
|
||||||
|
|
||||||
### protocol stages
|
### 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.
|
|
||||||
|
|
||||||
@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):
|
def _negotiate_keys(self, m):
|
||||||
# throws SSHException on anything unusual
|
# throws SSHException on anything unusual
|
||||||
|
@ -615,7 +754,7 @@ class BaseTransport(threading.Thread):
|
||||||
available_server_keys = self.preferred_keys
|
available_server_keys = self.preferred_keys
|
||||||
|
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_KEXINIT))
|
m.add_byte(chr(_MSG_KEXINIT))
|
||||||
m.add_bytes(randpool.get_bytes(16))
|
m.add_bytes(randpool.get_bytes(16))
|
||||||
m.add(','.join(self.preferred_kex))
|
m.add(','.join(self.preferred_kex))
|
||||||
m.add(','.join(available_server_keys))
|
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
|
# 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
|
# 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.
|
# 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):
|
def _activate_inbound(self):
|
||||||
"switch on newly negotiated encryption parameters for inbound traffic"
|
"switch on newly negotiated encryption parameters for inbound traffic"
|
||||||
|
@ -750,7 +889,7 @@ class BaseTransport(threading.Thread):
|
||||||
def _activate_outbound(self):
|
def _activate_outbound(self):
|
||||||
"switch on newly negotiated encryption parameters for outbound traffic"
|
"switch on newly negotiated encryption parameters for outbound traffic"
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_NEWKEYS))
|
m.add_byte(chr(_MSG_NEWKEYS))
|
||||||
self._send_message(m)
|
self._send_message(m)
|
||||||
self.block_size_out = self._cipher_info[self.local_cipher]['block-size']
|
self.block_size_out = self._cipher_info[self.local_cipher]['block-size']
|
||||||
if self.server_mode:
|
if self.server_mode:
|
||||||
|
@ -769,7 +908,7 @@ class BaseTransport(threading.Thread):
|
||||||
else:
|
else:
|
||||||
self.mac_key_out = self._compute_key('E', self.local_mac_engine.digest_size)
|
self.mac_key_out = self._compute_key('E', self.local_mac_engine.digest_size)
|
||||||
# we always expect to receive NEWKEYS now
|
# we always expect to receive NEWKEYS now
|
||||||
self.expected_packet = MSG_NEWKEYS
|
self.expected_packet = _MSG_NEWKEYS
|
||||||
|
|
||||||
def _parse_newkeys(self, m):
|
def _parse_newkeys(self, m):
|
||||||
self._log(DEBUG, 'Switch to new keys ...')
|
self._log(DEBUG, 'Switch to new keys ...')
|
||||||
|
@ -801,7 +940,7 @@ class BaseTransport(threading.Thread):
|
||||||
try:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
chan = self.channels[chanid]
|
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)
|
self._log(INFO, 'Secsh channel %d opened.' % chanid)
|
||||||
if self.channel_events.has_key(chanid):
|
if self.channel_events.has_key(chanid):
|
||||||
self.channel_events[chanid].set()
|
self.channel_events[chanid].set()
|
||||||
|
@ -815,8 +954,8 @@ class BaseTransport(threading.Thread):
|
||||||
reason = m.get_int()
|
reason = m.get_int()
|
||||||
reason_str = m.get_string()
|
reason_str = m.get_string()
|
||||||
lang = m.get_string()
|
lang = m.get_string()
|
||||||
if CONNECTION_FAILED_CODE.has_key(reason):
|
if _CONNECTION_FAILED_CODE.has_key(reason):
|
||||||
reason_text = CONNECTION_FAILED_CODE[reason]
|
reason_text = _CONNECTION_FAILED_CODE[reason]
|
||||||
else:
|
else:
|
||||||
reason_text = '(unknown code)'
|
reason_text = '(unknown code)'
|
||||||
self._log(INFO, 'Secsh channel %d open FAILED: %s: %s' % (chanid, reason_str, reason_text))
|
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()
|
self.lock.release()
|
||||||
return
|
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):
|
def _parse_channel_open(self, m):
|
||||||
kind = m.get_string()
|
kind = m.get_string()
|
||||||
chanid = m.get_int()
|
chanid = m.get_int()
|
||||||
|
@ -862,7 +997,7 @@ class BaseTransport(threading.Thread):
|
||||||
reason = self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
reason = self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||||
if reject:
|
if reject:
|
||||||
msg = Message()
|
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(chanid)
|
||||||
msg.add_int(reason)
|
msg.add_int(reason)
|
||||||
msg.add_string('')
|
msg.add_string('')
|
||||||
|
@ -873,12 +1008,12 @@ class BaseTransport(threading.Thread):
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
self.channels[my_chanid] = chan
|
self.channels[my_chanid] = chan
|
||||||
chan._set_transport(self)
|
chan._set_transport(self)
|
||||||
chan.set_window(self.window_size, self.max_packet_size)
|
chan._set_window(self.window_size, self.max_packet_size)
|
||||||
chan.set_remote_channel(chanid, initial_window_size, max_packet_size)
|
chan._set_remote_channel(chanid, initial_window_size, max_packet_size)
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
m = Message()
|
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(chanid)
|
||||||
m.add_int(my_chanid)
|
m.add_int(my_chanid)
|
||||||
m.add_int(self.window_size)
|
m.add_int(self.window_size)
|
||||||
|
@ -892,22 +1027,6 @@ class BaseTransport(threading.Thread):
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
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):
|
def _parse_debug(self, m):
|
||||||
always_display = m.get_boolean()
|
always_display = m.get_boolean()
|
||||||
msg = m.get_string()
|
msg = m.get_string()
|
||||||
|
@ -915,19 +1034,19 @@ class BaseTransport(threading.Thread):
|
||||||
self._log(DEBUG, 'Debug msg: ' + safe_string(msg))
|
self._log(DEBUG, 'Debug msg: ' + safe_string(msg))
|
||||||
|
|
||||||
_handler_table = {
|
_handler_table = {
|
||||||
MSG_NEWKEYS: _parse_newkeys,
|
_MSG_NEWKEYS: _parse_newkeys,
|
||||||
MSG_CHANNEL_OPEN_SUCCESS: _parse_channel_open_success,
|
_MSG_CHANNEL_OPEN_SUCCESS: _parse_channel_open_success,
|
||||||
MSG_CHANNEL_OPEN_FAILURE: _parse_channel_open_failure,
|
_MSG_CHANNEL_OPEN_FAILURE: _parse_channel_open_failure,
|
||||||
MSG_CHANNEL_OPEN: _parse_channel_open,
|
_MSG_CHANNEL_OPEN: _parse_channel_open,
|
||||||
MSG_KEXINIT: _negotiate_keys,
|
_MSG_KEXINIT: _negotiate_keys,
|
||||||
}
|
}
|
||||||
|
|
||||||
_channel_handler_table = {
|
_channel_handler_table = {
|
||||||
MSG_CHANNEL_SUCCESS: Channel.request_success,
|
_MSG_CHANNEL_SUCCESS: Channel._request_success,
|
||||||
MSG_CHANNEL_FAILURE: Channel.request_failed,
|
_MSG_CHANNEL_FAILURE: Channel._request_failed,
|
||||||
MSG_CHANNEL_DATA: Channel.feed,
|
_MSG_CHANNEL_DATA: Channel._feed,
|
||||||
MSG_CHANNEL_WINDOW_ADJUST: Channel.window_adjust,
|
_MSG_CHANNEL_WINDOW_ADJUST: Channel._window_adjust,
|
||||||
MSG_CHANNEL_REQUEST: Channel.handle_request,
|
_MSG_CHANNEL_REQUEST: Channel._handle_request,
|
||||||
MSG_CHANNEL_EOF: Channel.handle_eof,
|
_MSG_CHANNEL_EOF: Channel._handle_eof,
|
||||||
MSG_CHANNEL_CLOSE: Channel.handle_close,
|
_MSG_CHANNEL_CLOSE: Channel._handle_close,
|
||||||
}
|
}
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -3,7 +3,7 @@ from distutils.core import setup
|
||||||
longdesc = '''
|
longdesc = '''
|
||||||
This is a library for making SSH2 connections (client or server).
|
This is a library for making SSH2 connections (client or server).
|
||||||
Emphasis is on using SSH2 as an alternative to SSL for making secure
|
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.
|
are supported.
|
||||||
|
|
||||||
(Previous name: secsh)
|
(Previous name: secsh)
|
||||||
|
|
Loading…
Reference in New Issue