Package paramiko :: Module transport
[frames] | no frames]

Source Code for Module paramiko.transport

   1  # Copyright (C) 2003-2007  Robey Pointer <robey@lag.net> 
   2  # 
   3  # This file is part of paramiko. 
   4  # 
   5  # Paramiko is free software; you can redistribute it and/or modify it under the 
   6  # terms of the GNU Lesser General Public License as published by the Free 
   7  # Software Foundation; either version 2.1 of the License, or (at your option) 
   8  # any later version. 
   9  # 
  10  # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY 
  11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
  12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
  13  # details. 
  14  # 
  15  # You should have received a copy of the GNU Lesser General Public License 
  16  # along with Paramiko; if not, write to the Free Software Foundation, Inc., 
  17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. 
  18   
  19  """ 
  20  L{Transport} handles the core SSH2 protocol. 
  21  """ 
  22   
  23  import os 
  24  import socket 
  25  import string 
  26  import struct 
  27  import sys 
  28  import threading 
  29  import time 
  30  import weakref 
  31   
  32  from paramiko import util 
  33  from paramiko.auth_handler import AuthHandler 
  34  from paramiko.channel import Channel 
  35  from paramiko.common import * 
  36  from paramiko.compress import ZlibCompressor, ZlibDecompressor 
  37  from paramiko.dsskey import DSSKey 
  38  from paramiko.kex_gex import KexGex 
  39  from paramiko.kex_group1 import KexGroup1 
  40  from paramiko.message import Message 
  41  from paramiko.packet import Packetizer, NeedRekeyException 
  42  from paramiko.primes import ModulusPack 
  43  from paramiko.rsakey import RSAKey 
  44  from paramiko.server import ServerInterface 
  45  from paramiko.sftp_client import SFTPClient 
  46  from paramiko.ssh_exception import SSHException, BadAuthenticationType, ChannelException 
  47   
  48  # these come from PyCrypt 
  49  #     http://www.amk.ca/python/writing/pycrypt/ 
  50  # i believe this on the standards track. 
  51  # PyCrypt compiled for Win32 can be downloaded from the HashTar homepage: 
  52  #     http://nitace.bsd.uchicago.edu:8080/hashtar 
  53  from Crypto.Cipher import Blowfish, AES, DES3 
  54  from Crypto.Hash import SHA, MD5 
  55   
  56   
  57  # for thread cleanup 
  58  _active_threads = [] 
59 -def _join_lingering_threads():
60 for thr in _active_threads: 61 thr.stop_thread()
62 import atexit 63 atexit.register(_join_lingering_threads) 64 65
66 -class SecurityOptions (object):
67 """ 68 Simple object containing the security preferences of an ssh transport. 69 These are tuples of acceptable ciphers, digests, key types, and key 70 exchange algorithms, listed in order of preference. 71 72 Changing the contents and/or order of these fields affects the underlying 73 L{Transport} (but only if you change them before starting the session). 74 If you try to add an algorithm that paramiko doesn't recognize, 75 C{ValueError} will be raised. If you try to assign something besides a 76 tuple to one of the fields, C{TypeError} will be raised. 77 """ 78 __slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', 'compression', '_transport' ] 79
80 - def __init__(self, transport):
81 self._transport = transport
82
83 - def __repr__(self):
84 """ 85 Returns a string representation of this object, for debugging. 86 87 @rtype: str 88 """ 89 return '<paramiko.SecurityOptions for %s>' % repr(self._transport)
90
91 - def _get_ciphers(self):
92 return self._transport._preferred_ciphers
93
94 - def _get_digests(self):
95 return self._transport._preferred_macs
96
97 - def _get_key_types(self):
98 return self._transport._preferred_keys
99
100 - def _get_kex(self):
101 return self._transport._preferred_kex
102
103 - def _get_compression(self):
104 return self._transport._preferred_compression
105
106 - def _set(self, name, orig, x):
107 if type(x) is list: 108 x = tuple(x) 109 if type(x) is not tuple: 110 raise TypeError('expected tuple or list') 111 possible = getattr(self._transport, orig).keys() 112 forbidden = filter(lambda n: n not in possible, x) 113 if len(forbidden) > 0: 114 raise ValueError('unknown cipher') 115 setattr(self._transport, name, x)
116
117 - def _set_ciphers(self, x):
118 self._set('_preferred_ciphers', '_cipher_info', x)
119
120 - def _set_digests(self, x):
121 self._set('_preferred_macs', '_mac_info', x)
122
123 - def _set_key_types(self, x):
124 self._set('_preferred_keys', '_key_info', x)
125
126 - def _set_kex(self, x):
127 self._set('_preferred_kex', '_kex_info', x)
128
129 - def _set_compression(self, x):
130 self._set('_preferred_compression', '_compression_info', x)
131 132 ciphers = property(_get_ciphers, _set_ciphers, None, 133 "Symmetric encryption ciphers") 134 digests = property(_get_digests, _set_digests, None, 135 "Digest (one-way hash) algorithms") 136 key_types = property(_get_key_types, _set_key_types, None, 137 "Public-key algorithms") 138 kex = property(_get_kex, _set_kex, None, "Key exchange algorithms") 139 compression = property(_get_compression, _set_compression, None, 140 "Compression algorithms")
141 142
143 -class ChannelMap (object):
144 - def __init__(self):
145 # (id -> Channel) 146 self._map = weakref.WeakValueDictionary() 147 self._lock = threading.Lock()
148
149 - def put(self, chanid, chan):
150 self._lock.acquire() 151 try: 152 self._map[chanid] = chan 153 finally: 154 self._lock.release()
155
156 - def get(self, chanid):
157 self._lock.acquire() 158 try: 159 return self._map.get(chanid, None) 160 finally: 161 self._lock.release()
162
163 - def delete(self, chanid):
164 self._lock.acquire() 165 try: 166 try: 167 del self._map[chanid] 168 except KeyError: 169 pass 170 finally: 171 self._lock.release()
172
173 - def values(self):
174 self._lock.acquire() 175 try: 176 return self._map.values() 177 finally: 178 self._lock.release()
179
180 - def __len__(self):
181 self._lock.acquire() 182 try: 183 return len(self._map) 184 finally: 185 self._lock.release()
186 187
188 -class Transport (threading.Thread):
189 """ 190 An SSH Transport attaches to a stream (usually a socket), negotiates an 191 encrypted session, authenticates, and then creates stream tunnels, called 192 L{Channel}s, across the session. Multiple channels can be multiplexed 193 across a single session (and often are, in the case of port forwardings). 194 """ 195 196 _PROTO_ID = '2.0' 197 _CLIENT_ID = 'paramiko_1.7.4' 198 199 _preferred_ciphers = ( 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ) 200 _preferred_macs = ( 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ) 201 _preferred_keys = ( 'ssh-rsa', 'ssh-dss' ) 202 _preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ) 203 _preferred_compression = ( 'none', ) 204 205 _cipher_info = { 206 'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 }, 207 'aes128-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16 }, 208 'aes256-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32 }, 209 '3des-cbc': { 'class': DES3, 'mode': DES3.MODE_CBC, 'block-size': 8, 'key-size': 24 }, 210 } 211 212 _mac_info = { 213 'hmac-sha1': { 'class': SHA, 'size': 20 }, 214 'hmac-sha1-96': { 'class': SHA, 'size': 12 }, 215 'hmac-md5': { 'class': MD5, 'size': 16 }, 216 'hmac-md5-96': { 'class': MD5, 'size': 12 }, 217 } 218 219 _key_info = { 220 'ssh-rsa': RSAKey, 221 'ssh-dss': DSSKey, 222 } 223 224 _kex_info = { 225 'diffie-hellman-group1-sha1': KexGroup1, 226 'diffie-hellman-group-exchange-sha1': KexGex, 227 } 228 229 _compression_info = { 230 # zlib@openssh.com is just zlib, but only turned on after a successful 231 # authentication. openssh servers may only offer this type because 232 # they've had troubles with security holes in zlib in the past. 233 'zlib@openssh.com': ( ZlibCompressor, ZlibDecompressor ), 234 'zlib': ( ZlibCompressor, ZlibDecompressor ), 235 'none': ( None, None ), 236 } 237 238 239 _modulus_pack = None 240
241 - def __init__(self, sock):
242 """ 243 Create a new SSH session over an existing socket, or socket-like 244 object. This only creates the Transport object; it doesn't begin the 245 SSH session yet. Use L{connect} or L{start_client} to begin a client 246 session, or L{start_server} to begin a server session. 247 248 If the object is not actually a socket, it must have the following 249 methods: 250 - C{send(str)}: Writes from 1 to C{len(str)} bytes, and 251 returns an int representing the number of bytes written. Returns 252 0 or raises C{EOFError} if the stream has been closed. 253 - C{recv(int)}: Reads from 1 to C{int} bytes and returns them as a 254 string. Returns 0 or raises C{EOFError} if the stream has been 255 closed. 256 - C{close()}: Closes the socket. 257 - C{settimeout(n)}: Sets a (float) timeout on I/O operations. 258 259 For ease of use, you may also pass in an address (as a tuple) or a host 260 string as the C{sock} argument. (A host string is a hostname with an 261 optional port (separated by C{":"}) which will be converted into a 262 tuple of C{(hostname, port)}.) A socket will be connected to this 263 address and used for communication. Exceptions from the C{socket} call 264 may be thrown in this case. 265 266 @param sock: a socket or socket-like object to create the session over. 267 @type sock: socket 268 """ 269 if type(sock) is str: 270 # convert "host:port" into (host, port) 271 hl = sock.split(':', 1) 272 if len(hl) == 1: 273 sock = (hl[0], 22) 274 else: 275 sock = (hl[0], int(hl[1])) 276 if type(sock) is tuple: 277 # connect to the given (host, port) 278 hostname, port = sock 279 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 280 sock.connect((hostname, port)) 281 # okay, normal socket-ish flow here... 282 threading.Thread.__init__(self) 283 self.randpool = randpool 284 self.sock = sock 285 # Python < 2.3 doesn't have the settimeout method - RogerB 286 try: 287 # we set the timeout so we can check self.active periodically to 288 # see if we should bail. socket.timeout exception is never 289 # propagated. 290 self.sock.settimeout(0.1) 291 except AttributeError: 292 pass 293 294 # negotiated crypto parameters 295 self.packetizer = Packetizer(sock) 296 self.local_version = 'SSH-' + self._PROTO_ID + '-' + self._CLIENT_ID 297 self.remote_version = '' 298 self.local_cipher = self.remote_cipher = '' 299 self.local_kex_init = self.remote_kex_init = None 300 self.local_mac = self.remote_mac = None 301 self.local_compression = self.remote_compression = None 302 self.session_id = None 303 self.host_key_type = None 304 self.host_key = None 305 306 # state used during negotiation 307 self.kex_engine = None 308 self.H = None 309 self.K = None 310 311 self.active = False 312 self.initial_kex_done = False 313 self.in_kex = False 314 self.authenticated = False 315 self._expected_packet = tuple() 316 self.lock = threading.Lock() # synchronization (always higher level than write_lock) 317 318 # tracking open channels 319 self._channels = ChannelMap() 320 self.channel_events = { } # (id -> Event) 321 self.channels_seen = { } # (id -> True) 322 self._channel_counter = 1 323 self.window_size = 65536 324 self.max_packet_size = 34816 325 self._x11_handler = None 326 self._tcp_handler = None 327 328 self.saved_exception = None 329 self.clear_to_send = threading.Event() 330 self.clear_to_send_lock = threading.Lock() 331 self.log_name = 'paramiko.transport' 332 self.logger = util.get_logger(self.log_name) 333 self.packetizer.set_log(self.logger) 334 self.auth_handler = None 335 self.global_response = None # response Message from an arbitrary global request 336 self.completion_event = None # user-defined event callbacks 337 self.banner_timeout = 15 # how long (seconds) to wait for the SSH banner 338 339 # server mode: 340 self.server_mode = False 341 self.server_object = None 342 self.server_key_dict = { } 343 self.server_accepts = [ ] 344 self.server_accept_cv = threading.Condition(self.lock) 345 self.subsystem_table = { }
346
347 - def __repr__(self):
348 """ 349 Returns a string representation of this object, for debugging. 350 351 @rtype: str 352 """ 353 out = '<paramiko.Transport at %s' % hex(long(id(self)) & 0xffffffffL) 354 if not self.active: 355 out += ' (unconnected)' 356 else: 357 if self.local_cipher != '': 358 out += ' (cipher %s, %d bits)' % (self.local_cipher, 359 self._cipher_info[self.local_cipher]['key-size'] * 8) 360 if self.is_authenticated(): 361 out += ' (active; %d open channel(s))' % len(self._channels) 362 elif self.initial_kex_done: 363 out += ' (connected; awaiting auth)' 364 else: 365 out += ' (connecting)' 366 out += '>' 367 return out
368
369 - def atfork(self):
370 """ 371 Terminate this Transport without closing the session. On posix 372 systems, if a Transport is open during process forking, both parent 373 and child will share the underlying socket, but only one process can 374 use the connection (without corrupting the session). Use this method 375 to clean up a Transport object without disrupting the other process. 376 377 @since: 1.5.3 378 """ 379 self.sock.close() 380 self.close()
381
382 - def get_security_options(self):
383 """ 384 Return a L{SecurityOptions} object which can be used to tweak the 385 encryption algorithms this transport will permit, and the order of 386 preference for them. 387 388 @return: an object that can be used to change the preferred algorithms 389 for encryption, digest (hash), public key, and key e