From ed280d5ac360e2af796e9bd973d7b4df89f0c449 Mon Sep 17 00:00:00 2001 From: "Jeremy T. Bouse" Date: Fri, 27 Nov 2009 16:20:12 -0500 Subject: Imported Upstream version 1.7.4 --- docs/paramiko.transport-pysrc.html | 4154 ++++++++++++++++++++++++++++++++++++ 1 file changed, 4154 insertions(+) create mode 100644 docs/paramiko.transport-pysrc.html (limited to 'docs/paramiko.transport-pysrc.html') diff --git a/docs/paramiko.transport-pysrc.html b/docs/paramiko.transport-pysrc.html new file mode 100644 index 0000000..f0d6fb1 --- /dev/null +++ b/docs/paramiko.transport-pysrc.html @@ -0,0 +1,4154 @@ + + + + + paramiko.transport + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + 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 = [] 
+
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 exchange. + 390 @rtype: L{SecurityOptions} + 391 """ + 392 return SecurityOptions(self) +
393 +
394 - def start_client(self, event=None): +
395 """ + 396 Negotiate a new SSH2 session as a client. This is the first step after + 397 creating a new L{Transport}. A separate thread is created for protocol + 398 negotiation. + 399 + 400 If an event is passed in, this method returns immediately. When + 401 negotiation is done (successful or not), the given C{Event} will + 402 be triggered. On failure, L{is_active} will return C{False}. + 403 + 404 (Since 1.4) If C{event} is C{None}, this method will not return until + 405 negotation is done. On success, the method returns normally. + 406 Otherwise an SSHException is raised. + 407 + 408 After a successful negotiation, you will usually want to authenticate, + 409 calling L{auth_password <Transport.auth_password>} or + 410 L{auth_publickey <Transport.auth_publickey>}. + 411 + 412 @note: L{connect} is a simpler method for connecting as a client. + 413 + 414 @note: After calling this method (or L{start_server} or L{connect}), + 415 you should no longer directly read from or write to the original + 416 socket object. + 417 + 418 @param event: an event to trigger when negotiation is complete + 419 (optional) + 420 @type event: threading.Event + 421 + 422 @raise SSHException: if negotiation fails (and no C{event} was passed + 423 in) + 424 """ + 425 self.active = True + 426 if event is not None: + 427 # async, return immediately and let the app poll for completion + 428 self.completion_event = event + 429 self.start() + 430 return + 431 + 432 # synchronous, wait for a result + 433 self.completion_event = event = threading.Event() + 434 self.start() + 435 while True: + 436 event.wait(0.1) + 437 if not self.active: + 438 e = self.get_exception() + 439 if e is not None: + 440 raise e + 441 raise SSHException('Negotiation failed.') + 442 if event.isSet(): + 443 break +
444 +
445 - def start_server(self, event=None, server=None): +
446 """ + 447 Negotiate a new SSH2 session as a server. This is the first step after + 448 creating a new L{Transport} and setting up your server host key(s). A + 449 separate thread is created for protocol negotiation. + 450 + 451 If an event is passed in, this method returns immediately. When + 452 negotiation is done (successful or not), the given C{Event} will + 453 be triggered. On failure, L{is_active} will return C{False}. + 454 + 455 (Since 1.4) If C{event} is C{None}, this method will not return until + 456 negotation is done. On success, the method returns normally. + 457 Otherwise an SSHException is raised. + 458 + 459 After a successful negotiation, the client will need to authenticate. + 460 Override the methods + 461 L{get_allowed_auths <ServerInterface.get_allowed_auths>}, + 462 L{check_auth_none <ServerInterface.check_auth_none>}, + 463 L{check_auth_password <ServerInterface.check_auth_password>}, and + 464 L{check_auth_publickey <ServerInterface.check_auth_publickey>} in the + 465 given C{server} object to control the authentication process. + 466 + 467 After a successful authentication, the client should request to open + 468 a channel. Override + 469 L{check_channel_request <ServerInterface.check_channel_request>} in the + 470 given C{server} object to allow channels to be opened. + 471 + 472 @note: After calling this method (or L{start_client} or L{connect}), + 473 you should no longer directly read from or write to the original + 474 socket object. + 475 + 476 @param event: an event to trigger when negotiation is complete. + 477 @type event: threading.Event + 478 @param server: an object used to perform authentication and create + 479 L{Channel}s. + 480 @type server: L{server.ServerInterface} + 481 + 482 @raise SSHException: if negotiation fails (and no C{event} was passed + 483 in) + 484 """ + 485 if server is None: + 486 server = ServerInterface() + 487 self.server_mode = True + 488 self.server_object = server + 489 self.active = True + 490 if event is not None: + 491 # async, return immediately and let the app poll for completion + 492 self.completion_event = event + 493 self.start() + 494 return + 495 + 496 # synchronous, wait for a result + 497 self.completion_event = event = threading.Event() + 498 self.start() + 499 while True: + 500 event.wait(0.1) + 501 if not self.active: + 502 e = self.get_exception() + 503 if e is not None: + 504 raise e + 505 raise SSHException('Negotiation failed.') + 506 if event.isSet(): + 507 break +
508 +
509 - def add_server_key(self, key): +
510 """ + 511 Add a host key to the list of keys used for server mode. When behaving + 512 as a server, the host key is used to sign certain packets during the + 513 SSH2 negotiation, so that the client can trust that we are who we say + 514 we are. Because this is used for signing, the key must contain private + 515 key info, not just the public half. Only one key of each type (RSA or + 516 DSS) is kept. + 517 + 518 @param key: the host key to add, usually an L{RSAKey <rsakey.RSAKey>} or + 519 L{DSSKey <dsskey.DSSKey>}. + 520 @type key: L{PKey <pkey.PKey>} + 521 """ + 522 self.server_key_dict[key.get_name()] = key +
523 +
524 - def get_server_key(self): +
525 """ + 526 Return the active host key, in server mode. After negotiating with the + 527 client, this method will return the negotiated host key. If only one + 528 type of host key was set with L{add_server_key}, that's the only key + 529 that will ever be returned. But in cases where you have set more than + 530 one type of host key (for example, an RSA key and a DSS key), the key + 531 type will be negotiated by the client, and this method will return the + 532 key of the type agreed on. If the host key has not been negotiated + 533 yet, C{None} is returned. In client mode, the behavior is undefined. + 534 + 535 @return: host key of the type negotiated by the client, or C{None}. + 536 @rtype: L{PKey <pkey.PKey>} + 537 """ + 538 try: + 539 return self.server_key_dict[self.host_key_type] + 540 except KeyError: + 541 pass + 542 return None +
543 +
544 - def load_server_moduli(filename=None): +
545 """ + 546 I{(optional)} + 547 Load a file of prime moduli for use in doing group-exchange key + 548 negotiation in server mode. It's a rather obscure option and can be + 549 safely ignored. + 550 + 551 In server mode, the remote client may request "group-exchange" key + 552 negotiation, which asks the server to send a random prime number that + 553 fits certain criteria. These primes are pretty difficult to compute, + 554 so they can't be generated on demand. But many systems contain a file + 555 of suitable primes (usually named something like C{/etc/ssh/moduli}). + 556 If you call C{load_server_moduli} and it returns C{True}, then this + 557 file of primes has been loaded and we will support "group-exchange" in + 558 server mode. Otherwise server mode will just claim that it doesn't + 559 support that method of key negotiation. + 560 + 561 @param filename: optional path to the moduli file, if you happen to + 562 know that it's not in a standard location. + 563 @type filename: str + 564 @return: True if a moduli file was successfully loaded; False + 565 otherwise. + 566 @rtype: bool + 567 + 568 @note: This has no effect when used in client mode. + 569 """ + 570 Transport._modulus_pack = ModulusPack(randpool) + 571 # places to look for the openssh "moduli" file + 572 file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ] + 573 if filename is not None: + 574 file_list.insert(0, filename) + 575 for fn in file_list: + 576 try: + 577 Transport._modulus_pack.read_file(fn) + 578 return True + 579 except IOError: + 580 pass + 581 # none succeeded + 582 Transport._modulus_pack = None + 583 return False +
584 load_server_moduli = staticmethod(load_server_moduli) + 585 +
586 - def close(self): +
587 """ + 588 Close this session, and any open channels that are tied to it. + 589 """ + 590 if not self.active: + 591 return + 592 self.active = False + 593 self.packetizer.close() + 594 self.join() + 595 for chan in self._channels.values(): + 596 chan._unlink() +
597 +
598 - def get_remote_server_key(self): +
599 """ + 600 Return the host key of the server (in client mode). + 601 + 602 @note: Previously this call returned a tuple of (key type, key string). + 603 You can get the same effect by calling + 604 L{PKey.get_name <pkey.PKey.get_name>} for the key type, and + 605 C{str(key)} for the key string. + 606 + 607 @raise SSHException: if no session is currently active. + 608 + 609 @return: public key of the remote server + 610 @rtype: L{PKey <pkey.PKey>} + 611 """ + 612 if (not self.active) or (not self.initial_kex_done): + 613 raise SSHException('No existing session') + 614 return self.host_key +
615 +
616 - def is_active(self): +
617 """ + 618 Return true if this session is active (open). + 619 + 620 @return: True if the session is still active (open); False if the + 621 session is closed + 622 @rtype: bool + 623 """ + 624 return self.active +
625 +
626 - def open_session(self): +
627 """ + 628 Request a new channel to the server, of type C{"session"}. This + 629 is just an alias for C{open_channel('session')}. + 630 + 631 @return: a new L{Channel} + 632 @rtype: L{Channel} + 633 + 634 @raise SSHException: if the request is rejected or the session ends + 635 prematurely + 636 """ + 637 return self.open_channel('session') +
638 +
639 - def open_x11_channel(self, src_addr=None): +
640 """ + 641 Request a new channel to the client, of type C{"x11"}. This + 642 is just an alias for C{open_channel('x11', src_addr=src_addr)}. + 643 + 644 @param src_addr: the source address of the x11 server (port is the + 645 x11 port, ie. 6010) + 646 @type src_addr: (str, int) + 647 @return: a new L{Channel} + 648 @rtype: L{Channel} + 649 + 650 @raise SSHException: if the request is rejected or the session ends + 651 prematurely + 652 """ + 653 return self.open_channel('x11', src_addr=src_addr) +
654 +
655 - def open_forwarded_tcpip_channel(self, (src_addr, src_port), (dest_addr, dest_port)): +
656 """ + 657 Request a new channel back to the client, of type C{"forwarded-tcpip"}. + 658 This is used after a client has requested port forwarding, for sending + 659 incoming connections back to the client. + 660 + 661 @param src_addr: originator's address + 662 @param src_port: originator's port + 663 @param dest_addr: local (server) connected address + 664 @param dest_port: local (server) connected port + 665 """ + 666 return self.open_channel('forwarded-tcpip', (dest_addr, dest_port), (src_addr, src_port)) +
667 +
668 - def open_channel(self, kind, dest_addr=None, src_addr=None): +
669 """ + 670 Request a new channel to the server. L{Channel}s are socket-like + 671 objects used for the actual transfer of data across the session. + 672 You may only request a channel after negotiating encryption (using + 673 L{connect} or L{start_client}) and authenticating. + 674 + 675 @param kind: the kind of channel requested (usually C{"session"}, + 676 C{"forwarded-tcpip"}, C{"direct-tcpip"}, or C{"x11"}) + 677 @type kind: str + 678 @param dest_addr: the destination address of this port forwarding, + 679 if C{kind} is C{"forwarded-tcpip"} or C{"direct-tcpip"} (ignored + 680 for other channel types) + 681 @type dest_addr: (str, int) + 682 @param src_addr: the source address of this port forwarding, if + 683 C{kind} is C{"forwarded-tcpip"}, C{"direct-tcpip"}, or C{"x11"} + 684 @type src_addr: (str, int) + 685 @return: a new L{Channel} on success + 686 @rtype: L{Channel} + 687 + 688 @raise SSHException: if the request is rejected or the session ends + 689 prematurely + 690 """ + 691 chan = None + 692 if not self.active: + 693 # don't bother trying to allocate a channel + 694 return None + 695 self.lock.acquire() + 696 try: + 697 chanid = self._next_channel() + 698 m = Message() + 699 m.add_byte(chr(MSG_CHANNEL_OPEN)) + 700 m.add_string(kind) + 701 m.add_int(chanid) + 702 m.add_int(self.window_size) + 703 m.add_int(self.max_packet_size) + 704 if (kind == 'forwarded-tcpip') or (kind == 'direct-tcpip'): + 705 m.add_string(dest_addr[0]) + 706 m.add_int(dest_addr[1]) + 707 m.add_string(src_addr[0]) + 708 m.add_int(src_addr[1]) + 709 elif kind == 'x11': + 710 m.add_string(src_addr[0]) + 711 m.add_int(src_addr[1]) + 712 chan = Channel(chanid) + 713 self._channels.put(chanid, chan) + 714 self.channel_events[chanid] = event = threading.Event() + 715 self.channels_seen[chanid] = True + 716 chan._set_transport(self) + 717 chan._set_window(self.window_size, self.max_packet_size) + 718 finally: + 719 self.lock.release() + 720 self._send_user_message(m) + 721 while True: + 722 event.wait(0.1); + 723 if not self.active: + 724 e = self.get_exception() + 725 if e is None: + 726 e = SSHException('Unable to open channel.') + 727 raise e + 728 if event.isSet(): + 729 break + 730 chan = self._channels.get(chanid) + 731 if chan is not None: + 732 return chan + 733 e = self.get_exception() + 734 if e is None: + 735 e = SSHException('Unable to open channel.') + 736 raise e +
737 +
738 - def request_port_forward(self, address, port, handler=None): +
739 """ + 740 Ask the server to forward TCP connections from a listening port on + 741 the server, across this SSH session. + 742 + 743 If a handler is given, that handler is called from a different thread + 744 whenever a forwarded connection arrives. The handler parameters are:: + 745 + 746 handler(channel, (origin_addr, origin_port), (server_addr, server_port)) + 747 + 748 where C{server_addr} and C{server_port} are the address and port that + 749 the server was listening on. + 750 + 751 If no handler is set, the default behavior is to send new incoming + 752 forwarded connections into the accept queue, to be picked up via + 753 L{accept}. + 754 + 755 @param address: the address to bind when forwarding + 756 @type address: str + 757 @param port: the port to forward, or 0 to ask the server to allocate + 758 any port + 759 @type port: int + 760 @param handler: optional handler for incoming forwarded connections + 761 @type handler: function(Channel, (str, int), (str, int)) + 762 @return: the port # allocated by the server + 763 @rtype: int + 764 + 765 @raise SSHException: if the server refused the TCP forward request + 766 """ + 767 if not self.active: + 768 raise SSHException('SSH session not active') + 769 address = str(address) + 770 port = int(port) + 771 response = self.global_request('tcpip-forward', (address, port), wait=True) + 772 if response is None: + 773 raise SSHException('TCP forwarding request denied') + 774 if port == 0: + 775 port = response.get_int() + 776 if handler is None: + 777 def default_handler(channel, (src_addr, src_port), (dest_addr, dest_port)): + 778 self._queue_incoming_channel(channel) +
779 handler = default_handler + 780 self._tcp_handler = handler + 781 return port +
782 +
783 - def cancel_port_forward(self, address, port): +
784 """ + 785 Ask the server to cancel a previous port-forwarding request. No more + 786 connections to the given address & port will be forwarded across this + 787 ssh connection. + 788 + 789 @param address: the address to stop forwarding + 790 @type address: str + 791 @param port: the port to stop forwarding + 792 @type port: int + 793 """ + 794 if not self.active: + 795 return + 796 self._tcp_handler = None + 797 self.global_request('cancel-tcpip-forward', (address, port), wait=True) +
798 +
799 - def open_sftp_client(self): +
800 """ + 801 Create an SFTP client channel from an open transport. On success, + 802 an SFTP session will be opened with the remote host, and a new + 803 SFTPClient object will be returned. + 804 + 805 @return: a new L{SFTPClient} object, referring to an sftp session + 806 (channel) across this transport + 807 @rtype: L{SFTPClient} + 808 """ + 809 return SFTPClient.from_transport(self) +
810 +
811 - def send_ignore(self, bytes=None): +
812 """ + 813 Send a junk packet across the encrypted link. This is sometimes used + 814 to add "noise" to a connection to confuse would-be attackers. It can + 815 also be used as a keep-alive for long lived connections traversing + 816 firewalls. + 817 + 818 @param bytes: the number of random bytes to send in the payload of the + 819 ignored packet -- defaults to a random number from 10 to 41. + 820 @type bytes: int + 821 """ + 822 m = Message() + 823 m.add_byte(chr(MSG_IGNORE)) + 824 randpool.stir() + 825 if bytes is None: + 826 bytes = (ord(randpool.get_bytes(1)) % 32) + 10 + 827 m.add_bytes(randpool.get_bytes(bytes)) + 828 self._send_user_message(m) +
829 +
830 - def renegotiate_keys(self): +
831 """ + 832 Force this session to switch to new keys. Normally this is done + 833 automatically after the session hits a certain number of packets or + 834 bytes sent or received, but this method gives you the option of forcing + 835 new keys whenever you want. Negotiating new keys causes a pause in + 836 traffic both ways as the two sides swap keys and do computations. This + 837 method returns when the session has switched to new keys. + 838 + 839 @raise SSHException: if the key renegotiation failed (which causes the + 840 session to end) + 841 """ + 842 self.completion_event = threading.Event() + 843 self._send_kex_init() + 844 while True: + 845 self.completion_event.wait(0.1) + 846 if not self.active: + 847 e = self.get_exception() + 848 if e is not None: + 849 raise e + 850 raise SSHException('Negotiation failed.') + 851 if self.completion_event.isSet(): + 852 break + 853 return +
854 +
855 - def set_keepalive(self, interval): +
856 """ + 857 Turn on/off keepalive packets (default is off). If this is set, after + 858 C{interval} seconds without sending any data over the connection, a + 859 "keepalive" packet will be sent (and ignored by the remote host). This + 860 can be useful to keep connections alive over a NAT, for example. + 861 + 862 @param interval: seconds to wait before sending a keepalive packet (or + 863 0 to disable keepalives). + 864 @type interval: int + 865 """ + 866 self.packetizer.set_keepalive(interval, + 867 lambda x=weakref.proxy(self): x.global_request('keepalive@lag.net', wait=False)) +
868 +
869 - def global_request(self, kind, data=None, wait=True): +
870 """ + 871 Make a global request to the remote host. These are normally + 872 extensions to the SSH2 protocol. + 873 + 874 @param kind: name of the request. + 875 @type kind: str + 876 @param data: an optional tuple containing additional data to attach + 877 to the request. + 878 @type data: tuple + 879 @param wait: C{True} if this method should not return until a response + 880 is received; C{False} otherwise. + 881 @type wait: bool + 882 @return: a L{Message} containing possible additional data if the + 883 request was successful (or an empty L{Message} if C{wait} was + 884 C{False}); C{None} if the request was denied. + 885 @rtype: L{Message} + 886 """ + 887 if wait: + 888 self.completion_event = threading.Event() + 889 m = Message() + 890 m.add_byte(chr(MSG_GLOBAL_REQUEST)) + 891 m.add_string(kind) + 892 m.add_boolean(wait) + 893 if data is not None: + 894 m.add(*data) + 895 self._log(DEBUG, 'Sending global request "%s"' % kind) + 896 self._send_user_message(m) + 897 if not wait: + 898 return None + 899 while True: + 900 self.completion_event.wait(0.1) + 901 if not self.active: + 902 return None + 903 if self.completion_event.isSet(): + 904 break + 905 return self.global_response +
906 +
907 - def accept(self, timeout=None): +
908 """ + 909 Return the next channel opened by the client over this transport, in + 910 server mode. If no channel is opened before the given timeout, C{None} + 911 is returned. + 912 + 913 @param timeout: seconds to wait for a channel, or C{None} to wait + 914 forever + 915 @type timeout: int + 916 @return: a new Channel opened by the client + 917 @rtype: L{Channel} + 918 """ + 919 self.lock.acquire() + 920 try: + 921 if len(self.server_accepts) > 0: + 922 chan = self.server_accepts.pop(0) + 923 else: + 924 self.server_accept_cv.wait(timeout) + 925 if len(self.server_accepts) > 0: + 926 chan = self.server_accepts.pop(0) + 927 else: + 928 # timeout + 929 chan = None + 930 finally: + 931 self.lock.release() + 932 return chan +
933 +
934 - def connect(self, hostkey=None, username='', password=None, pkey=None): +
935 """ + 936 Negotiate an SSH2 session, and optionally verify the server's host key + 937 and authenticate using a password or private key. This is a shortcut + 938 for L{start_client}, L{get_remote_server_key}, and + 939 L{Transport.auth_password} or L{Transport.auth_publickey}. Use those + 940 methods if you want more control. + 941 + 942 You can use this method immediately after creating a Transport to + 943 negotiate encryption with a server. If it fails, an exception will be + 944 thrown. On success, the method will return cleanly, and an encrypted + 945 session exists. You may immediately call L{open_channel} or + 946 L{open_session} to get a L{Channel} object, which is used for data + 947 transfer. + 948 + 949 @note: If you fail to supply a password or private key, this method may + 950 succeed, but a subsequent L{open_channel} or L{open_session} call may + 951 fail because you haven't authenticated yet. + 952 + 953 @param hostkey: the host key expected from the server, or C{None} if + 954 you don't want to do host key verification. + 955 @type hostkey: L{PKey<pkey.PKey>} + 956 @param username: the username to authenticate as. + 957 @type username: str + 958 @param password: a password to use for authentication, if you want to + 959 use password authentication; otherwise C{None}. + 960 @type password: str + 961 @param pkey: a private key to use for authentication, if you want to + 962 use private key authentication; otherwise C{None}. + 963 @type pkey: L{PKey<pkey.PKey>} + 964 + 965 @raise SSHException: if the SSH2 negotiation fails, the host key + 966 supplied by the server is incorrect, or authentication fails. + 967 """ + 968 if hostkey is not None: + 969 self._preferred_keys = [ hostkey.get_name() ] + 970 + 971 self.start_client() + 972 + 973 # check host key if we were given one + 974 if (hostkey is not None): + 975 key = self.get_remote_server_key() + 976 if (key.get_name() != hostkey.get_name()) or (str(key) != str(hostkey)): + 977 self._log(DEBUG, 'Bad host key from server') + 978 self._log(DEBUG, 'Expected: %s: %s' % (hostkey.get_name(), repr(str(hostkey)))) + 979 self._log(DEBUG, 'Got : %s: %s' % (key.get_name(), repr(str(key)))) + 980 raise SSHException('Bad host key from server') + 981 self._log(DEBUG, 'Host key verified (%s)' % hostkey.get_name()) + 982 + 983 if (pkey is not None) or (password is not None): + 984 if password is not None: + 985 self._log(DEBUG, 'Attempting password auth...') + 986 self.auth_password(username, password) + 987 else: + 988 self._log(DEBUG, 'Attempting public-key auth...') + 989 self.auth_publickey(username, pkey) + 990 + 991 return +
992 +
993 - def get_exception(self): +
994 """ + 995 Return any exception that happened during the last server request. + 996 This can be used to fetch more specific error information after using + 997 calls like L{start_client}. The exception (if any) is cleared after + 998 this call. + 999 +1000 @return: an exception, or C{None} if there is no stored exception. +1001 @rtype: Exception +1002 +1003 @since: 1.1 +1004 """ +1005 self.lock.acquire() +1006 try: +1007 e = self.saved_exception +1008 self.saved_exception = None +1009 return e +1010 finally: +1011 self.lock.release() +
1012 +
1013 - def set_subsystem_handler(self, name, handler, *larg, **kwarg): +
1014 """ +1015 Set the handler class for a subsystem in server mode. If a request +1016 for this subsystem is made on an open ssh channel later, this handler +1017 will be constructed and called -- see L{SubsystemHandler} for more +1018 detailed documentation. +1019 +1020 Any extra parameters (including keyword arguments) are saved and +1021 passed to the L{SubsystemHandler} constructor later. +1022 +1023 @param name: name of the subsystem. +1024 @type name: str +1025 @param handler: subclass of L{SubsystemHandler} that handles this +1026 subsystem. +1027 @type handler: class +1028 """ +1029 try: +1030 self.lock.acquire() +1031 self.subsystem_table[name] = (handler, larg, kwarg) +1032 finally: +1033 self.lock.release() +
1034 +
1035 - def is_authenticated(self): +
1036 """ +1037 Return true if this session is active and authenticated. +1038 +1039 @return: True if the session is still open and has been authenticated +1040 successfully; False if authentication failed and/or the session is +1041 closed. +1042 @rtype: bool +1043 """ +1044 return self.active and (self.auth_handler is not None) and self.auth_handler.is_authenticated() +
1045 +
1046 - def get_username(self): +
1047 """ +1048 Return the username this connection is authenticated for. If the +1049 session is not authenticated (or authentication failed), this method +1050 returns C{None}. +1051 +1052 @return: username that was authenticated, or C{None}. +1053 @rtype: string +1054 """ +1055 if not self.active or (self.auth_handler is None): +1056 return None +1057 return self.auth_handler.get_username() +
1058 +
1059 - def auth_none(self, username): +
1060 """ +1061 Try to authenticate to the server using no authentication at all. +1062 This will almost always fail. It may be useful for determining the +1063 list of authentication types supported by the server, by catching the +1064 L{BadAuthenticationType} exception raised. +1065 +1066 @param username: the username to authenticate as +1067 @type username: string +1068 @return: list of auth types permissible for the next stage of +1069 authentication (normally empty) +1070 @rtype: list +1071 +1072 @raise BadAuthenticationType: if "none" authentication isn't allowed +1073 by the server for this user +1074 @raise SSHException: if the authentication failed due to a network +1075 error +1076 +1077 @since: 1.5 +1078 """ +1079 if (not self.active) or (not self.initial_kex_done): +1080 raise SSHException('No existing session') +1081 my_event = threading.Event() +1082 self.auth_handler = AuthHandler(self) +1083 self.auth_handler.auth_none(username, my_event) +1084 return self.auth_handler.wait_for_response(my_event) +
1085 +
1086 - def auth_password(self, username, password, event=None, fallback=True): +
1087 """ +1088 Authenticate to the server using a password. The username and password +1089 are sent over an encrypted link. +1090 +1091 If an C{event} is passed in, this method will return immediately, and +1092 the event will be triggered once authentication succeeds or fails. On +1093 success, L{is_authenticated} will return C{True}. On failure, you may +1094 use L{get_exception} to get more detailed error information. +1095 +1096 Since 1.1, if no event is passed, this method will block until the +1097 authentication succeeds or fails. On failure, an exception is raised. +1098 Otherwise, the method simply returns. +1099 +1100 Since 1.5, if no event is passed and C{fallback} is C{True} (the +1101 default), if the server doesn't support plain password authentication +1102 but does support so-called "keyboard-interactive" mode, an attempt +1103 will be made to authenticate using this interactive mode. If it fails, +1104 the normal exception will be thrown as if the attempt had never been +1105 made. This is useful for some recent Gentoo and Debian distributions, +1106 which turn off plain password authentication in a misguided belief +1107 that interactive authentication is "more secure". (It's not.) +1108 +1109 If the server requires multi-step authentication (which is very rare), +1110 this method will return a list of auth types permissible for the next +1111 step. Otherwise, in the normal case, an empty list is returned. +1112 +1113 @param username: the username to authenticate as +1114 @type username: str +1115 @param password: the password to authenticate with +1116 @type password: str or unicode +1117 @param event: an event to trigger when the authentication attempt is +1118 complete (whether it was successful or not) +1119 @type event: threading.Event +1120 @param fallback: C{True} if an attempt at an automated "interactive" +1121 password auth should be made if the server doesn't support normal +1122 password auth +1123 @type fallback: bool +1124 @return: list of auth types permissible for the next stage of +1125 authentication (normally empty) +1126 @rtype: list +1127 +1128 @raise BadAuthenticationType: if password authentication isn't +1129 allowed by the server for this user (and no event was passed in) +1130 @raise AuthenticationException: if the authentication failed (and no +1131 event was passed in) +1132 @raise SSHException: if there was a network error +1133 """ +1134 if (not self.active) or (not self.initial_kex_done): +1135 # we should never try to send the password unless we're on a secure link +1136 raise SSHException('No existing session') +1137 if event is None: +1138 my_event = threading.Event() +1139 else: +1140 my_event = event +1141 self.auth_handler = AuthHandler(self) +1142 self.auth_handler.auth_password(username, password, my_event) +1143 if event is not None: +1144 # caller wants to wait for event themselves +1145 return [] +1146 try: +1147 return self.auth_handler.wait_for_response(my_event) +1148 except BadAuthenticationType, x: +1149 # if password auth isn't allowed, but keyboard-interactive *is*, try to fudge it +1150 if not fallback or ('keyboard-interactive' not in x.allowed_types): +1151 raise +1152 try: +1153 def handler(title, instructions, fields): +1154 if len(fields) > 1: +1155 raise SSHException('Fallback authentication failed.') +1156 if len(fields) == 0: +1157 # for some reason, at least on os x, a 2nd request will +1158 # be made with zero fields requested. maybe it's just +1159 # to try to fake out automated scripting of the exact +1160 # type we're doing here. *shrug* :) +1161 return [] +1162 return [ password ] +
1163 return self.auth_interactive(username, handler) +1164 except SSHException, ignored: +1165 # attempt failed; just raise the original exception +1166 raise x +1167 return None +1168 +
1169 - def auth_publickey(self, username, key, event=None): +
1170 """ +1171 Authenticate to the server using a private key. The key is used to +1172 sign data from the server, so it must include the private part. +1173 +1174 If an C{event} is passed in, this method will return immediately, and +1175 the event will be triggered once authentication succeeds or fails. On +1176 success, L{is_authenticated} will return C{True}. On failure, you may +1177 use L{get_exception} to get more detailed error information. +1178 +1179 Since 1.1, if no event is passed, this method will block until the +1180 authentication succeeds or fails. On failure, an exception is raised. +1181 Otherwise, the method simply returns. +1182 +1183 If the server requires multi-step authentication (which is very rare), +1184 this method will return a list of auth types permissible for the next +1185 step. Otherwise, in the normal case, an empty list is returned. +1186 +1187 @param username: the username to authenticate as +1188 @type username: string +1189 @param key: the private key to authenticate with +1190 @type key: L{PKey <pkey.PKey>} +1191 @param event: an event to trigger when the authentication attempt is +1192 complete (whether it was successful or not) +1193 @type event: threading.Event +1194 @return: list of auth types permissible for the next stage of +1195 authentication (normally empty) +1196 @rtype: list +1197 +1198 @raise BadAuthenticationType: if public-key authentication isn't +1199 allowed by the server for this user (and no event was passed in) +1200 @raise AuthenticationException: if the authentication failed (and no +1201 event was passed in) +1202 @raise SSHException: if there was a network error +1203 """ +1204 if (not self.active) or (not self.initial_kex_done): +1205 # we should never try to authenticate unless we're on a secure link +1206 raise SSHException('No existing session') +1207 if event is None: +1208 my_event = threading.Event() +1209 else: +1210 my_event = event +1211 self.auth_handler = AuthHandler(self) +1212 self.auth_handler.auth_publickey(username, key, my_event) +1213 if event is not None: +1214 # caller wants to wait for event themselves +1215 return [] +1216 return self.auth_handler.wait_for_response(my_event) +
1217 +
1218 - def auth_interactive(self, username, handler, submethods=''): +
1219 """ +1220 Authenticate to the server interactively. A handler is used to answer +1221 arbitrary questions from the server. On many servers, this is just a +1222 dumb wrapper around PAM. +1223 +1224 This method will block until the authentication succeeds or fails, +1225 peroidically calling the handler asynchronously to get answers to +1226 authentication questions. The handler may be called more than once +1227 if the server continues to ask questions. +1228 +1229 The handler is expected to be a callable that will handle calls of the +1230 form: C{handler(title, instructions, prompt_list)}. The C{title} is +1231 meant to be a dialog-window title, and the C{instructions} are user +1232 instructions (both are strings). C{prompt_list} will be a list of +1233 prompts, each prompt being a tuple of C{(str, bool)}. The string is +1234 the prompt and the boolean indicates whether the user text should be +1235 echoed. +1236 +1237 A sample call would thus be: +1238 C{handler('title', 'instructions', [('Password:', False)])}. +1239 +1240 The handler should return a list or tuple of answers to the server's +1241 questions. +1242 +1243 If the server requires multi-step authentication (which is very rare), +1244 this method will return a list of auth types permissible for the next +1245 step. Otherwise, in the normal case, an empty list is returned. +1246 +1247 @param username: the username to authenticate as +1248 @type username: string +1249 @param handler: a handler for responding to server questions +1250 @type handler: callable +1251 @param submethods: a string list of desired submethods (optional) +1252 @type submethods: str +1253 @return: list of auth types permissible for the next stage of +1254 authentication (normally empty). +1255 @rtype: list +1256 +1257 @raise BadAuthenticationType: if public-key authentication isn't +1258 allowed by the server for this user +1259 @raise AuthenticationException: if the authentication failed +1260 @raise SSHException: if there was a network error +1261 +1262 @since: 1.5 +1263 """ +1264 if (not self.active) or (not self.initial_kex_done): +1265 # we should never try to authenticate unless we're on a secure link +1266 raise SSHException('No existing session') +1267 my_event = threading.Event() +1268 self.auth_handler = AuthHandler(self) +1269 self.auth_handler.auth_interactive(username, handler, my_event, submethods) +1270 return self.auth_handler.wait_for_response(my_event) +
1271 +
1272 - def set_log_channel(self, name): +
1273 """ +1274 Set the channel for this transport's logging. The default is +1275 C{"paramiko.transport"} but it can be set to anything you want. +1276 (See the C{logging} module for more info.) SSH Channels will log +1277 to a sub-channel of the one specified. +1278 +1279 @param name: new channel name for logging +1280 @type name: str +1281 +1282 @since: 1.1 +1283 """ +1284 self.log_name = name +1285 self.logger = util.get_logger(name) +1286 self.packetizer.set_log(self.logger) +
1287 +
1288 - def get_log_channel(self): +
1289 """ +1290 Return the channel name used for this transport's logging. +1291 +1292 @return: channel name. +1293 @rtype: str +1294 +1295 @since: 1.2 +1296 """ +1297 return self.log_name +
1298 +
1299 - def set_hexdump(self, hexdump): +
1300 """ +1301 Turn on/off logging a hex dump of protocol traffic at DEBUG level in +1302 the logs. Normally you would want this off (which is the default), +1303 but if you are debugging something, it may be useful. +1304 +1305 @param hexdump: C{True} to log protocol traffix (in hex) to the log; +1306 C{False} otherwise. +1307 @type hexdump: bool +1308 """ +1309 self.packetizer.set_hexdump(hexdump) +
1310 +
1311 - def get_hexdump(self): +
1312 """ +1313 Return C{True} if the transport is currently logging hex dumps of +1314 protocol traffic. +1315 +1316 @return: C{True} if hex dumps are being logged +1317 @rtype: bool +1318 +1319 @since: 1.4 +1320 """ +1321 return self.packetizer.get_hexdump() +
1322 +
1323 - def use_compression(self, compress=True): +
1324 """ +1325 Turn on/off compression. This will only have an affect before starting +1326 the transport (ie before calling L{connect}, etc). By default, +1327 compression is off since it negatively affects interactive sessions. +1328 +1329 @param compress: C{True} to ask the remote client/server to compress +1330 traffic; C{False} to refuse compression +1331 @type compress: bool +1332 +1333 @since: 1.5.2 +1334 """ +1335 if compress: +1336 self._preferred_compression = ( 'zlib@openssh.com', 'zlib', 'none' ) +1337 else: +1338 self._preferred_compression = ( 'none', ) +
1339 +
1340 - def getpeername(self): +
1341 """ +1342 Return the address of the remote side of this Transport, if possible. +1343 This is effectively a wrapper around C{'getpeername'} on the underlying +1344 socket. If the socket-like object has no C{'getpeername'} method, +1345 then C{("unknown", 0)} is returned. +1346 +1347 @return: the address if the remote host, if known +1348 @rtype: tuple(str, int) +1349 """ +1350 gp = getattr(self.sock, 'getpeername', None) +1351 if gp is None: +1352 return ('unknown', 0) +1353 return gp() +
1354 +
1355 - def stop_thread(self): +
1356 self.active = False +1357 self.packetizer.close() +
1358 +1359 +1360 ### internals... +1361 +1362 +
1363 - def _log(self, level, msg, *args): +
1364 if issubclass(type(msg), list): +1365 for m in msg: +1366 self.logger.log(level, m) +1367 else: +1368 self.logger.log(level, msg, *args) +
1369 +
1370 - def _get_modulus_pack(self): +
1371 "used by KexGex to find primes for group exchange" +1372 return self._modulus_pack +
1373 +
1374 - def _next_channel(self): +
1375 "you are holding the lock" +1376 chanid = self._channel_counter +1377 while self._channels.get(chanid) is not None: +1378 self._channel_counter = (self._channel_counter + 1) & 0xffffff +1379 chanid = self._channel_counter +1380 self._channel_counter = (self._channel_counter + 1) & 0xffffff +1381 return chanid +
1382 +1386 +
1387 - def _send_message(self, data): +
1388 self.packetizer.send_message(data) +
1389 +
1390 - def _send_user_message(self, data): +
1391 """ +1392 send a message, but block if we're in key negotiation. this is used +1393 for user-initiated requests. +1394 """ +1395 while True: +1396 self.clear_to_send.wait(0.1) +1397 if not self.active: +1398 self._log(DEBUG, 'Dropping user packet because connection is dead.') +1399 return +1400 self.clear_to_send_lock.acquire() +1401 if self.clear_to_send.isSet(): +1402 break +1403 self.clear_to_send_lock.release() +1404 try: +1405 self._send_message(data) +1406 finally: +1407 self.clear_to_send_lock.release() +
1408 +
1409 - def _set_K_H(self, k, h): +
1410 "used by a kex object to set the K (root key) and H (exchange hash)" +1411 self.K = k +1412 self.H = h +1413 if self.session_id == None: +1414 self.session_id = h +
1415 +
1416 - def _expect_packet(self, *ptypes): +
1417 "used by a kex object to register the next packet type it expects to see" +1418 self._expected_packet = tuple(ptypes) +
1419 +
1420 - def _verify_key(self, host_key, sig): +
1421 key = self._key_info[self.host_key_type](Message(host_key)) +1422 if key is None: +1423 raise SSHException('Unknown host key type') +1424 if not key.verify_ssh_sig(self.H, Message(sig)): +1425 raise SSHException('Signature verification (%s) failed. Boo. Robey should debug this.' % self.host_key_type) +1426 self.host_key = key +
1427 +
1428 - def _compute_key(self, id, nbytes): +
1429 "id is 'A' - 'F' for the various keys used by ssh" +1430 m = Message() +1431 m.add_mpint(self.K) +1432 m.add_bytes(self.H) +1433 m.add_byte(id) +1434 m.add_bytes(self.session_id) +1435 out = sofar = SHA.new(str(m)).digest() +1436 while len(out) < nbytes: +1437 m = Message() +1438 m.add_mpint(self.K) +1439 m.add_bytes(self.H) +1440 m.add_bytes(sofar) +1441 digest = SHA.new(str(m)).digest() +1442 out += digest +1443 sofar += digest +1444 return out[:nbytes] +
1445 +
1446 - def _get_cipher(self, name, key, iv): +
1447 if name not in self._cipher_info: +1448 raise SSHException('Unknown client cipher ' + name) +1449 return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv) +
1450 +
1451 - def _set_x11_handler(self, handler): +
1452 # only called if a channel has turned on x11 forwarding +1453 if handler is None: +1454 # by default, use the same mechanism as accept() +1455 def default_handler(channel, (src_addr, src_port)): +1456 self._queue_incoming_channel(channel) +
1457 self._x11_handler = default_handler +1458 else: +1459 self._x11_handler = handler +1460 +
1461 - def _queue_incoming_channel(self, channel): +
1462 self.lock.acquire() +1463 try: +1464 self.server_accepts.append(channel) +1465 self.server_accept_cv.notify() +1466 finally: +1467 self.lock.release() +
1468 +
1469 - def run(self): +
1470 # (use the exposed "run" method, because if we specify a thread target +1471 # of a private method, threading.Thread will keep a reference to it +1472 # indefinitely, creating a GC cycle and not letting Transport ever be +1473 # GC'd. it's a bug in Thread.) +1474 +1475 # active=True occurs before the thread is launched, to avoid a race +1476 _active_threads.append(self) +1477 if self.server_mode: +1478 self._log(DEBUG, 'starting thread (server mode): %s' % hex(long(id(self)) & 0xffffffffL)) +1479 else: +1480 self._log(DEBUG, 'starting thread (client mode): %s' % hex(long(id(self)) & 0xffffffffL)) +1481 try: +1482 self.packetizer.write_all(self.local_version + '\r\n') +1483 self._check_banner() +1484 self._send_kex_init() +1485 self._expect_packet(MSG_KEXINIT) +1486 +1487 while self.active: +1488 if self.packetizer.need_rekey() and not self.in_kex: +1489 self._send_kex_init() +1490 try: +1491 ptype, m = self.packetizer.read_message() +1492 except NeedRekeyException: +1493 continue +1494 if ptype == MSG_IGNORE: +1495 continue +1496 elif ptype == MSG_DISCONNECT: +1497 self._parse_disconnect(m) +1498 self.active = False +1499 self.packetizer.close() +1500 break +1501 elif ptype == MSG_DEBUG: +1502 self._parse_debug(m) +1503 continue +1504 if len(self._expected_packet) > 0: +1505 if ptype not in self._expected_packet: +1506 raise SSHException('Expecting packet from %r, got %d' % (self._expected_packet, ptype)) +1507 self._expected_packet = tuple() +1508 if (ptype >= 30) and (ptype <= 39): +1509 self.kex_engine.parse_next(ptype, m) +1510 continue +1511 +1512 if ptype in self._handler_table: +1513 self._handler_table[ptype](self, m) +1514 elif ptype in self._channel_handler_table: +1515 chanid = m.get_int() +1516 chan = self._channels.get(chanid) +1517 if chan is not None: +1518 self._channel_handler_table[ptype](chan, m) +1519 elif chanid in self.channels_seen: +1520 self._log(DEBUG, 'Ignoring message for dead channel %d' % chanid) +1521 else: +1522 self._log(ERROR, 'Channel request for unknown channel %d' % chanid) +1523 self.active = False +1524 self.packetizer.close() +1525 elif (self.auth_handler is not None) and (ptype in self.auth_handler._handler_table): +1526 self.auth_handler._handler_table[ptype](self.auth_handler, m) +1527 else: +1528 self._log(WARNING, 'Oops, unhandled type %d' % ptype) +1529 msg = Message() +1530 msg.add_byte(chr(MSG_UNIMPLEMENTED)) +1531 msg.add_int(m.seqno) +1532 self._send_message(msg) +1533 except SSHException, e: +1534 self._log(ERROR, 'Exception: ' + str(e)) +1535 self._log(ERROR, util.tb_strings()) +1536 self.saved_exception = e +1537 except EOFError, e: +1538 self._log(DEBUG, 'EOF in transport thread') +1539 #self._log(DEBUG, util.tb_strings()) +1540 self.saved_exception = e +1541 except socket.error, e: +1542 if type(e.args) is tuple: +1543 emsg = '%s (%d)' % (e.args[1], e.args[0]) +1544 else: +1545 emsg = e.args +1546 self._log(ERROR, 'Socket exception: ' + emsg) +1547 self.saved_exception = e +1548 except Exception, e: +1549 self._log(ERROR, 'Unknown exception: ' + str(e)) +1550 self._log(ERROR, util.tb_strings()) +1551 self.saved_exception = e +1552 _active_threads.remove(self) +1553 for chan in self._channels.values(): +1554 chan._unlink() +1555 if self.active: +1556 self.active = False +1557 self.packetizer.close() +1558 if self.completion_event != None: +1559 self.completion_event.set() +1560 if self.auth_handler is not None: +1561 self.auth_handler.abort() +1562 for event in self.channel_events.values(): +1563 event.set() +1564 try: +1565 self.lock.acquire() +1566 self.server_accept_cv.notify() +1567 finally: +1568 self.lock.release() +1569 self.sock.close() +
1570 +1571 +1572 ### protocol stages +1573 +1574 +
1575 - def _negotiate_keys(self, m): +
1576 # throws SSHException on anything unusual +1577 self.clear_to_send_lock.acquire() +1578 try: +1579 self.clear_to_send.clear() +1580 finally: +1581 self.clear_to_send_lock.release() +1582 if self.local_kex_init == None: +1583 # remote side wants to renegotiate +1584 self._send_kex_init() +1585 self._parse_kex_init(m) +1586 self.kex_engine.start_kex() +
1587 +
1588 - def _check_banner(self): +
1589 # this is slow, but we only have to do it once +1590 for i in range(5): +1591 # give them 15 seconds for the first line, then just 2 seconds +1592 # each additional line. (some sites have very high latency.) +1593 if i == 0: +1594 timeout = self.banner_timeout +1595 else: +1596 timeout = 2 +1597 try: +1598 buf = self.packetizer.readline(timeout) +1599 except Exception, x: +1600 raise SSHException('Error reading SSH protocol banner' + str(x)) +1601 if buf[:4] == 'SSH-': +1602 break +1603 self._log(DEBUG, 'Banner: ' + buf) +1604 if buf[:4] != 'SSH-': +1605 raise SSHException('Indecipherable protocol version "' + buf + '"') +1606 # save this server version string for later +1607 self.remote_version = buf +1608 # pull off any attached comment +1609 comment = '' +1610 i = string.find(buf, ' ') +1611 if i >= 0: +1612 comment = buf[i+1:] +1613 buf = buf[:i] +1614 # parse out version string and make sure it matches +1615 segs = buf.split('-', 2) +1616 if len(segs) < 3: +1617 raise SSHException('Invalid SSH banner') +1618 version = segs[1] +1619 client = segs[2] +1620 if version != '1.99' and version != '2.0': +1621 raise SSHException('Incompatible version (%s instead of 2.0)' % (version,)) +1622 self._log(INFO, 'Connected (version %s, client %s)' % (version, client)) +
1623 +
1624 - def _send_kex_init(self): +
1625 """ +1626 announce to the other side that we'd like to negotiate keys, and what +1627 kind of key negotiation we support. +1628 """ +1629 self.clear_to_send_lock.acquire() +1630 try: +1631 self.clear_to_send.clear() +1632 finally: +1633 self.clear_to_send_lock.release() +1634 self.in_kex = True +1635 if self.server_mode: +1636 if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex): +1637 # can't do group-exchange if we don't have a pack of potential primes +1638 pkex = list(self.get_security_options().kex) +1639 pkex.remove('diffie-hellman-group-exchange-sha1') +1640 self.get_security_options().kex = pkex +1641 available_server_keys = filter(self.server_key_dict.keys().__contains__, +1642 self._preferred_keys) +1643 else: +1644 available_server_keys = self._preferred_keys +1645 +1646 randpool.stir() +1647 m = Message() +1648 m.add_byte(chr(MSG_KEXINIT)) +1649 m.add_bytes(randpool.get_bytes(16)) +1650 m.add_list(self._preferred_kex) +1651 m.add_list(available_server_keys) +1652 m.add_list(self._preferred_ciphers) +1653 m.add_list(self._preferred_ciphers) +1654 m.add_list(self._preferred_macs) +1655 m.add_list(self._preferred_macs) +1656 m.add_list(self._preferred_compression) +1657 m.add_list(self._preferred_compression) +1658 m.add_string('') +1659 m.add_string('') +1660 m.add_boolean(False) +1661 m.add_int(0) +1662 # save a copy for later (needed to compute a hash) +1663 self.local_kex_init = str(m) +1664 self._send_message(m) +
1665 +
1666 - def _parse_kex_init(self, m): +
1667 cookie = m.get_bytes(16) +1668 kex_algo_list = m.get_list() +1669 server_key_algo_list = m.get_list() +1670 client_encrypt_algo_list = m.get_list() +1671 server_encrypt_algo_list = m.get_list() +1672 client_mac_algo_list = m.get_list() +1673 server_mac_algo_list = m.get_list() +1674 client_compress_algo_list = m.get_list() +1675 server_compress_algo_list = m.get_list() +1676 client_lang_list = m.get_list() +1677 server_lang_list = m.get_list() +1678 kex_follows = m.get_boolean() +1679 unused = m.get_int() +1680 +1681 self._log(DEBUG, 'kex algos:' + str(kex_algo_list) + ' server key:' + str(server_key_algo_list) + \ +1682 ' client encrypt:' + str(client_encrypt_algo_list) + \ +1683 ' server encrypt:' + str(server_encrypt_algo_list) + \ +1684 ' client mac:' + str(client_mac_algo_list) + \ +1685 ' server mac:' + str(server_mac_algo_list) + \ +1686 ' client compress:' + str(client_compress_algo_list) + \ +1687 ' server compress:' + str(server_compress_algo_list) + \ +1688 ' client lang:' + str(client_lang_list) + \ +1689 ' server lang:' + str(server_lang_list) + \ +1690 ' kex follows?' + str(kex_follows)) +1691 +1692 # as a server, we pick the first item in the client's list that we support. +1693 # as a client, we pick the first item in our list that the server supports. +1694 if self.server_mode: +1695 agreed_kex = filter(self._preferred_kex.__contains__, kex_algo_list) +1696 else: +1697 agreed_kex = filter(kex_algo_list.__contains__, self._preferred_kex) +1698 if len(agreed_kex) == 0: +1699 raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)') +1700 self.kex_engine = self._kex_info[agreed_kex[0]](self) +1701 +1702 if self.server_mode: +1703 available_server_keys = filter(self.server_key_dict.keys().__contains__, +1704 self._preferred_keys) +1705 agreed_keys = filter(available_server_keys.__contains__, server_key_algo_list) +1706 else: +1707 agreed_keys = filter(server_key_algo_list.__contains__, self._preferred_keys) +1708 if len(agreed_keys) == 0: +1709 raise SSHException('Incompatible ssh peer (no acceptable host key)') +1710 self.host_key_type = agreed_keys[0] +1711 if self.server_mode and (self.get_server_key() is None): +1712 raise SSHException('Incompatible ssh peer (can\'t match requested host key type)') +1713 +1714 if self.server_mode: +1715 agreed_local_ciphers = filter(self._preferred_ciphers.__contains__, +1716 server_encrypt_algo_list) +1717 agreed_remote_ciphers = filter(self._preferred_ciphers.__contains__, +1718 client_encrypt_algo_list) +1719 else: +1720 agreed_local_ciphers = filter(client_encrypt_algo_list.__contains__, +1721 self._preferred_ciphers) +1722 agreed_remote_ciphers = filter(server_encrypt_algo_list.__contains__, +1723 self._preferred_ciphers) +1724 if (len(agreed_local_ciphers) == 0) or (len(agreed_remote_ciphers) == 0): +1725 raise SSHException('Incompatible ssh server (no acceptable ciphers)') +1726 self.local_cipher = agreed_local_ciphers[0] +1727 self.remote_cipher = agreed_remote_ciphers[0] +1728 self._log(DEBUG, 'Ciphers agreed: local=%s, remote=%s' % (self.local_cipher, self.remote_cipher)) +1729 +1730 if self.server_mode: +1731 agreed_remote_macs = filter(self._preferred_macs.__contains__, client_mac_algo_list) +1732 agreed_local_macs = filter(self._preferred_macs.__contains__, server_mac_algo_list) +1733 else: +1734 agreed_local_macs = filter(client_mac_algo_list.__contains__, self._preferred_macs) +1735 agreed_remote_macs = filter(server_mac_algo_list.__contains__, self._preferred_macs) +1736 if (len(agreed_local_macs) == 0) or (len(agreed_remote_macs) == 0): +1737 raise SSHException('Incompatible ssh server (no acceptable macs)') +1738 self.local_mac = agreed_local_macs[0] +1739 self.remote_mac = agreed_remote_macs[0] +1740 +1741 if self.server_mode: +1742 agreed_remote_compression = filter(self._preferred_compression.__contains__, client_compress_algo_list) +1743 agreed_local_compression = filter(self._preferred_compression.__contains__, server_compress_algo_list) +1744 else: +1745 agreed_local_compression = filter(client_compress_algo_list.__contains__, self._preferred_compression) +1746 agreed_remote_compression = filter(server_compress_algo_list.__contains__, self._preferred_compression) +1747 if (len(agreed_local_compression) == 0) or (len(agreed_remote_compression) == 0): +1748 raise SSHException('Incompatible ssh server (no acceptable compression) %r %r %r' % (agreed_local_compression, agreed_remote_compression, self._preferred_compression)) +1749 self.local_compression = agreed_local_compression[0] +1750 self.remote_compression = agreed_remote_compression[0] +1751 +1752 self._log(DEBUG, 'using kex %s; server key type %s; cipher: local %s, remote %s; mac: local %s, remote %s; compression: local %s, remote %s' % +1753 (agreed_kex[0], self.host_key_type, self.local_cipher, self.remote_cipher, self.local_mac, +1754 self.remote_mac, self.local_compression, self.remote_compression)) +1755 +1756 # save for computing hash later... +1757 # now wait! openssh has a bug (and others might too) where there are +1758 # actually some extra bytes (one NUL byte in openssh's case) added to +1759 # the end of the packet but not parsed. turns out we need to throw +1760 # away those bytes because they aren't part of the hash. +1761 self.remote_kex_init = chr(MSG_KEXINIT) + m.get_so_far() +
1762 +
1763 - def _activate_inbound(self): +
1764 "switch on newly negotiated encryption parameters for inbound traffic" +1765 block_size = self._cipher_info[self.remote_cipher]['block-size'] +1766 if self.server_mode: +1767 IV_in = self._compute_key('A', block_size) +1768 key_in = self._compute_key('C', self._cipher_info[self.remote_cipher]['key-size']) +1769 else: +1770 IV_in = self._compute_key('B', block_size) +1771 key_in = self._compute_key('D', self._cipher_info[self.remote_cipher]['key-size']) +1772 engine = self._get_cipher(self.remote_cipher, key_in, IV_in) +1773 mac_size = self._mac_info[self.remote_mac]['size'] +1774 mac_engine = self._mac_info[self.remote_mac]['class'] +1775 # initial mac keys are done in the hash's natural size (not the potentially truncated +1776 # transmission size) +1777 if self.server_mode: +1778 mac_key = self._compute_key('E', mac_engine.digest_size) +1779 else: +1780 mac_key = self._compute_key('F', mac_engine.digest_size) +1781 self.packetizer.set_inbound_cipher(engine, block_size, mac_engine, mac_size, mac_key) +1782 compress_in = self._compression_info[self.remote_compression][1] +1783 if (compress_in is not None) and ((self.remote_compression != 'zlib@openssh.com') or self.authenticated): +1784 self._log(DEBUG, 'Switching on inbound compression ...') +1785 self.packetizer.set_inbound_compressor(compress_in()) +
1786 +
1787 - def _activate_outbound(self): +
1788 "switch on newly negotiated encryption parameters for outbound traffic" +1789 m = Message() +1790 m.add_byte(chr(MSG_NEWKEYS)) +1791 self._send_message(m) +1792 block_size = self._cipher_info[self.local_cipher]['block-size'] +1793 if self.server_mode: +1794 IV_out = self._compute_key('B', block_size) +1795 key_out = self._compute_key('D', self._cipher_info[self.local_cipher]['key-size']) +1796 else: +1797 IV_out = self._compute_key('A', block_size) +1798 key_out = self._compute_key('C', self._cipher_info[self.local_cipher]['key-size']) +1799 engine = self._get_cipher(self.local_cipher, key_out, IV_out) +1800 mac_size = self._mac_info[self.local_mac]['size'] +1801 mac_engine = self._mac_info[self.local_mac]['class'] +1802 # initial mac keys are done in the hash's natural size (not the potentially truncated +1803 # transmission size) +1804 if self.server_mode: +1805 mac_key = self._compute_key('F', mac_engine.digest_size) +1806 else: +1807 mac_key = self._compute_key('E', mac_engine.digest_size) +1808 self.packetizer.set_outbound_cipher(engine, block_size, mac_engine, mac_size, mac_key) +1809 compress_out = self._compression_info[self.local_compression][0] +1810 if (compress_out is not None) and ((self.local_compression != 'zlib@openssh.com') or self.authenticated): +1811 self._log(DEBUG, 'Switching on outbound compression ...') +1812 self.packetizer.set_outbound_compressor(compress_out()) +1813 if not self.packetizer.need_rekey(): +1814 self.in_kex = False +1815 # we always expect to receive NEWKEYS now +1816 self._expect_packet(MSG_NEWKEYS) +
1817 +
1818 - def _auth_trigger(self): +
1819 self.authenticated = True +1820 # delayed initiation of compression +1821 if self.local_compression == 'zlib@openssh.com': +1822 compress_out = self._compression_info[self.local_compression][0] +1823 self._log(DEBUG, 'Switching on outbound compression ...') +1824 self.packetizer.set_outbound_compressor(compress_out()) +1825 if self.remote_compression == 'zlib@openssh.com': +1826 compress_in = self._compression_info[self.remote_compression][1] +1827 self._log(DEBUG, 'Switching on inbound compression ...') +1828 self.packetizer.set_inbound_compressor(compress_in()) +
1829 +
1830 - def _parse_newkeys(self, m): +
1831 self._log(DEBUG, 'Switch to new keys ...') +1832 self._activate_inbound() +1833 # can also free a bunch of stuff here +1834 self.local_kex_init = self.remote_kex_init = None +1835 self.K = None +1836 self.kex_engine = None +1837 if self.server_mode and (self.auth_handler is None): +1838 # create auth handler for server mode +1839 self.auth_handler = AuthHandler(self) +1840 if not self.initial_kex_done: +1841 # this was the first key exchange +1842 self.initial_kex_done = True +1843 # send an event? +1844 if self.completion_event != None: +1845 self.completion_event.set() +1846 # it's now okay to send data again (if this was a re-key) +1847 if not self.packetizer.need_rekey(): +1848 self.in_kex = False +1849 self.clear_to_send_lock.acquire() +1850 try: +1851 self.clear_to_send.set() +1852 finally: +1853 self.clear_to_send_lock.release() +1854 return +
1855 +
1856 - def _parse_disconnect(self, m): +
1857 code = m.get_int() +1858 desc = m.get_string() +1859 self._log(INFO, 'Disconnect (code %d): %s' % (code, desc)) +
1860 +
1861 - def _parse_global_request(self, m): +
1862 kind = m.get_string() +1863 self._log(DEBUG, 'Received global request "%s"' % kind) +1864 want_reply = m.get_boolean() +1865 if not self.server_mode: +1866 self._log(DEBUG, 'Rejecting "%s" global request from server.' % kind) +1867 ok = False +1868 elif kind == 'tcpip-forward': +1869 address = m.get_string() +1870 port = m.get_int() +1871 ok = self.server_object.check_port_forward_request(address, port) +1872 if ok != False: +1873 ok = (ok,) +1874 elif kind == 'cancel-tcpip-forward': +1875 address = m.get_string() +1876 port = m.get_int() +1877 self.server_object.cancel_port_forward_request(address, port) +1878 ok = True +1879 else: +1880 ok = self.server_object.check_global_request(kind, m) +1881 extra = () +1882 if type(ok) is tuple: +1883 extra = ok +1884 ok = True +1885 if want_reply: +1886 msg = Message() +1887 if ok: +1888 msg.add_byte(chr(MSG_REQUEST_SUCCESS)) +1889 msg.add(*extra) +1890 else: +1891 msg.add_byte(chr(MSG_REQUEST_FAILURE)) +1892 self._send_message(msg) +
1893 +
1894 - def _parse_request_success(self, m): +
1895 self._log(DEBUG, 'Global request successful.') +1896 self.global_response = m +1897 if self.completion_event is not None: +1898 self.completion_event.set() +
1899 +
1900 - def _parse_request_failure(self, m): +
1901 self._log(DEBUG, 'Global request denied.') +1902 self.global_response = None +1903 if self.completion_event is not None: +1904 self.completion_event.set() +
1905 +
1906 - def _parse_channel_open_success(self, m): +
1907 chanid = m.get_int() +1908 server_chanid = m.get_int() +1909 server_window_size = m.get_int() +1910 server_max_packet_size = m.get_int() +1911 chan = self._channels.get(chanid) +1912 if chan is None: +1913 self._log(WARNING, 'Success for unrequested channel! [??]') +1914 return +1915 self.lock.acquire() +1916 try: +1917 chan._set_remote_channel(server_chanid, server_window_size, server_max_packet_size) +1918 self._log(INFO, 'Secsh channel %d opened.' % chanid) +1919 if chanid in self.channel_events: +1920 self.channel_events[chanid].set() +1921 del self.channel_events[chanid] +1922 finally: +1923 self.lock.release() +1924 return +
1925 +
1926 - def _parse_channel_open_failure(self, m): +
1927 chanid = m.get_int() +1928 reason = m.get_int() +1929 reason_str = m.get_string() +1930 lang = m.get_string() +1931 reason_text = CONNECTION_FAILED_CODE.get(reason, '(unknown code)') +1932 self._log(INFO, 'Secsh channel %d open FAILED: %s: %s' % (chanid, reason_str, reason_text)) +1933 self.lock.acquire() +1934 try: +1935 self.saved_exception = ChannelException(reason, reason_text) +1936 if chanid in self.channel_events: +1937 self._channels.delete(chanid) +1938 if chanid in self.channel_events: +1939 self.channel_events[chanid].set() +1940 del self.channel_events[chanid] +1941 finally: +1942 self.lock.release() +1943 return +
1944 +
1945 - def _parse_channel_open(self, m): +
1946 kind = m.get_string() +1947 chanid = m.get_int() +1948 initial_window_size = m.get_int() +1949 max_packet_size = m.get_int() +1950 reject = False +1951 if (kind == 'x11') and (self._x11_handler is not None): +1952 origin_addr = m.get_string() +1953 origin_port = m.get_int() +1954 self._log(DEBUG, 'Incoming x11 connection from %s:%d' % (origin_addr, origin_port)) +1955 self.lock.acquire() +1956 try: +1957 my_chanid = self._next_channel() +1958 finally: +1959 self.lock.release() +1960 elif (kind == 'forwarded-tcpip') and (self._tcp_handler is not None): +1961 server_addr = m.get_string() +1962 server_port = m.get_int() +1963 origin_addr = m.get_string() +1964 origin_port = m.get_int() +1965 self._log(DEBUG, 'Incoming tcp forwarded connection from %s:%d' % (origin_addr, origin_port)) +1966 self.lock.acquire() +1967 try: +1968 my_chanid = self._next_channel() +1969 finally: +1970 self.lock.release() +1971 elif not self.server_mode: +1972 self._log(DEBUG, 'Rejecting "%s" channel request from server.' % kind) +1973 reject = True +1974 reason = OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED +1975 else: +1976 self.lock.acquire() +1977 try: +1978 my_chanid = self._next_channel() +1979 finally: +1980 self.lock.release() +1981 if kind == 'direct-tcpip': +1982 # handle direct-tcpip requests comming from the client +1983 dest_addr = m.get_string() +1984 dest_port = m.get_int() +1985 origin_addr = m.get_string() +1986 origin_port = m.get_int() +1987 reason = self.server_object.check_channel_direct_tcpip_request( +1988 my_chanid, (origin_addr, origin_port), +1989 (dest_addr, dest_port)) +1990 else: +1991 reason = self.server_object.check_channel_request(kind, my_chanid) +1992 if reason != OPEN_SUCCEEDED: +1993 self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind) +1994 reject = True +1995 if reject: +1996 msg = Message() +1997 msg.add_byte(chr(MSG_CHANNEL_OPEN_FAILURE)) +1998 msg.add_int(chanid) +1999 msg.add_int(reason) +2000 msg.add_string('') +2001 msg.add_string('en') +2002 self._send_message(msg) +2003 return +2004 +2005 chan = Channel(my_chanid) +2006 self.lock.acquire() +2007 try: +2008 self._channels.put(my_chanid, chan) +2009 self.channels_seen[my_chanid] = True +2010 chan._set_transport(self) +2011 chan._set_window(self.window_size, self.max_packet_size) +2012 chan._set_remote_channel(chanid, initial_window_size, max_packet_size) +2013 finally: +2014 self.lock.release() +2015 m = Message() +2016 m.add_byte(chr(MSG_CHANNEL_OPEN_SUCCESS)) +2017 m.add_int(chanid) +2018 m.add_int(my_chanid) +2019 m.add_int(self.window_size) +2020 m.add_int(self.max_packet_size) +2021 self._send_message(m) +2022 self._log(INFO, 'Secsh channel %d (%s) opened.', my_chanid, kind) +2023 if kind == 'x11': +2024 self._x11_handler(chan, (origin_addr, origin_port)) +2025 elif kind == 'forwarded-tcpip': +2026 chan.origin_addr = (origin_addr, origin_port) +2027 self._tcp_handler(chan, (origin_addr, origin_port), (server_addr, server_port)) +2028 else: +2029 self._queue_incoming_channel(chan) +
2030 +
2031 - def _parse_debug(self, m): +
2032 always_display = m.get_boolean() +2033 msg = m.get_string() +2034 lang = m.get_string() +2035 self._log(DEBUG, 'Debug msg: ' + util.safe_string(msg)) +
2036 +
2037 - def _get_subsystem_handler(self, name): +
2038 try: +2039 self.lock.acquire() +2040 if name not in self.subsystem_table: +2041 return (None, [], {}) +2042 return self.subsystem_table[name] +2043 finally: +2044 self.lock.release() +
2045 +2046 _handler_table = { +2047 MSG_NEWKEYS: _parse_newkeys, +2048 MSG_GLOBAL_REQUEST: _parse_global_request, +2049 MSG_REQUEST_SUCCESS: _parse_request_success, +2050 MSG_REQUEST_FAILURE: _parse_request_failure, +2051 MSG_CHANNEL_OPEN_SUCCESS: _parse_channel_open_success, +2052 MSG_CHANNEL_OPEN_FAILURE: _parse_channel_open_failure, +2053 MSG_CHANNEL_OPEN: _parse_channel_open, +2054 MSG_KEXINIT: _negotiate_keys, +2055 } +2056 +2057 _channel_handler_table = { +2058 MSG_CHANNEL_SUCCESS: Channel._request_success, +2059 MSG_CHANNEL_FAILURE: Channel._request_failed, +2060 MSG_CHANNEL_DATA: Channel._feed, +2061 MSG_CHANNEL_EXTENDED_DATA: Channel._feed_extended, +2062 MSG_CHANNEL_WINDOW_ADJUST: Channel._window_adjust, +2063 MSG_CHANNEL_REQUEST: Channel._handle_request, +2064 MSG_CHANNEL_EOF: Channel._handle_eof, +2065 MSG_CHANNEL_CLOSE: Channel._handle_close, +2066 } +2067 +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + -- cgit v1.2.3