Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/transport.py: 15%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
2# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
3#
4# This file is part of paramiko.
5#
6# Paramiko is free software; you can redistribute it and/or modify it under the
7# terms of the GNU Lesser General Public License as published by the Free
8# Software Foundation; either version 2.1 of the License, or (at your option)
9# any later version.
10#
11# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
12# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14# details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20"""
21Core protocol implementation
22"""
24import os
25import socket
26import sys
27import threading
28import time
29import weakref
30from hashlib import md5, sha1, sha256, sha512
32from cryptography.hazmat.backends import default_backend
33from cryptography.hazmat.primitives.ciphers import (
34 algorithms,
35 Cipher,
36 modes,
37 aead,
38)
40import paramiko
41from paramiko import util
42from paramiko.auth_handler import AuthHandler, AuthOnlyHandler
43from paramiko.ssh_gss import GSSAuth
44from paramiko.channel import Channel
45from paramiko.common import (
46 xffffffff,
47 cMSG_CHANNEL_OPEN,
48 cMSG_IGNORE,
49 cMSG_GLOBAL_REQUEST,
50 DEBUG,
51 MSG_KEXINIT,
52 MSG_IGNORE,
53 MSG_DISCONNECT,
54 MSG_DEBUG,
55 ERROR,
56 WARNING,
57 cMSG_UNIMPLEMENTED,
58 INFO,
59 cMSG_KEXINIT,
60 cMSG_NEWKEYS,
61 MSG_NEWKEYS,
62 cMSG_REQUEST_SUCCESS,
63 cMSG_REQUEST_FAILURE,
64 CONNECTION_FAILED_CODE,
65 OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED,
66 OPEN_SUCCEEDED,
67 cMSG_CHANNEL_OPEN_FAILURE,
68 cMSG_CHANNEL_OPEN_SUCCESS,
69 MSG_GLOBAL_REQUEST,
70 MSG_REQUEST_SUCCESS,
71 MSG_REQUEST_FAILURE,
72 cMSG_SERVICE_REQUEST,
73 MSG_SERVICE_ACCEPT,
74 MSG_CHANNEL_OPEN_SUCCESS,
75 MSG_CHANNEL_OPEN_FAILURE,
76 MSG_CHANNEL_OPEN,
77 MSG_CHANNEL_SUCCESS,
78 MSG_CHANNEL_FAILURE,
79 MSG_CHANNEL_DATA,
80 MSG_CHANNEL_EXTENDED_DATA,
81 MSG_CHANNEL_WINDOW_ADJUST,
82 MSG_CHANNEL_REQUEST,
83 MSG_CHANNEL_EOF,
84 MSG_CHANNEL_CLOSE,
85 MIN_WINDOW_SIZE,
86 MIN_PACKET_SIZE,
87 MAX_WINDOW_SIZE,
88 DEFAULT_WINDOW_SIZE,
89 DEFAULT_MAX_PACKET_SIZE,
90 HIGHEST_USERAUTH_MESSAGE_ID,
91 MSG_UNIMPLEMENTED,
92 MSG_NAMES,
93 MSG_EXT_INFO,
94 cMSG_EXT_INFO,
95 byte_ord,
96)
97from paramiko.compress import ZlibCompressor, ZlibDecompressor
98from paramiko.dsskey import DSSKey
99from paramiko.ed25519key import Ed25519Key
100from paramiko.kex_curve25519 import KexCurve25519
101from paramiko.kex_gex import KexGex, KexGexSHA256
102from paramiko.kex_group1 import KexGroup1
103from paramiko.kex_group14 import KexGroup14, KexGroup14SHA256
104from paramiko.kex_group16 import KexGroup16SHA512
105from paramiko.kex_ecdh_nist import KexNistp256, KexNistp384, KexNistp521
106from paramiko.kex_gss import KexGSSGex, KexGSSGroup1, KexGSSGroup14
107from paramiko.message import Message
108from paramiko.packet import Packetizer, NeedRekeyException
109from paramiko.primes import ModulusPack
110from paramiko.rsakey import RSAKey
111from paramiko.ecdsakey import ECDSAKey
112from paramiko.server import ServerInterface
113from paramiko.sftp_client import SFTPClient
114from paramiko.ssh_exception import (
115 BadAuthenticationType,
116 ChannelException,
117 IncompatiblePeer,
118 MessageOrderError,
119 ProxyCommandFailure,
120 SSHException,
121)
122from paramiko.util import (
123 ClosingContextManager,
124 clamp_value,
125 b,
126)
129# TripleDES is moving from `cryptography.hazmat.primitives.ciphers.algorithms`
130# in cryptography>=43.0.0 to `cryptography.hazmat.decrepit.ciphers.algorithms`
131# It will be removed from `cryptography.hazmat.primitives.ciphers.algorithms`
132# in cryptography==48.0.0.
133#
134# Source References:
135# - https://github.com/pyca/cryptography/commit/722a6393e61b3ac
136# - https://github.com/pyca/cryptography/pull/11407/files
137try:
138 from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES
139except ImportError:
140 from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES
143# for thread cleanup
144_active_threads = []
147def _join_lingering_threads():
148 for thr in _active_threads:
149 thr.stop_thread()
152import atexit
154atexit.register(_join_lingering_threads)
157class Transport(threading.Thread, ClosingContextManager):
158 """
159 An SSH Transport attaches to a stream (usually a socket), negotiates an
160 encrypted session, authenticates, and then creates stream tunnels, called
161 `channels <.Channel>`, across the session. Multiple channels can be
162 multiplexed across a single session (and often are, in the case of port
163 forwardings).
165 Instances of this class may be used as context managers.
166 """
168 _ENCRYPT = object()
169 _DECRYPT = object()
171 _PROTO_ID = "2.0"
172 _CLIENT_ID = "paramiko_{}".format(paramiko.__version__)
174 # These tuples of algorithm identifiers are in preference order; do not
175 # reorder without reason!
176 # NOTE: if you need to modify these, we suggest leveraging the
177 # `disabled_algorithms` constructor argument (also available in SSHClient)
178 # instead of monkeypatching or subclassing.
179 _preferred_ciphers = (
180 "aes128-ctr",
181 "aes192-ctr",
182 "aes256-ctr",
183 "aes128-cbc",
184 "aes192-cbc",
185 "aes256-cbc",
186 "3des-cbc",
187 "aes128-gcm@openssh.com",
188 "aes256-gcm@openssh.com",
189 )
190 _preferred_macs = (
191 "hmac-sha2-256",
192 "hmac-sha2-512",
193 "hmac-sha2-256-etm@openssh.com",
194 "hmac-sha2-512-etm@openssh.com",
195 "hmac-sha1",
196 "hmac-md5",
197 "hmac-sha1-96",
198 "hmac-md5-96",
199 )
200 # ~= HostKeyAlgorithms in OpenSSH land
201 _preferred_keys = (
202 "ssh-ed25519",
203 "ecdsa-sha2-nistp256",
204 "ecdsa-sha2-nistp384",
205 "ecdsa-sha2-nistp521",
206 "rsa-sha2-512",
207 "rsa-sha2-256",
208 "ssh-rsa",
209 "ssh-dss",
210 )
211 # ~= PubKeyAcceptedAlgorithms
212 _preferred_pubkeys = (
213 "ssh-ed25519",
214 "ecdsa-sha2-nistp256",
215 "ecdsa-sha2-nistp384",
216 "ecdsa-sha2-nistp521",
217 "rsa-sha2-512",
218 "rsa-sha2-256",
219 "ssh-rsa",
220 "ssh-dss",
221 )
222 _preferred_kex = (
223 "ecdh-sha2-nistp256",
224 "ecdh-sha2-nistp384",
225 "ecdh-sha2-nistp521",
226 "diffie-hellman-group16-sha512",
227 "diffie-hellman-group-exchange-sha256",
228 "diffie-hellman-group14-sha256",
229 "diffie-hellman-group-exchange-sha1",
230 "diffie-hellman-group14-sha1",
231 "diffie-hellman-group1-sha1",
232 )
233 if KexCurve25519.is_available():
234 _preferred_kex = ("curve25519-sha256@libssh.org",) + _preferred_kex
235 _preferred_gsskex = (
236 "gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==",
237 "gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==",
238 "gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==",
239 )
240 _preferred_compression = ("none",)
242 _cipher_info = {
243 "aes128-ctr": {
244 "class": algorithms.AES,
245 "mode": modes.CTR,
246 "block-size": 16,
247 "key-size": 16,
248 },
249 "aes192-ctr": {
250 "class": algorithms.AES,
251 "mode": modes.CTR,
252 "block-size": 16,
253 "key-size": 24,
254 },
255 "aes256-ctr": {
256 "class": algorithms.AES,
257 "mode": modes.CTR,
258 "block-size": 16,
259 "key-size": 32,
260 },
261 "aes128-cbc": {
262 "class": algorithms.AES,
263 "mode": modes.CBC,
264 "block-size": 16,
265 "key-size": 16,
266 },
267 "aes192-cbc": {
268 "class": algorithms.AES,
269 "mode": modes.CBC,
270 "block-size": 16,
271 "key-size": 24,
272 },
273 "aes256-cbc": {
274 "class": algorithms.AES,
275 "mode": modes.CBC,
276 "block-size": 16,
277 "key-size": 32,
278 },
279 "3des-cbc": {
280 "class": TripleDES,
281 "mode": modes.CBC,
282 "block-size": 8,
283 "key-size": 24,
284 },
285 "aes128-gcm@openssh.com": {
286 "class": aead.AESGCM,
287 "block-size": 16,
288 "iv-size": 12,
289 "key-size": 16,
290 "is_aead": True,
291 },
292 "aes256-gcm@openssh.com": {
293 "class": aead.AESGCM,
294 "block-size": 16,
295 "iv-size": 12,
296 "key-size": 32,
297 "is_aead": True,
298 },
299 }
301 _mac_info = {
302 "hmac-sha1": {"class": sha1, "size": 20},
303 "hmac-sha1-96": {"class": sha1, "size": 12},
304 "hmac-sha2-256": {"class": sha256, "size": 32},
305 "hmac-sha2-256-etm@openssh.com": {"class": sha256, "size": 32},
306 "hmac-sha2-512": {"class": sha512, "size": 64},
307 "hmac-sha2-512-etm@openssh.com": {"class": sha512, "size": 64},
308 "hmac-md5": {"class": md5, "size": 16},
309 "hmac-md5-96": {"class": md5, "size": 12},
310 }
312 _key_info = {
313 # TODO: at some point we will want to drop this as it's no longer
314 # considered secure due to using SHA-1 for signatures. OpenSSH 8.8 no
315 # longer supports it. Question becomes at what point do we want to
316 # prevent users with older setups from using this?
317 "ssh-rsa": RSAKey,
318 "ssh-rsa-cert-v01@openssh.com": RSAKey,
319 "rsa-sha2-256": RSAKey,
320 "rsa-sha2-256-cert-v01@openssh.com": RSAKey,
321 "rsa-sha2-512": RSAKey,
322 "rsa-sha2-512-cert-v01@openssh.com": RSAKey,
323 "ssh-dss": DSSKey,
324 "ssh-dss-cert-v01@openssh.com": DSSKey,
325 "ecdsa-sha2-nistp256": ECDSAKey,
326 "ecdsa-sha2-nistp256-cert-v01@openssh.com": ECDSAKey,
327 "ecdsa-sha2-nistp384": ECDSAKey,
328 "ecdsa-sha2-nistp384-cert-v01@openssh.com": ECDSAKey,
329 "ecdsa-sha2-nistp521": ECDSAKey,
330 "ecdsa-sha2-nistp521-cert-v01@openssh.com": ECDSAKey,
331 "ssh-ed25519": Ed25519Key,
332 "ssh-ed25519-cert-v01@openssh.com": Ed25519Key,
333 }
335 _kex_info = {
336 "diffie-hellman-group1-sha1": KexGroup1,
337 "diffie-hellman-group14-sha1": KexGroup14,
338 "diffie-hellman-group-exchange-sha1": KexGex,
339 "diffie-hellman-group-exchange-sha256": KexGexSHA256,
340 "diffie-hellman-group14-sha256": KexGroup14SHA256,
341 "diffie-hellman-group16-sha512": KexGroup16SHA512,
342 "gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==": KexGSSGroup1,
343 "gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==": KexGSSGroup14,
344 "gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==": KexGSSGex,
345 "ecdh-sha2-nistp256": KexNistp256,
346 "ecdh-sha2-nistp384": KexNistp384,
347 "ecdh-sha2-nistp521": KexNistp521,
348 }
349 if KexCurve25519.is_available():
350 _kex_info["curve25519-sha256@libssh.org"] = KexCurve25519
352 _compression_info = {
353 # zlib@openssh.com is just zlib, but only turned on after a successful
354 # authentication. openssh servers may only offer this type because
355 # they've had troubles with security holes in zlib in the past.
356 "zlib@openssh.com": (ZlibCompressor, ZlibDecompressor),
357 "zlib": (ZlibCompressor, ZlibDecompressor),
358 "none": (None, None),
359 }
361 _modulus_pack = None
362 _active_check_timeout = 0.1
364 def __init__(
365 self,
366 sock,
367 default_window_size=DEFAULT_WINDOW_SIZE,
368 default_max_packet_size=DEFAULT_MAX_PACKET_SIZE,
369 gss_kex=False,
370 gss_deleg_creds=True,
371 disabled_algorithms=None,
372 server_sig_algs=True,
373 strict_kex=True,
374 packetizer_class=None,
375 ):
376 """
377 Create a new SSH session over an existing socket, or socket-like
378 object. This only creates the `.Transport` object; it doesn't begin
379 the SSH session yet. Use `connect` or `start_client` to begin a client
380 session, or `start_server` to begin a server session.
382 If the object is not actually a socket, it must have the following
383 methods:
385 - ``send(bytes)``: Writes from 1 to ``len(bytes)`` bytes, and returns
386 an int representing the number of bytes written. Returns
387 0 or raises ``EOFError`` if the stream has been closed.
388 - ``recv(int)``: Reads from 1 to ``int`` bytes and returns them as a
389 string. Returns 0 or raises ``EOFError`` if the stream has been
390 closed.
391 - ``close()``: Closes the socket.
392 - ``settimeout(n)``: Sets a (float) timeout on I/O operations.
394 For ease of use, you may also pass in an address (as a tuple) or a host
395 string as the ``sock`` argument. (A host string is a hostname with an
396 optional port (separated by ``":"``) which will be converted into a
397 tuple of ``(hostname, port)``.) A socket will be connected to this
398 address and used for communication. Exceptions from the ``socket``
399 call may be thrown in this case.
401 .. note::
402 Modifying the the window and packet sizes might have adverse
403 effects on your channels created from this transport. The default
404 values are the same as in the OpenSSH code base and have been
405 battle tested.
407 :param socket sock:
408 a socket or socket-like object to create the session over.
409 :param int default_window_size:
410 sets the default window size on the transport. (defaults to
411 2097152)
412 :param int default_max_packet_size:
413 sets the default max packet size on the transport. (defaults to
414 32768)
415 :param bool gss_kex:
416 Whether to enable GSSAPI key exchange when GSSAPI is in play.
417 Default: ``False``.
418 :param bool gss_deleg_creds:
419 Whether to enable GSSAPI credential delegation when GSSAPI is in
420 play. Default: ``True``.
421 :param dict disabled_algorithms:
422 If given, must be a dictionary mapping algorithm type to an
423 iterable of algorithm identifiers, which will be disabled for the
424 lifetime of the transport.
426 Keys should match the last word in the class' builtin algorithm
427 tuple attributes, such as ``"ciphers"`` to disable names within
428 ``_preferred_ciphers``; or ``"kex"`` to disable something defined
429 inside ``_preferred_kex``. Values should exactly match members of
430 the matching attribute.
432 For example, if you need to disable
433 ``diffie-hellman-group16-sha512`` key exchange (perhaps because
434 your code talks to a server which implements it differently from
435 Paramiko), specify ``disabled_algorithms={"kex":
436 ["diffie-hellman-group16-sha512"]}``.
437 :param bool server_sig_algs:
438 Whether to send an extra message to compatible clients, in server
439 mode, with a list of supported pubkey algorithms. Default:
440 ``True``.
441 :param bool strict_kex:
442 Whether to advertise (and implement, if client also advertises
443 support for) a "strict kex" mode for safer handshaking. Default:
444 ``True``.
445 :param packetizer_class:
446 Which class to use for instantiating the internal packet handler.
447 Default: ``None`` (i.e.: use `Packetizer` as normal).
449 .. versionchanged:: 1.15
450 Added the ``default_window_size`` and ``default_max_packet_size``
451 arguments.
452 .. versionchanged:: 1.15
453 Added the ``gss_kex`` and ``gss_deleg_creds`` kwargs.
454 .. versionchanged:: 2.6
455 Added the ``disabled_algorithms`` kwarg.
456 .. versionchanged:: 2.9
457 Added the ``server_sig_algs`` kwarg.
458 .. versionchanged:: 3.4
459 Added the ``strict_kex`` kwarg.
460 .. versionchanged:: 3.4
461 Added the ``packetizer_class`` kwarg.
462 """
463 self.active = False
464 self.hostname = None
465 self.server_extensions = {}
466 self.advertise_strict_kex = strict_kex
467 self.agreed_on_strict_kex = False
469 # TODO: these two overrides on sock's type should go away sometime, too
470 # many ways to do it!
471 if isinstance(sock, str):
472 # convert "host:port" into (host, port)
473 hl = sock.split(":", 1)
474 self.hostname = hl[0]
475 if len(hl) == 1:
476 sock = (hl[0], 22)
477 else:
478 sock = (hl[0], int(hl[1]))
479 if type(sock) is tuple:
480 # connect to the given (host, port)
481 hostname, port = sock
482 self.hostname = hostname
483 reason = "No suitable address family"
484 addrinfos = socket.getaddrinfo(
485 hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM
486 )
487 for family, socktype, proto, canonname, sockaddr in addrinfos:
488 if socktype == socket.SOCK_STREAM:
489 af = family
490 # addr = sockaddr
491 sock = socket.socket(af, socket.SOCK_STREAM)
492 try:
493 sock.connect((hostname, port))
494 except socket.error as e:
495 reason = str(e)
496 else:
497 break
498 else:
499 raise SSHException(
500 "Unable to connect to {}: {}".format(hostname, reason)
501 )
502 # okay, normal socket-ish flow here...
503 threading.Thread.__init__(self)
504 self.daemon = True
505 self.sock = sock
506 # we set the timeout so we can check self.active periodically to
507 # see if we should bail. socket.timeout exception is never propagated.
508 self.sock.settimeout(self._active_check_timeout)
510 # negotiated crypto parameters
511 self.packetizer = (packetizer_class or Packetizer)(sock)
512 self.local_version = "SSH-" + self._PROTO_ID + "-" + self._CLIENT_ID
513 self.remote_version = ""
514 self.local_cipher = self.remote_cipher = ""
515 self.local_kex_init = self.remote_kex_init = None
516 self.local_mac = self.remote_mac = None
517 self.local_compression = self.remote_compression = None
518 self.session_id = None
519 self.host_key_type = None
520 self.host_key = None
522 # GSS-API / SSPI Key Exchange
523 self.use_gss_kex = gss_kex
524 # This will be set to True if GSS-API Key Exchange was performed
525 self.gss_kex_used = False
526 self.kexgss_ctxt = None
527 self.gss_host = None
528 if self.use_gss_kex:
529 self.kexgss_ctxt = GSSAuth("gssapi-keyex", gss_deleg_creds)
530 self._preferred_kex = self._preferred_gsskex + self._preferred_kex
532 # state used during negotiation
533 self.kex_engine = None
534 self.H = None
535 self.K = None
537 self.initial_kex_done = False
538 self.in_kex = False
539 self.authenticated = False
540 self._expected_packet = tuple()
541 # synchronization (always higher level than write_lock)
542 self.lock = threading.Lock()
544 # tracking open channels
545 self._channels = ChannelMap()
546 self.channel_events = {} # (id -> Event)
547 self.channels_seen = {} # (id -> True)
548 self._channel_counter = 0
549 self.default_max_packet_size = default_max_packet_size
550 self.default_window_size = default_window_size
551 self._forward_agent_handler = None
552 self._x11_handler = None
553 self._tcp_handler = None
555 self.saved_exception = None
556 self.clear_to_send = threading.Event()
557 self.clear_to_send_lock = threading.Lock()
558 self.clear_to_send_timeout = 30.0
559 self.log_name = "paramiko.transport"
560 self.logger = util.get_logger(self.log_name)
561 self.packetizer.set_log(self.logger)
562 self.auth_handler = None
563 # response Message from an arbitrary global request
564 self.global_response = None
565 # user-defined event callbacks
566 self.completion_event = None
567 # how long (seconds) to wait for the SSH banner
568 self.banner_timeout = 15
569 # how long (seconds) to wait for the handshake to finish after SSH
570 # banner sent.
571 self.handshake_timeout = 15
572 # how long (seconds) to wait for the auth response.
573 self.auth_timeout = 30
574 # how long (seconds) to wait for opening a channel
575 self.channel_timeout = 60 * 60
576 self.disabled_algorithms = disabled_algorithms or {}
577 self.server_sig_algs = server_sig_algs
579 # server mode:
580 self.server_mode = False
581 self.server_object = None
582 self.server_key_dict = {}
583 self.server_accepts = []
584 self.server_accept_cv = threading.Condition(self.lock)
585 self.subsystem_table = {}
587 # Handler table, now set at init time for easier per-instance
588 # manipulation and subclass twiddling.
589 self._handler_table = {
590 MSG_EXT_INFO: self._parse_ext_info,
591 MSG_NEWKEYS: self._parse_newkeys,
592 MSG_GLOBAL_REQUEST: self._parse_global_request,
593 MSG_REQUEST_SUCCESS: self._parse_request_success,
594 MSG_REQUEST_FAILURE: self._parse_request_failure,
595 MSG_CHANNEL_OPEN_SUCCESS: self._parse_channel_open_success,
596 MSG_CHANNEL_OPEN_FAILURE: self._parse_channel_open_failure,
597 MSG_CHANNEL_OPEN: self._parse_channel_open,
598 MSG_KEXINIT: self._negotiate_keys,
599 }
601 def _filter_algorithm(self, type_):
602 default = getattr(self, "_preferred_{}".format(type_))
603 return tuple(
604 x
605 for x in default
606 if x not in self.disabled_algorithms.get(type_, [])
607 )
609 @property
610 def preferred_ciphers(self):
611 return self._filter_algorithm("ciphers")
613 @property
614 def preferred_macs(self):
615 return self._filter_algorithm("macs")
617 @property
618 def preferred_keys(self):
619 # Interleave cert variants here; resistant to various background
620 # overwriting of _preferred_keys, and necessary as hostkeys can't use
621 # the logic pubkey auth does re: injecting/checking for certs at
622 # runtime
623 filtered = self._filter_algorithm("keys")
624 return tuple(
625 filtered
626 + tuple("{}-cert-v01@openssh.com".format(x) for x in filtered)
627 )
629 @property
630 def preferred_pubkeys(self):
631 return self._filter_algorithm("pubkeys")
633 @property
634 def preferred_kex(self):
635 return self._filter_algorithm("kex")
637 @property
638 def preferred_compression(self):
639 return self._filter_algorithm("compression")
641 def __repr__(self):
642 """
643 Returns a string representation of this object, for debugging.
644 """
645 id_ = hex(id(self) & xffffffff)
646 out = "<paramiko.Transport at {}".format(id_)
647 if not self.active:
648 out += " (unconnected)"
649 else:
650 if self.local_cipher != "":
651 out += " (cipher {}, {:d} bits)".format(
652 self.local_cipher,
653 self._cipher_info[self.local_cipher]["key-size"] * 8,
654 )
655 if self.is_authenticated():
656 out += " (active; {} open channel(s))".format(
657 len(self._channels)
658 )
659 elif self.initial_kex_done:
660 out += " (connected; awaiting auth)"
661 else:
662 out += " (connecting)"
663 out += ">"
664 return out
666 def atfork(self):
667 """
668 Terminate this Transport without closing the session. On posix
669 systems, if a Transport is open during process forking, both parent
670 and child will share the underlying socket, but only one process can
671 use the connection (without corrupting the session). Use this method
672 to clean up a Transport object without disrupting the other process.
674 .. versionadded:: 1.5.3
675 """
676 self.sock.close()
677 self.close()
679 def get_security_options(self):
680 """
681 Return a `.SecurityOptions` object which can be used to tweak the
682 encryption algorithms this transport will permit (for encryption,
683 digest/hash operations, public keys, and key exchanges) and the order
684 of preference for them.
685 """
686 return SecurityOptions(self)
688 def set_gss_host(self, gss_host, trust_dns=True, gssapi_requested=True):
689 """
690 Normalize/canonicalize ``self.gss_host`` depending on various factors.
692 :param str gss_host:
693 The explicitly requested GSS-oriented hostname to connect to (i.e.
694 what the host's name is in the Kerberos database.) Defaults to
695 ``self.hostname`` (which will be the 'real' target hostname and/or
696 host portion of given socket object.)
697 :param bool trust_dns:
698 Indicates whether or not DNS is trusted; if true, DNS will be used
699 to canonicalize the GSS hostname (which again will either be
700 ``gss_host`` or the transport's default hostname.)
701 (Defaults to True due to backwards compatibility.)
702 :param bool gssapi_requested:
703 Whether GSSAPI key exchange or authentication was even requested.
704 If not, this is a no-op and nothing happens
705 (and ``self.gss_host`` is not set.)
706 (Defaults to True due to backwards compatibility.)
707 :returns: ``None``.
708 """
709 # No GSSAPI in play == nothing to do
710 if not gssapi_requested:
711 return
712 # Obtain the correct host first - did user request a GSS-specific name
713 # to use that is distinct from the actual SSH target hostname?
714 if gss_host is None:
715 gss_host = self.hostname
716 # Finally, canonicalize via DNS if DNS is trusted.
717 if trust_dns and gss_host is not None:
718 gss_host = socket.getfqdn(gss_host)
719 # And set attribute for reference later.
720 self.gss_host = gss_host
722 def start_client(self, event=None, timeout=None):
723 """
724 Negotiate a new SSH2 session as a client. This is the first step after
725 creating a new `.Transport`. A separate thread is created for protocol
726 negotiation.
728 If an event is passed in, this method returns immediately. When
729 negotiation is done (successful or not), the given ``Event`` will
730 be triggered. On failure, `is_active` will return ``False``.
732 (Since 1.4) If ``event`` is ``None``, this method will not return until
733 negotiation is done. On success, the method returns normally.
734 Otherwise an SSHException is raised.
736 After a successful negotiation, you will usually want to authenticate,
737 calling `auth_password <Transport.auth_password>` or
738 `auth_publickey <Transport.auth_publickey>`.
740 .. note:: `connect` is a simpler method for connecting as a client.
742 .. note::
743 After calling this method (or `start_server` or `connect`), you
744 should no longer directly read from or write to the original socket
745 object.
747 :param .threading.Event event:
748 an event to trigger when negotiation is complete (optional)
750 :param float timeout:
751 a timeout, in seconds, for SSH2 session negotiation (optional)
753 :raises:
754 `.SSHException` -- if negotiation fails (and no ``event`` was
755 passed in)
756 """
757 self.active = True
758 if event is not None:
759 # async, return immediately and let the app poll for completion
760 self.completion_event = event
761 self.start()
762 return
764 # synchronous, wait for a result
765 self.completion_event = event = threading.Event()
766 self.start()
767 max_time = time.time() + timeout if timeout is not None else None
768 while True:
769 event.wait(0.1)
770 if not self.active:
771 e = self.get_exception()
772 if e is not None:
773 raise e
774 raise SSHException("Negotiation failed.")
775 if event.is_set() or (
776 timeout is not None and time.time() >= max_time
777 ):
778 break
780 def start_server(self, event=None, server=None):
781 """
782 Negotiate a new SSH2 session as a server. This is the first step after
783 creating a new `.Transport` and setting up your server host key(s). A
784 separate thread is created for protocol negotiation.
786 If an event is passed in, this method returns immediately. When
787 negotiation is done (successful or not), the given ``Event`` will
788 be triggered. On failure, `is_active` will return ``False``.
790 (Since 1.4) If ``event`` is ``None``, this method will not return until
791 negotiation is done. On success, the method returns normally.
792 Otherwise an SSHException is raised.
794 After a successful negotiation, the client will need to authenticate.
795 Override the methods `get_allowed_auths
796 <.ServerInterface.get_allowed_auths>`, `check_auth_none
797 <.ServerInterface.check_auth_none>`, `check_auth_password
798 <.ServerInterface.check_auth_password>`, and `check_auth_publickey
799 <.ServerInterface.check_auth_publickey>` in the given ``server`` object
800 to control the authentication process.
802 After a successful authentication, the client should request to open a
803 channel. Override `check_channel_request
804 <.ServerInterface.check_channel_request>` in the given ``server``
805 object to allow channels to be opened.
807 .. note::
808 After calling this method (or `start_client` or `connect`), you
809 should no longer directly read from or write to the original socket
810 object.
812 :param .threading.Event event:
813 an event to trigger when negotiation is complete.
814 :param .ServerInterface server:
815 an object used to perform authentication and create `channels
816 <.Channel>`
818 :raises:
819 `.SSHException` -- if negotiation fails (and no ``event`` was
820 passed in)
821 """
822 if server is None:
823 server = ServerInterface()
824 self.server_mode = True
825 self.server_object = server
826 self.active = True
827 if event is not None:
828 # async, return immediately and let the app poll for completion
829 self.completion_event = event
830 self.start()
831 return
833 # synchronous, wait for a result
834 self.completion_event = event = threading.Event()
835 self.start()
836 while True:
837 event.wait(0.1)
838 if not self.active:
839 e = self.get_exception()
840 if e is not None:
841 raise e
842 raise SSHException("Negotiation failed.")
843 if event.is_set():
844 break
846 def add_server_key(self, key):
847 """
848 Add a host key to the list of keys used for server mode. When behaving
849 as a server, the host key is used to sign certain packets during the
850 SSH2 negotiation, so that the client can trust that we are who we say
851 we are. Because this is used for signing, the key must contain private
852 key info, not just the public half. Only one key of each type (RSA or
853 DSS) is kept.
855 :param .PKey key:
856 the host key to add, usually an `.RSAKey` or `.DSSKey`.
857 """
858 self.server_key_dict[key.get_name()] = key
859 # Handle SHA-2 extensions for RSA by ensuring that lookups into
860 # self.server_key_dict will yield this key for any of the algorithm
861 # names.
862 if isinstance(key, RSAKey):
863 self.server_key_dict["rsa-sha2-256"] = key
864 self.server_key_dict["rsa-sha2-512"] = key
866 def get_server_key(self):
867 """
868 Return the active host key, in server mode. After negotiating with the
869 client, this method will return the negotiated host key. If only one
870 type of host key was set with `add_server_key`, that's the only key
871 that will ever be returned. But in cases where you have set more than
872 one type of host key (for example, an RSA key and a DSS key), the key
873 type will be negotiated by the client, and this method will return the
874 key of the type agreed on. If the host key has not been negotiated
875 yet, ``None`` is returned. In client mode, the behavior is undefined.
877 :return:
878 host key (`.PKey`) of the type negotiated by the client, or
879 ``None``.
880 """
881 try:
882 return self.server_key_dict[self.host_key_type]
883 except KeyError:
884 pass
885 return None
887 @staticmethod
888 def load_server_moduli(filename=None):
889 """
890 (optional)
891 Load a file of prime moduli for use in doing group-exchange key
892 negotiation in server mode. It's a rather obscure option and can be
893 safely ignored.
895 In server mode, the remote client may request "group-exchange" key
896 negotiation, which asks the server to send a random prime number that
897 fits certain criteria. These primes are pretty difficult to compute,
898 so they can't be generated on demand. But many systems contain a file
899 of suitable primes (usually named something like ``/etc/ssh/moduli``).
900 If you call `load_server_moduli` and it returns ``True``, then this
901 file of primes has been loaded and we will support "group-exchange" in
902 server mode. Otherwise server mode will just claim that it doesn't
903 support that method of key negotiation.
905 :param str filename:
906 optional path to the moduli file, if you happen to know that it's
907 not in a standard location.
908 :return:
909 True if a moduli file was successfully loaded; False otherwise.
911 .. note:: This has no effect when used in client mode.
912 """
913 Transport._modulus_pack = ModulusPack()
914 # places to look for the openssh "moduli" file
915 file_list = ["/etc/ssh/moduli", "/usr/local/etc/moduli"]
916 if filename is not None:
917 file_list.insert(0, filename)
918 for fn in file_list:
919 try:
920 Transport._modulus_pack.read_file(fn)
921 return True
922 except IOError:
923 pass
924 # none succeeded
925 Transport._modulus_pack = None
926 return False
928 def close(self):
929 """
930 Close this session, and any open channels that are tied to it.
931 """
932 if not self.active:
933 return
934 self.stop_thread()
935 for chan in list(self._channels.values()):
936 chan._unlink()
937 self.sock.close()
939 def get_remote_server_key(self):
940 """
941 Return the host key of the server (in client mode).
943 .. note::
944 Previously this call returned a tuple of ``(key type, key
945 string)``. You can get the same effect by calling `.PKey.get_name`
946 for the key type, and ``str(key)`` for the key string.
948 :raises: `.SSHException` -- if no session is currently active.
950 :return: public key (`.PKey`) of the remote server
951 """
952 if (not self.active) or (not self.initial_kex_done):
953 raise SSHException("No existing session")
954 return self.host_key
956 def is_active(self):
957 """
958 Return true if this session is active (open).
960 :return:
961 True if the session is still active (open); False if the session is
962 closed
963 """
964 return self.active
966 def open_session(
967 self, window_size=None, max_packet_size=None, timeout=None
968 ):
969 """
970 Request a new channel to the server, of type ``"session"``. This is
971 just an alias for calling `open_channel` with an argument of
972 ``"session"``.
974 .. note:: Modifying the the window and packet sizes might have adverse
975 effects on the session created. The default values are the same
976 as in the OpenSSH code base and have been battle tested.
978 :param int window_size:
979 optional window size for this session.
980 :param int max_packet_size:
981 optional max packet size for this session.
983 :return: a new `.Channel`
985 :raises:
986 `.SSHException` -- if the request is rejected or the session ends
987 prematurely
989 .. versionchanged:: 1.13.4/1.14.3/1.15.3
990 Added the ``timeout`` argument.
991 .. versionchanged:: 1.15
992 Added the ``window_size`` and ``max_packet_size`` arguments.
993 """
994 return self.open_channel(
995 "session",
996 window_size=window_size,
997 max_packet_size=max_packet_size,
998 timeout=timeout,
999 )
1001 def open_x11_channel(self, src_addr=None):
1002 """
1003 Request a new channel to the client, of type ``"x11"``. This
1004 is just an alias for ``open_channel('x11', src_addr=src_addr)``.
1006 :param tuple src_addr:
1007 the source address (``(str, int)``) of the x11 server (port is the
1008 x11 port, ie. 6010)
1009 :return: a new `.Channel`
1011 :raises:
1012 `.SSHException` -- if the request is rejected or the session ends
1013 prematurely
1014 """
1015 return self.open_channel("x11", src_addr=src_addr)
1017 def open_forward_agent_channel(self):
1018 """
1019 Request a new channel to the client, of type
1020 ``"auth-agent@openssh.com"``.
1022 This is just an alias for ``open_channel('auth-agent@openssh.com')``.
1024 :return: a new `.Channel`
1026 :raises: `.SSHException` --
1027 if the request is rejected or the session ends prematurely
1028 """
1029 return self.open_channel("auth-agent@openssh.com")
1031 def open_forwarded_tcpip_channel(self, src_addr, dest_addr):
1032 """
1033 Request a new channel back to the client, of type ``forwarded-tcpip``.
1035 This is used after a client has requested port forwarding, for sending
1036 incoming connections back to the client.
1038 :param src_addr: originator's address
1039 :param dest_addr: local (server) connected address
1040 """
1041 return self.open_channel("forwarded-tcpip", dest_addr, src_addr)
1043 def open_channel(
1044 self,
1045 kind,
1046 dest_addr=None,
1047 src_addr=None,
1048 window_size=None,
1049 max_packet_size=None,
1050 timeout=None,
1051 ):
1052 """
1053 Request a new channel to the server. `Channels <.Channel>` are
1054 socket-like objects used for the actual transfer of data across the
1055 session. You may only request a channel after negotiating encryption
1056 (using `connect` or `start_client`) and authenticating.
1058 .. note:: Modifying the the window and packet sizes might have adverse
1059 effects on the channel created. The default values are the same
1060 as in the OpenSSH code base and have been battle tested.
1062 :param str kind:
1063 the kind of channel requested (usually ``"session"``,
1064 ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``)
1065 :param tuple dest_addr:
1066 the destination address (address + port tuple) of this port
1067 forwarding, if ``kind`` is ``"forwarded-tcpip"`` or
1068 ``"direct-tcpip"`` (ignored for other channel types)
1069 :param src_addr: the source address of this port forwarding, if
1070 ``kind`` is ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``
1071 :param int window_size:
1072 optional window size for this session.
1073 :param int max_packet_size:
1074 optional max packet size for this session.
1075 :param float timeout:
1076 optional timeout opening a channel, default 3600s (1h)
1078 :return: a new `.Channel` on success
1080 :raises:
1081 `.SSHException` -- if the request is rejected, the session ends
1082 prematurely or there is a timeout opening a channel
1084 .. versionchanged:: 1.15
1085 Added the ``window_size`` and ``max_packet_size`` arguments.
1086 """
1087 if not self.active:
1088 raise SSHException("SSH session not active")
1089 timeout = self.channel_timeout if timeout is None else timeout
1090 self.lock.acquire()
1091 try:
1092 window_size = self._sanitize_window_size(window_size)
1093 max_packet_size = self._sanitize_packet_size(max_packet_size)
1094 chanid = self._next_channel()
1095 m = Message()
1096 m.add_byte(cMSG_CHANNEL_OPEN)
1097 m.add_string(kind)
1098 m.add_int(chanid)
1099 m.add_int(window_size)
1100 m.add_int(max_packet_size)
1101 if (kind == "forwarded-tcpip") or (kind == "direct-tcpip"):
1102 m.add_string(dest_addr[0])
1103 m.add_int(dest_addr[1])
1104 m.add_string(src_addr[0])
1105 m.add_int(src_addr[1])
1106 elif kind == "x11":
1107 m.add_string(src_addr[0])
1108 m.add_int(src_addr[1])
1109 chan = Channel(chanid)
1110 self._channels.put(chanid, chan)
1111 self.channel_events[chanid] = event = threading.Event()
1112 self.channels_seen[chanid] = True
1113 chan._set_transport(self)
1114 chan._set_window(window_size, max_packet_size)
1115 finally:
1116 self.lock.release()
1117 self._send_user_message(m)
1118 start_ts = time.time()
1119 while True:
1120 event.wait(0.1)
1121 if not self.active:
1122 e = self.get_exception()
1123 if e is None:
1124 e = SSHException("Unable to open channel.")
1125 raise e
1126 if event.is_set():
1127 break
1128 elif start_ts + timeout < time.time():
1129 raise SSHException("Timeout opening channel.")
1130 chan = self._channels.get(chanid)
1131 if chan is not None:
1132 return chan
1133 e = self.get_exception()
1134 if e is None:
1135 e = SSHException("Unable to open channel.")
1136 raise e
1138 def request_port_forward(self, address, port, handler=None):
1139 """
1140 Ask the server to forward TCP connections from a listening port on
1141 the server, across this SSH session.
1143 If a handler is given, that handler is called from a different thread
1144 whenever a forwarded connection arrives. The handler parameters are::
1146 handler(
1147 channel,
1148 (origin_addr, origin_port),
1149 (server_addr, server_port),
1150 )
1152 where ``server_addr`` and ``server_port`` are the address and port that
1153 the server was listening on.
1155 If no handler is set, the default behavior is to send new incoming
1156 forwarded connections into the accept queue, to be picked up via
1157 `accept`.
1159 :param str address: the address to bind when forwarding
1160 :param int port:
1161 the port to forward, or 0 to ask the server to allocate any port
1162 :param callable handler:
1163 optional handler for incoming forwarded connections, of the form
1164 ``func(Channel, (str, int), (str, int))``.
1166 :return: the port number (`int`) allocated by the server
1168 :raises:
1169 `.SSHException` -- if the server refused the TCP forward request
1170 """
1171 if not self.active:
1172 raise SSHException("SSH session not active")
1173 port = int(port)
1174 response = self.global_request(
1175 "tcpip-forward", (address, port), wait=True
1176 )
1177 if response is None:
1178 raise SSHException("TCP forwarding request denied")
1179 if port == 0:
1180 port = response.get_int()
1181 if handler is None:
1183 def default_handler(channel, src_addr, dest_addr_port):
1184 # src_addr, src_port = src_addr_port
1185 # dest_addr, dest_port = dest_addr_port
1186 self._queue_incoming_channel(channel)
1188 handler = default_handler
1189 self._tcp_handler = handler
1190 return port
1192 def cancel_port_forward(self, address, port):
1193 """
1194 Ask the server to cancel a previous port-forwarding request. No more
1195 connections to the given address & port will be forwarded across this
1196 ssh connection.
1198 :param str address: the address to stop forwarding
1199 :param int port: the port to stop forwarding
1200 """
1201 if not self.active:
1202 return
1203 self._tcp_handler = None
1204 self.global_request("cancel-tcpip-forward", (address, port), wait=True)
1206 def open_sftp_client(self):
1207 """
1208 Create an SFTP client channel from an open transport. On success, an
1209 SFTP session will be opened with the remote host, and a new
1210 `.SFTPClient` object will be returned.
1212 :return:
1213 a new `.SFTPClient` referring to an sftp session (channel) across
1214 this transport
1215 """
1216 return SFTPClient.from_transport(self)
1218 def send_ignore(self, byte_count=None):
1219 """
1220 Send a junk packet across the encrypted link. This is sometimes used
1221 to add "noise" to a connection to confuse would-be attackers. It can
1222 also be used as a keep-alive for long lived connections traversing
1223 firewalls.
1225 :param int byte_count:
1226 the number of random bytes to send in the payload of the ignored
1227 packet -- defaults to a random number from 10 to 41.
1228 """
1229 m = Message()
1230 m.add_byte(cMSG_IGNORE)
1231 if byte_count is None:
1232 byte_count = (byte_ord(os.urandom(1)) % 32) + 10
1233 m.add_bytes(os.urandom(byte_count))
1234 self._send_user_message(m)
1236 def renegotiate_keys(self):
1237 """
1238 Force this session to switch to new keys. Normally this is done
1239 automatically after the session hits a certain number of packets or
1240 bytes sent or received, but this method gives you the option of forcing
1241 new keys whenever you want. Negotiating new keys causes a pause in
1242 traffic both ways as the two sides swap keys and do computations. This
1243 method returns when the session has switched to new keys.
1245 :raises:
1246 `.SSHException` -- if the key renegotiation failed (which causes
1247 the session to end)
1248 """
1249 self.completion_event = threading.Event()
1250 self._send_kex_init()
1251 while True:
1252 self.completion_event.wait(0.1)
1253 if not self.active:
1254 e = self.get_exception()
1255 if e is not None:
1256 raise e
1257 raise SSHException("Negotiation failed.")
1258 if self.completion_event.is_set():
1259 break
1260 return
1262 def set_keepalive(self, interval):
1263 """
1264 Turn on/off keepalive packets (default is off). If this is set, after
1265 ``interval`` seconds without sending any data over the connection, a
1266 "keepalive" packet will be sent (and ignored by the remote host). This
1267 can be useful to keep connections alive over a NAT, for example.
1269 :param int interval:
1270 seconds to wait before sending a keepalive packet (or
1271 0 to disable keepalives).
1272 """
1274 def _request(x=weakref.proxy(self)):
1275 return x.global_request("keepalive@lag.net", wait=False)
1277 self.packetizer.set_keepalive(interval, _request)
1279 def global_request(self, kind, data=None, wait=True):
1280 """
1281 Make a global request to the remote host. These are normally
1282 extensions to the SSH2 protocol.
1284 :param str kind: name of the request.
1285 :param tuple data:
1286 an optional tuple containing additional data to attach to the
1287 request.
1288 :param bool wait:
1289 ``True`` if this method should not return until a response is
1290 received; ``False`` otherwise.
1291 :return:
1292 a `.Message` containing possible additional data if the request was
1293 successful (or an empty `.Message` if ``wait`` was ``False``);
1294 ``None`` if the request was denied.
1295 """
1296 if wait:
1297 self.completion_event = threading.Event()
1298 m = Message()
1299 m.add_byte(cMSG_GLOBAL_REQUEST)
1300 m.add_string(kind)
1301 m.add_boolean(wait)
1302 if data is not None:
1303 m.add(*data)
1304 self._log(DEBUG, 'Sending global request "{}"'.format(kind))
1305 self._send_user_message(m)
1306 if not wait:
1307 return None
1308 while True:
1309 self.completion_event.wait(0.1)
1310 if not self.active:
1311 return None
1312 if self.completion_event.is_set():
1313 break
1314 return self.global_response
1316 def accept(self, timeout=None):
1317 """
1318 Return the next channel opened by the client over this transport, in
1319 server mode. If no channel is opened before the given timeout,
1320 ``None`` is returned.
1322 :param int timeout:
1323 seconds to wait for a channel, or ``None`` to wait forever
1324 :return: a new `.Channel` opened by the client
1325 """
1326 self.lock.acquire()
1327 try:
1328 if len(self.server_accepts) > 0:
1329 chan = self.server_accepts.pop(0)
1330 else:
1331 self.server_accept_cv.wait(timeout)
1332 if len(self.server_accepts) > 0:
1333 chan = self.server_accepts.pop(0)
1334 else:
1335 # timeout
1336 chan = None
1337 finally:
1338 self.lock.release()
1339 return chan
1341 def connect(
1342 self,
1343 hostkey=None,
1344 username="",
1345 password=None,
1346 pkey=None,
1347 gss_host=None,
1348 gss_auth=False,
1349 gss_kex=False,
1350 gss_deleg_creds=True,
1351 gss_trust_dns=True,
1352 ):
1353 """
1354 Negotiate an SSH2 session, and optionally verify the server's host key
1355 and authenticate using a password or private key. This is a shortcut
1356 for `start_client`, `get_remote_server_key`, and
1357 `Transport.auth_password` or `Transport.auth_publickey`. Use those
1358 methods if you want more control.
1360 You can use this method immediately after creating a Transport to
1361 negotiate encryption with a server. If it fails, an exception will be
1362 thrown. On success, the method will return cleanly, and an encrypted
1363 session exists. You may immediately call `open_channel` or
1364 `open_session` to get a `.Channel` object, which is used for data
1365 transfer.
1367 .. note::
1368 If you fail to supply a password or private key, this method may
1369 succeed, but a subsequent `open_channel` or `open_session` call may
1370 fail because you haven't authenticated yet.
1372 :param .PKey hostkey:
1373 the host key expected from the server, or ``None`` if you don't
1374 want to do host key verification.
1375 :param str username: the username to authenticate as.
1376 :param str password:
1377 a password to use for authentication, if you want to use password
1378 authentication; otherwise ``None``.
1379 :param .PKey pkey:
1380 a private key to use for authentication, if you want to use private
1381 key authentication; otherwise ``None``.
1382 :param str gss_host:
1383 The target's name in the kerberos database. Default: hostname
1384 :param bool gss_auth:
1385 ``True`` if you want to use GSS-API authentication.
1386 :param bool gss_kex:
1387 Perform GSS-API Key Exchange and user authentication.
1388 :param bool gss_deleg_creds:
1389 Whether to delegate GSS-API client credentials.
1390 :param gss_trust_dns:
1391 Indicates whether or not the DNS is trusted to securely
1392 canonicalize the name of the host being connected to (default
1393 ``True``).
1395 :raises: `.SSHException` -- if the SSH2 negotiation fails, the host key
1396 supplied by the server is incorrect, or authentication fails.
1398 .. versionchanged:: 2.3
1399 Added the ``gss_trust_dns`` argument.
1400 """
1401 if hostkey is not None:
1402 # TODO: a more robust implementation would be to ask each key class
1403 # for its nameS plural, and just use that.
1404 # TODO: that could be used in a bunch of other spots too
1405 if isinstance(hostkey, RSAKey):
1406 self._preferred_keys = [
1407 "rsa-sha2-512",
1408 "rsa-sha2-256",
1409 "ssh-rsa",
1410 ]
1411 else:
1412 self._preferred_keys = [hostkey.get_name()]
1414 self.set_gss_host(
1415 gss_host=gss_host,
1416 trust_dns=gss_trust_dns,
1417 gssapi_requested=gss_kex or gss_auth,
1418 )
1420 self.start_client()
1422 # check host key if we were given one
1423 # If GSS-API Key Exchange was performed, we are not required to check
1424 # the host key.
1425 if (hostkey is not None) and not gss_kex:
1426 key = self.get_remote_server_key()
1427 if (
1428 key.get_name() != hostkey.get_name()
1429 or key.asbytes() != hostkey.asbytes()
1430 ):
1431 self._log(DEBUG, "Bad host key from server")
1432 self._log(
1433 DEBUG,
1434 "Expected: {}: {}".format(
1435 hostkey.get_name(), repr(hostkey.asbytes())
1436 ),
1437 )
1438 self._log(
1439 DEBUG,
1440 "Got : {}: {}".format(
1441 key.get_name(), repr(key.asbytes())
1442 ),
1443 )
1444 raise SSHException("Bad host key from server")
1445 self._log(
1446 DEBUG, "Host key verified ({})".format(hostkey.get_name())
1447 )
1449 if (pkey is not None) or (password is not None) or gss_auth or gss_kex:
1450 if gss_auth:
1451 self._log(
1452 DEBUG, "Attempting GSS-API auth... (gssapi-with-mic)"
1453 ) # noqa
1454 self.auth_gssapi_with_mic(
1455 username, self.gss_host, gss_deleg_creds
1456 )
1457 elif gss_kex:
1458 self._log(DEBUG, "Attempting GSS-API auth... (gssapi-keyex)")
1459 self.auth_gssapi_keyex(username)
1460 elif pkey is not None:
1461 self._log(DEBUG, "Attempting public-key auth...")
1462 self.auth_publickey(username, pkey)
1463 else:
1464 self._log(DEBUG, "Attempting password auth...")
1465 self.auth_password(username, password)
1467 return
1469 def get_exception(self):
1470 """
1471 Return any exception that happened during the last server request.
1472 This can be used to fetch more specific error information after using
1473 calls like `start_client`. The exception (if any) is cleared after
1474 this call.
1476 :return:
1477 an exception, or ``None`` if there is no stored exception.
1479 .. versionadded:: 1.1
1480 """
1481 self.lock.acquire()
1482 try:
1483 e = self.saved_exception
1484 self.saved_exception = None
1485 return e
1486 finally:
1487 self.lock.release()
1489 def set_subsystem_handler(self, name, handler, *args, **kwargs):
1490 """
1491 Set the handler class for a subsystem in server mode. If a request
1492 for this subsystem is made on an open ssh channel later, this handler
1493 will be constructed and called -- see `.SubsystemHandler` for more
1494 detailed documentation.
1496 Any extra parameters (including keyword arguments) are saved and
1497 passed to the `.SubsystemHandler` constructor later.
1499 :param str name: name of the subsystem.
1500 :param handler:
1501 subclass of `.SubsystemHandler` that handles this subsystem.
1502 """
1503 try:
1504 self.lock.acquire()
1505 self.subsystem_table[name] = (handler, args, kwargs)
1506 finally:
1507 self.lock.release()
1509 def is_authenticated(self):
1510 """
1511 Return true if this session is active and authenticated.
1513 :return:
1514 True if the session is still open and has been authenticated
1515 successfully; False if authentication failed and/or the session is
1516 closed.
1517 """
1518 return (
1519 self.active
1520 and self.auth_handler is not None
1521 and self.auth_handler.is_authenticated()
1522 )
1524 def get_username(self):
1525 """
1526 Return the username this connection is authenticated for. If the
1527 session is not authenticated (or authentication failed), this method
1528 returns ``None``.
1530 :return: username that was authenticated (a `str`), or ``None``.
1531 """
1532 if not self.active or (self.auth_handler is None):
1533 return None
1534 return self.auth_handler.get_username()
1536 def get_banner(self):
1537 """
1538 Return the banner supplied by the server upon connect. If no banner is
1539 supplied, this method returns ``None``.
1541 :returns: server supplied banner (`str`), or ``None``.
1543 .. versionadded:: 1.13
1544 """
1545 if not self.active or (self.auth_handler is None):
1546 return None
1547 return self.auth_handler.banner
1549 def auth_none(self, username):
1550 """
1551 Try to authenticate to the server using no authentication at all.
1552 This will almost always fail. It may be useful for determining the
1553 list of authentication types supported by the server, by catching the
1554 `.BadAuthenticationType` exception raised.
1556 :param str username: the username to authenticate as
1557 :return:
1558 list of auth types permissible for the next stage of
1559 authentication (normally empty)
1561 :raises:
1562 `.BadAuthenticationType` -- if "none" authentication isn't allowed
1563 by the server for this user
1564 :raises:
1565 `.SSHException` -- if the authentication failed due to a network
1566 error
1568 .. versionadded:: 1.5
1569 """
1570 if (not self.active) or (not self.initial_kex_done):
1571 raise SSHException("No existing session")
1572 my_event = threading.Event()
1573 self.auth_handler = AuthHandler(self)
1574 self.auth_handler.auth_none(username, my_event)
1575 return self.auth_handler.wait_for_response(my_event)
1577 def auth_password(self, username, password, event=None, fallback=True):
1578 """
1579 Authenticate to the server using a password. The username and password
1580 are sent over an encrypted link.
1582 If an ``event`` is passed in, this method will return immediately, and
1583 the event will be triggered once authentication succeeds or fails. On
1584 success, `is_authenticated` will return ``True``. On failure, you may
1585 use `get_exception` to get more detailed error information.
1587 Since 1.1, if no event is passed, this method will block until the
1588 authentication succeeds or fails. On failure, an exception is raised.
1589 Otherwise, the method simply returns.
1591 Since 1.5, if no event is passed and ``fallback`` is ``True`` (the
1592 default), if the server doesn't support plain password authentication
1593 but does support so-called "keyboard-interactive" mode, an attempt
1594 will be made to authenticate using this interactive mode. If it fails,
1595 the normal exception will be thrown as if the attempt had never been
1596 made. This is useful for some recent Gentoo and Debian distributions,
1597 which turn off plain password authentication in a misguided belief
1598 that interactive authentication is "more secure". (It's not.)
1600 If the server requires multi-step authentication (which is very rare),
1601 this method will return a list of auth types permissible for the next
1602 step. Otherwise, in the normal case, an empty list is returned.
1604 :param str username: the username to authenticate as
1605 :param basestring password: the password to authenticate with
1606 :param .threading.Event event:
1607 an event to trigger when the authentication attempt is complete
1608 (whether it was successful or not)
1609 :param bool fallback:
1610 ``True`` if an attempt at an automated "interactive" password auth
1611 should be made if the server doesn't support normal password auth
1612 :return:
1613 list of auth types permissible for the next stage of
1614 authentication (normally empty)
1616 :raises:
1617 `.BadAuthenticationType` -- if password authentication isn't
1618 allowed by the server for this user (and no event was passed in)
1619 :raises:
1620 `.AuthenticationException` -- if the authentication failed (and no
1621 event was passed in)
1622 :raises: `.SSHException` -- if there was a network error
1623 """
1624 if (not self.active) or (not self.initial_kex_done):
1625 # we should never try to send the password unless we're on a secure
1626 # link
1627 raise SSHException("No existing session")
1628 if event is None:
1629 my_event = threading.Event()
1630 else:
1631 my_event = event
1632 self.auth_handler = AuthHandler(self)
1633 self.auth_handler.auth_password(username, password, my_event)
1634 if event is not None:
1635 # caller wants to wait for event themselves
1636 return []
1637 try:
1638 return self.auth_handler.wait_for_response(my_event)
1639 except BadAuthenticationType as e:
1640 # if password auth isn't allowed, but keyboard-interactive *is*,
1641 # try to fudge it
1642 if not fallback or ("keyboard-interactive" not in e.allowed_types):
1643 raise
1644 try:
1646 def handler(title, instructions, fields):
1647 if len(fields) > 1:
1648 raise SSHException("Fallback authentication failed.")
1649 if len(fields) == 0:
1650 # for some reason, at least on os x, a 2nd request will
1651 # be made with zero fields requested. maybe it's just
1652 # to try to fake out automated scripting of the exact
1653 # type we're doing here. *shrug* :)
1654 return []
1655 return [password]
1657 return self.auth_interactive(username, handler)
1658 except SSHException:
1659 # attempt failed; just raise the original exception
1660 raise e
1662 def auth_publickey(self, username, key, event=None):
1663 """
1664 Authenticate to the server using a private key. The key is used to
1665 sign data from the server, so it must include the private part.
1667 If an ``event`` is passed in, this method will return immediately, and
1668 the event will be triggered once authentication succeeds or fails. On
1669 success, `is_authenticated` will return ``True``. On failure, you may
1670 use `get_exception` to get more detailed error information.
1672 Since 1.1, if no event is passed, this method will block until the
1673 authentication succeeds or fails. On failure, an exception is raised.
1674 Otherwise, the method simply returns.
1676 If the server requires multi-step authentication (which is very rare),
1677 this method will return a list of auth types permissible for the next
1678 step. Otherwise, in the normal case, an empty list is returned.
1680 :param str username: the username to authenticate as
1681 :param .PKey key: the private key to authenticate with
1682 :param .threading.Event event:
1683 an event to trigger when the authentication attempt is complete
1684 (whether it was successful or not)
1685 :return:
1686 list of auth types permissible for the next stage of
1687 authentication (normally empty)
1689 :raises:
1690 `.BadAuthenticationType` -- if public-key authentication isn't
1691 allowed by the server for this user (and no event was passed in)
1692 :raises:
1693 `.AuthenticationException` -- if the authentication failed (and no
1694 event was passed in)
1695 :raises: `.SSHException` -- if there was a network error
1696 """
1697 if (not self.active) or (not self.initial_kex_done):
1698 # we should never try to authenticate unless we're on a secure link
1699 raise SSHException("No existing session")
1700 if event is None:
1701 my_event = threading.Event()
1702 else:
1703 my_event = event
1704 self.auth_handler = AuthHandler(self)
1705 self.auth_handler.auth_publickey(username, key, my_event)
1706 if event is not None:
1707 # caller wants to wait for event themselves
1708 return []
1709 return self.auth_handler.wait_for_response(my_event)
1711 def auth_interactive(self, username, handler, submethods=""):
1712 """
1713 Authenticate to the server interactively. A handler is used to answer
1714 arbitrary questions from the server. On many servers, this is just a
1715 dumb wrapper around PAM.
1717 This method will block until the authentication succeeds or fails,
1718 periodically calling the handler asynchronously to get answers to
1719 authentication questions. The handler may be called more than once
1720 if the server continues to ask questions.
1722 The handler is expected to be a callable that will handle calls of the
1723 form: ``handler(title, instructions, prompt_list)``. The ``title`` is
1724 meant to be a dialog-window title, and the ``instructions`` are user
1725 instructions (both are strings). ``prompt_list`` will be a list of
1726 prompts, each prompt being a tuple of ``(str, bool)``. The string is
1727 the prompt and the boolean indicates whether the user text should be
1728 echoed.
1730 A sample call would thus be:
1731 ``handler('title', 'instructions', [('Password:', False)])``.
1733 The handler should return a list or tuple of answers to the server's
1734 questions.
1736 If the server requires multi-step authentication (which is very rare),
1737 this method will return a list of auth types permissible for the next
1738 step. Otherwise, in the normal case, an empty list is returned.
1740 :param str username: the username to authenticate as
1741 :param callable handler: a handler for responding to server questions
1742 :param str submethods: a string list of desired submethods (optional)
1743 :return:
1744 list of auth types permissible for the next stage of
1745 authentication (normally empty).
1747 :raises: `.BadAuthenticationType` -- if public-key authentication isn't
1748 allowed by the server for this user
1749 :raises: `.AuthenticationException` -- if the authentication failed
1750 :raises: `.SSHException` -- if there was a network error
1752 .. versionadded:: 1.5
1753 """
1754 if (not self.active) or (not self.initial_kex_done):
1755 # we should never try to authenticate unless we're on a secure link
1756 raise SSHException("No existing session")
1757 my_event = threading.Event()
1758 self.auth_handler = AuthHandler(self)
1759 self.auth_handler.auth_interactive(
1760 username, handler, my_event, submethods
1761 )
1762 return self.auth_handler.wait_for_response(my_event)
1764 def auth_interactive_dumb(self, username, handler=None, submethods=""):
1765 """
1766 Authenticate to the server interactively but dumber.
1767 Just print the prompt and / or instructions to stdout and send back
1768 the response. This is good for situations where partial auth is
1769 achieved by key and then the user has to enter a 2fac token.
1770 """
1772 if not handler:
1774 def handler(title, instructions, prompt_list):
1775 answers = []
1776 if title:
1777 print(title.strip())
1778 if instructions:
1779 print(instructions.strip())
1780 for prompt, show_input in prompt_list:
1781 print(prompt.strip(), end=" ")
1782 answers.append(input())
1783 return answers
1785 return self.auth_interactive(username, handler, submethods)
1787 def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds):
1788 """
1789 Authenticate to the Server using GSS-API / SSPI.
1791 :param str username: The username to authenticate as
1792 :param str gss_host: The target host
1793 :param bool gss_deleg_creds: Delegate credentials or not
1794 :return: list of auth types permissible for the next stage of
1795 authentication (normally empty)
1796 :raises: `.BadAuthenticationType` -- if gssapi-with-mic isn't
1797 allowed by the server (and no event was passed in)
1798 :raises:
1799 `.AuthenticationException` -- if the authentication failed (and no
1800 event was passed in)
1801 :raises: `.SSHException` -- if there was a network error
1802 """
1803 if (not self.active) or (not self.initial_kex_done):
1804 # we should never try to authenticate unless we're on a secure link
1805 raise SSHException("No existing session")
1806 my_event = threading.Event()
1807 self.auth_handler = AuthHandler(self)
1808 self.auth_handler.auth_gssapi_with_mic(
1809 username, gss_host, gss_deleg_creds, my_event
1810 )
1811 return self.auth_handler.wait_for_response(my_event)
1813 def auth_gssapi_keyex(self, username):
1814 """
1815 Authenticate to the server with GSS-API/SSPI if GSS-API kex is in use.
1817 :param str username: The username to authenticate as.
1818 :returns:
1819 a list of auth types permissible for the next stage of
1820 authentication (normally empty)
1821 :raises: `.BadAuthenticationType` --
1822 if GSS-API Key Exchange was not performed (and no event was passed
1823 in)
1824 :raises: `.AuthenticationException` --
1825 if the authentication failed (and no event was passed in)
1826 :raises: `.SSHException` -- if there was a network error
1827 """
1828 if (not self.active) or (not self.initial_kex_done):
1829 # we should never try to authenticate unless we're on a secure link
1830 raise SSHException("No existing session")
1831 my_event = threading.Event()
1832 self.auth_handler = AuthHandler(self)
1833 self.auth_handler.auth_gssapi_keyex(username, my_event)
1834 return self.auth_handler.wait_for_response(my_event)
1836 def set_log_channel(self, name):
1837 """
1838 Set the channel for this transport's logging. The default is
1839 ``"paramiko.transport"`` but it can be set to anything you want. (See
1840 the `.logging` module for more info.) SSH Channels will log to a
1841 sub-channel of the one specified.
1843 :param str name: new channel name for logging
1845 .. versionadded:: 1.1
1846 """
1847 self.log_name = name
1848 self.logger = util.get_logger(name)
1849 self.packetizer.set_log(self.logger)
1851 def get_log_channel(self):
1852 """
1853 Return the channel name used for this transport's logging.
1855 :return: channel name as a `str`
1857 .. versionadded:: 1.2
1858 """
1859 return self.log_name
1861 def set_hexdump(self, hexdump):
1862 """
1863 Turn on/off logging a hex dump of protocol traffic at DEBUG level in
1864 the logs. Normally you would want this off (which is the default),
1865 but if you are debugging something, it may be useful.
1867 :param bool hexdump:
1868 ``True`` to log protocol traffix (in hex) to the log; ``False``
1869 otherwise.
1870 """
1871 self.packetizer.set_hexdump(hexdump)
1873 def get_hexdump(self):
1874 """
1875 Return ``True`` if the transport is currently logging hex dumps of
1876 protocol traffic.
1878 :return: ``True`` if hex dumps are being logged, else ``False``.
1880 .. versionadded:: 1.4
1881 """
1882 return self.packetizer.get_hexdump()
1884 def use_compression(self, compress=True):
1885 """
1886 Turn on/off compression. This will only have an affect before starting
1887 the transport (ie before calling `connect`, etc). By default,
1888 compression is off since it negatively affects interactive sessions.
1890 :param bool compress:
1891 ``True`` to ask the remote client/server to compress traffic;
1892 ``False`` to refuse compression
1894 .. versionadded:: 1.5.2
1895 """
1896 if compress:
1897 self._preferred_compression = ("zlib@openssh.com", "zlib", "none")
1898 else:
1899 self._preferred_compression = ("none",)
1901 def getpeername(self):
1902 """
1903 Return the address of the remote side of this Transport, if possible.
1905 This is effectively a wrapper around ``getpeername`` on the underlying
1906 socket. If the socket-like object has no ``getpeername`` method, then
1907 ``("unknown", 0)`` is returned.
1909 :return:
1910 the address of the remote host, if known, as a ``(str, int)``
1911 tuple.
1912 """
1913 gp = getattr(self.sock, "getpeername", None)
1914 if gp is None:
1915 return "unknown", 0
1916 return gp()
1918 def stop_thread(self):
1919 self.active = False
1920 self.packetizer.close()
1921 # Keep trying to join() our main thread, quickly, until:
1922 # * We join()ed successfully (self.is_alive() == False)
1923 # * Or it looks like we've hit issue #520 (socket.recv hitting some
1924 # race condition preventing it from timing out correctly), wherein
1925 # our socket and packetizer are both closed (but where we'd
1926 # otherwise be sitting forever on that recv()).
1927 while (
1928 self.is_alive()
1929 and self is not threading.current_thread()
1930 and not self.sock._closed
1931 and not self.packetizer.closed
1932 ):
1933 self.join(0.1)
1935 # internals...
1937 # TODO 4.0: make a public alias for this because multiple other classes
1938 # already explicitly rely on it...or just rewrite logging :D
1939 def _log(self, level, msg, *args):
1940 if issubclass(type(msg), list):
1941 for m in msg:
1942 self.logger.log(level, m)
1943 else:
1944 self.logger.log(level, msg, *args)
1946 def _get_modulus_pack(self):
1947 """used by KexGex to find primes for group exchange"""
1948 return self._modulus_pack
1950 def _next_channel(self):
1951 """you are holding the lock"""
1952 chanid = self._channel_counter
1953 while self._channels.get(chanid) is not None:
1954 self._channel_counter = (self._channel_counter + 1) & 0xFFFFFF
1955 chanid = self._channel_counter
1956 self._channel_counter = (self._channel_counter + 1) & 0xFFFFFF
1957 return chanid
1959 def _unlink_channel(self, chanid):
1960 """used by a Channel to remove itself from the active channel list"""
1961 self._channels.delete(chanid)
1963 def _send_message(self, data):
1964 self.packetizer.send_message(data)
1966 def _send_user_message(self, data):
1967 """
1968 send a message, but block if we're in key negotiation. this is used
1969 for user-initiated requests.
1970 """
1971 start = time.time()
1972 while True:
1973 self.clear_to_send.wait(0.1)
1974 if not self.active:
1975 self._log(
1976 DEBUG, "Dropping user packet because connection is dead."
1977 ) # noqa
1978 return
1979 self.clear_to_send_lock.acquire()
1980 if self.clear_to_send.is_set():
1981 break
1982 self.clear_to_send_lock.release()
1983 if time.time() > start + self.clear_to_send_timeout:
1984 raise SSHException(
1985 "Key-exchange timed out waiting for key negotiation"
1986 ) # noqa
1987 try:
1988 self._send_message(data)
1989 finally:
1990 self.clear_to_send_lock.release()
1992 def _set_K_H(self, k, h):
1993 """
1994 Used by a kex obj to set the K (root key) and H (exchange hash).
1995 """
1996 self.K = k
1997 self.H = h
1998 if self.session_id is None:
1999 self.session_id = h
2001 def _expect_packet(self, *ptypes):
2002 """
2003 Used by a kex obj to register the next packet type it expects to see.
2004 """
2005 self._expected_packet = tuple(ptypes)
2007 def _verify_key(self, host_key, sig):
2008 key = self._key_info[self.host_key_type](Message(host_key))
2009 if key is None:
2010 raise SSHException("Unknown host key type")
2011 if not key.verify_ssh_sig(self.H, Message(sig)):
2012 raise SSHException(
2013 "Signature verification ({}) failed.".format(
2014 self.host_key_type
2015 )
2016 ) # noqa
2017 self.host_key = key
2019 def _compute_key(self, id, nbytes):
2020 """id is 'A' - 'F' for the various keys used by ssh"""
2021 m = Message()
2022 m.add_mpint(self.K)
2023 m.add_bytes(self.H)
2024 m.add_byte(b(id))
2025 m.add_bytes(self.session_id)
2026 # Fallback to SHA1 for kex engines that fail to specify a hex
2027 # algorithm, or for e.g. transport tests that don't run kexinit.
2028 hash_algo = getattr(self.kex_engine, "hash_algo", None)
2029 hash_select_msg = "kex engine {} specified hash_algo {!r}".format(
2030 self.kex_engine.__class__.__name__, hash_algo
2031 )
2032 if hash_algo is None:
2033 hash_algo = sha1
2034 hash_select_msg += ", falling back to sha1"
2035 if not hasattr(self, "_logged_hash_selection"):
2036 self._log(DEBUG, hash_select_msg)
2037 setattr(self, "_logged_hash_selection", True)
2038 out = sofar = hash_algo(m.asbytes()).digest()
2039 while len(out) < nbytes:
2040 m = Message()
2041 m.add_mpint(self.K)
2042 m.add_bytes(self.H)
2043 m.add_bytes(sofar)
2044 digest = hash_algo(m.asbytes()).digest()
2045 out += digest
2046 sofar += digest
2047 return out[:nbytes]
2049 def _get_engine(self, name, key, iv=None, operation=None, aead=False):
2050 if name not in self._cipher_info:
2051 raise SSHException("Unknown cipher " + name)
2052 info = self._cipher_info[name]
2053 algorithm = info["class"](key)
2054 # AEAD types (eg GCM) use their algorithm class /as/ the encryption
2055 # engine (they expose the same encrypt/decrypt API as a CipherContext)
2056 if aead:
2057 return algorithm
2058 # All others go through the Cipher class.
2059 cipher = Cipher(
2060 algorithm=algorithm,
2061 # TODO: why is this getting tickled in aesgcm mode???
2062 mode=info["mode"](iv),
2063 backend=default_backend(),
2064 )
2065 if operation is self._ENCRYPT:
2066 return cipher.encryptor()
2067 else:
2068 return cipher.decryptor()
2070 def _set_forward_agent_handler(self, handler):
2071 if handler is None:
2073 def default_handler(channel):
2074 self._queue_incoming_channel(channel)
2076 self._forward_agent_handler = default_handler
2077 else:
2078 self._forward_agent_handler = handler
2080 def _set_x11_handler(self, handler):
2081 # only called if a channel has turned on x11 forwarding
2082 if handler is None:
2083 # by default, use the same mechanism as accept()
2084 def default_handler(channel, src_addr_port):
2085 self._queue_incoming_channel(channel)
2087 self._x11_handler = default_handler
2088 else:
2089 self._x11_handler = handler
2091 def _queue_incoming_channel(self, channel):
2092 self.lock.acquire()
2093 try:
2094 self.server_accepts.append(channel)
2095 self.server_accept_cv.notify()
2096 finally:
2097 self.lock.release()
2099 def _sanitize_window_size(self, window_size):
2100 if window_size is None:
2101 window_size = self.default_window_size
2102 return clamp_value(MIN_WINDOW_SIZE, window_size, MAX_WINDOW_SIZE)
2104 def _sanitize_packet_size(self, max_packet_size):
2105 if max_packet_size is None:
2106 max_packet_size = self.default_max_packet_size
2107 return clamp_value(MIN_PACKET_SIZE, max_packet_size, MAX_WINDOW_SIZE)
2109 def _ensure_authed(self, ptype, message):
2110 """
2111 Checks message type against current auth state.
2113 If server mode, and auth has not succeeded, and the message is of a
2114 post-auth type (channel open or global request) an appropriate error
2115 response Message is crafted and returned to caller for sending.
2117 Otherwise (client mode, authed, or pre-auth message) returns None.
2118 """
2119 if (
2120 not self.server_mode
2121 or ptype <= HIGHEST_USERAUTH_MESSAGE_ID
2122 or self.is_authenticated()
2123 ):
2124 return None
2125 # WELP. We must be dealing with someone trying to do non-auth things
2126 # without being authed. Tell them off, based on message class.
2127 reply = Message()
2128 # Global requests have no details, just failure.
2129 if ptype == MSG_GLOBAL_REQUEST:
2130 reply.add_byte(cMSG_REQUEST_FAILURE)
2131 # Channel opens let us reject w/ a specific type + message.
2132 elif ptype == MSG_CHANNEL_OPEN:
2133 kind = message.get_text() # noqa
2134 chanid = message.get_int()
2135 reply.add_byte(cMSG_CHANNEL_OPEN_FAILURE)
2136 reply.add_int(chanid)
2137 reply.add_int(OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED)
2138 reply.add_string("")
2139 reply.add_string("en")
2140 # NOTE: Post-open channel messages do not need checking; the above will
2141 # reject attempts to open channels, meaning that even if a malicious
2142 # user tries to send a MSG_CHANNEL_REQUEST, it will simply fall under
2143 # the logic that handles unknown channel IDs (as the channel list will
2144 # be empty.)
2145 return reply
2147 def _enforce_strict_kex(self, ptype):
2148 """
2149 Conditionally raise `MessageOrderError` during strict initial kex.
2151 This method should only be called inside code that handles non-KEXINIT
2152 messages; it does not interrogate ``ptype`` besides using it to log
2153 more accurately.
2154 """
2155 if self.agreed_on_strict_kex and not self.initial_kex_done:
2156 name = MSG_NAMES.get(ptype, f"msg {ptype}")
2157 raise MessageOrderError(
2158 f"In strict-kex mode, but was sent {name!r}!"
2159 )
2161 def run(self):
2162 # (use the exposed "run" method, because if we specify a thread target
2163 # of a private method, threading.Thread will keep a reference to it
2164 # indefinitely, creating a GC cycle and not letting Transport ever be
2165 # GC'd. it's a bug in Thread.)
2167 # Hold reference to 'sys' so we can test sys.modules to detect
2168 # interpreter shutdown.
2169 self.sys = sys
2171 # active=True occurs before the thread is launched, to avoid a race
2172 _active_threads.append(self)
2173 tid = hex(id(self) & xffffffff)
2174 if self.server_mode:
2175 self._log(DEBUG, "starting thread (server mode): {}".format(tid))
2176 else:
2177 self._log(DEBUG, "starting thread (client mode): {}".format(tid))
2178 try:
2179 try:
2180 self.packetizer.write_all(b(self.local_version + "\r\n"))
2181 self._log(
2182 DEBUG,
2183 "Local version/idstring: {}".format(self.local_version),
2184 ) # noqa
2185 self._check_banner()
2186 # The above is actually very much part of the handshake, but
2187 # sometimes the banner can be read but the machine is not
2188 # responding, for example when the remote ssh daemon is loaded
2189 # in to memory but we can not read from the disk/spawn a new
2190 # shell.
2191 # Make sure we can specify a timeout for the initial handshake.
2192 # Re-use the banner timeout for now.
2193 self.packetizer.start_handshake(self.handshake_timeout)
2194 self._send_kex_init()
2195 self._expect_packet(MSG_KEXINIT)
2197 while self.active:
2198 if self.packetizer.need_rekey() and not self.in_kex:
2199 self._send_kex_init()
2200 try:
2201 ptype, m = self.packetizer.read_message()
2202 except NeedRekeyException:
2203 continue
2204 if ptype == MSG_IGNORE:
2205 self._enforce_strict_kex(ptype)
2206 continue
2207 elif ptype == MSG_DISCONNECT:
2208 self._parse_disconnect(m)
2209 break
2210 elif ptype == MSG_DEBUG:
2211 self._enforce_strict_kex(ptype)
2212 self._parse_debug(m)
2213 continue
2214 if len(self._expected_packet) > 0:
2215 if ptype not in self._expected_packet:
2216 exc_class = SSHException
2217 if self.agreed_on_strict_kex:
2218 exc_class = MessageOrderError
2219 raise exc_class(
2220 "Expecting packet from {!r}, got {:d}".format(
2221 self._expected_packet, ptype
2222 )
2223 ) # noqa
2224 self._expected_packet = tuple()
2225 # These message IDs indicate key exchange & will differ
2226 # depending on exact exchange algorithm
2227 if (ptype >= 30) and (ptype <= 41):
2228 self.kex_engine.parse_next(ptype, m)
2229 continue
2231 if ptype in self._handler_table:
2232 error_msg = self._ensure_authed(ptype, m)
2233 if error_msg:
2234 self._send_message(error_msg)
2235 else:
2236 self._handler_table[ptype](m)
2237 elif ptype in self._channel_handler_table:
2238 chanid = m.get_int()
2239 chan = self._channels.get(chanid)
2240 if chan is not None:
2241 self._channel_handler_table[ptype](chan, m)
2242 elif chanid in self.channels_seen:
2243 self._log(
2244 DEBUG,
2245 "Ignoring message for dead channel {:d}".format( # noqa
2246 chanid
2247 ),
2248 )
2249 else:
2250 self._log(
2251 ERROR,
2252 "Channel request for unknown channel {:d}".format( # noqa
2253 chanid
2254 ),
2255 )
2256 break
2257 elif (
2258 self.auth_handler is not None
2259 and ptype in self.auth_handler._handler_table
2260 ):
2261 handler = self.auth_handler._handler_table[ptype]
2262 handler(m)
2263 if len(self._expected_packet) > 0:
2264 continue
2265 else:
2266 # Respond with "I don't implement this particular
2267 # message type" message (unless the message type was
2268 # itself literally MSG_UNIMPLEMENTED, in which case, we
2269 # just shut up to avoid causing a useless loop).
2270 name = MSG_NAMES[ptype]
2271 warning = "Oops, unhandled type {} ({!r})".format(
2272 ptype, name
2273 )
2274 self._log(WARNING, warning)
2275 if ptype != MSG_UNIMPLEMENTED:
2276 msg = Message()
2277 msg.add_byte(cMSG_UNIMPLEMENTED)
2278 msg.add_int(m.seqno)
2279 self._send_message(msg)
2280 self.packetizer.complete_handshake()
2281 except SSHException as e:
2282 self._log(
2283 ERROR,
2284 "Exception ({}): {}".format(
2285 "server" if self.server_mode else "client", e
2286 ),
2287 )
2288 self._log(ERROR, util.tb_strings())
2289 self.saved_exception = e
2290 except EOFError as e:
2291 self._log(DEBUG, "EOF in transport thread")
2292 self.saved_exception = e
2293 except socket.error as e:
2294 if type(e.args) is tuple:
2295 if e.args:
2296 emsg = "{} ({:d})".format(e.args[1], e.args[0])
2297 else: # empty tuple, e.g. socket.timeout
2298 emsg = str(e) or repr(e)
2299 else:
2300 emsg = e.args
2301 self._log(ERROR, "Socket exception: " + emsg)
2302 self.saved_exception = e
2303 except Exception as e:
2304 self._log(ERROR, "Unknown exception: " + str(e))
2305 self._log(ERROR, util.tb_strings())
2306 self.saved_exception = e
2307 _active_threads.remove(self)
2308 for chan in list(self._channels.values()):
2309 chan._unlink()
2310 if self.active:
2311 self.active = False
2312 self.packetizer.close()
2313 if self.completion_event is not None:
2314 self.completion_event.set()
2315 if self.auth_handler is not None:
2316 self.auth_handler.abort()
2317 for event in self.channel_events.values():
2318 event.set()
2319 try:
2320 self.lock.acquire()
2321 self.server_accept_cv.notify()
2322 finally:
2323 self.lock.release()
2324 self.sock.close()
2325 except:
2326 # Don't raise spurious 'NoneType has no attribute X' errors when we
2327 # wake up during interpreter shutdown. Or rather -- raise
2328 # everything *if* sys.modules (used as a convenient sentinel)
2329 # appears to still exist.
2330 if self.sys.modules is not None:
2331 raise
2333 def _log_agreement(self, which, local, remote):
2334 # Log useful, non-duplicative line re: an agreed-upon algorithm.
2335 # Old code implied algorithms could be asymmetrical (different for
2336 # inbound vs outbound) so we preserve that possibility.
2337 msg = "{}: ".format(which)
2338 if local == remote:
2339 msg += local
2340 else:
2341 msg += "local={}, remote={}".format(local, remote)
2342 self._log(DEBUG, msg)
2344 # protocol stages
2346 def _negotiate_keys(self, m):
2347 # throws SSHException on anything unusual
2348 self.clear_to_send_lock.acquire()
2349 try:
2350 self.clear_to_send.clear()
2351 finally:
2352 self.clear_to_send_lock.release()
2353 if self.local_kex_init is None:
2354 # remote side wants to renegotiate
2355 self._send_kex_init()
2356 self._parse_kex_init(m)
2357 self.kex_engine.start_kex()
2359 def _check_banner(self):
2360 # this is slow, but we only have to do it once
2361 for i in range(100):
2362 # give them 15 seconds for the first line, then just 2 seconds
2363 # each additional line. (some sites have very high latency.)
2364 if i == 0:
2365 timeout = self.banner_timeout
2366 else:
2367 timeout = 2
2368 try:
2369 buf = self.packetizer.readline(timeout)
2370 except ProxyCommandFailure:
2371 raise
2372 except Exception as e:
2373 raise SSHException(
2374 "Error reading SSH protocol banner" + str(e)
2375 )
2376 if buf[:4] == "SSH-":
2377 break
2378 self._log(DEBUG, "Banner: " + buf)
2379 if buf[:4] != "SSH-":
2380 raise SSHException('Indecipherable protocol version "' + buf + '"')
2381 # save this server version string for later
2382 self.remote_version = buf
2383 self._log(DEBUG, "Remote version/idstring: {}".format(buf))
2384 # pull off any attached comment
2385 # NOTE: comment used to be stored in a variable and then...never used.
2386 # since 2003. ca 877cd974b8182d26fa76d566072917ea67b64e67
2387 i = buf.find(" ")
2388 if i >= 0:
2389 buf = buf[:i]
2390 # parse out version string and make sure it matches
2391 segs = buf.split("-", 2)
2392 if len(segs) < 3:
2393 raise SSHException("Invalid SSH banner")
2394 version = segs[1]
2395 client = segs[2]
2396 if version != "1.99" and version != "2.0":
2397 msg = "Incompatible version ({} instead of 2.0)"
2398 raise IncompatiblePeer(msg.format(version))
2399 msg = "Connected (version {}, client {})".format(version, client)
2400 self._log(INFO, msg)
2402 def _send_kex_init(self):
2403 """
2404 announce to the other side that we'd like to negotiate keys, and what
2405 kind of key negotiation we support.
2406 """
2407 self.clear_to_send_lock.acquire()
2408 try:
2409 self.clear_to_send.clear()
2410 finally:
2411 self.clear_to_send_lock.release()
2412 self.gss_kex_used = False
2413 self.in_kex = True
2414 kex_algos = list(self.preferred_kex)
2415 if self.server_mode:
2416 mp_required_prefix = "diffie-hellman-group-exchange-sha"
2417 kex_mp = [k for k in kex_algos if k.startswith(mp_required_prefix)]
2418 if (self._modulus_pack is None) and (len(kex_mp) > 0):
2419 # can't do group-exchange if we don't have a pack of potential
2420 # primes
2421 pkex = [
2422 k
2423 for k in self.get_security_options().kex
2424 if not k.startswith(mp_required_prefix)
2425 ]
2426 self.get_security_options().kex = pkex
2427 available_server_keys = list(
2428 filter(
2429 list(self.server_key_dict.keys()).__contains__,
2430 # TODO: ensure tests will catch if somebody streamlines
2431 # this by mistake - case is the admittedly silly one where
2432 # the only calls to add_server_key() contain keys which
2433 # were filtered out of the below via disabled_algorithms.
2434 # If this is streamlined, we would then be allowing the
2435 # disabled algorithm(s) for hostkey use
2436 # TODO: honestly this prob just wants to get thrown out
2437 # when we make kex configuration more straightforward
2438 self.preferred_keys,
2439 )
2440 )
2441 else:
2442 available_server_keys = self.preferred_keys
2443 # Signal support for MSG_EXT_INFO so server will send it to us.
2444 # NOTE: doing this here handily means we don't even consider this
2445 # value when agreeing on real kex algo to use (which is a common
2446 # pitfall when adding this apparently).
2447 kex_algos.append("ext-info-c")
2449 # Similar to ext-info, but used in both server modes, so done outside
2450 # of above if/else.
2451 if self.advertise_strict_kex:
2452 which = "s" if self.server_mode else "c"
2453 kex_algos.append(f"kex-strict-{which}-v00@openssh.com")
2455 m = Message()
2456 m.add_byte(cMSG_KEXINIT)
2457 m.add_bytes(os.urandom(16))
2458 m.add_list(kex_algos)
2459 m.add_list(available_server_keys)
2460 m.add_list(self.preferred_ciphers)
2461 m.add_list(self.preferred_ciphers)
2462 m.add_list(self.preferred_macs)
2463 m.add_list(self.preferred_macs)
2464 m.add_list(self.preferred_compression)
2465 m.add_list(self.preferred_compression)
2466 m.add_string(bytes())
2467 m.add_string(bytes())
2468 m.add_boolean(False)
2469 m.add_int(0)
2470 # save a copy for later (needed to compute a hash)
2471 self.local_kex_init = self._latest_kex_init = m.asbytes()
2472 self._send_message(m)
2474 def _really_parse_kex_init(self, m, ignore_first_byte=False):
2475 parsed = {}
2476 if ignore_first_byte:
2477 m.get_byte()
2478 m.get_bytes(16) # cookie, discarded
2479 parsed["kex_algo_list"] = m.get_list()
2480 parsed["server_key_algo_list"] = m.get_list()
2481 parsed["client_encrypt_algo_list"] = m.get_list()
2482 parsed["server_encrypt_algo_list"] = m.get_list()
2483 parsed["client_mac_algo_list"] = m.get_list()
2484 parsed["server_mac_algo_list"] = m.get_list()
2485 parsed["client_compress_algo_list"] = m.get_list()
2486 parsed["server_compress_algo_list"] = m.get_list()
2487 parsed["client_lang_list"] = m.get_list()
2488 parsed["server_lang_list"] = m.get_list()
2489 parsed["kex_follows"] = m.get_boolean()
2490 m.get_int() # unused
2491 return parsed
2493 def _get_latest_kex_init(self):
2494 return self._really_parse_kex_init(
2495 Message(self._latest_kex_init),
2496 ignore_first_byte=True,
2497 )
2499 def _parse_kex_init(self, m):
2500 parsed = self._really_parse_kex_init(m)
2501 kex_algo_list = parsed["kex_algo_list"]
2502 server_key_algo_list = parsed["server_key_algo_list"]
2503 client_encrypt_algo_list = parsed["client_encrypt_algo_list"]
2504 server_encrypt_algo_list = parsed["server_encrypt_algo_list"]
2505 client_mac_algo_list = parsed["client_mac_algo_list"]
2506 server_mac_algo_list = parsed["server_mac_algo_list"]
2507 client_compress_algo_list = parsed["client_compress_algo_list"]
2508 server_compress_algo_list = parsed["server_compress_algo_list"]
2509 client_lang_list = parsed["client_lang_list"]
2510 server_lang_list = parsed["server_lang_list"]
2511 kex_follows = parsed["kex_follows"]
2513 self._log(DEBUG, "=== Key exchange possibilities ===")
2514 for prefix, value in (
2515 ("kex algos", kex_algo_list),
2516 ("server key", server_key_algo_list),
2517 # TODO: shouldn't these two lines say "cipher" to match usual
2518 # terminology (including elsewhere in paramiko!)?
2519 ("client encrypt", client_encrypt_algo_list),
2520 ("server encrypt", server_encrypt_algo_list),
2521 ("client mac", client_mac_algo_list),
2522 ("server mac", server_mac_algo_list),
2523 ("client compress", client_compress_algo_list),
2524 ("server compress", server_compress_algo_list),
2525 ("client lang", client_lang_list),
2526 ("server lang", server_lang_list),
2527 ):
2528 if value == [""]:
2529 value = ["<none>"]
2530 value = ", ".join(value)
2531 self._log(DEBUG, "{}: {}".format(prefix, value))
2532 self._log(DEBUG, "kex follows: {}".format(kex_follows))
2533 self._log(DEBUG, "=== Key exchange agreements ===")
2535 # Record, and strip out, ext-info and/or strict-kex non-algorithms
2536 self._remote_ext_info = None
2537 self._remote_strict_kex = None
2538 to_pop = []
2539 for i, algo in enumerate(kex_algo_list):
2540 if algo.startswith("ext-info-"):
2541 self._remote_ext_info = algo
2542 to_pop.insert(0, i)
2543 elif algo.startswith("kex-strict-"):
2544 # NOTE: this is what we are expecting from the /remote/ end.
2545 which = "c" if self.server_mode else "s"
2546 expected = f"kex-strict-{which}-v00@openssh.com"
2547 # Set strict mode if agreed.
2548 self.agreed_on_strict_kex = (
2549 algo == expected and self.advertise_strict_kex
2550 )
2551 self._log(
2552 DEBUG, f"Strict kex mode: {self.agreed_on_strict_kex}"
2553 )
2554 to_pop.insert(0, i)
2555 for i in to_pop:
2556 kex_algo_list.pop(i)
2558 # CVE mitigation: expect zeroed-out seqno anytime we are performing kex
2559 # init phase, if strict mode was negotiated.
2560 if (
2561 self.agreed_on_strict_kex
2562 and not self.initial_kex_done
2563 and m.seqno != 0
2564 ):
2565 raise MessageOrderError(
2566 "In strict-kex mode, but KEXINIT was not the first packet!"
2567 )
2569 # as a server, we pick the first item in the client's list that we
2570 # support.
2571 # as a client, we pick the first item in our list that the server
2572 # supports.
2573 if self.server_mode:
2574 agreed_kex = list(
2575 filter(self.preferred_kex.__contains__, kex_algo_list)
2576 )
2577 else:
2578 agreed_kex = list(
2579 filter(kex_algo_list.__contains__, self.preferred_kex)
2580 )
2581 if len(agreed_kex) == 0:
2582 # TODO: do an auth-overhaul style aggregate exception here?
2583 # TODO: would let us streamline log output & show all failures up
2584 # front
2585 raise IncompatiblePeer(
2586 "Incompatible ssh peer (no acceptable kex algorithm)"
2587 ) # noqa
2588 self.kex_engine = self._kex_info[agreed_kex[0]](self)
2589 self._log(DEBUG, "Kex: {}".format(agreed_kex[0]))
2591 if self.server_mode:
2592 available_server_keys = list(
2593 filter(
2594 list(self.server_key_dict.keys()).__contains__,
2595 self.preferred_keys,
2596 )
2597 )
2598 agreed_keys = list(
2599 filter(
2600 available_server_keys.__contains__, server_key_algo_list
2601 )
2602 )
2603 else:
2604 agreed_keys = list(
2605 filter(server_key_algo_list.__contains__, self.preferred_keys)
2606 )
2607 if len(agreed_keys) == 0:
2608 raise IncompatiblePeer(
2609 "Incompatible ssh peer (no acceptable host key)"
2610 ) # noqa
2611 self.host_key_type = agreed_keys[0]
2612 if self.server_mode and (self.get_server_key() is None):
2613 raise IncompatiblePeer(
2614 "Incompatible ssh peer (can't match requested host key type)"
2615 ) # noqa
2616 self._log_agreement("HostKey", agreed_keys[0], agreed_keys[0])
2618 if self.server_mode:
2619 agreed_local_ciphers = list(
2620 filter(
2621 self.preferred_ciphers.__contains__,
2622 server_encrypt_algo_list,
2623 )
2624 )
2625 agreed_remote_ciphers = list(
2626 filter(
2627 self.preferred_ciphers.__contains__,
2628 client_encrypt_algo_list,
2629 )
2630 )
2631 else:
2632 agreed_local_ciphers = list(
2633 filter(
2634 client_encrypt_algo_list.__contains__,
2635 self.preferred_ciphers,
2636 )
2637 )
2638 agreed_remote_ciphers = list(
2639 filter(
2640 server_encrypt_algo_list.__contains__,
2641 self.preferred_ciphers,
2642 )
2643 )
2644 if len(agreed_local_ciphers) == 0 or len(agreed_remote_ciphers) == 0:
2645 raise IncompatiblePeer(
2646 "Incompatible ssh server (no acceptable ciphers)"
2647 ) # noqa
2648 self.local_cipher = agreed_local_ciphers[0]
2649 self.remote_cipher = agreed_remote_ciphers[0]
2650 self._log_agreement(
2651 "Cipher", local=self.local_cipher, remote=self.remote_cipher
2652 )
2654 if self.server_mode:
2655 agreed_remote_macs = list(
2656 filter(self.preferred_macs.__contains__, client_mac_algo_list)
2657 )
2658 agreed_local_macs = list(
2659 filter(self.preferred_macs.__contains__, server_mac_algo_list)
2660 )
2661 else:
2662 agreed_local_macs = list(
2663 filter(client_mac_algo_list.__contains__, self.preferred_macs)
2664 )
2665 agreed_remote_macs = list(
2666 filter(server_mac_algo_list.__contains__, self.preferred_macs)
2667 )
2668 if (len(agreed_local_macs) == 0) or (len(agreed_remote_macs) == 0):
2669 raise IncompatiblePeer(
2670 "Incompatible ssh server (no acceptable macs)"
2671 )
2672 self.local_mac = agreed_local_macs[0]
2673 self.remote_mac = agreed_remote_macs[0]
2674 self._log_agreement(
2675 "MAC", local=self.local_mac, remote=self.remote_mac
2676 )
2678 if self.server_mode:
2679 agreed_remote_compression = list(
2680 filter(
2681 self.preferred_compression.__contains__,
2682 client_compress_algo_list,
2683 )
2684 )
2685 agreed_local_compression = list(
2686 filter(
2687 self.preferred_compression.__contains__,
2688 server_compress_algo_list,
2689 )
2690 )
2691 else:
2692 agreed_local_compression = list(
2693 filter(
2694 client_compress_algo_list.__contains__,
2695 self.preferred_compression,
2696 )
2697 )
2698 agreed_remote_compression = list(
2699 filter(
2700 server_compress_algo_list.__contains__,
2701 self.preferred_compression,
2702 )
2703 )
2704 if (
2705 len(agreed_local_compression) == 0
2706 or len(agreed_remote_compression) == 0
2707 ):
2708 msg = "Incompatible ssh server (no acceptable compression)"
2709 msg += " {!r} {!r} {!r}"
2710 raise IncompatiblePeer(
2711 msg.format(
2712 agreed_local_compression,
2713 agreed_remote_compression,
2714 self.preferred_compression,
2715 )
2716 )
2717 self.local_compression = agreed_local_compression[0]
2718 self.remote_compression = agreed_remote_compression[0]
2719 self._log_agreement(
2720 "Compression",
2721 local=self.local_compression,
2722 remote=self.remote_compression,
2723 )
2724 self._log(DEBUG, "=== End of kex handshake ===")
2726 # save for computing hash later...
2727 # now wait! openssh has a bug (and others might too) where there are
2728 # actually some extra bytes (one NUL byte in openssh's case) added to
2729 # the end of the packet but not parsed. turns out we need to throw
2730 # away those bytes because they aren't part of the hash.
2731 self.remote_kex_init = cMSG_KEXINIT + m.get_so_far()
2733 def _activate_inbound(self):
2734 """switch on newly negotiated encryption parameters for
2735 inbound traffic"""
2736 info = self._cipher_info[self.remote_cipher]
2737 aead = info.get("is_aead", False)
2738 block_size = info["block-size"]
2739 key_size = info["key-size"]
2740 # Non-AEAD/GCM type ciphers' IV size is their block size.
2741 iv_size = info.get("iv-size", block_size)
2742 if self.server_mode:
2743 iv_in = self._compute_key("A", iv_size)
2744 key_in = self._compute_key("C", key_size)
2745 else:
2746 iv_in = self._compute_key("B", iv_size)
2747 key_in = self._compute_key("D", key_size)
2749 engine = self._get_engine(
2750 name=self.remote_cipher,
2751 key=key_in,
2752 iv=iv_in,
2753 operation=self._DECRYPT,
2754 aead=aead,
2755 )
2756 etm = (not aead) and "etm@openssh.com" in self.remote_mac
2757 mac_size = self._mac_info[self.remote_mac]["size"]
2758 mac_engine = self._mac_info[self.remote_mac]["class"]
2759 # initial mac keys are done in the hash's natural size (not the
2760 # potentially truncated transmission size)
2761 if self.server_mode:
2762 mac_key = self._compute_key("E", mac_engine().digest_size)
2763 else:
2764 mac_key = self._compute_key("F", mac_engine().digest_size)
2766 self.packetizer.set_inbound_cipher(
2767 block_engine=engine,
2768 block_size=block_size,
2769 mac_engine=None if aead else mac_engine,
2770 mac_size=16 if aead else mac_size,
2771 mac_key=None if aead else mac_key,
2772 etm=etm,
2773 aead=aead,
2774 iv_in=iv_in if aead else None,
2775 )
2777 compress_in = self._compression_info[self.remote_compression][1]
2778 if compress_in is not None and (
2779 self.remote_compression != "zlib@openssh.com" or self.authenticated
2780 ):
2781 self._log(DEBUG, "Switching on inbound compression ...")
2782 self.packetizer.set_inbound_compressor(compress_in())
2783 # Reset inbound sequence number if strict mode.
2784 if self.agreed_on_strict_kex:
2785 self._log(
2786 DEBUG,
2787 "Resetting inbound seqno after NEWKEYS due to strict mode",
2788 )
2789 self.packetizer.reset_seqno_in()
2791 def _activate_outbound(self):
2792 """switch on newly negotiated encryption parameters for
2793 outbound traffic"""
2794 m = Message()
2795 m.add_byte(cMSG_NEWKEYS)
2796 self._send_message(m)
2797 # Reset outbound sequence number if strict mode.
2798 if self.agreed_on_strict_kex:
2799 self._log(
2800 DEBUG,
2801 "Resetting outbound seqno after NEWKEYS due to strict mode",
2802 )
2803 self.packetizer.reset_seqno_out()
2804 info = self._cipher_info[self.local_cipher]
2805 aead = info.get("is_aead", False)
2806 block_size = info["block-size"]
2807 key_size = info["key-size"]
2808 # Non-AEAD/GCM type ciphers' IV size is their block size.
2809 iv_size = info.get("iv-size", block_size)
2810 if self.server_mode:
2811 iv_out = self._compute_key("B", iv_size)
2812 key_out = self._compute_key("D", key_size)
2813 else:
2814 iv_out = self._compute_key("A", iv_size)
2815 key_out = self._compute_key("C", key_size)
2817 engine = self._get_engine(
2818 name=self.local_cipher,
2819 key=key_out,
2820 iv=iv_out,
2821 operation=self._ENCRYPT,
2822 aead=aead,
2823 )
2824 etm = (not aead) and "etm@openssh.com" in self.local_mac
2825 mac_size = self._mac_info[self.local_mac]["size"]
2826 mac_engine = self._mac_info[self.local_mac]["class"]
2827 # initial mac keys are done in the hash's natural size (not the
2828 # potentially truncated transmission size)
2829 if self.server_mode:
2830 mac_key = self._compute_key("F", mac_engine().digest_size)
2831 else:
2832 mac_key = self._compute_key("E", mac_engine().digest_size)
2833 sdctr = self.local_cipher.endswith("-ctr")
2835 self.packetizer.set_outbound_cipher(
2836 block_engine=engine,
2837 block_size=block_size,
2838 mac_engine=None if aead else mac_engine,
2839 mac_size=16 if aead else mac_size,
2840 mac_key=None if aead else mac_key,
2841 sdctr=sdctr,
2842 etm=etm,
2843 aead=aead,
2844 iv_out=iv_out if aead else None,
2845 )
2847 compress_out = self._compression_info[self.local_compression][0]
2848 if compress_out is not None and (
2849 self.local_compression != "zlib@openssh.com" or self.authenticated
2850 ):
2851 self._log(DEBUG, "Switching on outbound compression ...")
2852 self.packetizer.set_outbound_compressor(compress_out())
2853 if not self.packetizer.need_rekey():
2854 self.in_kex = False
2855 # If client indicated extension support, send that packet immediately
2856 if (
2857 self.server_mode
2858 and self.server_sig_algs
2859 and self._remote_ext_info == "ext-info-c"
2860 ):
2861 extensions = {"server-sig-algs": ",".join(self.preferred_pubkeys)}
2862 m = Message()
2863 m.add_byte(cMSG_EXT_INFO)
2864 m.add_int(len(extensions))
2865 for name, value in sorted(extensions.items()):
2866 m.add_string(name)
2867 m.add_string(value)
2868 self._send_message(m)
2869 # we always expect to receive NEWKEYS now
2870 self._expect_packet(MSG_NEWKEYS)
2872 def _auth_trigger(self):
2873 self.authenticated = True
2874 # delayed initiation of compression
2875 if self.local_compression == "zlib@openssh.com":
2876 compress_out = self._compression_info[self.local_compression][0]
2877 self._log(DEBUG, "Switching on outbound compression ...")
2878 self.packetizer.set_outbound_compressor(compress_out())
2879 if self.remote_compression == "zlib@openssh.com":
2880 compress_in = self._compression_info[self.remote_compression][1]
2881 self._log(DEBUG, "Switching on inbound compression ...")
2882 self.packetizer.set_inbound_compressor(compress_in())
2884 def _parse_ext_info(self, msg):
2885 # Packet is a count followed by that many key-string to possibly-bytes
2886 # pairs.
2887 extensions = {}
2888 for _ in range(msg.get_int()):
2889 name = msg.get_text()
2890 value = msg.get_string()
2891 extensions[name] = value
2892 self._log(DEBUG, "Got EXT_INFO: {}".format(extensions))
2893 # NOTE: this should work ok in cases where a server sends /two/ such
2894 # messages; the RFC explicitly states a 2nd one should overwrite the
2895 # 1st.
2896 self.server_extensions = extensions
2898 def _parse_newkeys(self, m):
2899 self._log(DEBUG, "Switch to new keys ...")
2900 self._activate_inbound()
2901 # can also free a bunch of stuff here
2902 self.local_kex_init = self.remote_kex_init = None
2903 self.K = None
2904 self.kex_engine = None
2905 if self.server_mode and (self.auth_handler is None):
2906 # create auth handler for server mode
2907 self.auth_handler = AuthHandler(self)
2908 if not self.initial_kex_done:
2909 # this was the first key exchange
2910 # (also signal to packetizer as it sometimes wants to know this
2911 # status as well, eg when seqnos rollover)
2912 self.initial_kex_done = self.packetizer._initial_kex_done = True
2913 # send an event?
2914 if self.completion_event is not None:
2915 self.completion_event.set()
2916 # it's now okay to send data again (if this was a re-key)
2917 if not self.packetizer.need_rekey():
2918 self.in_kex = False
2919 self.clear_to_send_lock.acquire()
2920 try:
2921 self.clear_to_send.set()
2922 finally:
2923 self.clear_to_send_lock.release()
2924 return
2926 def _parse_disconnect(self, m):
2927 code = m.get_int()
2928 desc = m.get_text()
2929 self._log(INFO, "Disconnect (code {:d}): {}".format(code, desc))
2931 def _parse_global_request(self, m):
2932 kind = m.get_text()
2933 self._log(DEBUG, 'Received global request "{}"'.format(kind))
2934 want_reply = m.get_boolean()
2935 if not self.server_mode:
2936 self._log(
2937 DEBUG,
2938 'Rejecting "{}" global request from server.'.format(kind),
2939 )
2940 ok = False
2941 elif kind == "tcpip-forward":
2942 address = m.get_text()
2943 port = m.get_int()
2944 ok = self.server_object.check_port_forward_request(address, port)
2945 if ok:
2946 ok = (ok,)
2947 elif kind == "cancel-tcpip-forward":
2948 address = m.get_text()
2949 port = m.get_int()
2950 self.server_object.cancel_port_forward_request(address, port)
2951 ok = True
2952 else:
2953 ok = self.server_object.check_global_request(kind, m)
2954 extra = ()
2955 if type(ok) is tuple:
2956 extra = ok
2957 ok = True
2958 if want_reply:
2959 msg = Message()
2960 if ok:
2961 msg.add_byte(cMSG_REQUEST_SUCCESS)
2962 msg.add(*extra)
2963 else:
2964 msg.add_byte(cMSG_REQUEST_FAILURE)
2965 self._send_message(msg)
2967 def _parse_request_success(self, m):
2968 self._log(DEBUG, "Global request successful.")
2969 self.global_response = m
2970 if self.completion_event is not None:
2971 self.completion_event.set()
2973 def _parse_request_failure(self, m):
2974 self._log(DEBUG, "Global request denied.")
2975 self.global_response = None
2976 if self.completion_event is not None:
2977 self.completion_event.set()
2979 def _parse_channel_open_success(self, m):
2980 chanid = m.get_int()
2981 server_chanid = m.get_int()
2982 server_window_size = m.get_int()
2983 server_max_packet_size = m.get_int()
2984 chan = self._channels.get(chanid)
2985 if chan is None:
2986 self._log(WARNING, "Success for unrequested channel! [??]")
2987 return
2988 self.lock.acquire()
2989 try:
2990 chan._set_remote_channel(
2991 server_chanid, server_window_size, server_max_packet_size
2992 )
2993 self._log(DEBUG, "Secsh channel {:d} opened.".format(chanid))
2994 if chanid in self.channel_events:
2995 self.channel_events[chanid].set()
2996 del self.channel_events[chanid]
2997 finally:
2998 self.lock.release()
2999 return
3001 def _parse_channel_open_failure(self, m):
3002 chanid = m.get_int()
3003 reason = m.get_int()
3004 reason_str = m.get_text()
3005 m.get_text() # ignored language
3006 reason_text = CONNECTION_FAILED_CODE.get(reason, "(unknown code)")
3007 self._log(
3008 ERROR,
3009 "Secsh channel {:d} open FAILED: {}: {}".format(
3010 chanid, reason_str, reason_text
3011 ),
3012 )
3013 self.lock.acquire()
3014 try:
3015 self.saved_exception = ChannelException(reason, reason_text)
3016 if chanid in self.channel_events:
3017 self._channels.delete(chanid)
3018 if chanid in self.channel_events:
3019 self.channel_events[chanid].set()
3020 del self.channel_events[chanid]
3021 finally:
3022 self.lock.release()
3023 return
3025 def _parse_channel_open(self, m):
3026 kind = m.get_text()
3027 chanid = m.get_int()
3028 initial_window_size = m.get_int()
3029 max_packet_size = m.get_int()
3030 reject = False
3031 if (
3032 kind == "auth-agent@openssh.com"
3033 and self._forward_agent_handler is not None
3034 ):
3035 self._log(DEBUG, "Incoming forward agent connection")
3036 self.lock.acquire()
3037 try:
3038 my_chanid = self._next_channel()
3039 finally:
3040 self.lock.release()
3041 elif (kind == "x11") and (self._x11_handler is not None):
3042 origin_addr = m.get_text()
3043 origin_port = m.get_int()
3044 self._log(
3045 DEBUG,
3046 "Incoming x11 connection from {}:{:d}".format(
3047 origin_addr, origin_port
3048 ),
3049 )
3050 self.lock.acquire()
3051 try:
3052 my_chanid = self._next_channel()
3053 finally:
3054 self.lock.release()
3055 elif (kind == "forwarded-tcpip") and (self._tcp_handler is not None):
3056 server_addr = m.get_text()
3057 server_port = m.get_int()
3058 origin_addr = m.get_text()
3059 origin_port = m.get_int()
3060 self._log(
3061 DEBUG,
3062 "Incoming tcp forwarded connection from {}:{:d}".format(
3063 origin_addr, origin_port
3064 ),
3065 )
3066 self.lock.acquire()
3067 try:
3068 my_chanid = self._next_channel()
3069 finally:
3070 self.lock.release()
3071 elif not self.server_mode:
3072 self._log(
3073 DEBUG,
3074 'Rejecting "{}" channel request from server.'.format(kind),
3075 )
3076 reject = True
3077 reason = OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
3078 else:
3079 self.lock.acquire()
3080 try:
3081 my_chanid = self._next_channel()
3082 finally:
3083 self.lock.release()
3084 if kind == "direct-tcpip":
3085 # handle direct-tcpip requests coming from the client
3086 dest_addr = m.get_text()
3087 dest_port = m.get_int()
3088 origin_addr = m.get_text()
3089 origin_port = m.get_int()
3090 reason = self.server_object.check_channel_direct_tcpip_request(
3091 my_chanid,
3092 (origin_addr, origin_port),
3093 (dest_addr, dest_port),
3094 )
3095 else:
3096 reason = self.server_object.check_channel_request(
3097 kind, my_chanid
3098 )
3099 if reason != OPEN_SUCCEEDED:
3100 self._log(
3101 DEBUG,
3102 'Rejecting "{}" channel request from client.'.format(kind),
3103 )
3104 reject = True
3105 if reject:
3106 msg = Message()
3107 msg.add_byte(cMSG_CHANNEL_OPEN_FAILURE)
3108 msg.add_int(chanid)
3109 msg.add_int(reason)
3110 msg.add_string("")
3111 msg.add_string("en")
3112 self._send_message(msg)
3113 return
3115 chan = Channel(my_chanid)
3116 self.lock.acquire()
3117 try:
3118 self._channels.put(my_chanid, chan)
3119 self.channels_seen[my_chanid] = True
3120 chan._set_transport(self)
3121 chan._set_window(
3122 self.default_window_size, self.default_max_packet_size
3123 )
3124 chan._set_remote_channel(
3125 chanid, initial_window_size, max_packet_size
3126 )
3127 finally:
3128 self.lock.release()
3129 m = Message()
3130 m.add_byte(cMSG_CHANNEL_OPEN_SUCCESS)
3131 m.add_int(chanid)
3132 m.add_int(my_chanid)
3133 m.add_int(self.default_window_size)
3134 m.add_int(self.default_max_packet_size)
3135 self._send_message(m)
3136 self._log(
3137 DEBUG, "Secsh channel {:d} ({}) opened.".format(my_chanid, kind)
3138 )
3139 if kind == "auth-agent@openssh.com":
3140 self._forward_agent_handler(chan)
3141 elif kind == "x11":
3142 self._x11_handler(chan, (origin_addr, origin_port))
3143 elif kind == "forwarded-tcpip":
3144 chan.origin_addr = (origin_addr, origin_port)
3145 self._tcp_handler(
3146 chan, (origin_addr, origin_port), (server_addr, server_port)
3147 )
3148 else:
3149 self._queue_incoming_channel(chan)
3151 def _parse_debug(self, m):
3152 m.get_boolean() # always_display
3153 msg = m.get_string()
3154 m.get_string() # language
3155 self._log(DEBUG, "Debug msg: {}".format(util.safe_string(msg)))
3157 def _get_subsystem_handler(self, name):
3158 try:
3159 self.lock.acquire()
3160 if name not in self.subsystem_table:
3161 return None, [], {}
3162 return self.subsystem_table[name]
3163 finally:
3164 self.lock.release()
3166 _channel_handler_table = {
3167 MSG_CHANNEL_SUCCESS: Channel._request_success,
3168 MSG_CHANNEL_FAILURE: Channel._request_failed,
3169 MSG_CHANNEL_DATA: Channel._feed,
3170 MSG_CHANNEL_EXTENDED_DATA: Channel._feed_extended,
3171 MSG_CHANNEL_WINDOW_ADJUST: Channel._window_adjust,
3172 MSG_CHANNEL_REQUEST: Channel._handle_request,
3173 MSG_CHANNEL_EOF: Channel._handle_eof,
3174 MSG_CHANNEL_CLOSE: Channel._handle_close,
3175 }
3178# TODO 4.0: drop this, we barely use it ourselves, it badly replicates the
3179# Transport-internal algorithm management, AND does so in a way which doesn't
3180# honor newer things like disabled_algorithms!
3181class SecurityOptions:
3182 """
3183 Simple object containing the security preferences of an ssh transport.
3184 These are tuples of acceptable ciphers, digests, key types, and key
3185 exchange algorithms, listed in order of preference.
3187 Changing the contents and/or order of these fields affects the underlying
3188 `.Transport` (but only if you change them before starting the session).
3189 If you try to add an algorithm that paramiko doesn't recognize,
3190 ``ValueError`` will be raised. If you try to assign something besides a
3191 tuple to one of the fields, ``TypeError`` will be raised.
3192 """
3194 __slots__ = "_transport"
3196 def __init__(self, transport):
3197 self._transport = transport
3199 def __repr__(self):
3200 """
3201 Returns a string representation of this object, for debugging.
3202 """
3203 return "<paramiko.SecurityOptions for {!r}>".format(self._transport)
3205 def _set(self, name, orig, x):
3206 if type(x) is list:
3207 x = tuple(x)
3208 if type(x) is not tuple:
3209 raise TypeError("expected tuple or list")
3210 possible = list(getattr(self._transport, orig).keys())
3211 forbidden = [n for n in x if n not in possible]
3212 if len(forbidden) > 0:
3213 raise ValueError("unknown cipher")
3214 setattr(self._transport, name, x)
3216 @property
3217 def ciphers(self):
3218 """Symmetric encryption ciphers"""
3219 return self._transport._preferred_ciphers
3221 @ciphers.setter
3222 def ciphers(self, x):
3223 self._set("_preferred_ciphers", "_cipher_info", x)
3225 @property
3226 def digests(self):
3227 """Digest (one-way hash) algorithms"""
3228 return self._transport._preferred_macs
3230 @digests.setter
3231 def digests(self, x):
3232 self._set("_preferred_macs", "_mac_info", x)
3234 @property
3235 def key_types(self):
3236 """Public-key algorithms"""
3237 return self._transport._preferred_keys
3239 @key_types.setter
3240 def key_types(self, x):
3241 self._set("_preferred_keys", "_key_info", x)
3243 @property
3244 def kex(self):
3245 """Key exchange algorithms"""
3246 return self._transport._preferred_kex
3248 @kex.setter
3249 def kex(self, x):
3250 self._set("_preferred_kex", "_kex_info", x)
3252 @property
3253 def compression(self):
3254 """Compression algorithms"""
3255 return self._transport._preferred_compression
3257 @compression.setter
3258 def compression(self, x):
3259 self._set("_preferred_compression", "_compression_info", x)
3262class ChannelMap:
3263 def __init__(self):
3264 # (id -> Channel)
3265 self._map = weakref.WeakValueDictionary()
3266 self._lock = threading.Lock()
3268 def put(self, chanid, chan):
3269 self._lock.acquire()
3270 try:
3271 self._map[chanid] = chan
3272 finally:
3273 self._lock.release()
3275 def get(self, chanid):
3276 self._lock.acquire()
3277 try:
3278 return self._map.get(chanid, None)
3279 finally:
3280 self._lock.release()
3282 def delete(self, chanid):
3283 self._lock.acquire()
3284 try:
3285 try:
3286 del self._map[chanid]
3287 except KeyError:
3288 pass
3289 finally:
3290 self._lock.release()
3292 def values(self):
3293 self._lock.acquire()
3294 try:
3295 return list(self._map.values())
3296 finally:
3297 self._lock.release()
3299 def __len__(self):
3300 self._lock.acquire()
3301 try:
3302 return len(self._map)
3303 finally:
3304 self._lock.release()
3307class ServiceRequestingTransport(Transport):
3308 """
3309 Transport, but also handling service requests, like it oughtta!
3311 .. versionadded:: 3.2
3312 """
3314 # NOTE: this purposefully duplicates some of the parent class in order to
3315 # modernize, refactor, etc. The intent is that eventually we will collapse
3316 # this one onto the parent in a backwards incompatible release.
3318 def __init__(self, *args, **kwargs):
3319 super().__init__(*args, **kwargs)
3320 self._service_userauth_accepted = False
3321 self._handler_table[MSG_SERVICE_ACCEPT] = self._parse_service_accept
3323 def _parse_service_accept(self, m):
3324 service = m.get_text()
3325 # Short-circuit for any service name not ssh-userauth.
3326 # NOTE: it's technically possible for 'service name' in
3327 # SERVICE_REQUEST/ACCEPT messages to be "ssh-connection" --
3328 # but I don't see evidence of Paramiko ever initiating or expecting to
3329 # receive one of these. We /do/ see the 'service name' field in
3330 # MSG_USERAUTH_REQUEST/ACCEPT/FAILURE set to this string, but that is a
3331 # different set of handlers, so...!
3332 if service != "ssh-userauth":
3333 # TODO 4.0: consider erroring here (with an ability to opt out?)
3334 # instead as it probably means something went Very Wrong.
3335 self._log(
3336 DEBUG, 'Service request "{}" accepted (?)'.format(service)
3337 )
3338 return
3339 # Record that we saw a service-userauth acceptance, meaning we are free
3340 # to submit auth requests.
3341 self._service_userauth_accepted = True
3342 self._log(DEBUG, "MSG_SERVICE_ACCEPT received; auth may begin")
3344 def ensure_session(self):
3345 # Make sure we're not trying to auth on a not-yet-open or
3346 # already-closed transport session; that's our responsibility, not that
3347 # of AuthHandler.
3348 if (not self.active) or (not self.initial_kex_done):
3349 # TODO: better error message? this can happen in many places, eg
3350 # user error (authing before connecting) or developer error (some
3351 # improperly handled pre/mid auth shutdown didn't become fatal
3352 # enough). The latter is much more common & should ideally be fixed
3353 # by terminating things harder?
3354 raise SSHException("No existing session")
3355 # Also make sure we've actually been told we are allowed to auth.
3356 if self._service_userauth_accepted:
3357 return
3358 # Or request to do so, otherwise.
3359 m = Message()
3360 m.add_byte(cMSG_SERVICE_REQUEST)
3361 m.add_string("ssh-userauth")
3362 self._log(DEBUG, "Sending MSG_SERVICE_REQUEST: ssh-userauth")
3363 self._send_message(m)
3364 # Now we wait to hear back; the user is expecting a blocking-style auth
3365 # request so there's no point giving control back anywhere.
3366 while not self._service_userauth_accepted:
3367 # TODO: feels like we're missing an AuthHandler Event like
3368 # 'self.auth_event' which is set when AuthHandler shuts down in
3369 # ways good AND bad. Transport only seems to have completion_event
3370 # which is unclear re: intent, eg it's set by newkeys which always
3371 # happens on connection, so it'll always be set by the time we get
3372 # here.
3373 # NOTE: this copies the timing of event.wait() in
3374 # AuthHandler.wait_for_response, re: 1/10 of a second. Could
3375 # presumably be smaller, but seems unlikely this period is going to
3376 # be "too long" for any code doing ssh networking...
3377 time.sleep(0.1)
3378 self.auth_handler = self.get_auth_handler()
3380 def get_auth_handler(self):
3381 # NOTE: using new sibling subclass instead of classic AuthHandler
3382 return AuthOnlyHandler(self)
3384 def auth_none(self, username):
3385 # TODO 4.0: merge to parent, preserving (most of) docstring
3386 self.ensure_session()
3387 return self.auth_handler.auth_none(username)
3389 def auth_password(self, username, password, fallback=True):
3390 # TODO 4.0: merge to parent, preserving (most of) docstring
3391 self.ensure_session()
3392 try:
3393 return self.auth_handler.auth_password(username, password)
3394 except BadAuthenticationType as e:
3395 # if password auth isn't allowed, but keyboard-interactive *is*,
3396 # try to fudge it
3397 if not fallback or ("keyboard-interactive" not in e.allowed_types):
3398 raise
3399 try:
3401 def handler(title, instructions, fields):
3402 if len(fields) > 1:
3403 raise SSHException("Fallback authentication failed.")
3404 if len(fields) == 0:
3405 # for some reason, at least on os x, a 2nd request will
3406 # be made with zero fields requested. maybe it's just
3407 # to try to fake out automated scripting of the exact
3408 # type we're doing here. *shrug* :)
3409 return []
3410 return [password]
3412 return self.auth_interactive(username, handler)
3413 except SSHException:
3414 # attempt to fudge failed; just raise the original exception
3415 raise e
3417 def auth_publickey(self, username, key):
3418 # TODO 4.0: merge to parent, preserving (most of) docstring
3419 self.ensure_session()
3420 return self.auth_handler.auth_publickey(username, key)
3422 def auth_interactive(self, username, handler, submethods=""):
3423 # TODO 4.0: merge to parent, preserving (most of) docstring
3424 self.ensure_session()
3425 return self.auth_handler.auth_interactive(
3426 username, handler, submethods
3427 )
3429 def auth_interactive_dumb(self, username, handler=None, submethods=""):
3430 # TODO 4.0: merge to parent, preserving (most of) docstring
3431 # NOTE: legacy impl omitted equiv of ensure_session since it just wraps
3432 # another call to an auth method. however we reinstate it for
3433 # consistency reasons.
3434 self.ensure_session()
3435 if not handler:
3437 def handler(title, instructions, prompt_list):
3438 answers = []
3439 if title:
3440 print(title.strip())
3441 if instructions:
3442 print(instructions.strip())
3443 for prompt, show_input in prompt_list:
3444 print(prompt.strip(), end=" ")
3445 answers.append(input())
3446 return answers
3448 return self.auth_interactive(username, handler, submethods)
3450 def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds):
3451 # TODO 4.0: merge to parent, preserving (most of) docstring
3452 self.ensure_session()
3453 self.auth_handler = self.get_auth_handler()
3454 return self.auth_handler.auth_gssapi_with_mic(
3455 username, gss_host, gss_deleg_creds
3456 )
3458 def auth_gssapi_keyex(self, username):
3459 # TODO 4.0: merge to parent, preserving (most of) docstring
3460 self.ensure_session()
3461 self.auth_handler = self.get_auth_handler()
3462 return self.auth_handler.auth_gssapi_keyex(username)