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