1# Copyright (c) 2013-2021 by Ron Frederick <ronf@timeheart.net> and others. 2# 3# This program and the accompanying materials are made available under 4# the terms of the Eclipse Public License v2.0 which accompanies this 5# distribution and is available at: 6# 7# http://www.eclipse.org/legal/epl-2.0/ 8# 9# This program may also be made available under the following secondary 10# licenses when the conditions for such availability set forth in the 11# Eclipse Public License v2.0 are satisfied: 12# 13# GNU General Public License, Version 2.0, or any later versions of 14# that license 15# 16# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later 17# 18# Contributors: 19# Ron Frederick - initial implementation, API, and documentation 20 21"""SSH connection handlers""" 22 23import asyncio 24import getpass 25import inspect 26import io 27import os 28import shlex 29import socket 30import sys 31import tempfile 32import time 33 34from collections import OrderedDict 35from functools import partial 36from pathlib import Path 37 38from .agent import SSHAgentClient, SSHAgentListener 39 40from .auth import get_client_auth_methods, lookup_client_auth 41from .auth import get_server_auth_methods, lookup_server_auth 42 43from .auth_keys import read_authorized_keys 44 45from .channel import SSHClientChannel, SSHServerChannel 46from .channel import SSHTCPChannel, SSHUNIXChannel 47from .channel import SSHX11Channel, SSHAgentChannel 48 49from .client import SSHClient 50 51from .compression import get_compression_algs, get_default_compression_algs 52from .compression import get_compression_params 53from .compression import get_compressor, get_decompressor 54 55from .config import SSHClientConfig, SSHServerConfig 56 57from .constants import DEFAULT_LANG, DEFAULT_PORT 58from .constants import DISC_BY_APPLICATION 59from .constants import EXTENDED_DATA_STDERR 60from .constants import MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG 61from .constants import MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, MSG_EXT_INFO 62from .constants import MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_CONFIRMATION 63from .constants import MSG_CHANNEL_OPEN_FAILURE 64from .constants import MSG_CHANNEL_FIRST, MSG_CHANNEL_LAST 65from .constants import MSG_KEXINIT, MSG_NEWKEYS, MSG_KEX_FIRST, MSG_KEX_LAST 66from .constants import MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE 67from .constants import MSG_USERAUTH_SUCCESS, MSG_USERAUTH_BANNER 68from .constants import MSG_USERAUTH_FIRST, MSG_USERAUTH_LAST 69from .constants import MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS 70from .constants import MSG_REQUEST_FAILURE 71from .constants import OPEN_ADMINISTRATIVELY_PROHIBITED, OPEN_CONNECT_FAILED 72from .constants import OPEN_UNKNOWN_CHANNEL_TYPE 73 74from .encryption import get_encryption_algs, get_default_encryption_algs 75from .encryption import get_encryption_params, get_encryption 76 77from .forward import SSHForwarder 78 79from .gss import GSSClient, GSSServer, GSSError 80 81from .kex import get_kex_algs, get_default_kex_algs, expand_kex_algs, get_kex 82 83from .keysign import find_keysign, get_keysign_keys 84 85from .known_hosts import match_known_hosts 86 87from .listener import SSHTCPClientListener, SSHUNIXClientListener 88from .listener import create_tcp_forward_listener, create_unix_forward_listener 89from .listener import create_socks_listener 90 91from .logging import logger 92 93from .mac import get_mac_algs, get_default_mac_algs 94 95from .misc import ChannelListenError, ChannelOpenError, DisconnectError 96from .misc import CompressionError, ConnectionLost, HostKeyNotVerifiable 97from .misc import KeyExchangeFailed, IllegalUserName, MACError 98from .misc import PasswordChangeRequired, PermissionDenied, ProtocolError 99from .misc import ProtocolNotSupported, ServiceNotAvailable, Options 100from .misc import async_context_manager, construct_disc_error 101from .misc import get_symbol_names, ip_address, map_handler_name 102from .misc import parse_byte_count, parse_time_interval 103 104from .packet import Boolean, Byte, NameList, String, UInt32 105from .packet import PacketDecodeError, SSHPacket, SSHPacketHandler 106 107from .pattern import WildcardPattern 108 109from .pkcs11 import load_pkcs11_keys 110 111from .process import PIPE, SSHClientProcess, SSHServerProcess 112 113from .public_key import CERT_TYPE_HOST, CERT_TYPE_USER, KeyImportError 114from .public_key import decode_ssh_public_key, decode_ssh_certificate 115from .public_key import get_public_key_algs, get_default_public_key_algs 116from .public_key import get_certificate_algs, get_default_certificate_algs 117from .public_key import get_x509_certificate_algs 118from .public_key import get_default_x509_certificate_algs 119from .public_key import load_keypairs, load_default_keypairs 120from .public_key import load_public_keys, load_default_host_public_keys 121from .public_key import load_certificates 122from .public_key import load_identities, load_default_identities 123 124from .saslprep import saslprep, SASLPrepError 125 126from .server import SSHServer 127 128from .sftp import SFTPServer, start_sftp_client 129 130from .stream import SSHClientStreamSession, SSHServerStreamSession 131from .stream import SSHTCPStreamSession, SSHUNIXStreamSession 132from .stream import SSHReader, SSHWriter 133 134from .subprocess import SSHSubprocessTransport 135 136from .version import __version__ 137 138from .x11 import create_x11_client_listener, create_x11_server_listener 139 140 141# SSH service names 142_USERAUTH_SERVICE = b'ssh-userauth' 143_CONNECTION_SERVICE = b'ssh-connection' 144 145# Max banner and version line length and count 146_MAX_BANNER_LINES = 1024 147_MAX_BANNER_LINE_LEN = 8192 148_MAX_VERSION_LINE_LEN = 255 149 150# Max allowed username length 151_MAX_USERNAME_LEN = 1024 152 153# Default rekey parameters 154_DEFAULT_REKEY_BYTES = 1 << 30 # 1 GiB 155_DEFAULT_REKEY_SECONDS = 3600 # 1 hour 156 157# Default login timeout 158_DEFAULT_LOGIN_TIMEOUT = 120 # 2 minutes 159 160# Default keepalive interval and count max 161_DEFAULT_KEEPALIVE_INTERVAL = 0 # disabled by default 162_DEFAULT_KEEPALIVE_COUNT_MAX = 3 163 164# Default channel parameters 165_DEFAULT_WINDOW = 2*1024*1024 # 2 MiB 166_DEFAULT_MAX_PKTSIZE = 32768 # 32 kiB 167 168# Default line editor parameters 169_DEFAULT_LINE_HISTORY = 1000 # 1000 lines 170_DEFAULT_MAX_LINE_LENGTH = 1024 # 1024 characters 171 172 173async def _open_proxy(loop, command, conn_factory): 174 """Open a tunnel running a proxy command""" 175 176 class _ProxyCommandTunnel(asyncio.SubprocessProtocol): 177 """SSH proxy command tunnel""" 178 179 def __init__(self): 180 self._transport = None 181 self._stdin = None 182 self._conn = conn_factory() 183 self._close_event = asyncio.Event() 184 185 def get_owner(self): 186 """Return the connection running over this tunnel""" 187 188 return self._conn 189 190 def get_extra_info(self, name, default=None): 191 """Return extra information associated with this tunnel""" 192 193 return self._transport.get_extra_info(name, default) 194 195 def connection_made(self, transport): 196 """Handle startup of the subprocess""" 197 198 self._transport = transport 199 self._stdin = transport.get_pipe_transport(0) 200 self._conn.connection_made(self) 201 202 def pipe_data_received(self, fd, data): 203 """Handle data received from this tunnel""" 204 205 # pylint: disable=unused-argument 206 207 self._conn.data_received(data) 208 209 def pipe_connection_lost(self, fd, exc): 210 """Handle when this tunnel is closed""" 211 212 # pylint: disable=unused-argument 213 214 self._conn.connection_lost(exc) 215 216 def is_closing(self): 217 """Return whether the transport is closing or not""" 218 219 return self._transport.is_closing() 220 221 def write(self, data): 222 """Write data to this tunnel""" 223 224 self._stdin.write(data) 225 226 def abort(self): 227 """Forcibly close this tunnel""" 228 229 self.close() 230 231 def close(self): 232 """Close this tunnel""" 233 234 self._transport.close() 235 self._close_event.set() 236 237 238 _, tunnel = await loop.subprocess_exec(_ProxyCommandTunnel, *command) 239 240 return tunnel.get_owner() 241 242 243async def _open_tunnel(tunnel, passphrase): 244 """Parse and open connection to tunnel over""" 245 246 if isinstance(tunnel, str): 247 if '@' in tunnel: 248 username, host = tunnel.rsplit('@', 1) 249 else: 250 username, host = (), tunnel 251 252 if ':' in host: 253 host, port = host.rsplit(':', 1) 254 port = int(port) 255 else: 256 port = () 257 258 return await connect(host, port, username=username, 259 passphrase=passphrase) 260 else: 261 return None 262 263 264async def _connect(options, loop, flags, conn_factory, msg): 265 """Make outbound TCP or SSH tunneled connection""" 266 267 host = options.host 268 port = options.port 269 tunnel = options.tunnel 270 family = options.family 271 local_addr = options.local_addr 272 proxy_command = options.proxy_command 273 free_conn = True 274 275 new_tunnel = await _open_tunnel(tunnel, options.passphrase) 276 277 if new_tunnel: 278 new_tunnel.logger.info('%s %s via %s', msg, (host, port), tunnel) 279 280 # pylint: disable=broad-except 281 try: 282 _, conn = await new_tunnel.create_connection(conn_factory, 283 host, port) 284 except Exception: 285 new_tunnel.close() 286 await new_tunnel.wait_closed() 287 raise 288 else: 289 conn.set_tunnel(new_tunnel) 290 elif tunnel: 291 tunnel_logger = getattr(tunnel, 'logger', logger) 292 tunnel_logger.info('%s %s via SSH tunnel', msg, (host, port)) 293 _, conn = await tunnel.create_connection(conn_factory, host, port) 294 elif proxy_command: 295 conn = await _open_proxy(loop, proxy_command, conn_factory) 296 else: 297 logger.info('%s %s', msg, (host, port)) 298 _, conn = await loop.create_connection(conn_factory, host, port, 299 family=family, flags=flags, 300 local_addr=local_addr) 301 302 try: 303 await conn.wait_established() 304 free_conn = False 305 306 if new_tunnel: 307 conn.set_tunnel(new_tunnel) 308 309 return conn 310 finally: 311 if free_conn: 312 conn.abort() 313 await conn.wait_closed() 314 315 316async def _listen(options, loop, flags, backlog, reuse_address, 317 reuse_port, conn_factory, msg): 318 """Make inbound TCP or SSH tunneled listener""" 319 320 def tunnel_factory(_orig_host, _orig_port): 321 """Ignore original host and port""" 322 323 return conn_factory() 324 325 host = options.host 326 port = options.port 327 tunnel = options.tunnel 328 family = options.family 329 330 new_tunnel = await _open_tunnel(tunnel, options.passphrase) 331 332 if new_tunnel: 333 new_tunnel.logger.info('%s %s via %s', msg, (host, port), tunnel) 334 335 # pylint: disable=broad-except 336 try: 337 server = await new_tunnel.create_server(tunnel_factory, host, port) 338 except Exception: 339 new_tunnel.close() 340 await new_tunnel.wait_closed() 341 raise 342 else: 343 server.set_tunnel(new_tunnel) 344 elif tunnel: 345 tunnel_logger = getattr(tunnel, 'logger', logger) 346 tunnel_logger.info('%s %s via SSH tunnel', msg, (host, port)) 347 server = await tunnel.create_server(tunnel_factory, host, port) 348 else: 349 logger.info('%s %s', msg, (host, port)) 350 server = await loop.create_server(conn_factory, host, port, 351 family=family, flags=flags, 352 backlog=backlog, 353 reuse_address=reuse_address, 354 reuse_port=reuse_port) 355 356 return SSHAcceptor(server, options) 357 358 359def _validate_version(version): 360 """Validate requested SSH version""" 361 362 if version == (): 363 version = b'AsyncSSH_' + __version__.encode('ascii') 364 else: 365 if isinstance(version, str): 366 version = version.encode('ascii') 367 368 # Version including 'SSH-2.0-' and CRLF must be 255 chars or less 369 if len(version) > 245: 370 raise ValueError('Version string is too long') 371 372 for b in version: 373 if b < 0x20 or b > 0x7e: 374 raise ValueError('Version string must be printable ASCII') 375 376 return version 377 378 379def _expand_algs(alg_type, algs, possible_algs, default_algs, strict_match): 380 """Expand the set of allowed algorithms""" 381 382 if algs[:1] in '^+-': 383 prefix = algs[:1] 384 algs = algs[1:] 385 else: 386 prefix = '' 387 388 matched = [] 389 390 for pat in algs.split(','): 391 pattern = WildcardPattern(pat) 392 393 matches = [alg for alg in possible_algs 394 if pattern.matches(alg.decode('ascii'))] 395 396 if not matches and strict_match: 397 raise ValueError('"%s" matches no valid %s algorithms' % 398 (pat, alg_type)) 399 400 matched.extend(matches) 401 402 if prefix == '^': 403 return matched + default_algs 404 elif prefix == '+': 405 return default_algs + matched 406 elif prefix == '-': 407 return [alg for alg in default_algs if alg not in matched] 408 else: 409 return matched 410 411 412def _select_algs(alg_type, algs, possible_algs, default_algs, 413 config_algs, none_value=None): 414 """Select a set of allowed algorithms""" 415 416 if algs == (): 417 algs = config_algs 418 strict_match = False 419 else: 420 strict_match = True 421 422 if algs in ((), 'default'): 423 return default_algs 424 elif algs: 425 if isinstance(algs, str): 426 algs = _expand_algs(alg_type, algs, possible_algs, 427 default_algs, strict_match) 428 else: 429 algs = [alg.encode('ascii') for alg in algs] 430 431 result = [] 432 433 for alg in algs: 434 if alg not in possible_algs: 435 raise ValueError('%s is not a valid %s algorithm' % 436 (alg.decode('ascii'), alg_type)) 437 438 if alg not in result: 439 result.append(alg) 440 441 return result 442 elif none_value: 443 return [none_value] 444 else: 445 raise ValueError('No %s algorithms selected' % alg_type) 446 447 448def _select_host_key_algs(algs, config_algs, default_algs): 449 """Select a set of allowed host key algorithms""" 450 451 possible_algs = (get_x509_certificate_algs() + get_certificate_algs() + 452 get_public_key_algs()) 453 454 return _select_algs('host key', algs, possible_algs, 455 default_algs, config_algs) 456 457 458def _validate_algs(config, kex_algs, enc_algs, mac_algs, cmp_algs, 459 sig_algs, allow_x509): 460 """Validate requested algorithms""" 461 462 kex_algs = _select_algs('key exchange', kex_algs, get_kex_algs(), 463 get_default_kex_algs(), 464 config.get('KexAlgorithms', ())) 465 enc_algs = _select_algs('encryption', enc_algs, get_encryption_algs(), 466 get_default_encryption_algs(), 467 config.get('Ciphers', ())) 468 mac_algs = _select_algs('MAC', mac_algs, get_mac_algs(), 469 get_default_mac_algs(), config.get('MACs', ())) 470 cmp_algs = _select_algs('compression', cmp_algs, get_compression_algs(), 471 get_default_compression_algs(), 472 config.get_compression_algs(()), b'none') 473 474 allowed_sig_algs = get_x509_certificate_algs() if allow_x509 else [] 475 allowed_sig_algs = allowed_sig_algs + get_public_key_algs() 476 477 default_sig_algs = get_default_x509_certificate_algs() if allow_x509 else [] 478 default_sig_algs = allowed_sig_algs + get_default_public_key_algs() 479 480 sig_algs = _select_algs('signature', sig_algs, 481 allowed_sig_algs, default_sig_algs, 482 config.get('CASignatureAlgorithms', ())) 483 484 return kex_algs, enc_algs, mac_algs, cmp_algs, sig_algs 485 486 487class SSHAcceptor: 488 """SSH acceptor 489 490 This class in a wrapper around an :class:`asyncio.Server` listener 491 which provides the ability to update the the set of SSH client or 492 server connection options associated wtih that listener. This is 493 accomplished by calling the :meth:`update` method, which takes the 494 same keyword arguments as the :class:`SSHClientConnectionOptions` 495 and :class:`SSHServerConnectionOptions` classes. 496 497 In addition, this class supports all of the methods supported by 498 :class:`asyncio.Server` to control accepting of new connections. 499 500 """ 501 502 def __init__(self, server, options): 503 self._server = server 504 self._options = options 505 506 async def __aenter__(self): 507 return self 508 509 async def __aexit__(self, *exc_info): 510 self.close() 511 await self.wait_closed() 512 513 def __getattr__(self, name): 514 return getattr(self._server, name) 515 516 def update(self, **kwargs): 517 """Update options on an SSH listener 518 519 Acceptors started by :func:`listen` support options defined 520 in :class:`SSHServerConnectionOptions`. Acceptors started 521 by :func:`listen_reverse` support options defined in 522 :class:`SSHClientConnectionOptions`. 523 524 Changes apply only to SSH client/server connections accepted 525 after the change is made. Previously accepted connections 526 will continue to use the options set when they were accepted. 527 528 """ 529 530 self._options.update(kwargs) 531 532 533class SSHConnection(SSHPacketHandler, asyncio.Protocol): 534 """Parent class for SSH connections""" 535 536 _handler_names = get_symbol_names(globals(), 'MSG_') 537 538 next_conn = 0 # Next connection number, for logging 539 540 @staticmethod 541 def _get_next_conn(): 542 """Return the next available connection number (for logging)""" 543 544 next_conn = SSHConnection.next_conn 545 SSHConnection.next_conn += 1 546 return next_conn 547 548 def __init__(self, loop, options, acceptor, error_handler, wait, server): 549 self._loop = loop 550 self._options = options 551 self._protocol_factory = options.protocol_factory 552 self._acceptor = acceptor 553 self._error_handler = error_handler 554 self._server = server 555 self._wait = wait 556 self._waiter = loop.create_future() 557 558 self._transport = None 559 self._local_addr = None 560 self._local_port = None 561 self._peer_host = None 562 self._peer_addr = None 563 self._peer_port = None 564 self._tcp_keepalive = options.tcp_keepalive 565 self._owner = None 566 self._extra = {} 567 568 self._inpbuf = b'' 569 self._packet = b'' 570 self._pktlen = 0 571 self._banner_lines = 0 572 573 self._version = options.version 574 self._client_version = b'' 575 self._server_version = b'' 576 self._client_kexinit = b'' 577 self._server_kexinit = b'' 578 self._session_id = None 579 580 self._send_seq = 0 581 self._send_encryption = None 582 self._send_enchdrlen = 5 583 self._send_blocksize = 8 584 self._compressor = None 585 self._compress_after_auth = False 586 self._deferred_packets = [] 587 588 self._recv_handler = self._recv_version 589 self._recv_seq = 0 590 self._recv_encryption = None 591 self._recv_blocksize = 8 592 self._recv_macsize = 0 593 self._decompressor = None 594 self._decompress_after_auth = None 595 self._next_recv_encryption = None 596 self._next_recv_blocksize = 0 597 self._next_recv_macsize = 0 598 self._next_decompressor = None 599 self._next_decompress_after_auth = None 600 601 self._trusted_host_keys = set() 602 self._trusted_host_key_algs = [] 603 self._trusted_ca_keys = set() 604 self._revoked_host_keys = set() 605 606 self._x509_trusted_certs = options.x509_trusted_certs 607 self._x509_trusted_cert_paths = options.x509_trusted_cert_paths 608 self._x509_revoked_certs = [] 609 self._x509_trusted_subjects = [] 610 self._x509_revoked_subjects = [] 611 self._x509_purposes = options.x509_purposes 612 613 self._kex_algs = options.kex_algs 614 self._enc_algs = options.encryption_algs 615 self._mac_algs = options.mac_algs 616 self._cmp_algs = options.compression_algs 617 self._sig_algs = options.signature_algs 618 619 self._host_based_auth = options.host_based_auth 620 self._public_key_auth = options.public_key_auth 621 self._kbdint_auth = options.kbdint_auth 622 self._password_auth = options.password_auth 623 624 self._kex = None 625 self._kexinit_sent = False 626 self._kex_complete = False 627 self._ignore_first_kex = False 628 629 self._gss = None 630 self._gss_kex = False 631 self._gss_auth = False 632 self._gss_kex_auth = False 633 self._gss_mic_auth = False 634 635 self._preferred_auth = None 636 637 self._rekey_bytes = options.rekey_bytes 638 self._rekey_seconds = options.rekey_seconds 639 self._rekey_bytes_sent = 0 640 self._rekey_time = 0 641 642 self._keepalive_count = 0 643 self._keepalive_count_max = options.keepalive_count_max 644 self._keepalive_interval = options.keepalive_interval 645 self._keepalive_timer = None 646 647 self._tunnel = None 648 649 self._enc_alg_cs = None 650 self._enc_alg_sc = None 651 652 self._mac_alg_cs = None 653 self._mac_alg_sc = None 654 655 self._cmp_alg_cs = None 656 self._cmp_alg_sc = None 657 658 self._can_send_ext_info = False 659 self._extensions_to_send = OrderedDict() 660 661 self._server_sig_algs = () 662 663 self._next_service = None 664 665 self._agent = None 666 self._auth = None 667 self._auth_in_progress = False 668 self._auth_complete = False 669 self._auth_methods = [b'none'] 670 self._auth_was_trivial = True 671 self._username = None 672 673 self._channels = {} 674 self._next_recv_chan = 0 675 676 self._global_request_queue = [] 677 self._global_request_waiters = [] 678 679 self._local_listeners = {} 680 681 self._x11_listener = None 682 683 self._close_event = asyncio.Event() 684 685 self._server_host_key_algs = [] 686 687 self._logger = logger.get_child(context='conn=%d' % 688 self._get_next_conn()) 689 690 if options.login_timeout: 691 self._login_timer = self._loop.call_later( 692 options.login_timeout, self._login_timer_callback) 693 else: 694 self._login_timer = None 695 696 self._disable_trivial_auth = False 697 698 async def __aenter__(self): 699 """Allow SSHConnection to be used as an async context manager""" 700 701 return self 702 703 async def __aexit__(self, *exc_info): 704 """Wait for connection close when used as an async context manager""" 705 706 if not self._loop.is_closed(): # pragma: no branch 707 self.close() 708 709 await self.wait_closed() 710 711 @property 712 def logger(self): 713 """A logger associated with this connection""" 714 715 return self._logger 716 717 def _cleanup(self, exc): 718 """Clean up this connection""" 719 720 self._cancel_keepalive_timer() 721 722 for chan in list(self._channels.values()): 723 chan.process_connection_close(exc) 724 725 for listener in list(self._local_listeners.values()): 726 listener.close() 727 728 while self._global_request_waiters: 729 self._process_global_response(MSG_REQUEST_FAILURE, None, 730 SSHPacket(b'')) 731 732 if self._auth: 733 self._auth.cancel() 734 self._auth = None 735 736 if self._error_handler: 737 self._error_handler(self, exc) 738 self._acceptor = None 739 self._error_handler = None 740 741 if self._wait and not self._waiter.cancelled(): 742 self._waiter.set_exception(exc) 743 self._wait = None 744 745 if self._owner: # pragma: no branch 746 self._owner.connection_lost(exc) 747 self._owner = None 748 749 self._cancel_login_timer() 750 self._close_event.set() 751 752 self._inpbuf = b'' 753 self._recv_handler = None 754 755 if self._tunnel: 756 self._tunnel.close() 757 self._tunnel = None 758 759 def _cancel_login_timer(self): 760 """Cancel the login timer""" 761 762 if self._login_timer: 763 self._login_timer.cancel() 764 self._login_timer = None 765 766 def _login_timer_callback(self): 767 """Close the connection if authentication hasn't completed yet""" 768 769 self._login_timer = None 770 771 self.connection_lost(ConnectionLost('Login timeout expired')) 772 773 def _cancel_keepalive_timer(self): 774 """Cancel the keepalive timer""" 775 776 if self._keepalive_timer: 777 self._keepalive_timer.cancel() 778 self._keepalive_timer = None 779 780 def _set_keepalive_timer(self): 781 """Set the keepalive timer""" 782 783 if self._keepalive_interval: 784 self._keepalive_timer = self._loop.call_later( 785 self._keepalive_interval, self._keepalive_timer_callback) 786 787 def _reset_keepalive_timer(self): 788 """Reset the keepalive timer""" 789 790 if self._auth_complete: 791 self._cancel_keepalive_timer() 792 self._set_keepalive_timer() 793 794 async def _make_keepalive_request(self): 795 """Send keepalive request""" 796 797 self.logger.debug1('Sending keepalive request') 798 799 await self._make_global_request('keepalive@openssh.com') 800 801 if self._keepalive_timer: 802 self.logger.debug1('Got keepalive response') 803 804 self._keepalive_count = 0 805 806 def _keepalive_timer_callback(self): 807 """Handle keepalive check""" 808 809 self._keepalive_count += 1 810 811 if self._keepalive_count > self._keepalive_count_max: 812 self.connection_lost( 813 ConnectionLost(('Server' if self.is_client() else 'Client') + 814 ' not responding to keepalive')) 815 else: 816 self._set_keepalive_timer() 817 self.create_task(self._make_keepalive_request()) 818 819 def _force_close(self, exc): 820 """Force this connection to close immediately""" 821 822 if not self._transport: 823 return 824 825 self._transport.abort() 826 self._transport = None 827 828 self._loop.call_soon(self._cleanup, exc) 829 830 def _reap_task(self, task_logger, task): 831 """Collect result of an async task, reporting errors""" 832 833 # pylint: disable=broad-except 834 try: 835 task.result() 836 except asyncio.CancelledError: 837 pass 838 except DisconnectError as exc: 839 self._send_disconnect(exc.code, exc.reason, exc.lang) 840 self._force_close(exc) 841 except Exception: 842 self.internal_error(error_logger=task_logger) 843 844 def create_task(self, coro, task_logger=None): 845 """Create an asynchronous task which catches and reports errors""" 846 847 task = asyncio.ensure_future(coro) 848 task.add_done_callback(partial(self._reap_task, task_logger)) 849 return task 850 851 def is_client(self): 852 """Return if this is a client connection""" 853 854 return not self._server 855 856 def is_server(self): 857 """Return if this is a server connection""" 858 859 return self._server 860 861 def get_owner(self): 862 """Return the SSHClient or SSHServer which owns this connection""" 863 864 return self._owner 865 866 def get_hash_prefix(self): 867 """Return the bytes used in calculating unique connection hashes 868 869 This methods returns a packetized version of the client and 870 server version and kexinit strings which is needed to perform 871 key exchange hashes. 872 873 """ 874 875 return b''.join((String(self._client_version), 876 String(self._server_version), 877 String(self._client_kexinit), 878 String(self._server_kexinit))) 879 880 def set_tunnel(self, tunnel): 881 """Set tunnel used to open this connection""" 882 883 self._tunnel = tunnel 884 885 def _match_known_hosts(self, known_hosts, host, addr, port): 886 """Determine the set of trusted host keys and certificates""" 887 888 trusted_host_keys, trusted_ca_keys, revoked_host_keys, \ 889 trusted_x509_certs, revoked_x509_certs, \ 890 trusted_x509_subjects, revoked_x509_subjects = \ 891 match_known_hosts(known_hosts, host, addr, port) 892 893 for key in trusted_host_keys: 894 self._trusted_host_keys.add(key) 895 896 if key.algorithm not in self._trusted_host_key_algs: 897 self._trusted_host_key_algs.extend(key.sig_algorithms) 898 899 self._trusted_ca_keys = set(trusted_ca_keys) 900 self._revoked_host_keys = set(revoked_host_keys) 901 902 if self._x509_trusted_certs is not None: 903 self._x509_trusted_certs = list(self._x509_trusted_certs) 904 self._x509_trusted_certs.extend(trusted_x509_certs) 905 self._x509_revoked_certs = set(revoked_x509_certs) 906 907 self._x509_trusted_subjects = trusted_x509_subjects 908 self._x509_revoked_subjects = revoked_x509_subjects 909 910 def _validate_openssh_host_certificate(self, host, addr, port, cert): 911 """Validate an OpenSSH host certificate""" 912 913 if self._trusted_ca_keys is not None: 914 if cert.signing_key in self._revoked_host_keys: 915 raise ValueError('Host CA key is revoked') 916 917 if cert.signing_key not in self._trusted_ca_keys and \ 918 not self._owner.validate_host_ca_key(host, addr, port, 919 cert.signing_key): 920 raise ValueError('Host CA key is not trusted') 921 922 cert.validate(CERT_TYPE_HOST, host) 923 924 return cert.key 925 926 def _validate_x509_host_certificate_chain(self, host, cert): 927 """Validate an X.509 host certificate""" 928 929 if (self._x509_revoked_subjects and 930 any(pattern.matches(cert.subject) 931 for pattern in self._x509_revoked_subjects)): 932 raise ValueError('X.509 subject name is revoked') 933 934 if (self._x509_trusted_subjects and 935 not any(pattern.matches(cert.subject) 936 for pattern in self._x509_trusted_subjects)): 937 raise ValueError('X.509 subject name is not trusted') 938 939 # Only validate hostname against X.509 certificate host 940 # principals when there are no X.509 trusted subject 941 # entries matched in known_hosts. 942 if self._x509_trusted_subjects: 943 host = None 944 945 cert.validate_chain(self._x509_trusted_certs, 946 self._x509_trusted_cert_paths, 947 self._x509_revoked_certs, 948 self._x509_purposes, 949 host_principal=host) 950 951 return cert.key 952 953 def _validate_host_key(self, host, addr, port, key_data): 954 """Validate and return a trusted host key""" 955 956 try: 957 cert = decode_ssh_certificate(key_data) 958 except KeyImportError: 959 pass 960 else: 961 if cert.is_x509_chain: 962 return self._validate_x509_host_certificate_chain(host, cert) 963 else: 964 return self._validate_openssh_host_certificate(host, addr, 965 port, cert) 966 967 try: 968 key = decode_ssh_public_key(key_data) 969 except KeyImportError: 970 pass 971 else: 972 if self._trusted_host_keys is not None: 973 if key in self._revoked_host_keys: 974 raise ValueError('Host key is revoked') 975 976 if key not in self._trusted_host_keys and \ 977 not self._owner.validate_host_public_key(host, addr, 978 port, key): 979 raise ValueError('Host key is not trusted') 980 981 return key 982 983 raise ValueError('Unable to decode host key') 984 985 def connection_made(self, transport): 986 """Handle a newly opened connection""" 987 988 self._transport = transport 989 990 sock = transport.get_extra_info('socket') 991 992 if sock: 993 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 994 self._tcp_keepalive) 995 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 996 997 sockname = transport.get_extra_info('sockname') 998 999 if sockname: # pragma: no branch 1000 self._local_addr, self._local_port = sockname[:2] 1001 1002 peername = transport.get_extra_info('peername') 1003 1004 if peername: # pragma: no branch 1005 self._peer_addr, self._peer_port = peername[:2] 1006 1007 self._owner = self._protocol_factory() 1008 self._protocol_factory = None 1009 1010 # pylint: disable=broad-except 1011 try: 1012 self._connection_made() 1013 self._owner.connection_made(self) 1014 self._send_version() 1015 except Exception: 1016 self._loop.call_soon(self.internal_error, sys.exc_info()) 1017 1018 def connection_lost(self, exc=None): 1019 """Handle the closing of a connection""" 1020 1021 if exc is None and self._transport: 1022 exc = ConnectionLost('Connection lost') 1023 1024 self._force_close(exc) 1025 1026 def internal_error(self, exc_info=None, error_logger=None): 1027 """Handle a fatal error in connection processing""" 1028 1029 if not exc_info: 1030 exc_info = sys.exc_info() 1031 1032 if not error_logger: 1033 error_logger = self.logger 1034 1035 error_logger.debug1('Uncaught exception', exc_info=exc_info) 1036 self._force_close(exc_info[1]) 1037 1038 def session_started(self): 1039 """Handle session start when opening tunneled SSH connection""" 1040 1041 # pylint: disable=arguments-differ 1042 def data_received(self, data, datatype=None): 1043 """Handle incoming data on the connection""" 1044 1045 # pylint: disable=unused-argument 1046 1047 self._inpbuf += data 1048 1049 self._reset_keepalive_timer() 1050 1051 # pylint: disable=broad-except 1052 try: 1053 while self._inpbuf and self._recv_handler(): 1054 pass 1055 except DisconnectError as exc: 1056 self._send_disconnect(exc.code, exc.reason, exc.lang) 1057 self._force_close(exc) 1058 except Exception: 1059 self.internal_error() 1060 # pylint: enable=arguments-differ 1061 1062 def eof_received(self): 1063 """Handle an incoming end of file on the connection""" 1064 1065 self.connection_lost(None) 1066 1067 def pause_writing(self): 1068 """Handle a request from the transport to pause writing data""" 1069 1070 # Do nothing with this for now 1071 1072 def resume_writing(self): 1073 """Handle a request from the transport to resume writing data""" 1074 1075 # Do nothing with this for now 1076 1077 def add_channel(self, chan): 1078 """Add a new channel, returning its channel number""" 1079 1080 if not self._transport: 1081 raise ChannelOpenError(OPEN_CONNECT_FAILED, 1082 'SSH connection closed') 1083 1084 while self._next_recv_chan in self._channels: # pragma: no cover 1085 self._next_recv_chan = (self._next_recv_chan + 1) & 0xffffffff 1086 1087 recv_chan = self._next_recv_chan 1088 self._next_recv_chan = (self._next_recv_chan + 1) & 0xffffffff 1089 1090 self._channels[recv_chan] = chan 1091 return recv_chan 1092 1093 def remove_channel(self, recv_chan): 1094 """Remove the channel with the specified channel number""" 1095 1096 del self._channels[recv_chan] 1097 1098 def get_gss_context(self): 1099 """Return the GSS context associated with this connection""" 1100 1101 return self._gss 1102 1103 def enable_gss_kex_auth(self): 1104 """Enable GSS key exchange authentication""" 1105 1106 self._gss_kex_auth = self._gss_auth 1107 1108 def _choose_alg(self, alg_type, local_algs, remote_algs): 1109 """Choose a common algorithm from the client & server lists 1110 1111 This method returns the earliest algorithm on the client's 1112 list which is supported by the server. 1113 1114 """ 1115 1116 if self.is_client(): 1117 client_algs, server_algs = local_algs, remote_algs 1118 else: 1119 client_algs, server_algs = remote_algs, local_algs 1120 1121 for alg in client_algs: 1122 if alg in server_algs: 1123 return alg 1124 1125 raise KeyExchangeFailed( 1126 'No matching %s algorithm found, sent %s and received %s' % 1127 (alg_type, b','.join(local_algs).decode('ascii'), 1128 b','.join(remote_algs).decode('ascii'))) 1129 1130 def _get_ext_info_kex_alg(self): 1131 """Return the kex alg to add if any to request extension info""" 1132 1133 return [b'ext-info-c'] if self.is_client() else [b'ext-info-s'] 1134 1135 def _send(self, data): 1136 """Send data to the SSH connection""" 1137 1138 if self._transport: 1139 if self._transport.is_closing(): 1140 self._force_close(BrokenPipeError) 1141 else: 1142 self._transport.write(data) 1143 1144 def _send_version(self): 1145 """Start the SSH handshake""" 1146 1147 version = b'SSH-2.0-' + self._version 1148 1149 self.logger.debug1('Sending version %s', version) 1150 1151 if self.is_client(): 1152 self._client_version = version 1153 self.set_extra_info(client_version=version.decode('ascii')) 1154 else: 1155 self._server_version = version 1156 self.set_extra_info(server_version=version.decode('ascii')) 1157 1158 self._send(version + b'\r\n') 1159 1160 def _recv_version(self): 1161 """Receive and parse the remote SSH version""" 1162 1163 idx = self._inpbuf.find(b'\n', 0, _MAX_BANNER_LINE_LEN) 1164 if idx < 0: 1165 if len(self._inpbuf) >= _MAX_BANNER_LINE_LEN: 1166 self._force_close(ProtocolError('Banner line too long')) 1167 1168 return False 1169 1170 version = self._inpbuf[:idx] 1171 if version.endswith(b'\r'): 1172 version = version[:-1] 1173 1174 self._inpbuf = self._inpbuf[idx+1:] 1175 1176 if (version.startswith(b'SSH-2.0-') or 1177 (self.is_client() and version.startswith(b'SSH-1.99-'))): 1178 if len(version) > _MAX_VERSION_LINE_LEN: 1179 self._force_close(ProtocolError('Version too long')) 1180 1181 # Accept version 2.0, or 1.99 if we're a client 1182 if self.is_server(): 1183 self._client_version = version 1184 self.set_extra_info(client_version=version.decode('ascii')) 1185 else: 1186 self._server_version = version 1187 self.set_extra_info(server_version=version.decode('ascii')) 1188 1189 self.logger.debug1('Received version %s', version) 1190 1191 self._send_kexinit() 1192 self._kexinit_sent = True 1193 self._recv_handler = self._recv_pkthdr 1194 elif self.is_client() and not version.startswith(b'SSH-'): 1195 # As a client, ignore the line if it doesn't appear to be a version 1196 self._banner_lines += 1 1197 1198 if self._banner_lines > _MAX_BANNER_LINES: 1199 self._force_close(ProtocolError('Too many banner lines')) 1200 return False 1201 else: 1202 # Otherwise, reject the unknown version 1203 self._force_close(ProtocolNotSupported('Unsupported SSH version')) 1204 return False 1205 1206 return True 1207 1208 def _recv_pkthdr(self): 1209 """Receive and parse an SSH packet header""" 1210 1211 if len(self._inpbuf) < self._recv_blocksize: 1212 return False 1213 1214 self._packet = self._inpbuf[:self._recv_blocksize] 1215 self._inpbuf = self._inpbuf[self._recv_blocksize:] 1216 1217 if self._recv_encryption: 1218 self._packet, pktlen = \ 1219 self._recv_encryption.decrypt_header(self._recv_seq, 1220 self._packet, 4) 1221 else: 1222 pktlen = self._packet[:4] 1223 1224 self._pktlen = int.from_bytes(pktlen, 'big') 1225 self._recv_handler = self._recv_packet 1226 return True 1227 1228 def _recv_packet(self): 1229 """Receive the remainder of an SSH packet and process it""" 1230 1231 rem = 4 + self._pktlen + self._recv_macsize - self._recv_blocksize 1232 if len(self._inpbuf) < rem: 1233 return False 1234 1235 seq = self._recv_seq 1236 rest = self._inpbuf[:rem-self._recv_macsize] 1237 mac = self._inpbuf[rem-self._recv_macsize:rem] 1238 1239 if self._recv_encryption: 1240 packet = self._recv_encryption.decrypt_packet(seq, self._packet, 1241 rest, 4, mac) 1242 1243 if not packet: 1244 raise MACError('MAC verification failed') 1245 else: 1246 packet = self._packet[4:] + rest 1247 1248 self._inpbuf = self._inpbuf[rem:] 1249 self._packet = b'' 1250 1251 payload = packet[1:-packet[0]] 1252 1253 if self._decompressor and (self._auth_complete or 1254 not self._decompress_after_auth): 1255 payload = self._decompressor.decompress(payload) 1256 1257 if payload is None: 1258 raise CompressionError('Decompression failed') 1259 1260 packet = SSHPacket(payload) 1261 pkttype = packet.get_byte() 1262 handler = self 1263 skip_reason = '' 1264 exc_reason = '' 1265 1266 if self._kex and MSG_KEX_FIRST <= pkttype <= MSG_KEX_LAST: 1267 if self._ignore_first_kex: # pragma: no cover 1268 skip_reason = 'ignored first kex' 1269 self._ignore_first_kex = False 1270 else: 1271 handler = self._kex 1272 elif (self._auth and 1273 MSG_USERAUTH_FIRST <= pkttype <= MSG_USERAUTH_LAST): 1274 handler = self._auth 1275 elif pkttype > MSG_USERAUTH_LAST and not self._auth_complete: 1276 skip_reason = 'invalid request before auth complete' 1277 exc_reason = 'Invalid request before authentication was complete' 1278 elif MSG_CHANNEL_FIRST <= pkttype <= MSG_CHANNEL_LAST: 1279 try: 1280 recv_chan = packet.get_uint32() 1281 handler = self._channels[recv_chan] 1282 except KeyError: 1283 skip_reason = 'invalid channel number' 1284 exc_reason = 'Invalid channel number %d received' % recv_chan 1285 except PacketDecodeError: 1286 skip_reason = 'incomplete channel request' 1287 exc_reason = 'Incomplete channel request received' 1288 1289 handler.log_received_packet(pkttype, seq, packet, skip_reason) 1290 1291 if not skip_reason: 1292 try: 1293 processed = handler.process_packet(pkttype, seq, packet) 1294 except PacketDecodeError as exc: 1295 raise ProtocolError(str(exc)) from None 1296 1297 if not processed: 1298 self.logger.debug1('Unknown packet type %d received', pkttype) 1299 self.send_packet(MSG_UNIMPLEMENTED, UInt32(seq)) 1300 1301 if exc_reason: 1302 raise ProtocolError(exc_reason) 1303 1304 if self._transport: 1305 self._recv_seq = (seq + 1) & 0xffffffff 1306 self._recv_handler = self._recv_pkthdr 1307 1308 return True 1309 1310 def send_packet(self, pkttype, *args, handler=None): 1311 """Send an SSH packet""" 1312 1313 if (self._auth_complete and self._kex_complete and 1314 (self._rekey_bytes_sent >= self._rekey_bytes or 1315 (self._rekey_seconds and 1316 time.monotonic() >= self._rekey_time))): 1317 self._send_kexinit() 1318 self._kexinit_sent = True 1319 1320 if (((pkttype in {MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT} or 1321 pkttype > MSG_KEX_LAST) and not self._kex_complete) or 1322 (pkttype == MSG_USERAUTH_BANNER and 1323 not (self._auth_in_progress or self._auth_complete)) or 1324 (pkttype > MSG_USERAUTH_LAST and not self._auth_complete)): 1325 self._deferred_packets.append((pkttype, args)) 1326 return 1327 1328 # If we're encrypting and we have no data outstanding, insert an 1329 # ignore packet into the stream 1330 if self._send_encryption and pkttype not in (MSG_IGNORE, MSG_EXT_INFO): 1331 self.send_packet(MSG_IGNORE, String(b'')) 1332 1333 payload = Byte(pkttype) + b''.join(args) 1334 log_data = payload 1335 1336 if self._compressor and (self._auth_complete or 1337 not self._compress_after_auth): 1338 payload = self._compressor.compress(payload) 1339 1340 if payload is None: # pragma: no cover 1341 raise CompressionError('Compression failed') 1342 1343 padlen = -(self._send_enchdrlen + len(payload)) % self._send_blocksize 1344 if padlen < 4: 1345 padlen += self._send_blocksize 1346 1347 packet = Byte(padlen) + payload + os.urandom(padlen) 1348 pktlen = len(packet) 1349 hdr = UInt32(pktlen) 1350 seq = self._send_seq 1351 1352 if self._send_encryption: 1353 packet, mac = self._send_encryption.encrypt_packet(seq, hdr, packet) 1354 else: 1355 packet = hdr + packet 1356 mac = b'' 1357 1358 self._send(packet + mac) 1359 self._send_seq = (seq + 1) & 0xffffffff 1360 1361 if self._kex_complete: 1362 self._rekey_bytes_sent += pktlen 1363 1364 if not handler: 1365 handler = self 1366 1367 handler.log_sent_packet(pkttype, seq, log_data) 1368 1369 def _send_deferred_packets(self): 1370 """Send packets deferred due to key exchange or auth""" 1371 1372 deferred_packets = self._deferred_packets 1373 self._deferred_packets = [] 1374 1375 for pkttype, args in deferred_packets: 1376 self.send_packet(pkttype, *args) 1377 1378 def _send_disconnect(self, code, reason, lang): 1379 """Send a disconnect packet""" 1380 1381 self.logger.info('Sending disconnect: %s (%d)', reason, code) 1382 1383 self.send_packet(MSG_DISCONNECT, UInt32(code), 1384 String(reason), String(lang)) 1385 1386 def _send_kexinit(self): 1387 """Start a key exchange""" 1388 1389 self._kex_complete = False 1390 self._rekey_bytes_sent = 0 1391 1392 if self._rekey_seconds: 1393 self._rekey_time = time.monotonic() + self._rekey_seconds 1394 1395 gss_mechs = self._gss.mechs if self._gss_kex else [] 1396 kex_algs = expand_kex_algs(self._kex_algs, gss_mechs, 1397 bool(self._server_host_key_algs)) 1398 1399 kex_algs += self._get_ext_info_kex_alg() 1400 host_key_algs = self._server_host_key_algs or [b'null'] 1401 1402 self.logger.debug1('Requesting key exchange') 1403 self.logger.debug2(' Key exchange algs: %s', kex_algs) 1404 self.logger.debug2(' Host key algs: %s', host_key_algs) 1405 self.logger.debug2(' Encryption algs: %s', self._enc_algs) 1406 self.logger.debug2(' MAC algs: %s', self._mac_algs) 1407 self.logger.debug2(' Compression algs: %s', self._cmp_algs) 1408 1409 cookie = os.urandom(16) 1410 kex_algs = NameList(kex_algs) 1411 host_key_algs = NameList(host_key_algs) 1412 enc_algs = NameList(self._enc_algs) 1413 mac_algs = NameList(self._mac_algs) 1414 cmp_algs = NameList(self._cmp_algs) 1415 langs = NameList([]) 1416 1417 packet = b''.join((Byte(MSG_KEXINIT), cookie, kex_algs, host_key_algs, 1418 enc_algs, enc_algs, mac_algs, mac_algs, cmp_algs, 1419 cmp_algs, langs, langs, Boolean(False), UInt32(0))) 1420 1421 if self.is_server(): 1422 self._server_kexinit = packet 1423 else: 1424 self._client_kexinit = packet 1425 1426 self.send_packet(MSG_KEXINIT, packet[1:]) 1427 1428 def _send_ext_info(self): 1429 """Send extension information""" 1430 1431 packet = UInt32(len(self._extensions_to_send)) 1432 1433 self.logger.debug2('Sending extension info') 1434 1435 for name, value in self._extensions_to_send.items(): 1436 packet += String(name) + String(value) 1437 1438 self.logger.debug2(' %s: %s', name, value) 1439 1440 self.send_packet(MSG_EXT_INFO, packet) 1441 1442 def send_newkeys(self, k, h): 1443 """Finish a key exchange and send a new keys message""" 1444 1445 if not self._session_id: 1446 first_kex = True 1447 self._session_id = h 1448 else: 1449 first_kex = False 1450 1451 enc_keysize_cs, enc_ivsize_cs, enc_blocksize_cs, \ 1452 mac_keysize_cs, mac_hashsize_cs, etm_cs = \ 1453 get_encryption_params(self._enc_alg_cs, self._mac_alg_cs) 1454 1455 enc_keysize_sc, enc_ivsize_sc, enc_blocksize_sc, \ 1456 mac_keysize_sc, mac_hashsize_sc, etm_sc = \ 1457 get_encryption_params(self._enc_alg_sc, self._mac_alg_sc) 1458 1459 if mac_keysize_cs == 0: 1460 self._mac_alg_cs = self._enc_alg_cs 1461 1462 if mac_keysize_sc == 0: 1463 self._mac_alg_sc = self._enc_alg_sc 1464 1465 cmp_after_auth_cs = get_compression_params(self._cmp_alg_cs) 1466 cmp_after_auth_sc = get_compression_params(self._cmp_alg_sc) 1467 1468 self.logger.debug2(' Client to server:') 1469 self.logger.debug2(' Encryption alg: %s', self._enc_alg_cs) 1470 self.logger.debug2(' MAC alg: %s', self._mac_alg_cs) 1471 self.logger.debug2(' Compression alg: %s', self._cmp_alg_cs) 1472 self.logger.debug2(' Server to client:') 1473 self.logger.debug2(' Encryption alg: %s', self._enc_alg_sc) 1474 self.logger.debug2(' MAC alg: %s', self._mac_alg_sc) 1475 self.logger.debug2(' Compression alg: %s', self._cmp_alg_sc) 1476 1477 iv_cs = self._kex.compute_key(k, h, b'A', self._session_id, 1478 enc_ivsize_cs) 1479 iv_sc = self._kex.compute_key(k, h, b'B', self._session_id, 1480 enc_ivsize_sc) 1481 enc_key_cs = self._kex.compute_key(k, h, b'C', self._session_id, 1482 enc_keysize_cs) 1483 enc_key_sc = self._kex.compute_key(k, h, b'D', self._session_id, 1484 enc_keysize_sc) 1485 mac_key_cs = self._kex.compute_key(k, h, b'E', self._session_id, 1486 mac_keysize_cs) 1487 mac_key_sc = self._kex.compute_key(k, h, b'F', self._session_id, 1488 mac_keysize_sc) 1489 self._kex = None 1490 1491 next_enc_cs = get_encryption(self._enc_alg_cs, enc_key_cs, iv_cs, 1492 self._mac_alg_cs, mac_key_cs, etm_cs) 1493 next_enc_sc = get_encryption(self._enc_alg_sc, enc_key_sc, iv_sc, 1494 self._mac_alg_sc, mac_key_sc, etm_sc) 1495 1496 self.send_packet(MSG_NEWKEYS) 1497 1498 self._extensions_to_send[b'global-requests-ok'] = '' 1499 1500 if self.is_client(): 1501 self._send_encryption = next_enc_cs 1502 self._send_enchdrlen = 1 if etm_cs else 5 1503 self._send_blocksize = max(8, enc_blocksize_cs) 1504 self._compressor = get_compressor(self._cmp_alg_cs) 1505 self._compress_after_auth = cmp_after_auth_cs 1506 1507 self._next_recv_encryption = next_enc_sc 1508 self._next_recv_blocksize = max(8, enc_blocksize_sc) 1509 self._next_recv_macsize = mac_hashsize_sc 1510 self._next_decompressor = get_decompressor(self._cmp_alg_sc) 1511 self._next_decompress_after_auth = cmp_after_auth_sc 1512 1513 self.set_extra_info( 1514 send_cipher=self._enc_alg_cs.decode('ascii'), 1515 send_mac=self._mac_alg_cs.decode('ascii'), 1516 send_compression=self._cmp_alg_cs.decode('ascii'), 1517 recv_cipher=self._enc_alg_sc.decode('ascii'), 1518 recv_mac=self._mac_alg_sc.decode('ascii'), 1519 recv_compression=self._cmp_alg_sc.decode('ascii')) 1520 1521 if first_kex: 1522 if self._wait == 'kex' and not self._waiter.cancelled(): 1523 self._waiter.set_result(None) 1524 self._wait = None 1525 else: 1526 self.send_service_request(_USERAUTH_SERVICE) 1527 else: 1528 self._send_encryption = next_enc_sc 1529 self._send_enchdrlen = 1 if etm_sc else 5 1530 self._send_blocksize = max(8, enc_blocksize_sc) 1531 self._compressor = get_compressor(self._cmp_alg_sc) 1532 self._compress_after_auth = cmp_after_auth_sc 1533 1534 self._next_recv_encryption = next_enc_cs 1535 self._next_recv_blocksize = max(8, enc_blocksize_cs) 1536 self._next_recv_macsize = mac_hashsize_cs 1537 self._next_decompressor = get_decompressor(self._cmp_alg_cs) 1538 self._next_decompress_after_auth = cmp_after_auth_cs 1539 1540 self.set_extra_info( 1541 send_cipher=self._enc_alg_sc.decode('ascii'), 1542 send_mac=self._mac_alg_sc.decode('ascii'), 1543 send_compression=self._cmp_alg_sc.decode('ascii'), 1544 recv_cipher=self._enc_alg_cs.decode('ascii'), 1545 recv_mac=self._mac_alg_cs.decode('ascii'), 1546 recv_compression=self._cmp_alg_cs.decode('ascii')) 1547 1548 if first_kex: 1549 self._next_service = _USERAUTH_SERVICE 1550 1551 self._extensions_to_send[b'server-sig-algs'] = \ 1552 b','.join(self._sig_algs) 1553 1554 if self._can_send_ext_info: 1555 self._send_ext_info() 1556 self._can_send_ext_info = False 1557 1558 self._kex_complete = True 1559 self._send_deferred_packets() 1560 1561 def send_service_request(self, service): 1562 """Send a service request""" 1563 1564 self.logger.debug2('Requesting service %s', service) 1565 1566 self._next_service = service 1567 self.send_packet(MSG_SERVICE_REQUEST, String(service)) 1568 1569 def _get_userauth_request_packet(self, method, args): 1570 """Get packet data for a user authentication request""" 1571 1572 return b''.join((Byte(MSG_USERAUTH_REQUEST), String(self._username), 1573 String(_CONNECTION_SERVICE), String(method)) + args) 1574 1575 def get_userauth_request_data(self, method, *args): 1576 """Get signature data for a user authentication request""" 1577 1578 return (String(self._session_id) + 1579 self._get_userauth_request_packet(method, args)) 1580 1581 def send_userauth_packet(self, pkttype, *args, handler=None, 1582 trivial=True): 1583 """Send a user authentication packet""" 1584 1585 self._auth_was_trivial &= trivial 1586 self.send_packet(pkttype, *args, handler=handler) 1587 1588 async def send_userauth_request(self, method, *args, key=None, 1589 trivial=True): 1590 """Send a user authentication request""" 1591 1592 packet = self._get_userauth_request_packet(method, args) 1593 1594 if key: 1595 data = String(self._session_id) + packet 1596 1597 if getattr(key, 'use_executor', False): 1598 sig = await self._loop.run_in_executor(None, key.sign, data) 1599 else: 1600 sig = key.sign(data) 1601 1602 if inspect.isawaitable(sig): 1603 sig = await sig 1604 1605 packet += String(sig) 1606 1607 self.send_userauth_packet(MSG_USERAUTH_REQUEST, packet[1:], 1608 trivial=trivial) 1609 1610 def send_userauth_failure(self, partial_success): 1611 """Send a user authentication failure response""" 1612 1613 methods = get_server_auth_methods(self) 1614 1615 self.logger.debug2('Remaining auth methods: %s', methods or 'None') 1616 1617 self._auth = None 1618 self.send_packet(MSG_USERAUTH_FAILURE, NameList(methods), 1619 Boolean(partial_success)) 1620 1621 def send_userauth_success(self): 1622 """Send a user authentication success response""" 1623 1624 self.logger.info('Auth for user %s succeeded', self._username) 1625 1626 self.send_packet(MSG_USERAUTH_SUCCESS) 1627 self._auth = None 1628 self._auth_in_progress = False 1629 self._auth_complete = True 1630 self._next_service = None 1631 self.set_extra_info(username=self._username) 1632 self._send_deferred_packets() 1633 1634 self._cancel_login_timer() 1635 self._set_keepalive_timer() 1636 self._owner.auth_completed() 1637 1638 if self._acceptor: 1639 result = self._acceptor(self) 1640 1641 if inspect.isawaitable(result): 1642 self.create_task(result) 1643 1644 self._acceptor = None 1645 self._error_handler = None 1646 1647 if self._wait == 'auth' and not self._waiter.cancelled(): 1648 self._waiter.set_result(None) 1649 self._wait = None 1650 1651 def send_channel_open_confirmation(self, send_chan, recv_chan, 1652 recv_window, recv_pktsize, 1653 *result_args): 1654 """Send a channel open confirmation""" 1655 1656 self.send_packet(MSG_CHANNEL_OPEN_CONFIRMATION, UInt32(send_chan), 1657 UInt32(recv_chan), UInt32(recv_window), 1658 UInt32(recv_pktsize), *result_args) 1659 1660 def send_channel_open_failure(self, send_chan, code, reason, lang): 1661 """Send a channel open failure""" 1662 1663 self.send_packet(MSG_CHANNEL_OPEN_FAILURE, UInt32(send_chan), 1664 UInt32(code), String(reason), String(lang)) 1665 1666 async def _make_global_request(self, request, *args): 1667 """Send a global request and wait for the response""" 1668 1669 if not self._transport: 1670 return MSG_REQUEST_FAILURE, SSHPacket(b'') 1671 1672 waiter = self._loop.create_future() 1673 self._global_request_waiters.append(waiter) 1674 1675 self.send_packet(MSG_GLOBAL_REQUEST, String(request), 1676 Boolean(True), *args) 1677 1678 return await waiter 1679 1680 def _report_global_response(self, result): 1681 """Report back the response to a previously issued global request""" 1682 1683 _, _, want_reply = self._global_request_queue.pop(0) 1684 1685 if want_reply: # pragma: no branch 1686 if result: 1687 response = b'' if result is True else result 1688 self.send_packet(MSG_REQUEST_SUCCESS, response) 1689 else: 1690 self.send_packet(MSG_REQUEST_FAILURE) 1691 1692 if self._global_request_queue: 1693 self._service_next_global_request() 1694 1695 def _service_next_global_request(self): 1696 """Process next item on global request queue""" 1697 1698 handler, packet, _ = self._global_request_queue[0] 1699 if callable(handler): 1700 handler(packet) 1701 else: 1702 self._report_global_response(False) 1703 1704 def _connection_made(self): 1705 """Handle the opening of a new connection""" 1706 1707 raise NotImplementedError 1708 1709 def _process_disconnect(self, _pkttype, _pktid, packet): 1710 """Process a disconnect message""" 1711 1712 code = packet.get_uint32() 1713 reason = packet.get_string() 1714 lang = packet.get_string() 1715 packet.check_end() 1716 1717 try: 1718 reason = reason.decode('utf-8') 1719 lang = lang.decode('ascii') 1720 except UnicodeDecodeError: 1721 raise ProtocolError('Invalid disconnect message') from None 1722 1723 self.logger.debug1('Received disconnect: %s (%d)', reason, code) 1724 1725 if code != DISC_BY_APPLICATION or self._wait: 1726 exc = construct_disc_error(code, reason, lang) 1727 else: 1728 exc = None 1729 1730 self._force_close(exc) 1731 1732 def _process_ignore(self, _pkttype, _pktid, packet): 1733 """Process an ignore message""" 1734 1735 # pylint: disable=no-self-use 1736 1737 _ = packet.get_string() # data 1738 packet.check_end() 1739 1740 def _process_unimplemented(self, _pkttype, _pktid, packet): 1741 """Process an unimplemented message response""" 1742 1743 # pylint: disable=no-self-use 1744 1745 _ = packet.get_uint32() # seq 1746 packet.check_end() 1747 1748 def _process_debug(self, _pkttype, _pktid, packet): 1749 """Process a debug message""" 1750 1751 always_display = packet.get_boolean() 1752 msg = packet.get_string() 1753 lang = packet.get_string() 1754 packet.check_end() 1755 1756 try: 1757 msg = msg.decode('utf-8') 1758 lang = lang.decode('ascii') 1759 except UnicodeDecodeError: 1760 raise ProtocolError('Invalid debug message') from None 1761 1762 self.logger.debug1('Received debug message: %s%s', msg, 1763 ' (always display)' if always_display else '') 1764 1765 self._owner.debug_msg_received(msg, lang, always_display) 1766 1767 def _process_service_request(self, _pkttype, _pktid, packet): 1768 """Process a service request""" 1769 1770 service = packet.get_string() 1771 packet.check_end() 1772 1773 if service == self._next_service: 1774 self.logger.debug2('Accepting request for service %s', service) 1775 1776 self.send_packet(MSG_SERVICE_ACCEPT, String(service)) 1777 1778 if (self.is_server() and # pragma: no branch 1779 not self._auth_in_progress and 1780 service == _USERAUTH_SERVICE): 1781 self._auth_in_progress = True 1782 self._send_deferred_packets() 1783 else: 1784 raise ServiceNotAvailable('Unexpected service request received') 1785 1786 def _process_service_accept(self, _pkttype, _pktid, packet): 1787 """Process a service accept response""" 1788 1789 service = packet.get_string() 1790 packet.check_end() 1791 1792 if service == self._next_service: 1793 self.logger.debug2('Request for service %s accepted', service) 1794 1795 self._next_service = None 1796 1797 if (self.is_client() and # pragma: no branch 1798 service == _USERAUTH_SERVICE): 1799 self.logger.info('Beginning auth for user %s', self._username) 1800 1801 self._auth_in_progress = True 1802 1803 # This method is only in SSHClientConnection 1804 # pylint: disable=no-member 1805 self.try_next_auth() 1806 else: 1807 raise ServiceNotAvailable('Unexpected service accept received') 1808 1809 def _process_ext_info(self, _pkttype, _pktid, packet): 1810 """Process extension information""" 1811 1812 extensions = {} 1813 1814 self.logger.debug2('Received extension info') 1815 1816 num_extensions = packet.get_uint32() 1817 for _ in range(num_extensions): 1818 name = packet.get_string() 1819 value = packet.get_string() 1820 extensions[name] = value 1821 1822 self.logger.debug2(' %s: %s', name, value) 1823 1824 packet.check_end() 1825 1826 if self.is_client(): 1827 self._server_sig_algs = \ 1828 extensions.get(b'server-sig-algs').split(b',') 1829 1830 def _process_kexinit(self, _pkttype, _pktid, packet): 1831 """Process a key exchange request""" 1832 1833 if self._kex: 1834 raise ProtocolError('Key exchange already in progress') 1835 1836 _ = packet.get_bytes(16) # cookie 1837 peer_kex_algs = packet.get_namelist() 1838 peer_host_key_algs = packet.get_namelist() 1839 enc_algs_cs = packet.get_namelist() 1840 enc_algs_sc = packet.get_namelist() 1841 mac_algs_cs = packet.get_namelist() 1842 mac_algs_sc = packet.get_namelist() 1843 cmp_algs_cs = packet.get_namelist() 1844 cmp_algs_sc = packet.get_namelist() 1845 _ = packet.get_namelist() # lang_cs 1846 _ = packet.get_namelist() # lang_sc 1847 first_kex_follows = packet.get_boolean() 1848 _ = packet.get_uint32() # reserved 1849 packet.check_end() 1850 1851 if self.is_server(): 1852 self._client_kexinit = packet.get_consumed_payload() 1853 1854 if b'ext-info-c' in peer_kex_algs and not self._session_id: 1855 self._can_send_ext_info = True 1856 else: 1857 self._server_kexinit = packet.get_consumed_payload() 1858 1859 if b'ext-info-s' in peer_kex_algs and not self._session_id: 1860 self._can_send_ext_info = True 1861 1862 if self._kexinit_sent: 1863 self._kexinit_sent = False 1864 else: 1865 self._send_kexinit() 1866 1867 if self._gss: 1868 self._gss.reset() 1869 1870 gss_mechs = self._gss.mechs if self._gss_kex else [] 1871 kex_algs = expand_kex_algs(self._kex_algs, gss_mechs, 1872 bool(self._server_host_key_algs)) 1873 1874 self.logger.debug1('Received key exchange request') 1875 self.logger.debug2(' Key exchange algs: %s', peer_kex_algs) 1876 self.logger.debug2(' Host key algs: %s', peer_host_key_algs) 1877 self.logger.debug2(' Client to server:') 1878 self.logger.debug2(' Encryption algs: %s', enc_algs_cs) 1879 self.logger.debug2(' MAC algs: %s', mac_algs_cs) 1880 self.logger.debug2(' Compression algs: %s', cmp_algs_cs) 1881 self.logger.debug2(' Server to client:') 1882 self.logger.debug2(' Encryption algs: %s', enc_algs_sc) 1883 self.logger.debug2(' MAC algs: %s', mac_algs_sc) 1884 self.logger.debug2(' Compression algs: %s', cmp_algs_sc) 1885 1886 kex_alg = self._choose_alg('key exchange', kex_algs, peer_kex_algs) 1887 self._kex = get_kex(self, kex_alg) 1888 self._ignore_first_kex = (first_kex_follows and 1889 self._kex.algorithm != peer_kex_algs[0]) 1890 1891 if self.is_server(): 1892 # This method is only in SSHServerConnection 1893 # pylint: disable=no-member 1894 if (not self._choose_server_host_key(peer_host_key_algs) and 1895 not kex_alg.startswith(b'gss-')): 1896 raise KeyExchangeFailed('Unable to find compatible ' 1897 'server host key') 1898 1899 self._enc_alg_cs = self._choose_alg('encryption', self._enc_algs, 1900 enc_algs_cs) 1901 self._enc_alg_sc = self._choose_alg('encryption', self._enc_algs, 1902 enc_algs_sc) 1903 1904 self._mac_alg_cs = self._choose_alg('MAC', self._mac_algs, mac_algs_cs) 1905 self._mac_alg_sc = self._choose_alg('MAC', self._mac_algs, mac_algs_sc) 1906 1907 self._cmp_alg_cs = self._choose_alg('compression', self._cmp_algs, 1908 cmp_algs_cs) 1909 self._cmp_alg_sc = self._choose_alg('compression', self._cmp_algs, 1910 cmp_algs_sc) 1911 1912 self.logger.debug1('Beginning key exchange') 1913 self.logger.debug2(' Key exchange alg: %s', self._kex.algorithm) 1914 1915 self._kex.start() 1916 1917 def _process_newkeys(self, _pkttype, _pktid, packet): 1918 """Process a new keys message, finishing a key exchange""" 1919 1920 packet.check_end() 1921 1922 if self._next_recv_encryption: 1923 self._recv_encryption = self._next_recv_encryption 1924 self._recv_blocksize = self._next_recv_blocksize 1925 self._recv_macsize = self._next_recv_macsize 1926 self._decompressor = self._next_decompressor 1927 self._decompress_after_auth = self._next_decompress_after_auth 1928 1929 self._next_recv_encryption = None 1930 else: 1931 raise ProtocolError('New keys not negotiated') 1932 1933 self.logger.debug1('Completed key exchange') 1934 1935 def _process_userauth_request(self, _pkttype, _pktid, packet): 1936 """Process a user authentication request""" 1937 1938 username = packet.get_string() 1939 service = packet.get_string() 1940 method = packet.get_string() 1941 1942 if len(username) >= _MAX_USERNAME_LEN: 1943 raise IllegalUserName('Username too long') 1944 1945 if service != _CONNECTION_SERVICE: 1946 raise ServiceNotAvailable('Unexpected service in auth request') 1947 1948 try: 1949 username = saslprep(username.decode('utf-8')) 1950 except (UnicodeDecodeError, SASLPrepError) as exc: 1951 raise IllegalUserName(str(exc)) from None 1952 1953 if self.is_client(): 1954 raise ProtocolError('Unexpected userauth request') 1955 elif self._auth_complete: 1956 # Silently ignore requests if we're already authenticated 1957 pass 1958 else: 1959 if username != self._username: 1960 self.logger.info('Beginning auth for user %s', username) 1961 1962 self._username = username 1963 begin_auth = True 1964 else: 1965 begin_auth = False 1966 1967 self.create_task(self._finish_userauth(begin_auth, method, packet)) 1968 1969 async def _finish_userauth(self, begin_auth, method, packet): 1970 """Finish processing a user authentication request""" 1971 1972 if not self._owner: # pragma: no cover 1973 return 1974 1975 if begin_auth: 1976 # This method is only in SSHServerConnection 1977 # pylint: disable=no-member 1978 await self._reload_config() 1979 1980 result = self._owner.begin_auth(self._username) 1981 1982 if inspect.isawaitable(result): 1983 result = await result 1984 1985 if not result: 1986 self.send_userauth_success() 1987 return 1988 1989 if not self._owner: # pragma: no cover 1990 return 1991 1992 if self._auth: 1993 self._auth.cancel() 1994 1995 self._auth = lookup_server_auth(self, self._username, method, packet) 1996 1997 def _process_userauth_failure(self, _pkttype, pktid, packet): 1998 """Process a user authentication failure response""" 1999 2000 auth_methods = packet.get_namelist() 2001 partial_success = packet.get_boolean() 2002 packet.check_end() 2003 2004 self.logger.debug2('Remaining auth methods: %s', 2005 auth_methods or 'None') 2006 2007 if self._preferred_auth: 2008 self.logger.debug2('Preferred auth methods: %s', 2009 self._preferred_auth or 'None') 2010 2011 auth_methods = [method for method in self._preferred_auth 2012 if method in auth_methods] 2013 2014 self._auth_methods = auth_methods 2015 2016 if self.is_client() and self._auth: 2017 if partial_success: # pragma: no cover 2018 # Partial success not implemented yet 2019 self._auth.auth_succeeded() 2020 else: 2021 self._auth.auth_failed() 2022 2023 # This method is only in SSHClientConnection 2024 # pylint: disable=no-member 2025 self.try_next_auth() 2026 else: 2027 self.logger.debug2('Unexpected userauth failure response') 2028 self.send_packet(MSG_UNIMPLEMENTED, UInt32(pktid)) 2029 2030 def _process_userauth_success(self, _pkttype, pktid, packet): 2031 """Process a user authentication success response""" 2032 2033 packet.check_end() 2034 2035 if self.is_client() and self._auth: 2036 if self._auth_was_trivial and self._disable_trivial_auth: 2037 raise PermissionDenied('Trivial auth disabled') 2038 2039 self.logger.info('Auth for user %s succeeded', self._username) 2040 2041 self._auth.auth_succeeded() 2042 self._auth.cancel() 2043 self._auth = None 2044 self._auth_in_progress = False 2045 self._auth_complete = True 2046 2047 if self._agent: 2048 self._agent.close() 2049 2050 self.set_extra_info(username=self._username) 2051 self._cancel_login_timer() 2052 self._send_deferred_packets() 2053 self._set_keepalive_timer() 2054 self._owner.auth_completed() 2055 2056 if self._acceptor: 2057 result = self._acceptor(self) 2058 2059 if inspect.isawaitable(result): 2060 self.create_task(result) 2061 2062 self._acceptor = None 2063 self._error_handler = None 2064 2065 if self._wait == 'auth' and not self._waiter.cancelled(): 2066 self._waiter.set_result(None) 2067 self._wait = None 2068 else: 2069 self.logger.debug2('Unexpected userauth success response') 2070 self.send_packet(MSG_UNIMPLEMENTED, UInt32(pktid)) 2071 2072 def _process_userauth_banner(self, _pkttype, _pktid, packet): 2073 """Process a user authentication banner message""" 2074 2075 msg = packet.get_string() 2076 lang = packet.get_string() 2077 packet.check_end() 2078 2079 try: 2080 msg = msg.decode('utf-8') 2081 lang = lang.decode('ascii') 2082 except UnicodeDecodeError: 2083 raise ProtocolError('Invalid userauth banner') from None 2084 2085 self.logger.debug1('Received authentication banner') 2086 2087 if self.is_client(): 2088 self._owner.auth_banner_received(msg, lang) 2089 else: 2090 raise ProtocolError('Unexpected userauth banner') 2091 2092 def _process_global_request(self, _pkttype, _pktid, packet): 2093 """Process a global request""" 2094 2095 request = packet.get_string() 2096 want_reply = packet.get_boolean() 2097 2098 try: 2099 request = request.decode('ascii') 2100 except UnicodeDecodeError: 2101 raise ProtocolError('Invalid global request') from None 2102 2103 name = '_process_' + map_handler_name(request) + '_global_request' 2104 handler = getattr(self, name, None) 2105 2106 if not handler: 2107 self.logger.debug1('Received unknown global request: %s', request) 2108 2109 self._global_request_queue.append((handler, packet, want_reply)) 2110 if len(self._global_request_queue) == 1: 2111 self._service_next_global_request() 2112 2113 def _process_global_response(self, pkttype, _pktid, packet): 2114 """Process a global response""" 2115 2116 if self._global_request_waiters: 2117 waiter = self._global_request_waiters.pop(0) 2118 if not waiter.cancelled(): # pragma: no branch 2119 waiter.set_result((pkttype, packet)) 2120 else: 2121 raise ProtocolError('Unexpected global response') 2122 2123 def _process_channel_open(self, _pkttype, _pktid, packet): 2124 """Process a channel open request""" 2125 2126 chantype = packet.get_string() 2127 send_chan = packet.get_uint32() 2128 send_window = packet.get_uint32() 2129 send_pktsize = packet.get_uint32() 2130 2131 try: 2132 chantype = chantype.decode('ascii') 2133 except UnicodeDecodeError: 2134 raise ProtocolError('Invalid channel open request') from None 2135 2136 try: 2137 name = '_process_' + map_handler_name(chantype) + '_open' 2138 handler = getattr(self, name, None) 2139 if callable(handler): 2140 chan, session = handler(packet) 2141 chan.process_open(send_chan, send_window, 2142 send_pktsize, session) 2143 else: 2144 raise ChannelOpenError(OPEN_UNKNOWN_CHANNEL_TYPE, 2145 'Unknown channel type') 2146 except ChannelOpenError as exc: 2147 self.logger.debug1('Open failed for channel type %s: %s', 2148 chantype, exc.reason) 2149 2150 self.send_channel_open_failure(send_chan, exc.code, 2151 exc.reason, exc.lang) 2152 2153 def _process_channel_open_confirmation(self, _pkttype, _pktid, packet): 2154 """Process a channel open confirmation response""" 2155 2156 recv_chan = packet.get_uint32() 2157 send_chan = packet.get_uint32() 2158 send_window = packet.get_uint32() 2159 send_pktsize = packet.get_uint32() 2160 2161 chan = self._channels.get(recv_chan) 2162 if chan: 2163 chan.process_open_confirmation(send_chan, send_window, 2164 send_pktsize, packet) 2165 else: 2166 self.logger.debug1('Received open confirmation for unknown ' 2167 'channel %d', recv_chan) 2168 2169 raise ProtocolError('Invalid channel number') 2170 2171 def _process_channel_open_failure(self, _pkttype, _pktid, packet): 2172 """Process a channel open failure response""" 2173 2174 recv_chan = packet.get_uint32() 2175 code = packet.get_uint32() 2176 reason = packet.get_string() 2177 lang = packet.get_string() 2178 packet.check_end() 2179 2180 try: 2181 reason = reason.decode('utf-8') 2182 lang = lang.decode('ascii') 2183 except UnicodeDecodeError: 2184 raise ProtocolError('Invalid channel open failure') from None 2185 2186 chan = self._channels.get(recv_chan) 2187 if chan: 2188 chan.process_open_failure(code, reason, lang) 2189 else: 2190 self.logger.debug1('Received open failure for unknown ' 2191 'channel %d', recv_chan) 2192 2193 raise ProtocolError('Invalid channel number') 2194 2195 def _process_keepalive_at_openssh_dot_com_global_request(self, packet): 2196 """Process an incoming OpenSSH keepalive request""" 2197 2198 packet.check_end() 2199 2200 self.logger.debug2('Received OpenSSH keepalive request') 2201 self._report_global_response(True) 2202 2203 _packet_handlers = { 2204 MSG_DISCONNECT: _process_disconnect, 2205 MSG_IGNORE: _process_ignore, 2206 MSG_UNIMPLEMENTED: _process_unimplemented, 2207 MSG_DEBUG: _process_debug, 2208 MSG_SERVICE_REQUEST: _process_service_request, 2209 MSG_SERVICE_ACCEPT: _process_service_accept, 2210 MSG_EXT_INFO: _process_ext_info, 2211 2212 MSG_KEXINIT: _process_kexinit, 2213 MSG_NEWKEYS: _process_newkeys, 2214 2215 MSG_USERAUTH_REQUEST: _process_userauth_request, 2216 MSG_USERAUTH_FAILURE: _process_userauth_failure, 2217 MSG_USERAUTH_SUCCESS: _process_userauth_success, 2218 MSG_USERAUTH_BANNER: _process_userauth_banner, 2219 2220 MSG_GLOBAL_REQUEST: _process_global_request, 2221 MSG_REQUEST_SUCCESS: _process_global_response, 2222 MSG_REQUEST_FAILURE: _process_global_response, 2223 2224 MSG_CHANNEL_OPEN: _process_channel_open, 2225 MSG_CHANNEL_OPEN_CONFIRMATION: _process_channel_open_confirmation, 2226 MSG_CHANNEL_OPEN_FAILURE: _process_channel_open_failure 2227 } 2228 2229 def abort(self): 2230 """Forcibly close the SSH connection 2231 2232 This method closes the SSH connection immediately, without 2233 waiting for pending operations to complete and wihtout sending 2234 an explicit SSH disconnect message. Buffered data waiting to be 2235 sent will be lost and no more data will be received. When the 2236 the connection is closed, :meth:`connection_lost() 2237 <SSHClient.connection_lost>` on the associated :class:`SSHClient` 2238 object will be called with the value `None`. 2239 2240 """ 2241 2242 self.logger.info('Aborting connection') 2243 2244 self._force_close(None) 2245 2246 def close(self): 2247 """Cleanly close the SSH connection 2248 2249 This method calls :meth:`disconnect` with the reason set to 2250 indicate that the connection was closed explicitly by the 2251 application. 2252 2253 """ 2254 2255 self.logger.info('Closing connection') 2256 2257 self.disconnect(DISC_BY_APPLICATION, 'Disconnected by application') 2258 2259 async def wait_established(self): 2260 """Wait for connection to be established""" 2261 2262 await self._waiter 2263 2264 async def wait_closed(self): 2265 """Wait for this connection to close 2266 2267 This method is a coroutine which can be called to block until 2268 this connection has finished closing. 2269 2270 """ 2271 2272 if self._agent: 2273 await self._agent.wait_closed() 2274 2275 await self._close_event.wait() 2276 2277 def disconnect(self, code, reason, lang=DEFAULT_LANG): 2278 """Disconnect the SSH connection 2279 2280 This method sends a disconnect message and closes the SSH 2281 connection after buffered data waiting to be written has been 2282 sent. No more data will be received. When the connection is 2283 fully closed, :meth:`connection_lost() <SSHClient.connection_lost>` 2284 on the associated :class:`SSHClient` or :class:`SSHServer` object 2285 will be called with the value `None`. 2286 2287 :param code: 2288 The reason for the disconnect, from 2289 :ref:`disconnect reason codes <DisconnectReasons>` 2290 :param reason: 2291 A human readable reason for the disconnect 2292 :param lang: 2293 The language the reason is in 2294 :type code: `int` 2295 :type reason: `str` 2296 :type lang: `str` 2297 2298 """ 2299 2300 for chan in list(self._channels.values()): 2301 chan.close() 2302 2303 self._send_disconnect(code, reason, lang) 2304 self._force_close(None) 2305 2306 def get_extra_info(self, name, default=None): 2307 """Get additional information about the connection 2308 2309 This method returns extra information about the connection once 2310 it is established. Supported values include everything supported 2311 by a socket transport plus: 2312 2313 | username 2314 | client_version 2315 | server_version 2316 | send_cipher 2317 | send_mac 2318 | send_compression 2319 | recv_cipher 2320 | recv_mac 2321 | recv_compression 2322 2323 See :meth:`get_extra_info() <asyncio.BaseTransport.get_extra_info>` 2324 in :class:`asyncio.BaseTransport` for more information. 2325 2326 Additional information stored on the connection by calling 2327 :meth:`set_extra_info` can also be returned here. 2328 2329 """ 2330 2331 return self._extra.get(name, 2332 self._transport.get_extra_info(name, default) 2333 if self._transport else default) 2334 2335 def set_extra_info(self, **kwargs): 2336 """Store additional information associated with the connection 2337 2338 This method allows extra information to be associated with the 2339 connection. The information to store should be passed in as 2340 keyword parameters and can later be returned by calling 2341 :meth:`get_extra_info` with one of the keywords as the name 2342 to retrieve. 2343 2344 """ 2345 2346 self._extra.update(**kwargs) 2347 2348 def set_keepalive(self, interval=None, count_max=None): 2349 """Set keep-alive timer on this connection 2350 2351 This method sets the parameters of the keepalive timer on the 2352 connection. If *interval* is set to a non-zero value, 2353 keep-alive requests will be sent whenever the connection is 2354 idle, and if a response is not received after *count_max* 2355 attempts, the connection is closed. 2356 2357 :param interval: (optional) 2358 The time in seconds to wait before sending a keep-alive message 2359 if no data has been received. This defaults to 0, which 2360 disables sending these messages. 2361 :param count_max: (optional) 2362 The maximum number of keepalive messages which will be sent 2363 without getting a response before closing the connection. 2364 This defaults to 3, but only applies when *interval* is 2365 non-zero. 2366 :type interval: `int`, `float`, or `str` 2367 :type count_max: `int` 2368 2369 """ 2370 2371 if interval is not None: 2372 if isinstance(interval, str): 2373 interval = parse_time_interval(interval) 2374 2375 if interval < 0: 2376 raise ValueError('Keepalive interval cannot be negative') 2377 2378 self._keepalive_interval = interval 2379 2380 if count_max is not None: 2381 if count_max < 0: 2382 raise ValueError('Keepalive count max cannot be negative') 2383 2384 self._keepalive_count_max = count_max 2385 2386 self._reset_keepalive_timer() 2387 2388 def send_debug(self, msg, lang=DEFAULT_LANG, always_display=False): 2389 """Send a debug message on this connection 2390 2391 This method can be called to send a debug message to the 2392 other end of the connection. 2393 2394 :param msg: 2395 The debug message to send 2396 :param lang: 2397 The language the message is in 2398 :param always_display: 2399 Whether or not to display the message 2400 :type msg: `str` 2401 :type lang: `str` 2402 :type always_display: `bool` 2403 2404 """ 2405 2406 self.logger.debug1('Sending debug message: %s%s', msg, 2407 ' (always display)' if always_display else '') 2408 2409 self.send_packet(MSG_DEBUG, Boolean(always_display), 2410 String(msg), String(lang)) 2411 2412 def create_tcp_channel(self, encoding=None, errors='strict', 2413 window=_DEFAULT_WINDOW, 2414 max_pktsize=_DEFAULT_MAX_PKTSIZE): 2415 """Create an SSH TCP channel for a new direct TCP connection 2416 2417 This method can be called by :meth:`connection_requested() 2418 <SSHServer.connection_requested>` to create an 2419 :class:`SSHTCPChannel` with the desired encoding, Unicode 2420 error handling strategy, window, and max packet size for 2421 a newly created SSH direct connection. 2422 2423 :param encoding: (optional) 2424 The Unicode encoding to use for data exchanged on the 2425 connection. This defaults to `None`, allowing the 2426 application to send and receive raw bytes. 2427 :param errors: (optional) 2428 The error handling strategy to apply on encode/decode errors 2429 :param window: (optional) 2430 The receive window size for this session 2431 :param max_pktsize: (optional) 2432 The maximum packet size for this session 2433 :type encoding: `str` 2434 :type errors: `str` 2435 :type window: `int` 2436 :type max_pktsize: `int` 2437 2438 :returns: :class:`SSHTCPChannel` 2439 2440 """ 2441 2442 return SSHTCPChannel(self, self._loop, encoding, 2443 errors, window, max_pktsize) 2444 2445 def create_unix_channel(self, encoding=None, errors='strict', 2446 window=_DEFAULT_WINDOW, 2447 max_pktsize=_DEFAULT_MAX_PKTSIZE): 2448 """Create an SSH UNIX channel for a new direct UNIX domain connection 2449 2450 This method can be called by :meth:`unix_connection_requested() 2451 <SSHServer.unix_connection_requested>` to create an 2452 :class:`SSHUNIXChannel` with the desired encoding, Unicode 2453 error handling strategy, window, and max packet size for 2454 a newly created SSH direct UNIX domain socket connection. 2455 2456 :param encoding: (optional) 2457 The Unicode encoding to use for data exchanged on the 2458 connection. This defaults to `None`, allowing the 2459 application to send and receive raw bytes. 2460 :param errors: (optional) 2461 The error handling strategy to apply on encode/decode errors 2462 :param window: (optional) 2463 The receive window size for this session 2464 :param max_pktsize: (optional) 2465 The maximum packet size for this session 2466 :type encoding: `str` 2467 :type errors: `str` 2468 :type window: `int` 2469 :type max_pktsize: `int` 2470 2471 :returns: :class:`SSHUNIXChannel` 2472 2473 """ 2474 2475 return SSHUNIXChannel(self, self._loop, encoding, 2476 errors, window, max_pktsize) 2477 2478 def create_x11_channel(self, window=_DEFAULT_WINDOW, 2479 max_pktsize=_DEFAULT_MAX_PKTSIZE): 2480 """Create an SSH X11 channel to use in X11 forwarding""" 2481 2482 return SSHX11Channel(self, self._loop, None, 'strict', 2483 window, max_pktsize) 2484 2485 def create_agent_channel(self, window=_DEFAULT_WINDOW, 2486 max_pktsize=_DEFAULT_MAX_PKTSIZE): 2487 """Create an SSH agent channel to use in agent forwarding""" 2488 2489 return SSHAgentChannel(self, self._loop, None, 'strict', 2490 window, max_pktsize) 2491 2492 async def create_connection(self, session_factory, remote_host, remote_port, 2493 orig_host='', orig_port=0, *, encoding=None, 2494 errors='strict', window=_DEFAULT_WINDOW, 2495 max_pktsize=_DEFAULT_MAX_PKTSIZE): 2496 """Create an SSH direct or forwarded TCP connection""" 2497 2498 raise NotImplementedError 2499 2500 async def create_unix_connection(self, session_factory, remote_path, *, 2501 encoding=None, errors='strict', 2502 window=_DEFAULT_WINDOW, 2503 max_pktsize=_DEFAULT_MAX_PKTSIZE): 2504 """Create an SSH direct or forwarded UNIX domain socket connection""" 2505 2506 raise NotImplementedError 2507 2508 async def forward_connection(self, dest_host, dest_port): 2509 """Forward a tunneled TCP connection 2510 2511 This method is a coroutine which can be returned by a 2512 `session_factory` to forward connections tunneled over 2513 SSH to the specified destination host and port. 2514 2515 :param dest_host: 2516 The hostname or address to forward the connections to 2517 :param dest_port: 2518 The port number to forward the connections to 2519 :type dest_host: `str` 2520 :type dest_port: `int` 2521 2522 :returns: :class:`SSHTCPSession` 2523 2524 """ 2525 2526 try: 2527 if dest_host == '': 2528 dest_host = None 2529 2530 _, peer = await self._loop.create_connection(SSHForwarder, 2531 dest_host, dest_port) 2532 2533 self.logger.info(' Forwarding TCP connection to %s', 2534 (dest_host, dest_port)) 2535 except OSError as exc: 2536 raise ChannelOpenError(OPEN_CONNECT_FAILED, str(exc)) from None 2537 2538 return SSHForwarder(peer) 2539 2540 async def forward_unix_connection(self, dest_path): 2541 """Forward a tunneled UNIX domain socket connection 2542 2543 This method is a coroutine which can be returned by a 2544 `session_factory` to forward connections tunneled over 2545 SSH to the specified destination path. 2546 2547 :param dest_path: 2548 The path to forward the connection to 2549 :type dest_path: `str` 2550 2551 :returns: :class:`SSHUNIXSession` 2552 2553 """ 2554 2555 try: 2556 _, peer = \ 2557 await self._loop.create_unix_connection(SSHForwarder, dest_path) 2558 2559 self.logger.info(' Forwarding UNIX connection to %s', dest_path) 2560 except OSError as exc: 2561 raise ChannelOpenError(OPEN_CONNECT_FAILED, str(exc)) from None 2562 2563 return SSHForwarder(peer) 2564 2565 @async_context_manager 2566 async def forward_local_port(self, listen_host, listen_port, 2567 dest_host, dest_port): 2568 """Set up local port forwarding 2569 2570 This method is a coroutine which attempts to set up port 2571 forwarding from a local listening port to a remote host and port 2572 via the SSH connection. If the request is successful, the 2573 return value is an :class:`SSHListener` object which can be used 2574 later to shut down the port forwarding. 2575 2576 :param listen_host: 2577 The hostname or address on the local host to listen on 2578 :param listen_port: 2579 The port number on the local host to listen on 2580 :param dest_host: 2581 The hostname or address to forward the connections to 2582 :param dest_port: 2583 The port number to forward the connections to 2584 :type listen_host: `str` 2585 :type listen_port: `int` 2586 :type dest_host: `str` 2587 :type dest_port: `int` 2588 2589 :returns: :class:`SSHListener` 2590 2591 :raises: :exc:`OSError` if the listener can't be opened 2592 2593 """ 2594 2595 async def tunnel_connection(session_factory, orig_host, orig_port): 2596 """Forward a local connection over SSH""" 2597 2598 return (await self.create_connection(session_factory, 2599 dest_host, dest_port, 2600 orig_host, orig_port)) 2601 2602 if (listen_host, listen_port) == (dest_host, dest_port): 2603 self.logger.info('Creating local TCP forwarder on %s', 2604 (listen_host, listen_port)) 2605 else: 2606 self.logger.info('Creating local TCP forwarder from %s to %s', 2607 (listen_host, listen_port), (dest_host, dest_port)) 2608 2609 try: 2610 listener = await create_tcp_forward_listener(self, self._loop, 2611 tunnel_connection, 2612 listen_host, 2613 listen_port) 2614 except OSError as exc: 2615 self.logger.debug1('Failed to create local TCP listener: %s', exc) 2616 raise 2617 2618 if listen_port == 0: 2619 listen_port = listener.get_port() 2620 2621 self._local_listeners[listen_host, listen_port] = listener 2622 2623 return listener 2624 2625 @async_context_manager 2626 async def forward_local_path(self, listen_path, dest_path): 2627 """Set up local UNIX domain socket forwarding 2628 2629 This method is a coroutine which attempts to set up UNIX domain 2630 socket forwarding from a local listening path to a remote path 2631 via the SSH connection. If the request is successful, the 2632 return value is an :class:`SSHListener` object which can be used 2633 later to shut down the UNIX domain socket forwarding. 2634 2635 :param listen_path: 2636 The path on the local host to listen on 2637 :param dest_path: 2638 The path on the remote host to forward the connections to 2639 :type listen_path: `str` 2640 :type dest_path: `str` 2641 2642 :returns: :class:`SSHListener` 2643 2644 :raises: :exc:`OSError` if the listener can't be opened 2645 2646 """ 2647 2648 async def tunnel_connection(session_factory): 2649 """Forward a local connection over SSH""" 2650 2651 return await self.create_unix_connection(session_factory, dest_path) 2652 2653 self.logger.info('Creating local UNIX forwarder from %s to %s', 2654 listen_path, dest_path) 2655 2656 try: 2657 listener = await create_unix_forward_listener(self, self._loop, 2658 tunnel_connection, 2659 listen_path) 2660 except OSError as exc: 2661 self.logger.debug1('Failed to create local UNIX listener: %s', exc) 2662 raise 2663 2664 self._local_listeners[listen_path] = listener 2665 2666 return listener 2667 2668 def close_forward_listener(self, listen_key): 2669 """Mark a local forwarding listener as closed""" 2670 2671 self._local_listeners.pop(listen_key, None) 2672 2673 2674class SSHClientConnection(SSHConnection): 2675 """SSH client connection 2676 2677 This class represents an SSH client connection. 2678 2679 Once authentication is successful on a connection, new client 2680 sessions can be opened by calling :meth:`create_session`. 2681 2682 Direct TCP connections can be opened by calling 2683 :meth:`create_connection`. 2684 2685 Remote listeners for forwarded TCP connections can be opened by 2686 calling :meth:`create_server`. 2687 2688 Direct UNIX domain socket connections can be opened by calling 2689 :meth:`create_unix_connection`. 2690 2691 Remote listeners for forwarded UNIX domain socket connections 2692 can be opened by calling :meth:`create_unix_server`. 2693 2694 TCP port forwarding can be set up by calling :meth:`forward_local_port` 2695 or :meth:`forward_remote_port`. 2696 2697 UNIX domain socket forwarding can be set up by calling 2698 :meth:`forward_local_path` or :meth:`forward_remote_path`. 2699 2700 """ 2701 2702 def __init__(self, loop, options, acceptor=None, 2703 error_handler=None, wait=None): 2704 super().__init__(loop, options, acceptor, error_handler, 2705 wait, server=False) 2706 2707 self._host = options.host 2708 self._port = options.port 2709 2710 self._known_hosts = options.known_hosts 2711 self._host_key_alias = options.host_key_alias 2712 2713 self._server_host_key_algs = options.server_host_key_algs 2714 self._server_host_key = None 2715 2716 self._username = options.username 2717 self._password = options.password 2718 2719 self._client_host_keysign = options.client_host_keysign 2720 self._client_host_keys = None if options.client_host_keys is None else \ 2721 list(options.client_host_keys) 2722 self._client_host = options.client_host 2723 self._client_username = options.client_username 2724 self._client_keys = None if options.client_keys is None else \ 2725 list(options.client_keys) 2726 2727 if options.preferred_auth != (): 2728 self._preferred_auth = [method.encode('ascii') for method in 2729 options.preferred_auth] 2730 else: 2731 self._preferred_auth = get_client_auth_methods() 2732 2733 self._disable_trivial_auth = options.disable_trivial_auth 2734 2735 if options.agent_path is not None: 2736 self._agent = SSHAgentClient(options.agent_path) 2737 2738 self._agent_identities = options.agent_identities 2739 self._agent_forward_path = options.agent_forward_path 2740 self._get_agent_keys = bool(self._agent) 2741 2742 self._pkcs11_provider = options.pkcs11_provider 2743 self._pkcs11_pin = options.pkcs11_pin 2744 self._get_pkcs11_keys = bool(self._pkcs11_provider) 2745 2746 gss_host = options.gss_host if options.gss_host != () else options.host 2747 2748 if gss_host: 2749 try: 2750 self._gss = GSSClient(gss_host, options.gss_delegate_creds) 2751 self._gss_kex = options.gss_kex 2752 self._gss_auth = options.gss_auth 2753 self._gss_mic_auth = self._gss_auth 2754 except GSSError: 2755 pass 2756 2757 self._kbdint_password_auth = False 2758 2759 self._remote_listeners = {} 2760 self._dynamic_remote_listeners = {} 2761 2762 def _connection_made(self): 2763 """Handle the opening of a new connection""" 2764 2765 if not self._host: 2766 if self._peer_addr: 2767 self._host = self._peer_addr 2768 self._port = self._peer_port 2769 else: 2770 remote_peer = self.get_extra_info('remote_peername') 2771 self._host, self._port = remote_peer 2772 2773 if self._client_host_keysign: 2774 sock = self._transport.get_extra_info('socket') 2775 self._client_host_keys = get_keysign_keys(self._client_host_keysign, 2776 sock.fileno(), 2777 self._client_host_keys) 2778 2779 if self._known_hosts is None: 2780 self._trusted_host_keys = None 2781 self._trusted_ca_keys = None 2782 else: 2783 if not self._known_hosts: 2784 default_known_hosts = Path('~', '.ssh', 2785 'known_hosts').expanduser() 2786 2787 if (default_known_hosts.is_file() and 2788 os.access(default_known_hosts, os.R_OK)): 2789 self._known_hosts = str(default_known_hosts) 2790 else: 2791 self._known_hosts = b'' 2792 2793 port = self._port if self._port != DEFAULT_PORT else None 2794 2795 self._match_known_hosts(self._known_hosts, 2796 self._host_key_alias or self._host, 2797 self._peer_addr, port) 2798 2799 default_host_key_algs = [] 2800 2801 if self._server_host_key_algs != 'default': 2802 if self._trusted_host_key_algs: 2803 default_host_key_algs = self._trusted_host_key_algs 2804 2805 if self._trusted_ca_keys: 2806 default_host_key_algs = \ 2807 get_default_certificate_algs() + default_host_key_algs 2808 2809 if not default_host_key_algs: 2810 default_host_key_algs = \ 2811 get_default_certificate_algs() + get_default_public_key_algs() 2812 2813 if self._x509_trusted_certs is not None: 2814 if self._x509_trusted_certs or self._x509_trusted_cert_paths: 2815 default_host_key_algs = \ 2816 get_default_x509_certificate_algs() + default_host_key_algs 2817 2818 self._server_host_key_algs = \ 2819 _select_host_key_algs( 2820 self._server_host_key_algs, 2821 self._options.config.get('HostKeyAlgorithms', ()), 2822 default_host_key_algs) 2823 2824 self.logger.info('Connected to SSH server at %s', 2825 (self._host, self._port)) 2826 2827 if self._options.proxy_command: 2828 proxy_command = ' '.join(shlex.quote(arg) for arg in 2829 self._options.proxy_command) 2830 self.logger.info(' Proxy command: %s', proxy_command) 2831 else: 2832 self.logger.info(' Local address: %s', 2833 (self._local_addr, self._local_port)) 2834 self.logger.info(' Peer address: %s', 2835 (self._peer_addr, self._peer_port)) 2836 2837 2838 def _cleanup(self, exc): 2839 """Clean up this client connection""" 2840 2841 if self._agent: 2842 self._agent.close() 2843 2844 if self._remote_listeners: 2845 for listener in list(self._remote_listeners.values()): 2846 listener.close() 2847 2848 self._remote_listeners = {} 2849 self._dynamic_remote_listeners = {} 2850 2851 if exc is None: 2852 self.logger.info('Connection closed') 2853 elif isinstance(exc, ConnectionLost): 2854 self.logger.info(str(exc)) 2855 else: 2856 self.logger.info('Connection failure: ' + str(exc)) 2857 2858 super()._cleanup(exc) 2859 2860 2861 def _choose_signature_alg(self, keypair): 2862 """Choose signature algorithm to use for key-based authentication""" 2863 2864 if self._server_sig_algs: 2865 for alg in keypair.sig_algorithms: 2866 if alg in self._sig_algs and alg in self._server_sig_algs: 2867 keypair.set_sig_algorithm(alg) 2868 return True 2869 2870 return keypair.sig_algorithms[-1] in self._sig_algs 2871 2872 def validate_server_host_key(self, key_data): 2873 """Validate and return the server's host key""" 2874 2875 try: 2876 host_key = self._validate_host_key( 2877 self._host_key_alias or self._host, 2878 self._peer_addr, self._port, key_data) 2879 except ValueError as exc: 2880 raise HostKeyNotVerifiable(str(exc)) from None 2881 2882 self._server_host_key = host_key 2883 return host_key 2884 2885 def get_server_host_key(self): 2886 """Return the server host key used in the key exchange 2887 2888 This method returns the server host key used to complete the 2889 key exchange with the server. 2890 2891 If GSS key exchange is used, `None` is returned. 2892 2893 :returns: An :class:`SSHKey` public key or `None` 2894 2895 """ 2896 2897 return self._server_host_key 2898 2899 def try_next_auth(self): 2900 """Attempt client authentication using the next compatible method""" 2901 2902 if self._auth: 2903 self._auth.cancel() 2904 self._auth = None 2905 2906 while self._auth_methods: 2907 method = self._auth_methods.pop(0) 2908 2909 self._auth = lookup_client_auth(self, method) 2910 2911 if self._auth: 2912 return 2913 2914 self.logger.info('Auth failed for user %s', self._username) 2915 2916 self._force_close(PermissionDenied('Permission denied')) 2917 2918 def gss_kex_auth_requested(self): 2919 """Return whether to allow GSS key exchange authentication or not""" 2920 2921 if self._gss_kex_auth: 2922 self._gss_kex_auth = False 2923 return True 2924 else: 2925 return False 2926 2927 def gss_mic_auth_requested(self): 2928 """Return whether to allow GSS MIC authentication or not""" 2929 2930 if self._gss_mic_auth: 2931 self._gss_mic_auth = False 2932 return True 2933 else: 2934 return False 2935 2936 async def host_based_auth_requested(self): 2937 """Return a host key pair, host, and user to authenticate with""" 2938 2939 if not self._host_based_auth: 2940 return None, None, None 2941 2942 while True: 2943 try: 2944 keypair = self._client_host_keys.pop(0) 2945 except IndexError: 2946 keypair = None 2947 break 2948 2949 if self._choose_signature_alg(keypair): 2950 break 2951 2952 if self._client_host is None: 2953 self._client_host, _ = await self._loop.getnameinfo( 2954 self.get_extra_info('sockname'), socket.NI_NUMERICSERV) 2955 2956 # Add a trailing '.' to the client host to be compatible with 2957 # ssh-keysign from OpenSSH 2958 if self._client_host_keysign and self._client_host[-1:] != '.': 2959 self._client_host += '.' 2960 2961 return keypair, self._client_host, self._client_username 2962 2963 async def public_key_auth_requested(self): 2964 """Return a client key pair to authenticate with""" 2965 2966 if not self._public_key_auth: 2967 return None 2968 2969 if self._get_agent_keys: 2970 try: 2971 agent_keys = await self._agent.get_keys(self._agent_identities) 2972 self._client_keys = agent_keys + (self._client_keys or []) 2973 except ValueError: 2974 pass 2975 2976 self._get_agent_keys = False 2977 2978 if self._get_pkcs11_keys: 2979 pkcs11_keys = await self._loop.run_in_executor( 2980 None, load_pkcs11_keys, self._pkcs11_provider, self._pkcs11_pin) 2981 2982 self._client_keys = pkcs11_keys + (self._client_keys or []) 2983 self._get_pkcs11_keys = False 2984 2985 while True: 2986 if not self._client_keys: 2987 result = self._owner.public_key_auth_requested() 2988 2989 if inspect.isawaitable(result): 2990 result = await result 2991 2992 if not result: 2993 return None 2994 2995 self._client_keys = load_keypairs(result) 2996 2997 keypair = self._client_keys.pop(0) 2998 2999 if self._choose_signature_alg(keypair): 3000 return keypair 3001 3002 async def password_auth_requested(self): 3003 """Return a password to authenticate with""" 3004 3005 if not self._password_auth and not self._kbdint_password_auth: 3006 return None 3007 3008 if self._password is not None: 3009 result = self._password 3010 self._password = None 3011 else: 3012 result = self._owner.password_auth_requested() 3013 3014 if inspect.isawaitable(result): 3015 result = await result 3016 3017 return result 3018 3019 async def password_change_requested(self, prompt, lang): 3020 """Return a password to authenticate with and what to change it to""" 3021 3022 result = self._owner.password_change_requested(prompt, lang) 3023 3024 if inspect.isawaitable(result): 3025 result = await result 3026 3027 return result 3028 3029 def password_changed(self): 3030 """Report a successful password change""" 3031 3032 self._owner.password_changed() 3033 3034 def password_change_failed(self): 3035 """Report a failed password change""" 3036 3037 self._owner.password_change_failed() 3038 3039 async def kbdint_auth_requested(self): 3040 """Return the list of supported keyboard-interactive auth methods 3041 3042 If keyboard-interactive auth is not supported in the client but 3043 a password was provided when the connection was opened, this 3044 will allow sending the password via keyboard-interactive auth. 3045 3046 """ 3047 3048 if not self._kbdint_auth: 3049 return None 3050 3051 result = self._owner.kbdint_auth_requested() 3052 3053 if inspect.isawaitable(result): 3054 result = await result 3055 3056 if result is NotImplemented: 3057 if self._password is not None and not self._kbdint_password_auth: 3058 self._kbdint_password_auth = True 3059 result = '' 3060 else: 3061 result = None 3062 3063 return result 3064 3065 async def kbdint_challenge_received(self, name, instructions, 3066 lang, prompts): 3067 """Return responses to a keyboard-interactive auth challenge""" 3068 3069 if self._kbdint_password_auth: 3070 if not prompts: 3071 # Silently drop any empty challenges used to print messages 3072 result = [] 3073 elif len(prompts) == 1: 3074 prompt = prompts[0][0].lower() 3075 3076 if 'password' in prompt or 'passcode' in prompt: 3077 password = await self.password_auth_requested() 3078 3079 result = [password] if password is not None else None 3080 else: 3081 result = None 3082 else: 3083 result = None 3084 else: 3085 result = self._owner.kbdint_challenge_received(name, instructions, 3086 lang, prompts) 3087 3088 if inspect.isawaitable(result): 3089 result = await result 3090 3091 return result 3092 3093 def _process_session_open(self, _packet): 3094 """Process an inbound session open request 3095 3096 These requests are disallowed on an SSH client. 3097 3098 """ 3099 3100 # pylint: disable=no-self-use 3101 3102 raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED, 3103 'Session open forbidden on client') 3104 3105 def _process_direct_tcpip_open(self, _packet): 3106 """Process an inbound direct TCP/IP channel open request 3107 3108 These requests are disallowed on an SSH client. 3109 3110 """ 3111 3112 # pylint: disable=no-self-use 3113 3114 raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED, 3115 'Direct TCP/IP open forbidden on client') 3116 3117 def _process_forwarded_tcpip_open(self, packet): 3118 """Process an inbound forwarded TCP/IP channel open request""" 3119 3120 dest_host = packet.get_string() 3121 dest_port = packet.get_uint32() 3122 orig_host = packet.get_string() 3123 orig_port = packet.get_uint32() 3124 packet.check_end() 3125 3126 try: 3127 dest_host = dest_host.decode('utf-8') 3128 orig_host = orig_host.decode('utf-8') 3129 except UnicodeDecodeError: 3130 raise ProtocolError('Invalid forwarded TCP/IP channel ' 3131 'open request') from None 3132 3133 # Some buggy servers send back a port of `0` instead of the actual 3134 # listening port when reporting connections which arrive on a listener 3135 # set up on a dynamic port. This lookup attempts to work around that. 3136 listener = (self._remote_listeners.get((dest_host, dest_port)) or 3137 self._dynamic_remote_listeners.get(dest_host)) 3138 3139 if listener: 3140 chan, session = listener.process_connection(orig_host, orig_port) 3141 3142 self.logger.info('Accepted forwarded TCP connection on %s', 3143 (dest_host, dest_port)) 3144 self.logger.info(' Client address: %s', (orig_host, orig_port)) 3145 3146 return chan, session 3147 else: 3148 raise ChannelOpenError(OPEN_CONNECT_FAILED, 'No such listener') 3149 3150 async def close_client_tcp_listener(self, listen_host, listen_port): 3151 """Close a remote TCP/IP listener""" 3152 3153 await self._make_global_request( 3154 b'cancel-tcpip-forward', String(listen_host), UInt32(listen_port)) 3155 3156 self.logger.info('Closed remote TCP listener on %s', 3157 (listen_host, listen_port)) 3158 3159 listener = self._remote_listeners.get((listen_host, listen_port)) 3160 3161 if listener: 3162 if self._dynamic_remote_listeners.get(listen_host) == listener: 3163 del self._dynamic_remote_listeners[listen_host] 3164 3165 del self._remote_listeners[listen_host, listen_port] 3166 3167 def _process_direct_streamlocal_at_openssh_dot_com_open(self, _packet): 3168 """Process an inbound direct UNIX domain channel open request 3169 3170 These requests are disallowed on an SSH client. 3171 3172 """ 3173 3174 # pylint: disable=no-self-use 3175 3176 raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED, 3177 'Direct UNIX domain socket open ' 3178 'forbidden on client') 3179 3180 def _process_forwarded_streamlocal_at_openssh_dot_com_open(self, packet): 3181 """Process an inbound forwarded UNIX domain channel open request""" 3182 3183 dest_path = packet.get_string() 3184 _ = packet.get_string() # reserved 3185 packet.check_end() 3186 3187 try: 3188 dest_path = dest_path.decode('utf-8') 3189 except UnicodeDecodeError: 3190 raise ProtocolError('Invalid forwarded UNIX domain channel ' 3191 'open request') from None 3192 3193 listener = self._remote_listeners.get(dest_path) 3194 3195 if listener: 3196 chan, session = listener.process_connection() 3197 3198 self.logger.info('Accepted remote UNIX connection on %s', dest_path) 3199 3200 return chan, session 3201 else: 3202 raise ChannelOpenError(OPEN_CONNECT_FAILED, 'No such listener') 3203 3204 async def close_client_unix_listener(self, listen_path): 3205 """Close a remote UNIX domain socket listener""" 3206 3207 await self._make_global_request( 3208 b'cancel-streamlocal-forward@openssh.com', String(listen_path)) 3209 3210 self.logger.info('Closed UNIX listener on %s', listen_path) 3211 3212 if listen_path in self._remote_listeners: 3213 del self._remote_listeners[listen_path] 3214 3215 def _process_x11_open(self, packet): 3216 """Process an inbound X11 channel open request""" 3217 3218 orig_host = packet.get_string() 3219 orig_port = packet.get_uint32() 3220 3221 packet.check_end() 3222 3223 if self._x11_listener: 3224 self.logger.info('Accepted X11 connection') 3225 self.logger.info(' Client address: %s', (orig_host, orig_port)) 3226 3227 chan = self.create_x11_channel() 3228 3229 chan.set_inbound_peer_names(orig_host, orig_port) 3230 3231 return chan, self._x11_listener.forward_connection() 3232 else: 3233 raise ChannelOpenError(OPEN_CONNECT_FAILED, 3234 'X11 forwarding disabled') 3235 3236 def _process_auth_agent_at_openssh_dot_com_open(self, packet): 3237 """Process an inbound auth agent channel open request""" 3238 3239 packet.check_end() 3240 3241 if self._agent_forward_path: 3242 self.logger.info('Accepted SSH agent connection') 3243 3244 return (self.create_unix_channel(), 3245 self.forward_unix_connection(self._agent_forward_path)) 3246 else: 3247 raise ChannelOpenError(OPEN_CONNECT_FAILED, 3248 'Auth agent forwarding disabled') 3249 3250 async def attach_x11_listener(self, chan, display, auth_path, 3251 single_connection): 3252 """Attach a channel to a local X11 display""" 3253 3254 if not display: 3255 display = os.environ.get('DISPLAY') 3256 3257 if not display: 3258 raise ValueError('X11 display not set') 3259 3260 if not self._x11_listener: 3261 self._x11_listener = await create_x11_client_listener( 3262 self._loop, display, auth_path) 3263 3264 return self._x11_listener.attach(display, chan, single_connection) 3265 3266 def detach_x11_listener(self, chan): 3267 """Detach a session from a local X11 listener""" 3268 3269 if self._x11_listener: 3270 if self._x11_listener.detach(chan): 3271 self._x11_listener = None 3272 3273 async def create_session(self, session_factory, command=(), *, 3274 subsystem=(), env=(), send_env=(), 3275 request_pty=(), term_type=(), term_size=(), 3276 term_modes=(), x11_forwarding=(), 3277 x11_display=(), x11_auth_path=(), 3278 x11_single_connection=(), encoding=(), 3279 errors=(), window=(), max_pktsize=()): 3280 """Create an SSH client session 3281 3282 This method is a coroutine which can be called to create an SSH 3283 client session used to execute a command, start a subsystem 3284 such as sftp, or if no command or subsystem is specified run an 3285 interactive shell. Optional arguments allow terminal and 3286 environment information to be provided. 3287 3288 By default, this class expects string data in its send and 3289 receive functions, which it encodes on the SSH connection in 3290 UTF-8 (ISO 10646) format. An optional encoding argument can 3291 be passed in to select a different encoding, or `None` can 3292 be passed in if the application wishes to send and receive 3293 raw bytes. When an encoding is set, an optional errors 3294 argument can be passed in to select what Unicode error 3295 handling strategy to use. 3296 3297 Other optional arguments include the SSH receive window size and 3298 max packet size which default to 2 MB and 32 KB, respectively. 3299 3300 :param session_factory: 3301 A `callable` which returns an :class:`SSHClientSession` object 3302 that will be created to handle activity on this session 3303 :param command: (optional) 3304 The remote command to execute. By default, an interactive 3305 shell is started if no command or subsystem is provided. 3306 :param subsystem: (optional) 3307 The name of a remote subsystem to start up. 3308 :param env: (optional) 3309 The environment variables to set for this session. Keys and 3310 values passed in here will be converted to Unicode strings 3311 encoded as UTF-8 (ISO 10646) for transmission. 3312 3313 .. note:: Many SSH servers restrict which environment 3314 variables a client is allowed to set. The 3315 server's configuration may need to be edited 3316 before environment variables can be 3317 successfully set in the remote environment. 3318 :param send_env: (optional) 3319 A list of environment variable names to pull from 3320 `os.environ` and set for this session. Wildcards patterns 3321 using `'*'` and `'?'` are allowed, and all variables with 3322 matching names will be sent with whatever value is set 3323 in the local environment. If a variable is present in both 3324 env and send_env, the value from env will be used. 3325 :param request_pty: (optional) 3326 Whether or not to request a pseudo-terminal (PTY) for this 3327 session. This defaults to `True`, which means to request a 3328 PTY whenever the `term_type` is set. Other possible values 3329 include `False` to never request a PTY, `'force'` to always 3330 request a PTY even without `term_type` being set, or `'auto'` 3331 to request a TTY when `term_type` is set but only when 3332 starting an interactive shell. 3333 :param term_type: (optional) 3334 The terminal type to set for this session. 3335 :param term_size: (optional) 3336 The terminal width and height in characters and optionally 3337 the width and height in pixels. 3338 :param term_modes: (optional) 3339 POSIX terminal modes to set for this session, where keys are 3340 taken from :ref:`POSIX terminal modes <PTYModes>` with values 3341 defined in section 8 of :rfc:`RFC 4254 <4254#section-8>`. 3342 :param x11_forwarding: (optional) 3343 Whether or not to request X11 forwarding for this session, 3344 defaulting to `False`. If set to `True`, X11 forwarding will 3345 be requested and a failure will raise :exc:`ChannelOpenError`. 3346 It can also be set to `'ignore_failure'` to attempt X11 3347 forwarding but ignore failures. 3348 :param x11_display: (optional) 3349 The display that X11 connections should be forwarded to, 3350 defaulting to the value in the environment variable `DISPLAY`. 3351 :param x11_auth_path: (optional) 3352 The path to the Xauthority file to read X11 authentication 3353 data from, defaulting to the value in the environment variable 3354 `XAUTHORITY` or the file :file:`.Xauthority` in the user's 3355 home directory if that's not set. 3356 :param x11_single_connection: (optional) 3357 Whether or not to limit X11 forwarding to a single connection, 3358 defaulting to `False`. 3359 :param encoding: (optional) 3360 The Unicode encoding to use for data exchanged on this session. 3361 :param errors: (optional) 3362 The error handling strategy to apply on Unicode encode/decode 3363 errors. 3364 :param window: (optional) 3365 The receive window size for this session. 3366 :param max_pktsize: (optional) 3367 The maximum packet size for this session. 3368 :type session_factory: `callable` 3369 :type command: `str` 3370 :type subsystem: `str` 3371 :type env: `dict` 3372 :type send_env: `str` or `list` of `str` 3373 :type request_pty: `bool`, `'force'`, or `'auto'` 3374 :type term_type: `str` 3375 :type term_size: `tuple` of 2 or 4 `int` values 3376 :type term_modes: `dict` 3377 :type x11_forwarding: `bool` or `'ignore_failure'` 3378 :type x11_display: `str` 3379 :type x11_auth_path: `str` 3380 :type x11_single_connection: `bool` 3381 :type encoding: `str` 3382 :type errors: `str` 3383 :type window: `int` 3384 :type max_pktsize: `int` 3385 3386 :returns: an :class:`SSHClientChannel` and :class:`SSHClientSession` 3387 3388 :raises: :exc:`ChannelOpenError` if the session can't be opened 3389 3390 """ 3391 3392 if command == (): 3393 command = self._options.command 3394 3395 if subsystem == (): 3396 subsystem = self._options.subsystem 3397 3398 if env == (): 3399 env = self._options.env 3400 3401 if send_env == (): 3402 send_env = self._options.send_env 3403 3404 if request_pty == (): 3405 request_pty = self._options.request_pty 3406 3407 if term_type == (): 3408 term_type = self._options.term_type 3409 3410 if term_size == (): 3411 term_size = self._options.term_size 3412 3413 if term_modes == (): 3414 term_modes = self._options.term_modes 3415 3416 if x11_forwarding == (): 3417 x11_forwarding = self._options.x11_forwarding 3418 3419 if x11_display == (): 3420 x11_display = self._options.x11_display 3421 3422 if x11_auth_path == (): 3423 x11_auth_path = self._options.x11_auth_path 3424 3425 if x11_single_connection == (): 3426 x11_single_connection = self._options.x11_single_connection 3427 3428 if encoding == (): 3429 encoding = self._options.encoding 3430 3431 if errors == (): 3432 errors = self._options.errors 3433 3434 if window == (): 3435 window = self._options.window 3436 3437 if max_pktsize == (): 3438 max_pktsize = self._options.max_pktsize 3439 3440 new_env = {} 3441 3442 if send_env: 3443 for key in send_env: 3444 pattern = WildcardPattern(key) 3445 new_env.update((key, value) for key, value in os.environ.items() 3446 if pattern.matches(key)) 3447 3448 if env: 3449 try: 3450 if isinstance(env, list): 3451 env = (item.split('=', 2) for item in env) 3452 3453 new_env.update(env) 3454 except ValueError: 3455 raise ValueError('Invalid environment value') from None 3456 3457 if request_pty == 'force': 3458 request_pty = True 3459 elif request_pty == 'auto': 3460 request_pty = bool(term_type and not (command or subsystem)) 3461 elif request_pty: 3462 request_pty = bool(term_type) 3463 3464 chan = SSHClientChannel(self, self._loop, encoding, errors, 3465 window, max_pktsize) 3466 3467 session = await chan.create(session_factory, command, subsystem, 3468 new_env, request_pty, term_type, term_size, 3469 term_modes or {}, x11_forwarding, 3470 x11_display, x11_auth_path, 3471 x11_single_connection, 3472 bool(self._agent_forward_path)) 3473 3474 return chan, session 3475 3476 async def open_session(self, *args, **kwargs): 3477 """Open an SSH client session 3478 3479 This method is a coroutine wrapper around :meth:`create_session` 3480 designed to provide a "high-level" stream interface for creating 3481 an SSH client session. Instead of taking a `session_factory` 3482 argument for constructing an object which will handle activity 3483 on the session via callbacks, it returns an :class:`SSHWriter` 3484 and two :class:`SSHReader` objects representing stdin, stdout, 3485 and stderr which can be used to perform I/O on the session. With 3486 the exception of `session_factory`, all of the arguments to 3487 :meth:`create_session` are supported and have the same meaning. 3488 3489 """ 3490 3491 chan, session = await self.create_session(SSHClientStreamSession, 3492 *args, **kwargs) 3493 3494 return (SSHWriter(session, chan), SSHReader(session, chan), 3495 SSHReader(session, chan, EXTENDED_DATA_STDERR)) 3496 3497 # pylint: disable=redefined-builtin 3498 @async_context_manager 3499 async def create_process(self, *args, bufsize=io.DEFAULT_BUFFER_SIZE, 3500 input=None, stdin=PIPE, stdout=PIPE, stderr=PIPE, 3501 **kwargs): 3502 """Create a process on the remote system 3503 3504 This method is a coroutine wrapper around :meth:`create_session` 3505 which can be used to execute a command, start a subsystem, 3506 or start an interactive shell, optionally redirecting stdin, 3507 stdout, and stderr to and from files or pipes attached to 3508 other local and remote processes. 3509 3510 By default, the stdin, stdout, and stderr arguments default 3511 to the special value `PIPE` which means that they can be 3512 read and written interactively via stream objects which are 3513 members of the :class:`SSHClientProcess` object this method 3514 returns. If other file-like objects are provided as arguments, 3515 input or output will automatically be redirected to them. The 3516 special value `DEVNULL` can be used to provide no input or 3517 discard all output, and the special value `STDOUT` can be 3518 provided as `stderr` to send its output to the same stream 3519 as `stdout`. 3520 3521 In addition to the arguments below, all arguments to 3522 :meth:`create_session` except for `session_factory` are 3523 supported and have the same meaning. 3524 3525 :param bufsize: (optional) 3526 Buffer size to use when feeding data from a file to stdin 3527 :param input: (optional) 3528 Input data to feed to standard input of the remote process. 3529 If specified, this argument takes precedence over stdin. 3530 Data should be a `str` if encoding is set, or `bytes` if not. 3531 :param stdin: (optional) 3532 A filename, file-like object, file descriptor, socket, or 3533 :class:`SSHReader` to feed to standard input of the remote 3534 process, or `DEVNULL` to provide no input. 3535 :param stdout: (optional) 3536 A filename, file-like object, file descriptor, socket, or 3537 :class:`SSHWriter` to feed standard output of the remote 3538 process to, or `DEVNULL` to discard this output. 3539 :param stderr: (optional) 3540 A filename, file-like object, file descriptor, socket, or 3541 :class:`SSHWriter` to feed standard error of the remote 3542 process to, `DEVNULL` to discard this output, or `STDOUT` 3543 to feed standard error to the same place as stdout. 3544 :type bufsize: `int` 3545 :type input: `str` or `bytes` 3546 3547 :returns: :class:`SSHClientProcess` 3548 3549 :raises: :exc:`ChannelOpenError` if the channel can't be opened 3550 3551 """ 3552 3553 chan, process = await self.create_session(SSHClientProcess, 3554 *args, **kwargs) 3555 3556 if input: 3557 chan.write(input) 3558 chan.write_eof() 3559 stdin = None 3560 3561 await process.redirect(stdin, stdout, stderr, bufsize) 3562 3563 return process 3564 3565 async def create_subprocess(self, protocol_factory, *args, input=None, 3566 bufsize=io.DEFAULT_BUFFER_SIZE, encoding=None, 3567 stdin=PIPE, stdout=PIPE, stderr=PIPE, **kwargs): 3568 """Create a subprocess on the remote system 3569 3570 This method is a coroutine wrapper around :meth:`create_session` 3571 which can be used to execute a command, start a subsystem, 3572 or start an interactive shell, optionally redirecting stdin, 3573 stdout, and stderr to and from files or pipes attached to 3574 other local and remote processes similar to :meth:`create_process`. 3575 However, instead of performing interactive I/O using 3576 :class:`SSHReader` and :class:`SSHWriter` objects, the caller 3577 provides a function which returns an object which conforms 3578 to the :class:`asyncio.SubprocessProtocol` and this call 3579 returns that and an :class:`SSHSubprocessTransport` object which 3580 conforms to :class:`asyncio.SubprocessTransport`. 3581 3582 With the exception of the addition of `protocol_factory`, all 3583 of the arguments are the same as :meth:`create_process`. 3584 3585 :param protocol_factory: 3586 A `callable` which returns an :class:`SSHSubprocessProtocol` 3587 object that will be created to handle activity on this 3588 session. 3589 :type protocol_factory: `callable` 3590 3591 :returns: an :class:`SSHSubprocessTransport` and 3592 :class:`SSHSubprocessProtocol` 3593 3594 :raises: :exc:`ChannelOpenError` if the channel can't be opened 3595 3596 """ 3597 3598 def transport_factory(): 3599 """Return a subprocess transport""" 3600 3601 return SSHSubprocessTransport(protocol_factory) 3602 3603 _, transport = await self.create_session( 3604 transport_factory, *args, encoding=encoding, **kwargs) 3605 3606 if input: 3607 stdin_pipe = transport.get_pipe_transport(0) 3608 stdin_pipe.write(input) 3609 stdin_pipe.write_eof() 3610 stdin = None 3611 3612 await transport.redirect(stdin, stdout, stderr, bufsize) 3613 3614 return transport, transport.get_protocol() 3615 # pylint: enable=redefined-builtin 3616 3617 async def run(self, *args, check=False, timeout=None, **kwargs): 3618 """Run a command on the remote system and collect its output 3619 3620 This method is a coroutine wrapper around :meth:`create_process` 3621 which can be used to run a process to completion when no 3622 interactivity is needed. All of the arguments to 3623 :meth:`create_process` can be passed in to provide input or 3624 redirect stdin, stdout, and stderr, but this method waits until 3625 the process exits and returns an :class:`SSHCompletedProcess` 3626 object with the exit status or signal information and the 3627 output to stdout and stderr (if not redirected). 3628 3629 If the check argument is set to `True`, a non-zero exit status 3630 from the remote process will trigger the :exc:`ProcessError` 3631 exception to be raised. 3632 3633 In addition to the argument below, all arguments to 3634 :meth:`create_process` are supported and have the same meaning. 3635 3636 If a timeout is specified and it expires before the process 3637 exits, the :exc:`TimeoutError` exception will be raised. By 3638 default, no timeout is set and this call will wait indefinitely. 3639 3640 :param check: (optional) 3641 Whether or not to raise :exc:`ProcessError` when a non-zero 3642 exit status is returned 3643 :param timeout: 3644 Amount of time in seconds to wait for process to exit or 3645 `None` to wait indefinitely 3646 :type check: `bool` 3647 :type timeout: `int`, `float`, or `None` 3648 3649 :returns: :class:`SSHCompletedProcess` 3650 3651 :raises: | :exc:`ChannelOpenError` if the session can't be opened 3652 | :exc:`ProcessError` if checking non-zero exit status 3653 | :exc:`TimeoutError` if the timeout expires before exit 3654 3655 """ 3656 3657 process = await self.create_process(*args, **kwargs) 3658 3659 return await process.wait(check, timeout) 3660 3661 async def create_connection(self, session_factory, remote_host, remote_port, 3662 orig_host='', orig_port=0, *, encoding=None, 3663 errors='strict', window=_DEFAULT_WINDOW, 3664 max_pktsize=_DEFAULT_MAX_PKTSIZE): 3665 """Create an SSH TCP direct connection 3666 3667 This method is a coroutine which can be called to request that 3668 the server open a new outbound TCP connection to the specified 3669 destination host and port. If the connection is successfully 3670 opened, a new SSH channel will be opened with data being handled 3671 by a :class:`SSHTCPSession` object created by `session_factory`. 3672 3673 Optional arguments include the host and port of the original 3674 client opening the connection when performing TCP port forwarding. 3675 3676 By default, this class expects data to be sent and received as 3677 raw bytes. However, an optional encoding argument can be passed 3678 in to select the encoding to use, allowing the application send 3679 and receive string data. When encoding is set, an optional errors 3680 argument can be passed in to select what Unicode error handling 3681 strategy to use. 3682 3683 Other optional arguments include the SSH receive window size and 3684 max packet size which default to 2 MB and 32 KB, respectively. 3685 3686 :param session_factory: 3687 A `callable` which returns an :class:`SSHClientSession` object 3688 that will be created to handle activity on this session 3689 :param remote_host: 3690 The remote hostname or address to connect to 3691 :param remote_port: 3692 The remote port number to connect to 3693 :param orig_host: (optional) 3694 The hostname or address of the client requesting the connection 3695 :param orig_port: (optional) 3696 The port number of the client requesting the connection 3697 :param encoding: (optional) 3698 The Unicode encoding to use for data exchanged on the connection 3699 :param errors: (optional) 3700 The error handling strategy to apply on encode/decode errors 3701 :param window: (optional) 3702 The receive window size for this session 3703 :param max_pktsize: (optional) 3704 The maximum packet size for this session 3705 :type session_factory: `callable` 3706 :type remote_host: `str` 3707 :type remote_port: `int` 3708 :type orig_host: `str` 3709 :type orig_port: `int` 3710 :type encoding: `str` 3711 :type errors: `str` 3712 :type window: `int` 3713 :type max_pktsize: `int` 3714 3715 :returns: an :class:`SSHTCPChannel` and :class:`SSHTCPSession` 3716 3717 :raises: :exc:`ChannelOpenError` if the connection can't be opened 3718 3719 """ 3720 3721 self.logger.info('Opening direct TCP connection to %s', 3722 (remote_host, remote_port)) 3723 self.logger.info(' Client address: %s', (orig_host, orig_port)) 3724 3725 chan = self.create_tcp_channel(encoding, errors, window, max_pktsize) 3726 3727 session = await chan.connect(session_factory, remote_host, remote_port, 3728 orig_host, orig_port) 3729 3730 return chan, session 3731 3732 async def open_connection(self, *args, **kwargs): 3733 """Open an SSH TCP direct connection 3734 3735 This method is a coroutine wrapper around :meth:`create_connection` 3736 designed to provide a "high-level" stream interface for creating 3737 an SSH TCP direct connection. Instead of taking a 3738 `session_factory` argument for constructing an object which will 3739 handle activity on the session via callbacks, it returns 3740 :class:`SSHReader` and :class:`SSHWriter` objects which can be 3741 used to perform I/O on the connection. 3742 3743 With the exception of `session_factory`, all of the arguments 3744 to :meth:`create_connection` are supported and have the same 3745 meaning here. 3746 3747 :returns: an :class:`SSHReader` and :class:`SSHWriter` 3748 3749 :raises: :exc:`ChannelOpenError` if the connection can't be opened 3750 3751 """ 3752 3753 chan, session = await self.create_connection(SSHTCPStreamSession, 3754 *args, **kwargs) 3755 3756 return SSHReader(session, chan), SSHWriter(session, chan) 3757 3758 @async_context_manager 3759 async def create_server(self, session_factory, listen_host, listen_port, *, 3760 encoding=None, errors='strict', 3761 window=_DEFAULT_WINDOW, 3762 max_pktsize=_DEFAULT_MAX_PKTSIZE): 3763 """Create a remote SSH TCP listener 3764 3765 This method is a coroutine which can be called to request that 3766 the server listen on the specified remote address and port for 3767 incoming TCP connections. If the request is successful, the 3768 return value is an :class:`SSHListener` object which can be 3769 used later to shut down the listener. If the request fails, 3770 `None` is returned. 3771 3772 :param session_factory: 3773 A `callable` or coroutine which takes arguments of the 3774 original host and port of the client and decides whether 3775 to accept the connection or not, either returning an 3776 :class:`SSHTCPSession` object used to handle activity on 3777 that connection or raising :exc:`ChannelOpenError` to 3778 indicate that the connection should not be accepted 3779 :param listen_host: 3780 The hostname or address on the remote host to listen on 3781 :param listen_port: 3782 The port number on the remote host to listen on 3783 :param encoding: (optional) 3784 The Unicode encoding to use for data exchanged on the connection 3785 :param errors: (optional) 3786 The error handling strategy to apply on encode/decode errors 3787 :param window: (optional) 3788 The receive window size for this session 3789 :param max_pktsize: (optional) 3790 The maximum packet size for this session 3791 :type session_factory: `callable` or coroutine 3792 :type listen_host: `str` 3793 :type listen_port: `int` 3794 :type encoding: `str` 3795 :type errors: `str` 3796 :type window: `int` 3797 :type max_pktsize: `int` 3798 3799 :returns: :class:`SSHListener` 3800 3801 :raises: :class:`ChannelListenError` if the listener can't be opened 3802 3803 """ 3804 3805 listen_host = listen_host.lower() 3806 3807 self.logger.info('Creating remote TCP listener on %s', 3808 (listen_host, listen_port)) 3809 3810 pkttype, packet = await self._make_global_request( 3811 b'tcpip-forward', String(listen_host), UInt32(listen_port)) 3812 3813 if pkttype == MSG_REQUEST_SUCCESS: 3814 if listen_port == 0: 3815 listen_port = packet.get_uint32() 3816 dynamic = True 3817 else: 3818 # OpenSSH 6.8 introduced a bug which causes the reply 3819 # to contain an extra uint32 value of 0 when non-dynamic 3820 # ports are requested, causing the check_end() call below 3821 # to fail. This check works around this problem. 3822 if len(packet.get_remaining_payload()) == 4: # pragma: no cover 3823 packet.get_uint32() 3824 3825 dynamic = False 3826 3827 packet.check_end() 3828 3829 listener = SSHTCPClientListener(self, session_factory, 3830 listen_host, listen_port, encoding, 3831 errors, window, max_pktsize) 3832 3833 if dynamic: 3834 self.logger.debug1('Assigning dynamic port %d', listen_port) 3835 3836 self._dynamic_remote_listeners[listen_host] = listener 3837 3838 self._remote_listeners[listen_host, listen_port] = listener 3839 return listener 3840 else: 3841 packet.check_end() 3842 self.logger.debug1('Failed to create remote TCP listener') 3843 raise ChannelListenError('Failed to create remote TCP listener') 3844 3845 @async_context_manager 3846 async def start_server(self, handler_factory, *args, **kwargs): 3847 """Start a remote SSH TCP listener 3848 3849 This method is a coroutine wrapper around :meth:`create_server` 3850 designed to provide a "high-level" stream interface for creating 3851 remote SSH TCP listeners. Instead of taking a `session_factory` 3852 argument for constructing an object which will handle activity on 3853 the session via callbacks, it takes a `handler_factory` which 3854 returns a `callable` or coroutine that will be passed 3855 :class:`SSHReader` and :class:`SSHWriter` objects which can be 3856 used to perform I/O on each new connection which arrives. Like 3857 :meth:`create_server`, `handler_factory` can also raise 3858 :exc:`ChannelOpenError` if the connection should not be accepted. 3859 3860 With the exception of `handler_factory` replacing 3861 `session_factory`, all of the arguments to :meth:`create_server` 3862 are supported and have the same meaning here. 3863 3864 :param handler_factory: 3865 A `callable` or coroutine which takes arguments of the 3866 original host and port of the client and decides whether to 3867 accept the connection or not, either returning a callback 3868 or coroutine used to handle activity on that connection 3869 or raising :exc:`ChannelOpenError` to indicate that the 3870 connection should not be accepted 3871 :type handler_factory: `callable` or coroutine 3872 3873 :returns: :class:`SSHListener` 3874 3875 :raises: :class:`ChannelListenError` if the listener can't be opened 3876 3877 """ 3878 3879 def session_factory(orig_host, orig_port): 3880 """Return a TCP stream session handler""" 3881 3882 return SSHTCPStreamSession(handler_factory(orig_host, orig_port)) 3883 3884 return await self.create_server(session_factory, *args, **kwargs) 3885 3886 async def create_unix_connection(self, session_factory, remote_path, *, 3887 encoding=None, errors='strict', 3888 window=_DEFAULT_WINDOW, 3889 max_pktsize=_DEFAULT_MAX_PKTSIZE): 3890 """Create an SSH UNIX domain socket direct connection 3891 3892 This method is a coroutine which can be called to request that 3893 the server open a new outbound UNIX domain socket connection to 3894 the specified destination path. If the connection is successfully 3895 opened, a new SSH channel will be opened with data being handled 3896 by a :class:`SSHUNIXSession` object created by `session_factory`. 3897 3898 By default, this class expects data to be sent and received as 3899 raw bytes. However, an optional encoding argument can be passed 3900 in to select the encoding to use, allowing the application to 3901 send and receive string data. When encoding is set, an optional 3902 errors argument can be passed in to select what Unicode error 3903 handling strategy to use. 3904 3905 Other optional arguments include the SSH receive window size and 3906 max packet size which default to 2 MB and 32 KB, respectively. 3907 3908 :param session_factory: 3909 A `callable` which returns an :class:`SSHClientSession` object 3910 that will be created to handle activity on this session 3911 :param remote_path: 3912 The remote path to connect to 3913 :param encoding: (optional) 3914 The Unicode encoding to use for data exchanged on the connection 3915 :param errors: (optional) 3916 The error handling strategy to apply on encode/decode errors 3917 :param window: (optional) 3918 The receive window size for this session 3919 :param max_pktsize: (optional) 3920 The maximum packet size for this session 3921 :type session_factory: `callable` 3922 :type remote_path: `str` 3923 :type encoding: `str` 3924 :type errors: `str` 3925 :type window: `int` 3926 :type max_pktsize: `int` 3927 3928 :returns: an :class:`SSHUNIXChannel` and :class:`SSHUNIXSession` 3929 3930 :raises: :exc:`ChannelOpenError` if the connection can't be opened 3931 3932 """ 3933 3934 self.logger.info('Opening direct UNIX connection to %s', remote_path) 3935 3936 chan = self.create_unix_channel(encoding, errors, window, max_pktsize) 3937 3938 session = await chan.connect(session_factory, remote_path) 3939 3940 return chan, session 3941 3942 async def open_unix_connection(self, *args, **kwargs): 3943 """Open an SSH UNIX domain socket direct connection 3944 3945 This method is a coroutine wrapper around 3946 :meth:`create_unix_connection` designed to provide a "high-level" 3947 stream interface for creating an SSH UNIX domain socket direct 3948 connection. Instead of taking a `session_factory` argument for 3949 constructing an object which will handle activity on the session 3950 via callbacks, it returns :class:`SSHReader` and :class:`SSHWriter` 3951 objects which can be used to perform I/O on the connection. 3952 3953 With the exception of `session_factory`, all of the arguments 3954 to :meth:`create_unix_connection` are supported and have the same 3955 meaning here. 3956 3957 :returns: an :class:`SSHReader` and :class:`SSHWriter` 3958 3959 :raises: :exc:`ChannelOpenError` if the connection can't be opened 3960 3961 """ 3962 3963 chan, session = \ 3964 await self.create_unix_connection(SSHUNIXStreamSession, 3965 *args, **kwargs) 3966 3967 return SSHReader(session, chan), SSHWriter(session, chan) 3968 3969 @async_context_manager 3970 async def create_unix_server(self, session_factory, listen_path, *, 3971 encoding=None, errors='strict', 3972 window=_DEFAULT_WINDOW, 3973 max_pktsize=_DEFAULT_MAX_PKTSIZE): 3974 """Create a remote SSH UNIX domain socket listener 3975 3976 This method is a coroutine which can be called to request that 3977 the server listen on the specified remote path for incoming UNIX 3978 domain socket connections. If the request is successful, the 3979 return value is an :class:`SSHListener` object which can be 3980 used later to shut down the listener. If the request fails, 3981 `None` is returned. 3982 3983 :param session_factory: 3984 A `callable` or coroutine which takes arguments of the 3985 original host and port of the client and decides whether 3986 to accept the connection or not, either returning an 3987 :class:`SSHUNIXSession` object used to handle activity 3988 on that connection or raising :exc:`ChannelOpenError` 3989 to indicate that the connection should not be accepted 3990 :param listen_path: 3991 The path on the remote host to listen on 3992 :param encoding: (optional) 3993 The Unicode encoding to use for data exchanged on the connection 3994 :param errors: (optional) 3995 The error handling strategy to apply on encode/decode errors 3996 :param window: (optional) 3997 The receive window size for this session 3998 :param max_pktsize: (optional) 3999 The maximum packet size for this session 4000 :type session_factory: `callable` or coroutine 4001 :type listen_path: `str` 4002 :type encoding: `str` 4003 :type errors: `str` 4004 :type window: `int` 4005 :type max_pktsize: `int` 4006 4007 :returns: :class:`SSHListener` 4008 4009 :raises: :class:`ChannelListenError` if the listener can't be opened 4010 4011 """ 4012 4013 self.logger.info('Creating remote UNIX listener on %s', listen_path) 4014 4015 pkttype, packet = await self._make_global_request( 4016 b'streamlocal-forward@openssh.com', String(listen_path)) 4017 4018 packet.check_end() 4019 4020 if pkttype == MSG_REQUEST_SUCCESS: 4021 listener = SSHUNIXClientListener(self, session_factory, 4022 listen_path, encoding, errors, 4023 window, max_pktsize) 4024 4025 self._remote_listeners[listen_path] = listener 4026 return listener 4027 else: 4028 self.logger.debug1('Failed to create remote UNIX listener') 4029 raise ChannelListenError('Failed to create remote UNIX listener') 4030 4031 @async_context_manager 4032 async def start_unix_server(self, handler_factory, *args, **kwargs): 4033 """Start a remote SSH UNIX domain socket listener 4034 4035 This method is a coroutine wrapper around :meth:`create_unix_server` 4036 designed to provide a "high-level" stream interface for creating 4037 remote SSH UNIX domain socket listeners. Instead of taking a 4038 `session_factory` argument for constructing an object which 4039 will handle activity on the session via callbacks, it takes a 4040 `handler_factory` which returns a `callable` or coroutine that 4041 will be passed :class:`SSHReader` and :class:`SSHWriter` objects 4042 which can be used to perform I/O on each new connection which 4043 arrives. Like :meth:`create_unix_server`, `handler_factory` 4044 can also raise :exc:`ChannelOpenError` if the connection should 4045 not be accepted. 4046 4047 With the exception of `handler_factory` replacing 4048 `session_factory`, all of the arguments to 4049 :meth:`create_unix_server` are supported and have the same 4050 meaning here. 4051 4052 :param handler_factory: 4053 A `callable` or coroutine which takes arguments of the 4054 original host and port of the client and decides whether to 4055 accept the connection or not, either returning a callback 4056 or coroutine used to handle activity on that connection 4057 or raising :exc:`ChannelOpenError` to indicate that the 4058 connection should not be accepted 4059 :type handler_factory: `callable` or coroutine 4060 4061 :returns: :class:`SSHListener` 4062 4063 :raises: :class:`ChannelListenError` if the listener can't be opened 4064 4065 """ 4066 4067 def session_factory(): 4068 """Return a UNIX domain socket stream session handler""" 4069 4070 return SSHUNIXStreamSession(handler_factory()) 4071 4072 return await self.create_unix_server(session_factory, *args, **kwargs) 4073 4074 async def create_ssh_connection(self, client_factory, host, 4075 port=(), **kwargs): 4076 """Create a tunneled SSH client connection 4077 4078 This method is a coroutine which can be called to open an 4079 SSH client connection to the requested host and port tunneled 4080 inside this already established connection. It takes all the 4081 same arguments as :func:`create_connection` but requests 4082 that the upstream SSH server open the connection rather than 4083 connecting directly. 4084 4085 """ 4086 4087 return (await create_connection(client_factory, host, port, 4088 tunnel=self, **kwargs)) 4089 4090 @async_context_manager 4091 async def connect_ssh(self, host, port=(), **kwargs): 4092 """Make a tunneled SSH client connection 4093 4094 This method is a coroutine which can be called to open an 4095 SSH client connection to the requested host and port tunneled 4096 inside this already established connection. It takes all the 4097 same arguments as :func:`connect` but requests that the upstream 4098 SSH server open the connection rather than connecting directly. 4099 4100 """ 4101 4102 return await connect(host, port, tunnel=self, **kwargs) 4103 4104 @async_context_manager 4105 async def connect_reverse_ssh(self, host, port=(), **kwargs): 4106 """Make a tunneled reverse direction SSH connection 4107 4108 This method is a coroutine which can be called to open an 4109 SSH client connection to the requested host and port tunneled 4110 inside this already established connection. It takes all the 4111 same arguments as :func:`connect` but requests that the upstream 4112 SSH server open the connection rather than connecting directly. 4113 4114 """ 4115 4116 return await connect_reverse(host, port, tunnel=self, **kwargs) 4117 4118 @async_context_manager 4119 async def listen_ssh(self, host='', port=(), **kwargs): 4120 """Create a tunneled SSH listener 4121 4122 This method is a coroutine which can be called to open a remote 4123 SSH listener on the requested host and port tunneled inside this 4124 already established connection. It takes all the same arguments as 4125 :func:`listen` but requests that the upstream SSH server open the 4126 listener rather than listening directly via TCP/IP. 4127 4128 """ 4129 4130 return await listen(host, port, tunnel=self, **kwargs) 4131 4132 @async_context_manager 4133 async def listen_reverse_ssh(self, host='', port=(), **kwargs): 4134 """Create a tunneled reverse direction SSH listener 4135 4136 This method is a coroutine which can be called to open a remote 4137 SSH listener on the requested host and port tunneled inside this 4138 already established connection. It takes all the same arguments as 4139 :func:`listen_reverse` but requests that the upstream SSH server 4140 open the listener rather than listening directly via TCP/IP. 4141 4142 """ 4143 4144 return await listen_reverse(host, port, tunnel=self, **kwargs) 4145 4146 @async_context_manager 4147 async def forward_remote_port(self, listen_host, listen_port, 4148 dest_host, dest_port): 4149 """Set up remote port forwarding 4150 4151 This method is a coroutine which attempts to set up port 4152 forwarding from a remote listening port to a local host and port 4153 via the SSH connection. If the request is successful, the 4154 return value is an :class:`SSHListener` object which can be 4155 used later to shut down the port forwarding. If the request 4156 fails, `None` is returned. 4157 4158 :param listen_host: 4159 The hostname or address on the remote host to listen on 4160 :param listen_port: 4161 The port number on the remote host to listen on 4162 :param dest_host: 4163 The hostname or address to forward connections to 4164 :param dest_port: 4165 The port number to forward connections to 4166 :type listen_host: `str` 4167 :type listen_port: `int` 4168 :type dest_host: `str` 4169 :type dest_port: `int` 4170 4171 :returns: :class:`SSHListener` 4172 4173 :raises: :class:`ChannelListenError` if the listener can't be opened 4174 4175 """ 4176 4177 def session_factory(_orig_host, _orig_port): 4178 """Return an SSHTCPSession used to do remote port forwarding""" 4179 4180 return self.forward_connection(dest_host, dest_port) 4181 4182 self.logger.info('Creating remote TCP forwarder from %s to %s', 4183 (listen_host, listen_port), (dest_host, dest_port)) 4184 4185 return await self.create_server(session_factory, listen_host, 4186 listen_port) 4187 4188 @async_context_manager 4189 async def forward_remote_path(self, listen_path, dest_path): 4190 """Set up remote UNIX domain socket forwarding 4191 4192 This method is a coroutine which attempts to set up UNIX domain 4193 socket forwarding from a remote listening path to a local path 4194 via the SSH connection. If the request is successful, the 4195 return value is an :class:`SSHListener` object which can be 4196 used later to shut down the port forwarding. If the request 4197 fails, `None` is returned. 4198 4199 :param listen_path: 4200 The path on the remote host to listen on 4201 :param dest_path: 4202 The path on the local host to forward connections to 4203 :type listen_path: `str` 4204 :type dest_path: `str` 4205 4206 :returns: :class:`SSHListener` 4207 4208 :raises: :class:`ChannelListenError` if the listener can't be opened 4209 4210 """ 4211 4212 def session_factory(): 4213 """Return an SSHUNIXSession used to do remote path forwarding""" 4214 4215 return self.forward_unix_connection(dest_path) 4216 4217 self.logger.info('Creating remote UNIX forwarder from %s to %s', 4218 listen_path, dest_path) 4219 4220 return await self.create_unix_server(session_factory, listen_path) 4221 4222 @async_context_manager 4223 async def forward_socks(self, listen_host, listen_port): 4224 """Set up local port forwarding via SOCKS 4225 4226 This method is a coroutine which attempts to set up dynamic 4227 port forwarding via SOCKS on the specified local host and 4228 port. Each SOCKS request contains the destination host and 4229 port to connect to and triggers a request to tunnel traffic 4230 to the requested host and port via the SSH connection. 4231 4232 If the request is successful, the return value is an 4233 :class:`SSHListener` object which can be used later to shut 4234 down the port forwarding. 4235 4236 :param listen_host: 4237 The hostname or address on the local host to listen on 4238 :param listen_port: 4239 The port number on the local host to listen on 4240 :type listen_host: `str` 4241 :type listen_port: `int` 4242 4243 :returns: :class:`SSHListener` 4244 4245 :raises: :exc:`OSError` if the listener can't be opened 4246 4247 """ 4248 4249 async def tunnel_socks(session_factory, dest_host, dest_port, 4250 orig_host, orig_port): 4251 """Forward a local SOCKS connection over SSH""" 4252 4253 return await self.create_connection(session_factory, 4254 dest_host, dest_port, 4255 orig_host, orig_port) 4256 4257 self.logger.info('Creating local SOCKS forwarder on %s', 4258 (listen_host, listen_port)) 4259 4260 try: 4261 listener = await create_socks_listener(self, self._loop, 4262 tunnel_socks, 4263 listen_host, listen_port) 4264 except OSError as exc: 4265 self.logger.debug1('Failed to create local SOCKS listener: %s', exc) 4266 raise 4267 4268 if listen_port == 0: 4269 listen_port = listener.get_port() 4270 4271 self._local_listeners[listen_host, listen_port] = listener 4272 4273 return listener 4274 4275 @async_context_manager 4276 async def start_sftp_client(self, env=(), send_env=(), 4277 path_encoding='utf-8', path_errors='strict'): 4278 """Start an SFTP client 4279 4280 This method is a coroutine which attempts to start a secure 4281 file transfer session. If it succeeds, it returns an 4282 :class:`SFTPClient` object which can be used to copy and 4283 access files on the remote host. 4284 4285 An optional Unicode encoding can be specified for sending and 4286 receiving pathnames, defaulting to UTF-8 with strict error 4287 checking. If an encoding of `None` is specified, pathnames 4288 will be left as bytes rather than being converted to & from 4289 strings. 4290 4291 :param env: (optional) 4292 The environment variables to set for this SFTP session. Keys 4293 and values passed in here will be converted to Unicode 4294 strings encoded as UTF-8 (ISO 10646) for transmission. 4295 4296 .. note:: Many SSH servers restrict which environment 4297 variables a client is allowed to set. The 4298 server's configuration may need to be edited 4299 before environment variables can be 4300 successfully set in the remote environment. 4301 :param send_env: (optional) 4302 A list of environment variable names to pull from 4303 `os.environ` and set for this SFTP session. Wildcards 4304 patterns using `'*'` and `'?'` are allowed, and all variables 4305 with matching names will be sent with whatever value is set 4306 in the local environment. If a variable is present in both 4307 env and send_env, the value from env will be used. 4308 :param path_encoding: 4309 The Unicode encoding to apply when sending and receiving 4310 remote pathnames 4311 :param path_errors: 4312 The error handling strategy to apply on encode/decode errors 4313 :type env: `dict` 4314 :type send_env: `list` of `str` 4315 :type path_encoding: `str` 4316 :type path_errors: `str` 4317 4318 :returns: :class:`SFTPClient` 4319 4320 :raises: :exc:`SFTPError` if the session can't be opened 4321 4322 """ 4323 4324 writer, reader, _ = await self.open_session(subsystem='sftp', 4325 env=env, send_env=send_env, 4326 encoding=None) 4327 4328 return await start_sftp_client(self, self._loop, reader, writer, 4329 path_encoding, path_errors) 4330 4331 4332class SSHServerConnection(SSHConnection): 4333 """SSH server connection 4334 4335 This class represents an SSH server connection. 4336 4337 During authentication, :meth:`send_auth_banner` can be called to 4338 send an authentication banner to the client. 4339 4340 Once authenticated, :class:`SSHServer` objects wishing to create 4341 session objects with non-default channel properties can call 4342 :meth:`create_server_channel` from their :meth:`session_requested() 4343 <SSHServer.session_requested>` method and return a tuple of 4344 the :class:`SSHServerChannel` object returned from that and either 4345 an :class:`SSHServerSession` object or a coroutine which returns 4346 an :class:`SSHServerSession`. 4347 4348 Similarly, :class:`SSHServer` objects wishing to create TCP 4349 connection objects with non-default channel properties can call 4350 :meth:`create_tcp_channel` from their :meth:`connection_requested() 4351 <SSHServer.connection_requested>` method and return a tuple of 4352 the :class:`SSHTCPChannel` object returned from that and either 4353 an :class:`SSHTCPSession` object or a coroutine which returns an 4354 :class:`SSHTCPSession`. 4355 4356 :class:`SSHServer` objects wishing to create UNIX domain socket 4357 connection objects with non-default channel properties can call 4358 :meth:`create_unix_channel` from the :meth:`unix_connection_requested() 4359 <SSHServer.unix_connection_requested>` method and return a tuple of 4360 the :class:`SSHUNIXChannel` object returned from that and either 4361 an :class:`SSHUNIXSession` object or a coroutine which returns an 4362 :class:`SSHUNIXSession`. 4363 4364 """ 4365 4366 def __init__(self, loop, options, acceptor=None, 4367 error_handler=None, wait=None): 4368 super().__init__(loop, options, acceptor, error_handler, 4369 wait, server=True) 4370 4371 self._options = options 4372 4373 self._server_host_keys = options.server_host_keys 4374 self._server_host_key_algs = list(options.server_host_keys.keys()) 4375 self._known_client_hosts = options.known_client_hosts 4376 self._trust_client_host = options.trust_client_host 4377 self._client_keys = options.authorized_client_keys 4378 self._allow_pty = options.allow_pty 4379 self._line_editor = options.line_editor 4380 self._line_history = options.line_history 4381 self._max_line_length = options.max_line_length 4382 self._rdns_lookup = options.rdns_lookup 4383 self._x11_forwarding = options.x11_forwarding 4384 self._x11_auth_path = options.x11_auth_path 4385 self._agent_forwarding = options.agent_forwarding 4386 self._process_factory = options.process_factory 4387 self._session_factory = options.session_factory 4388 self._encoding = options.encoding 4389 self._errors = options.errors 4390 self._sftp_factory = options.sftp_factory 4391 self._allow_scp = options.allow_scp 4392 self._window = options.window 4393 self._max_pktsize = options.max_pktsize 4394 4395 if options.gss_host: 4396 try: 4397 self._gss = GSSServer(options.gss_host) 4398 self._gss_kex = options.gss_kex 4399 self._gss_auth = options.gss_auth 4400 self._gss_mic_auth = self._gss_auth 4401 except GSSError: 4402 pass 4403 4404 self._server_host_key = None 4405 self._key_options = {} 4406 self._cert_options = None 4407 self._kbdint_password_auth = False 4408 4409 self._agent_listener = None 4410 4411 def _cleanup(self, exc): 4412 """Clean up this server connection""" 4413 4414 if self._agent_listener: 4415 self._agent_listener.close() 4416 self._agent_listener = None 4417 4418 super()._cleanup(exc) 4419 4420 def _connection_made(self): 4421 """Handle the opening of a new connection""" 4422 4423 self.logger.info('Accepted SSH client connection') 4424 4425 if self._options.proxy_command: 4426 proxy_command = ' '.join(shlex.quote(arg) for arg in 4427 self._options.proxy_command) 4428 self.logger.info(' Proxy command: %s', proxy_command) 4429 else: 4430 self.logger.info(' Local address: %s', 4431 (self._local_addr, self._local_port)) 4432 self.logger.info(' Peer address: %s', 4433 (self._peer_addr, self._peer_port)) 4434 4435 async def _reload_config(self): 4436 """Re-evaluate config with updated match options""" 4437 4438 if self._rdns_lookup: 4439 self._peer_host, _ = await self._loop.getnameinfo( 4440 (self._peer_addr, self._peer_port), socket.NI_NUMERICSERV) 4441 4442 options = SSHServerConnectionOptions( 4443 options=self._options, reload=True, 4444 accept_addr=self._local_addr, accept_port=self._local_port, 4445 username=self._username, client_host=self._peer_host, 4446 client_addr=self._peer_addr) 4447 4448 self._options = options 4449 4450 self._host_based_auth = options.host_based_auth 4451 self._public_key_auth = options.public_key_auth 4452 self._kbdint_auth = options.kbdint_auth 4453 self._password_auth = options.password_auth 4454 4455 self._client_keys = options.authorized_client_keys 4456 self._allow_pty = options.allow_pty 4457 self._x11_forwarding = options.x11_forwarding 4458 self._agent_forwarding = options.agent_forwarding 4459 4460 self._rekey_bytes = options.rekey_bytes 4461 self._rekey_seconds = options.rekey_seconds 4462 4463 self._keepalive_count_max = options.keepalive_count_max 4464 self._keepalive_interval = options.keepalive_interval 4465 4466 def _choose_server_host_key(self, peer_host_key_algs): 4467 """Choose the server host key to use 4468 4469 Given a list of host key algorithms supported by the client, 4470 select the first compatible server host key we have and return 4471 whether or not we were able to find a match. 4472 4473 """ 4474 4475 for alg in peer_host_key_algs: 4476 keypair = self._server_host_keys.get(alg) 4477 if keypair: 4478 if alg != keypair.algorithm: 4479 keypair.set_sig_algorithm(alg) 4480 4481 self._server_host_key = keypair 4482 return True 4483 4484 return False 4485 4486 def get_server_host_key(self): 4487 """Return the chosen server host key 4488 4489 This method returns a keypair object containing the 4490 chosen server host key and a corresponding public key 4491 or certificate. 4492 4493 """ 4494 4495 return self._server_host_key 4496 4497 def gss_kex_auth_supported(self): 4498 """Return whether GSS key exchange authentication is supported""" 4499 4500 return self._gss_kex_auth and self._gss.complete 4501 4502 def gss_mic_auth_supported(self): 4503 """Return whether GSS MIC authentication is supported""" 4504 4505 return self._gss_mic_auth 4506 4507 async def validate_gss_principal(self, username, user_principal, 4508 host_principal): 4509 """Validate the GSS principal name for the specified user 4510 4511 Return whether the user principal acquired during GSS 4512 authentication is valid for the specified user. 4513 4514 """ 4515 4516 result = self._owner.validate_gss_principal(username, user_principal, 4517 host_principal) 4518 4519 if inspect.isawaitable(result): 4520 result = await result 4521 4522 return result 4523 4524 def host_based_auth_supported(self): 4525 """Return whether or not host based authentication is supported""" 4526 4527 return (self._host_based_auth and 4528 (bool(self._known_client_hosts) or 4529 self._owner.host_based_auth_supported())) 4530 4531 async def validate_host_based_auth(self, username, key_data, client_host, 4532 client_username, msg, signature): 4533 """Validate host based authentication for the specified host and user""" 4534 4535 # Remove a trailing '.' from the client host if present 4536 if client_host[-1:] == '.': 4537 client_host = client_host[:-1] 4538 4539 if self._trust_client_host: 4540 resolved_host = client_host 4541 else: 4542 resolved_host, _ = await self._loop.getnameinfo( 4543 self.get_extra_info('peername'), socket.NI_NUMERICSERV) 4544 4545 if resolved_host != client_host: 4546 self.logger.info('Client host mismatch: received %s, ' 4547 'resolved %s', client_host, resolved_host) 4548 4549 if self._known_client_hosts: 4550 self._match_known_hosts(self._known_client_hosts, resolved_host, 4551 self._peer_addr, None) 4552 4553 try: 4554 key = self._validate_host_key(resolved_host, self._peer_addr, 4555 self._peer_port, key_data) 4556 except ValueError as exc: 4557 self.logger.debug1('Invalid host key: %s', exc) 4558 return False 4559 4560 if not key.verify(String(self._session_id) + msg, signature): 4561 self.logger.debug1('Invalid host-based auth signature') 4562 return False 4563 4564 result = self._owner.validate_host_based_user(username, client_host, 4565 client_username) 4566 4567 if inspect.isawaitable(result): 4568 result = await result 4569 4570 return result 4571 4572 async def _validate_openssh_certificate(self, username, cert): 4573 """Validate an OpenSSH client certificate for the specified user""" 4574 4575 options = None 4576 4577 if self._client_keys: 4578 options = self._client_keys.validate(cert.signing_key, 4579 self._peer_host, 4580 self._peer_addr, 4581 cert.principals, ca=True) 4582 4583 if options is None: 4584 result = self._owner.validate_ca_key(username, cert.signing_key) 4585 4586 if inspect.isawaitable(result): 4587 result = await result 4588 4589 if not result: 4590 return None 4591 4592 options = {} 4593 4594 self._key_options = options 4595 4596 if self.get_key_option('principals'): 4597 username = None 4598 4599 try: 4600 cert.validate(CERT_TYPE_USER, username) 4601 except ValueError: 4602 return None 4603 4604 allowed_addresses = cert.options.get('source-address') 4605 if allowed_addresses: 4606 ip = ip_address(self._peer_addr) 4607 if not any(ip in network for network in allowed_addresses): 4608 return None 4609 4610 self._cert_options = cert.options 4611 4612 cert.key.set_touch_required( 4613 not (self.get_key_option('no-touch-required', False) and 4614 self.get_certificate_option('no-touch-required', False))) 4615 4616 return cert.key 4617 4618 async def _validate_x509_certificate_chain(self, username, cert): 4619 """Validate an X.509 client certificate for the specified user""" 4620 4621 if not self._client_keys: 4622 return None 4623 4624 options, trusted_cert = \ 4625 self._client_keys.validate_x509(cert, self._peer_host, 4626 self._peer_addr) 4627 4628 if options is None: 4629 return None 4630 4631 self._key_options = options 4632 4633 if self.get_key_option('principals'): 4634 username = None 4635 4636 if trusted_cert: 4637 trusted_certs = self._x509_trusted_certs + [trusted_cert] 4638 else: 4639 trusted_certs = self._x509_trusted_certs 4640 4641 try: 4642 cert.validate_chain(trusted_certs, self._x509_trusted_cert_paths, 4643 None, self._x509_purposes, 4644 user_principal=username) 4645 except ValueError: 4646 return None 4647 4648 return cert.key 4649 4650 async def _validate_client_certificate(self, username, key_data): 4651 """Validate a client certificate for the specified user""" 4652 4653 try: 4654 cert = decode_ssh_certificate(key_data) 4655 except KeyImportError: 4656 return None 4657 4658 if cert.is_x509_chain: 4659 return await self._validate_x509_certificate_chain(username, cert) 4660 else: 4661 return await self._validate_openssh_certificate(username, cert) 4662 4663 async def _validate_client_public_key(self, username, key_data): 4664 """Validate a client public key for the specified user""" 4665 4666 try: 4667 key = decode_ssh_public_key(key_data) 4668 except KeyImportError: 4669 return None 4670 4671 options = None 4672 4673 if self._client_keys: 4674 options = self._client_keys.validate(key, self._peer_host, 4675 self._peer_addr) 4676 4677 if options is None: 4678 result = self._owner.validate_public_key(username, key) 4679 4680 if inspect.isawaitable(result): 4681 result = await result 4682 4683 if not result: 4684 return None 4685 4686 options = {} 4687 4688 self._key_options = options 4689 4690 key.set_touch_required( 4691 not self.get_key_option('no-touch-required', False)) 4692 4693 return key 4694 4695 def public_key_auth_supported(self): 4696 """Return whether or not public key authentication is supported""" 4697 4698 return (self._public_key_auth and 4699 (bool(self._client_keys) or 4700 self._owner.public_key_auth_supported())) 4701 4702 async def validate_public_key(self, username, key_data, msg, signature): 4703 """Validate the public key or certificate for the specified user 4704 4705 This method validates that the public key or certificate provided 4706 is allowed for the specified user. If msg and signature are 4707 provided, the key is used to also validate the message signature. 4708 It returns `True` when the key is allowed and the signature (if 4709 present) is valid. Otherwise, it returns `False`. 4710 4711 """ 4712 4713 key = ((await self._validate_client_certificate(username, key_data)) or 4714 (await self._validate_client_public_key(username, key_data))) 4715 4716 if key is None: 4717 return False 4718 elif msg: 4719 return key.verify(String(self._session_id) + msg, signature) 4720 else: 4721 return True 4722 4723 def password_auth_supported(self): 4724 """Return whether or not password authentication is supported""" 4725 4726 return self._password_auth and self._owner.password_auth_supported() 4727 4728 async def validate_password(self, username, password): 4729 """Return whether password is valid for this user""" 4730 4731 result = self._owner.validate_password(username, password) 4732 4733 if inspect.isawaitable(result): 4734 result = await result 4735 4736 return result 4737 4738 async def change_password(self, username, old_password, new_password): 4739 """Handle a password change request for a user""" 4740 4741 result = self._owner.change_password(username, old_password, 4742 new_password) 4743 4744 if inspect.isawaitable(result): 4745 result = await result 4746 4747 return result 4748 4749 def kbdint_auth_supported(self): 4750 """Return whether or not keyboard-interactive authentication 4751 is supported""" 4752 4753 result = self._kbdint_auth and self._owner.kbdint_auth_supported() 4754 4755 if result is True: 4756 return True 4757 elif (result is NotImplemented and 4758 self._owner.password_auth_supported()): 4759 self._kbdint_password_auth = True 4760 return True 4761 else: 4762 return False 4763 4764 async def get_kbdint_challenge(self, username, lang, submethods): 4765 """Return a keyboard-interactive auth challenge""" 4766 4767 if self._kbdint_password_auth: 4768 result = ('', '', DEFAULT_LANG, (('Password:', False),)) 4769 else: 4770 result = self._owner.get_kbdint_challenge(username, lang, 4771 submethods) 4772 4773 if inspect.isawaitable(result): 4774 result = await result 4775 4776 return result 4777 4778 async def validate_kbdint_response(self, username, responses): 4779 """Return whether the keyboard-interactive response is valid 4780 for this user""" 4781 4782 if self._kbdint_password_auth: 4783 if len(responses) != 1: 4784 return False 4785 4786 try: 4787 result = self._owner.validate_password(username, responses[0]) 4788 4789 if inspect.isawaitable(result): 4790 result = await result 4791 except PasswordChangeRequired: 4792 # Don't support password change requests for now in 4793 # keyboard-interactive auth 4794 result = False 4795 else: 4796 result = self._owner.validate_kbdint_response(username, responses) 4797 4798 if inspect.isawaitable(result): 4799 result = await result 4800 4801 return result 4802 4803 def _process_session_open(self, packet): 4804 """Process an incoming session open request""" 4805 4806 packet.check_end() 4807 4808 if self._process_factory or self._session_factory or self._sftp_factory: 4809 chan = self.create_server_channel(self._encoding, self._errors, 4810 self._window, self._max_pktsize) 4811 4812 if self._process_factory: 4813 session = SSHServerProcess(self._process_factory, 4814 self._sftp_factory, 4815 self._allow_scp) 4816 else: 4817 session = SSHServerStreamSession(self._session_factory, 4818 self._sftp_factory, 4819 self._allow_scp) 4820 else: 4821 result = self._owner.session_requested() 4822 4823 if not result: 4824 raise ChannelOpenError(OPEN_CONNECT_FAILED, 'Session refused') 4825 4826 if isinstance(result, tuple): 4827 chan, result = result 4828 else: 4829 chan = self.create_server_channel(self._encoding, self._errors, 4830 self._window, 4831 self._max_pktsize) 4832 4833 if callable(result): 4834 session = SSHServerStreamSession(result, None, False) 4835 else: 4836 session = result 4837 4838 return chan, session 4839 4840 def _process_direct_tcpip_open(self, packet): 4841 """Process an incoming direct TCP/IP open request""" 4842 4843 dest_host = packet.get_string() 4844 dest_port = packet.get_uint32() 4845 orig_host = packet.get_string() 4846 orig_port = packet.get_uint32() 4847 packet.check_end() 4848 4849 try: 4850 dest_host = dest_host.decode('utf-8') 4851 orig_host = orig_host.decode('utf-8') 4852 except UnicodeDecodeError: 4853 raise ProtocolError('Invalid direct TCP/IP channel ' 4854 'open request') from None 4855 4856 if not self.check_key_permission('port-forwarding') or \ 4857 not self.check_certificate_permission('port-forwarding'): 4858 raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED, 4859 'Port forwarding not permitted') 4860 4861 permitted_opens = self.get_key_option('permitopen') 4862 4863 if permitted_opens and \ 4864 (dest_host, dest_port) not in permitted_opens and \ 4865 (dest_host, None) not in permitted_opens: 4866 raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED, 4867 'Port forwarding not permitted to %s ' 4868 'port %s' % (dest_host, dest_port)) 4869 4870 result = self._owner.connection_requested(dest_host, dest_port, 4871 orig_host, orig_port) 4872 4873 if not result: 4874 raise ChannelOpenError(OPEN_CONNECT_FAILED, 'Connection refused') 4875 4876 if result is True: 4877 result = self.forward_connection(dest_host, dest_port) 4878 4879 if isinstance(result, tuple): 4880 chan, result = result 4881 else: 4882 chan = self.create_tcp_channel() 4883 4884 if callable(result): 4885 session = SSHTCPStreamSession(result) 4886 else: 4887 session = result 4888 4889 self.logger.info('Accepted direct TCP connection request to %s', 4890 (dest_host, dest_port)) 4891 self.logger.info(' Client address: %s', (orig_host, orig_port)) 4892 4893 chan.set_inbound_peer_names(dest_host, dest_port, orig_host, orig_port) 4894 4895 return chan, session 4896 4897 def _process_tcpip_forward_global_request(self, packet): 4898 """Process an incoming TCP/IP port forwarding request""" 4899 4900 listen_host = packet.get_string() 4901 listen_port = packet.get_uint32() 4902 packet.check_end() 4903 4904 try: 4905 listen_host = listen_host.decode('utf-8').lower() 4906 except UnicodeDecodeError: 4907 raise ProtocolError('Invalid TCP/IP forward request') from None 4908 4909 if not self.check_key_permission('port-forwarding') or \ 4910 not self.check_certificate_permission('port-forwarding'): 4911 self.logger.info('Request for TCP listener on %s denied: port ' 4912 'forwarding not permitted', 4913 (listen_host, listen_port)) 4914 4915 self._report_global_response(False) 4916 return 4917 4918 result = self._owner.server_requested(listen_host, listen_port) 4919 4920 self.create_task(self._finish_port_forward(result, listen_host, 4921 listen_port)) 4922 4923 async def _finish_port_forward(self, listener, listen_host, listen_port): 4924 """Finish processing a TCP/IP port forwarding request""" 4925 4926 try: 4927 if inspect.isawaitable(listener): 4928 listener = await listener 4929 4930 if listener is True: 4931 listener = await self.forward_local_port( 4932 listen_host, listen_port, listen_host, listen_port) 4933 except OSError: 4934 self.logger.debug1('Failed to create TCP listener') 4935 self._report_global_response(False) 4936 return 4937 4938 if not listener: 4939 self.logger.info('Request for TCP listener on %s denied by ' 4940 'application', (listen_host, listen_port)) 4941 4942 self._report_global_response(False) 4943 return 4944 4945 if listen_port == 0: 4946 listen_port = listener.get_port() 4947 result = UInt32(listen_port) 4948 else: 4949 result = True 4950 4951 self._local_listeners[listen_host, listen_port] = listener 4952 4953 self.logger.info('Created TCP listener on %s', 4954 (listen_host, listen_port)) 4955 4956 self._report_global_response(result) 4957 4958 def _process_cancel_tcpip_forward_global_request(self, packet): 4959 """Process a request to cancel TCP/IP port forwarding""" 4960 4961 listen_host = packet.get_string() 4962 listen_port = packet.get_uint32() 4963 packet.check_end() 4964 4965 try: 4966 listen_host = listen_host.decode('utf-8').lower() 4967 except UnicodeDecodeError: 4968 raise ProtocolError('Invalid TCP/IP cancel ' 4969 'forward request') from None 4970 4971 try: 4972 listener = self._local_listeners.pop((listen_host, listen_port)) 4973 except KeyError: 4974 raise ProtocolError('TCP/IP listener not found') from None 4975 4976 self.logger.info('Closed TCP listener on %s', 4977 (listen_host, listen_port)) 4978 4979 listener.close() 4980 4981 self._report_global_response(True) 4982 4983 def _process_direct_streamlocal_at_openssh_dot_com_open(self, packet): 4984 """Process an incoming direct UNIX domain socket open request""" 4985 4986 dest_path = packet.get_string() 4987 4988 # OpenSSH appears to have a bug which sends this extra data 4989 _ = packet.get_string() # originator 4990 _ = packet.get_uint32() # originator_port 4991 4992 packet.check_end() 4993 4994 try: 4995 dest_path = dest_path.decode('utf-8') 4996 except UnicodeDecodeError: 4997 raise ProtocolError('Invalid direct UNIX domain channel ' 4998 'open request') from None 4999 5000 if not self.check_key_permission('port-forwarding') or \ 5001 not self.check_certificate_permission('port-forwarding'): 5002 raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED, 5003 'Port forwarding not permitted') 5004 5005 result = self._owner.unix_connection_requested(dest_path) 5006 5007 if not result: 5008 raise ChannelOpenError(OPEN_CONNECT_FAILED, 'Connection refused') 5009 5010 if result is True: 5011 result = self.forward_unix_connection(dest_path) 5012 5013 if isinstance(result, tuple): 5014 chan, result = result 5015 else: 5016 chan = self.create_unix_channel() 5017 5018 if callable(result): 5019 session = SSHUNIXStreamSession(result) 5020 else: 5021 session = result 5022 5023 self.logger.info('Accepted direct UNIX connection on %s', dest_path) 5024 5025 chan.set_inbound_peer_names(dest_path) 5026 5027 return chan, session 5028 5029 def _process_streamlocal_forward_at_openssh_dot_com_global_request(self, 5030 packet): 5031 """Process an incoming UNIX domain socket forwarding request""" 5032 5033 listen_path = packet.get_string() 5034 packet.check_end() 5035 5036 try: 5037 listen_path = listen_path.decode('utf-8') 5038 except UnicodeDecodeError: 5039 raise ProtocolError('Invalid UNIX domain socket ' 5040 'forward request') from None 5041 5042 if not self.check_key_permission('port-forwarding') or \ 5043 not self.check_certificate_permission('port-forwarding'): 5044 self.logger.info('Request for UNIX listener on %s denied: port ' 5045 'forwarding not permitted', listen_path) 5046 5047 self._report_global_response(False) 5048 return 5049 5050 result = self._owner.unix_server_requested(listen_path) 5051 5052 if not result: 5053 self.logger.info('Request for UNIX listener on %s denied by ' 5054 'application', listen_path) 5055 5056 self._report_global_response(False) 5057 return 5058 5059 self.logger.info('Creating UNIX listener on %s', listen_path) 5060 5061 if result is True: 5062 result = self.forward_local_path(listen_path, listen_path) 5063 5064 self.create_task(self._finish_path_forward(result, listen_path)) 5065 5066 async def _finish_path_forward(self, listener, listen_path): 5067 """Finish processing a UNIX domain socket forwarding request""" 5068 5069 try: 5070 if inspect.isawaitable(listener): 5071 listener = await listener 5072 5073 self._local_listeners[listen_path] = listener 5074 self._report_global_response(True) 5075 except OSError: 5076 self.logger.debug1('Failed to create UNIX listener') 5077 self._report_global_response(False) 5078 5079 def _process_cancel_streamlocal_forward_at_openssh_dot_com_global_request( 5080 self, packet): 5081 """Process a request to cancel UNIX domain socket forwarding""" 5082 5083 listen_path = packet.get_string() 5084 packet.check_end() 5085 5086 try: 5087 listen_path = listen_path.decode('utf-8') 5088 except UnicodeDecodeError: 5089 raise ProtocolError('Invalid UNIX domain cancel ' 5090 'forward request') from None 5091 5092 try: 5093 listener = self._local_listeners.pop(listen_path) 5094 except KeyError: 5095 raise ProtocolError('UNIX domain listener not found') from None 5096 5097 self.logger.info('Closed UNIX listener on %s', listen_path) 5098 5099 listener.close() 5100 5101 self._report_global_response(True) 5102 5103 async def attach_x11_listener(self, chan, auth_proto, auth_data, screen): 5104 """Attach a channel to a remote X11 display""" 5105 5106 if (not self._x11_forwarding or 5107 not self.check_key_permission('X11-forwarding') or 5108 not self.check_certificate_permission('X11-forwarding')): 5109 self.logger.info('X11 forwarding request denied: X11 ' 5110 'forwarding not permitted') 5111 5112 return None 5113 5114 if not self._x11_listener: 5115 self._x11_listener = await create_x11_server_listener( 5116 self, self._loop, self._x11_auth_path, auth_proto, auth_data) 5117 5118 if self._x11_listener: 5119 return self._x11_listener.attach(chan, screen) 5120 else: 5121 return None 5122 5123 def detach_x11_listener(self, chan): 5124 """Detach a session from a remote X11 listener""" 5125 5126 if self._x11_listener: 5127 if self._x11_listener.detach(chan): 5128 self._x11_listener = None 5129 5130 async def create_agent_listener(self): 5131 """Create a listener for forwarding ssh-agent connections""" 5132 5133 if (not self._agent_forwarding or 5134 not self.check_key_permission('agent-forwarding') or 5135 not self.check_certificate_permission('agent-forwarding')): 5136 self.logger.info('Agent forwarding request denied: Agent ' 5137 'forwarding not permitted') 5138 5139 return False 5140 5141 if self._agent_listener: 5142 return True 5143 5144 try: 5145 tempdir = tempfile.TemporaryDirectory(prefix='asyncssh-') 5146 path = str(Path(tempdir.name, 'agent')) 5147 5148 unix_listener = await create_unix_forward_listener( 5149 self, self._loop, self.create_agent_connection, path) 5150 5151 self._agent_listener = SSHAgentListener(tempdir, path, 5152 unix_listener) 5153 return True 5154 except OSError: 5155 return False 5156 5157 def get_agent_path(self): 5158 """Return the path of the ssh-agent listener, if one exists""" 5159 5160 if self._agent_listener: 5161 return self._agent_listener.get_path() 5162 else: 5163 return None 5164 5165 def send_auth_banner(self, msg, lang=DEFAULT_LANG): 5166 """Send an authentication banner to the client 5167 5168 This method can be called to send an authentication banner to 5169 the client, displaying information while authentication is 5170 in progress. It is an error to call this method after the 5171 authentication is complete. 5172 5173 :param msg: 5174 The message to display 5175 :param lang: 5176 The language the message is in 5177 :type msg: `str` 5178 :type lang: `str` 5179 5180 :raises: :exc:`OSError` if authentication is already completed 5181 5182 """ 5183 5184 if self._auth_complete: 5185 raise OSError('Authentication already completed') 5186 5187 self.logger.debug1('Sending authentication banner') 5188 5189 self.send_packet(MSG_USERAUTH_BANNER, String(msg), String(lang)) 5190 5191 def set_authorized_keys(self, authorized_keys): 5192 """Set the keys trusted for client public key authentication 5193 5194 This method can be called to set the trusted user and 5195 CA keys for client public key authentication. It should 5196 generally be called from the :meth:`begin_auth 5197 <SSHServer.begin_auth>` method of :class:`SSHServer` to 5198 set the appropriate keys for the user attempting to 5199 authenticate. 5200 5201 :param authorized_keys: 5202 The keys to trust for client public key authentication 5203 :type authorized_keys: *see* :ref:`SpecifyingAuthorizedKeys` 5204 5205 """ 5206 5207 if isinstance(authorized_keys, (str, list)): 5208 authorized_keys = read_authorized_keys(authorized_keys) 5209 5210 self._client_keys = authorized_keys 5211 5212 def get_key_option(self, option, default=None): 5213 """Return option from authorized_keys 5214 5215 If a client key or certificate was presented during authentication, 5216 this method returns the value of the requested option in the 5217 corresponding authorized_keys entry if it was set. Otherwise, it 5218 returns the default value provided. 5219 5220 The following standard options are supported: 5221 5222 | command (string) 5223 | environment (dictionary of name/value pairs) 5224 | from (list of host patterns) 5225 | no-touch-required (boolean) 5226 | permitopen (list of host/port tuples) 5227 | principals (list of usernames) 5228 5229 Non-standard options are also supported and will return the 5230 value `True` if the option is present without a value or 5231 return a list of strings containing the values associated 5232 with each occurrence of that option name. If the option is 5233 not present, the specified default value is returned. 5234 5235 :param option: 5236 The name of the option to look up. 5237 :param default: 5238 The default value to return if the option is not present. 5239 :type option: `str` 5240 5241 :returns: The value of the option in authorized_keys, if set 5242 5243 """ 5244 5245 return self._key_options.get(option, default) 5246 5247 def check_key_permission(self, permission): 5248 """Check permissions in authorized_keys 5249 5250 If a client key or certificate was presented during 5251 authentication, this method returns whether the specified 5252 permission is allowed by the corresponding authorized_keys 5253 entry. By default, all permissions are granted, but they 5254 can be revoked by specifying an option starting with 5255 'no-' without a value. 5256 5257 The following standard options are supported: 5258 5259 | X11-forwarding 5260 | agent-forwarding 5261 | port-forwarding 5262 | pty 5263 | user-rc 5264 5265 AsyncSSH internally enforces X11-forwarding, agent-forwarding, 5266 port-forwarding and pty permissions but ignores user-rc since 5267 it does not implement that feature. 5268 5269 Non-standard permissions can also be checked, as long as the 5270 option follows the convention of starting with 'no-'. 5271 5272 :param permission: 5273 The name of the permission to check (without the 'no-'). 5274 :type permission: `str` 5275 5276 :returns: A `bool` indicating if the permission is granted. 5277 5278 """ 5279 5280 return not self._key_options.get('no-' + permission, False) 5281 5282 def get_certificate_option(self, option, default=None): 5283 """Return option from user certificate 5284 5285 If a user certificate was presented during authentication, 5286 this method returns the value of the requested option in 5287 the certificate if it was set. Otherwise, it returns the 5288 default value provided. 5289 5290 The following options are supported: 5291 5292 | force-command (string) 5293 | no-touch-required (boolean) 5294 | source-address (list of CIDR-style IP network addresses) 5295 5296 :param option: 5297 The name of the option to look up. 5298 :param default: 5299 The default value to return if the option is not present. 5300 :type option: `str` 5301 5302 :returns: The value of the option in the user certificate, if set 5303 5304 """ 5305 5306 if self._cert_options is not None: 5307 return self._cert_options.get(option, default) 5308 else: 5309 return default 5310 5311 def check_certificate_permission(self, permission): 5312 """Check permissions in user certificate 5313 5314 If a user certificate was presented during authentication, 5315 this method returns whether the specified permission was 5316 granted in the certificate. Otherwise, it acts as if all 5317 permissions are granted and returns `True`. 5318 5319 The following permissions are supported: 5320 5321 | X11-forwarding 5322 | agent-forwarding 5323 | port-forwarding 5324 | pty 5325 | user-rc 5326 5327 AsyncSSH internally enforces agent-forwarding, port-forwarding 5328 and pty permissions but ignores the other values since it does 5329 not implement those features. 5330 5331 :param permission: 5332 The name of the permission to check (without the 'permit-'). 5333 :type permission: `str` 5334 5335 :returns: A `bool` indicating if the permission is granted. 5336 5337 """ 5338 5339 if self._cert_options is not None: 5340 return self._cert_options.get('permit-' + permission, False) 5341 else: 5342 return True 5343 5344 def create_server_channel(self, encoding='utf-8', errors='strict', 5345 window=_DEFAULT_WINDOW, 5346 max_pktsize=_DEFAULT_MAX_PKTSIZE): 5347 """Create an SSH server channel for a new SSH session 5348 5349 This method can be called by :meth:`session_requested() 5350 <SSHServer.session_requested>` to create an 5351 :class:`SSHServerChannel` with the desired encoding, Unicode 5352 error handling strategy, window, and max packet size for a 5353 newly created SSH server session. 5354 5355 :param encoding: (optional) 5356 The Unicode encoding to use for data exchanged on the 5357 session, defaulting to UTF-8 (ISO 10646) format. If `None` 5358 is passed in, the application can send and receive raw 5359 bytes. 5360 :param errors: (optional) 5361 The error handling strategy to apply on encode/decode errors 5362 :param window: (optional) 5363 The receive window size for this session 5364 :param max_pktsize: (optional) 5365 The maximum packet size for this session 5366 :type encoding: `str` 5367 :type errors: `str` 5368 :type window: `int` 5369 :type max_pktsize: `int` 5370 5371 :returns: :class:`SSHServerChannel` 5372 5373 """ 5374 5375 return SSHServerChannel(self, self._loop, self._allow_pty, 5376 self._line_editor, self._line_history, 5377 self._max_line_length, encoding, errors, 5378 window, max_pktsize) 5379 5380 async def create_connection(self, session_factory, remote_host, remote_port, 5381 orig_host='', orig_port=0, *, encoding=None, 5382 errors='strict', window=_DEFAULT_WINDOW, 5383 max_pktsize=_DEFAULT_MAX_PKTSIZE): 5384 """Create an SSH TCP forwarded connection 5385 5386 This method is a coroutine which can be called to notify the 5387 client about a new inbound TCP connection arriving on the 5388 specified remote host and port. If the connection is successfully 5389 opened, a new SSH channel will be opened with data being handled 5390 by a :class:`SSHTCPSession` object created by `session_factory`. 5391 5392 Optional arguments include the host and port of the original 5393 client opening the connection when performing TCP port forwarding. 5394 5395 By default, this class expects data to be sent and received as 5396 raw bytes. However, an optional encoding argument can be 5397 passed in to select the encoding to use, allowing the 5398 application to send and receive string data. When encoding is 5399 set, an optional errors argument can be passed in to select 5400 what Unicode error handling strategy to use. 5401 5402 Other optional arguments include the SSH receive window size and 5403 max packet size which default to 2 MB and 32 KB, respectively. 5404 5405 :param session_factory: 5406 A `callable` which returns an :class:`SSHClientSession` object 5407 that will be created to handle activity on this session 5408 :param remote_host: 5409 The hostname or address the connection was received on 5410 :param remote_port: 5411 The port number the connection was received on 5412 :param orig_host: (optional) 5413 The hostname or address of the client requesting the connection 5414 :param orig_port: (optional) 5415 The port number of the client requesting the connection 5416 :param encoding: (optional) 5417 The Unicode encoding to use for data exchanged on the connection 5418 :param errors: (optional) 5419 The error handling strategy to apply on encode/decode errors 5420 :param window: (optional) 5421 The receive window size for this session 5422 :param max_pktsize: (optional) 5423 The maximum packet size for this session 5424 :type session_factory: `callable` 5425 :type remote_host: `str` 5426 :type remote_port: `int` 5427 :type orig_host: `str` 5428 :type orig_port: `int` 5429 :type encoding: `str` 5430 :type errors: `str` 5431 :type window: `int` 5432 :type max_pktsize: `int` 5433 5434 :returns: an :class:`SSHTCPChannel` and :class:`SSHTCPSession` 5435 5436 """ 5437 5438 self.logger.info('Opening forwarded TCP connection to %s', 5439 (remote_host, remote_port)) 5440 self.logger.info(' Client address: %s', (orig_host, orig_port)) 5441 5442 chan = self.create_tcp_channel(encoding, errors, window, max_pktsize) 5443 5444 session = await chan.accept(session_factory, remote_host, 5445 remote_port, orig_host, orig_port) 5446 5447 return chan, session 5448 5449 async def open_connection(self, *args, **kwargs): 5450 """Open an SSH TCP forwarded connection 5451 5452 This method is a coroutine wrapper around :meth:`create_connection` 5453 designed to provide a "high-level" stream interface for creating 5454 an SSH TCP forwarded connection. Instead of taking a 5455 `session_factory` argument for constructing an object which will 5456 handle activity on the session via callbacks, it returns 5457 :class:`SSHReader` and :class:`SSHWriter` objects which can be 5458 used to perform I/O on the connection. 5459 5460 With the exception of `session_factory`, all of the arguments 5461 to :meth:`create_connection` are supported and have the same 5462 meaning here. 5463 5464 :returns: an :class:`SSHReader` and :class:`SSHWriter` 5465 5466 """ 5467 5468 chan, session = await self.create_connection(SSHTCPStreamSession, 5469 *args, **kwargs) 5470 5471 return SSHReader(session, chan), SSHWriter(session, chan) 5472 5473 async def create_unix_connection(self, session_factory, remote_path, *, 5474 encoding=None, errors='strict', 5475 window=_DEFAULT_WINDOW, 5476 max_pktsize=_DEFAULT_MAX_PKTSIZE): 5477 """Create an SSH UNIX domain socket forwarded connection 5478 5479 This method is a coroutine which can be called to notify the 5480 client about a new inbound UNIX domain socket connection arriving 5481 on the specified remote path. If the connection is successfully 5482 opened, a new SSH channel will be opened with data being handled 5483 by a :class:`SSHUNIXSession` object created by `session_factory`. 5484 5485 By default, this class expects data to be sent and received as 5486 raw bytes. However, an optional encoding argument can be 5487 passed in to select the encoding to use, allowing the 5488 application to send and receive string data. When encoding is 5489 set, an optional errors argument can be passed in to select 5490 what Unicode error handling strategy to use. 5491 5492 Other optional arguments include the SSH receive window size and 5493 max packet size which default to 2 MB and 32 KB, respectively. 5494 5495 :param session_factory: 5496 A `callable` which returns an :class:`SSHClientSession` object 5497 that will be created to handle activity on this session 5498 :param remote_path: 5499 The path the connection was received on 5500 :param encoding: (optional) 5501 The Unicode encoding to use for data exchanged on the connection 5502 :param errors: (optional) 5503 The error handling strategy to apply on encode/decode errors 5504 :param window: (optional) 5505 The receive window size for this session 5506 :param max_pktsize: (optional) 5507 The maximum packet size for this session 5508 :type session_factory: `callable` 5509 :type remote_path: `str` 5510 :type encoding: `str` 5511 :type errors: `str` 5512 :type window: `int` 5513 :type max_pktsize: `int` 5514 5515 :returns: an :class:`SSHTCPChannel` and :class:`SSHUNIXSession` 5516 5517 """ 5518 5519 self.logger.info('Opening forwarded UNIX connection to %s', remote_path) 5520 5521 chan = self.create_unix_channel(encoding, errors, window, max_pktsize) 5522 5523 session = await chan.accept(session_factory, remote_path) 5524 5525 return chan, session 5526 5527 async def open_unix_connection(self, *args, **kwargs): 5528 """Open an SSH UNIX domain socket forwarded connection 5529 5530 This method is a coroutine wrapper around 5531 :meth:`create_unix_connection` designed to provide a "high-level" 5532 stream interface for creating an SSH UNIX domain socket forwarded 5533 connection. Instead of taking a `session_factory` argument for 5534 constructing an object which will handle activity on the session 5535 via callbacks, it returns :class:`SSHReader` and :class:`SSHWriter` 5536 objects which can be used to perform I/O on the connection. 5537 5538 With the exception of `session_factory`, all of the arguments 5539 to :meth:`create_unix_connection` are supported and have the same 5540 meaning here. 5541 5542 :returns: an :class:`SSHReader` and :class:`SSHWriter` 5543 5544 """ 5545 5546 chan, session = \ 5547 await self.create_unix_connection(SSHUNIXStreamSession, 5548 *args, **kwargs) 5549 5550 return SSHReader(session, chan), SSHWriter(session, chan) 5551 5552 async def create_x11_connection(self, session_factory, orig_host='', 5553 orig_port=0, *, window=_DEFAULT_WINDOW, 5554 max_pktsize=_DEFAULT_MAX_PKTSIZE): 5555 """Create an SSH X11 forwarded connection""" 5556 5557 self.logger.info('Opening forwarded X11 connection') 5558 5559 chan = self.create_x11_channel(window, max_pktsize) 5560 5561 session = await chan.open(session_factory, orig_host, orig_port) 5562 5563 return chan, session 5564 5565 async def create_agent_connection(self, session_factory, *, 5566 window=_DEFAULT_WINDOW, 5567 max_pktsize=_DEFAULT_MAX_PKTSIZE): 5568 """Create a forwarded ssh-agent connection back to the client""" 5569 5570 if not self._agent_listener: 5571 raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED, 5572 'Agent forwarding not permitted') 5573 5574 self.logger.info('Opening forwarded agent connection') 5575 5576 chan = self.create_agent_channel(window, max_pktsize) 5577 5578 session = await chan.open(session_factory) 5579 5580 return chan, session 5581 5582 async def open_agent_connection(self): 5583 """Open a forwarded ssh-agent connection back to the client""" 5584 5585 chan, session = \ 5586 await self.create_agent_connection(SSHUNIXStreamSession) 5587 5588 return SSHReader(session, chan), SSHWriter(session, chan) 5589 5590 5591class SSHConnectionOptions(Options): 5592 """SSH connection options""" 5593 5594 def __init__(self, options=None, **kwargs): 5595 last_config = options.config if options else None 5596 super().__init__(options=options, last_config=last_config, **kwargs) 5597 5598 # pylint: disable=arguments-differ 5599 def prepare(self, config, protocol_factory, version, host, port, tunnel, 5600 passphrase, proxy_command, family, local_addr, tcp_keepalive, 5601 kex_algs, encryption_algs, mac_algs, compression_algs, 5602 signature_algs, host_based_auth, public_key_auth, kbdint_auth, 5603 password_auth, x509_trusted_certs, x509_trusted_cert_paths, 5604 x509_purposes, rekey_bytes, rekey_seconds, connect_timeout, 5605 login_timeout, keepalive_interval, keepalive_count_max): 5606 """Prepare common connection configuration options""" 5607 5608 self.config = config 5609 self.protocol_factory = protocol_factory 5610 self.version = _validate_version(version) 5611 5612 self.host = config.get('Hostname', host) 5613 self.port = port if port != () else config.get('Port', DEFAULT_PORT) 5614 5615 self.tunnel = tunnel if tunnel != () else config.get('ProxyJump') 5616 self.passphrase = passphrase 5617 5618 if isinstance(proxy_command, str): 5619 proxy_command = shlex.split(proxy_command) 5620 5621 self.proxy_command = proxy_command or config.get('ProxyCommand') 5622 5623 self.family = family if family != () else \ 5624 config.get('AddressFamily', socket.AF_UNSPEC) 5625 self.local_addr = local_addr if local_addr != () else \ 5626 (config.get('BindAddress'), 0) 5627 self.tcp_keepalive = tcp_keepalive if tcp_keepalive != () else \ 5628 config.get('TCPKeepAlive', True) 5629 5630 self.kex_algs, self.encryption_algs, self.mac_algs, \ 5631 self.compression_algs, self.signature_algs = \ 5632 _validate_algs(config, kex_algs, encryption_algs, mac_algs, 5633 compression_algs, signature_algs, 5634 x509_trusted_certs is not None) 5635 5636 if host_based_auth == (): 5637 host_based_auth = config.get('HostbasedAuthentication', True) 5638 5639 if public_key_auth == (): 5640 public_key_auth = config.get('PubkeyAuthentication', True) 5641 5642 if kbdint_auth == (): 5643 kbdint_auth = \ 5644 config.get('KbdInteractiveAuthentication', 5645 config.get('ChallengeResponseAuthentication', True)) 5646 5647 if password_auth == (): 5648 password_auth = config.get('PasswordAuthentication', True) 5649 5650 self.host_based_auth = host_based_auth 5651 self.public_key_auth = public_key_auth 5652 self.kbdint_auth = kbdint_auth 5653 self.password_auth = password_auth 5654 5655 if x509_trusted_certs is not None: 5656 x509_trusted_certs = load_certificates(x509_trusted_certs) 5657 5658 if x509_trusted_cert_paths: 5659 for path in x509_trusted_cert_paths: 5660 if not Path(path).is_dir(): 5661 raise ValueError('Path not a directory: ' + str(path)) 5662 5663 self.x509_trusted_certs = x509_trusted_certs 5664 self.x509_trusted_cert_paths = x509_trusted_cert_paths 5665 self.x509_purposes = x509_purposes 5666 5667 config_rekey_bytes, config_rekey_seconds = \ 5668 config.get('RekeyLimit', ((), ())) 5669 5670 if rekey_bytes == (): 5671 rekey_bytes = config_rekey_bytes 5672 5673 if rekey_bytes == (): 5674 rekey_bytes = _DEFAULT_REKEY_BYTES 5675 elif isinstance(rekey_bytes, str): 5676 rekey_bytes = parse_byte_count(rekey_bytes) 5677 5678 if rekey_bytes <= 0: 5679 raise ValueError('Rekey bytes cannot be negative or zero') 5680 5681 if rekey_seconds == (): 5682 rekey_seconds = config_rekey_seconds 5683 5684 if rekey_seconds == (): 5685 rekey_seconds = _DEFAULT_REKEY_SECONDS 5686 elif isinstance(rekey_seconds, str): 5687 rekey_seconds = parse_time_interval(rekey_seconds) 5688 5689 if rekey_seconds and rekey_seconds <= 0: 5690 raise ValueError('Rekey seconds cannot be negative or zero') 5691 5692 if isinstance(connect_timeout, str): 5693 connect_timeout = parse_time_interval(connect_timeout) 5694 5695 if connect_timeout and connect_timeout < 0: 5696 raise ValueError('Connect timeout cannot be negative') 5697 5698 if isinstance(login_timeout, str): 5699 login_timeout = parse_time_interval(login_timeout) 5700 5701 if login_timeout and login_timeout < 0: 5702 raise ValueError('Login timeout cannot be negative') 5703 5704 if isinstance(keepalive_interval, str): 5705 keepalive_interval = parse_time_interval(keepalive_interval) 5706 5707 if keepalive_interval and keepalive_interval < 0: 5708 raise ValueError('Keepalive interval cannot be negative') 5709 5710 if keepalive_count_max <= 0: 5711 raise ValueError('Keepalive count max cannot be negative or zero') 5712 5713 self.rekey_bytes = int(rekey_bytes) 5714 self.rekey_seconds = rekey_seconds 5715 self.connect_timeout = connect_timeout or None 5716 self.login_timeout = login_timeout 5717 self.keepalive_interval = keepalive_interval 5718 self.keepalive_count_max = keepalive_count_max 5719 5720 5721class SSHClientConnectionOptions(SSHConnectionOptions): 5722 """SSH client connection options 5723 5724 The following options are available to control the establishment 5725 of SSH client connections: 5726 5727 :param client_factory: (optional) 5728 A `callable` which returns an :class:`SSHClient` object that will 5729 be created for each new connection. 5730 :param proxy_command: (optional) 5731 A string or list of strings specifying a command and arguments 5732 to run to make a connection to the SSH server. Data will be 5733 forwarded to this process over stdin/stdout instead of opening a 5734 TCP connection. If specified as a string, standard shell quoting 5735 will be applied when splitting the command and its arguments. 5736 :param known_hosts: (optional) 5737 The list of keys which will be used to validate the server host 5738 key presented during the SSH handshake. If this is not specified, 5739 the keys will be looked up in the file :file:`.ssh/known_hosts`. 5740 If this is explicitly set to `None`, server host key validation 5741 will be disabled. 5742 :param host_key_alias: (optional) 5743 An alias to use instead of the real host name when looking up a host 5744 key in known_hosts and when validating host certificates. 5745 :param server_host_key_algs: (optional) 5746 A list of server host key algorithms to use instead of the 5747 default of those present in known_hosts when performing the SSH 5748 handshake, taken from :ref:`server host key algorithms 5749 <PublicKeyAlgs>`. This is useful when using the 5750 validate_host_public_key callback to validate server host keys, 5751 since AsyncSSH can not determine which server host key algorithms 5752 are preferred. This argument can also be set to 'default' to 5753 specify that the client should always send its default list of 5754 supported algorithms to avoid leaking information about what 5755 algorithms are present for the server in known_hosts. 5756 5757 .. note:: The 'default' keyword should be used with 5758 caution, as it can result in a host key mismatch 5759 if the client trusts only a subset of the host 5760 keys the server might return. 5761 :param x509_trusted_certs: (optional) 5762 A list of certificates which should be trusted for X.509 server 5763 certificate authentication. If no trusted certificates are 5764 specified, an attempt will be made to load them from the file 5765 :file:`.ssh/ca-bundle.crt`. If this argument is explicitly set 5766 to `None`, X.509 server certificate authentication will not 5767 be performed. 5768 5769 .. note:: X.509 certificates to trust can also be provided 5770 through a :ref:`known_hosts <KnownHosts>` file 5771 if they are converted into OpenSSH format. 5772 This allows their trust to be limited to only 5773 specific host names. 5774 :param x509_trusted_cert_paths: (optional) 5775 A list of path names to "hash directories" containing certificates 5776 which should be trusted for X.509 server certificate authentication. 5777 Each certificate should be in a separate file with a name of the 5778 form *hash.number*, where *hash* is the OpenSSL hash value of the 5779 certificate subject name and *number* is an integer counting up 5780 from zero if multiple certificates have the same hash. If no 5781 paths are specified, an attempt with be made to use the directory 5782 :file:`.ssh/crt` as a certificate hash directory. 5783 :param x509_purposes: (optional) 5784 A list of purposes allowed in the ExtendedKeyUsage of a 5785 certificate used for X.509 server certificate authentication, 5786 defulting to 'secureShellServer'. If this argument is explicitly 5787 set to `None`, the server certificate's ExtendedKeyUsage will 5788 not be checked. 5789 :param username: (optional) 5790 Username to authenticate as on the server. If not specified, 5791 the currently logged in user on the local machine will be used. 5792 :param password: (optional) 5793 The password to use for client password authentication or 5794 keyboard-interactive authentication which prompts for a password. 5795 If this is not specified, client password authentication will 5796 not be performed. 5797 :param client_host_keysign: (optional) 5798 Whether or not to use `ssh-keysign` to sign host-based 5799 authentication requests. If set to `True`, an attempt will be 5800 made to find `ssh-keysign` in its typical locations. If set to 5801 a string, that will be used as the `ssh-keysign` path. When set, 5802 client_host_keys should be a list of public keys. Otherwise, 5803 client_host_keys should be a list of private keys with optional 5804 paired certificates. 5805 :param client_host_keys: (optional) 5806 A list of keys to use to authenticate this client via host-based 5807 authentication. If `client_host_keysign` is set and no host keys 5808 or certificates are specified, an attempt will be made to find 5809 them in their typical locations. If `client_host_keysign` is 5810 not set, host private keys must be specified explicitly or 5811 host-based authentication will not be performed. 5812 :param client_host_certs: (optional) 5813 A list of optional certificates which can be paired with the 5814 provided client host keys. 5815 :param client_host: (optional) 5816 The local hostname to use when performing host-based 5817 authentication. If not specified, the hostname associated with 5818 the local IP address of the SSH connection will be used. 5819 :param client_username: (optional) 5820 The local username to use when performing host-based 5821 authentication. If not specified, the username of the currently 5822 logged in user will be used. 5823 :param client_keys: (optional) 5824 A list of keys which will be used to authenticate this client 5825 via public key authentication. If no client keys are specified, 5826 an attempt will be made to get them from an ssh-agent process 5827 and/or load them from the files :file:`.ssh/id_ed25519_sk`, 5828 :file:`.ssh/id_ecdsa_sk`, :file:`.ssh/id_ed448`, 5829 :file:`.ssh/id_ed25519`, :file:`.ssh/id_ecdsa`, 5830 :file:`.ssh/id_rsa`, and :file:`.ssh/id_dsa` in the user's 5831 home directory, with optional certificates loaded from the files 5832 :file:`.ssh/id_ed25519_sk-cert.pub`, 5833 :file:`.ssh/id_ecdsa_sk-cert.pub`, :file:`.ssh/id_ed448-cert.pub`, 5834 :file:`.ssh/id_ed25519-cert.pub`, :file:`.ssh/id_ecdsa-cert.pub`, 5835 :file:`.ssh/id_rsa-cert.pub`, and :file:`.ssh/id_dsa-cert.pub`. 5836 If this argument is explicitly set to `None`, client public key 5837 authentication will not be performed. 5838 :param client_certs: (optional) 5839 A list of optional certificates which can be paired with the 5840 provided client keys. 5841 :param passphrase: (optional) 5842 The passphrase to use to decrypt client keys when loading them, 5843 if they are encrypted. If this is not specified, only unencrypted 5844 client keys can be loaded. If the keys passed into client_keys 5845 are already loaded, this argument is ignored. 5846 :param ignore_encrypted: (optional) 5847 Whether or not to ignore encrypted keys when no passphrase is 5848 provided. This is intended to allow encrypted keys specified via 5849 the IdentityFile config option to be ignored if a passphrase 5850 is not specified, loading only unencrypted local keys. Note 5851 that encrypted keys loaded into an SSH agent can still be used 5852 when this option is set. 5853 :param host_based_auth: (optional) 5854 Whether or not to allow host-based authentication. By default, 5855 host-based authentication is enabled if client host keys are 5856 made available. 5857 :param public_key_auth: (optional) 5858 Whether or not to allow public key authentication. By default, 5859 public key authentication is enabled if client keys are made 5860 available. 5861 :param kbdint_auth: (optional) 5862 Whether or not to allow keyboard-interactive authentication. By 5863 default, keyboard-interactive authentication is enabled if a 5864 password is specified or if callbacks to respond to challenges 5865 are made available. 5866 :param password_auth: (optional) 5867 Whether or not to allow password authentication. By default, 5868 password authentication is enabled if a password is specified 5869 or if callbacks to provide a password are made availble. 5870 :param gss_host: (optional) 5871 The principal name to use for the host in GSS key exchange and 5872 authentication. If not specified, this value will be the same 5873 as the `host` argument. If this argument is explicitly set to 5874 `None`, GSS key exchange and authentication will not be performed. 5875 :param gss_kex: (optional) 5876 Whether or not to allow GSS key exchange. By default, GSS 5877 key exchange is enabled. 5878 :param gss_auth: (optional) 5879 Whether or not to allow GSS authentication. By default, GSS 5880 authentication is enabled. 5881 :param gss_delegate_creds: (optional) 5882 Whether or not to forward GSS credentials to the server being 5883 accessed. By default, GSS credential delegation is disabled. 5884 :param preferred_auth: 5885 A list of authentication methods the client should attempt to 5886 use in order of preference. By default, the preferred list is 5887 gssapi-keyex, gssapi-with-mic, hostbased, publickey, 5888 keyboard-interactive, and then password. This list may be 5889 limited by which auth methods are implemented by the client 5890 and which methods the server accepts. 5891 :param disable_trivial_auth: (optional) 5892 Whether or not to allow "trivial" forms of auth where the 5893 client is not actually challenged for credentials. Setting 5894 this will cause the connection to fail if a server does not 5895 perform some non-trivial form of auth during the initial 5896 SSH handshake. If not specified, all forms of auth supported 5897 by the server are allowed, including none. 5898 :param agent_path: (optional) 5899 The path of a UNIX domain socket to use to contact an ssh-agent 5900 process which will perform the operations needed for client 5901 public key authentication, or the :class:`SSHServerConnection` 5902 to use to forward ssh-agent requests over. If this is not 5903 specified and the environment variable `SSH_AUTH_SOCK` is 5904 set, its value will be used as the path. If `client_keys` 5905 is specified or this argument is explicitly set to `None`, 5906 an ssh-agent will not be used. 5907 :param agent_identities: (optional) 5908 A list of identities used to restrict which SSH agent keys may 5909 be used. These may be specified as byte strings in binary SSH 5910 format or as public keys or certificates (*see* 5911 :ref:`SpecifyingPublicKeys` and :ref:`SpecifyingCertificates`). 5912 If set to `None`, all keys loaded into the SSH agent will be 5913 made available for use. This is the default. 5914 :param agent_forwarding: (optional) 5915 Whether or not to allow forwarding of ssh-agent requests from 5916 processes running on the server. By default, ssh-agent forwarding 5917 requests from the server are not allowed. 5918 :param pkcs11_provider: (optional) 5919 The path of a shared library which should be used as a PKCS#11 5920 provider for accessing keys on PIV security tokens. By default, 5921 no local security tokens will be accessed. 5922 :param pkcs11_pin: (optional) 5923 The PIN to use when accessing security tokens via PKCS#11. 5924 5925 .. note:: If your application opens multiple SSH connections 5926 using PKCS#11 keys, you should consider calling 5927 :func:`load_pkcs11_keys` explicitly instead of 5928 using these arguments. This allows you to pay 5929 the cost of loading the key information from the 5930 security tokens only once. You can then pass the 5931 returned keys via the `client_keys` argument to 5932 any calls that need them. 5933 5934 Calling :func:`load_pkcs11_keys` explicitly also 5935 gives you the ability to load keys from multiple 5936 tokens with different PINs and to select which 5937 tokens to load keys from and which keys on those 5938 tokens to load. 5939 5940 :param client_version: (optional) 5941 An ASCII string to advertise to the SSH server as the version of 5942 this client, defaulting to `'AsyncSSH'` and its version number. 5943 :param kex_algs: (optional) 5944 A list of allowed key exchange algorithms in the SSH handshake, 5945 taken from :ref:`key exchange algorithms <KexAlgs>`. 5946 :param encryption_algs: (optional) 5947 A list of encryption algorithms to use during the SSH handshake, 5948 taken from :ref:`encryption algorithms <EncryptionAlgs>`. 5949 :param mac_algs: (optional) 5950 A list of MAC algorithms to use during the SSH handshake, taken 5951 from :ref:`MAC algorithms <MACAlgs>`. 5952 :param compression_algs: (optional) 5953 A list of compression algorithms to use during the SSH handshake, 5954 taken from :ref:`compression algorithms <CompressionAlgs>`, or 5955 `None` to disable compression. 5956 :param signature_algs: (optional) 5957 A list of public key signature algorithms to use during the SSH 5958 handshake, taken from :ref:`signature algorithms <SignatureAlgs>`. 5959 :param rekey_bytes: (optional) 5960 The number of bytes which can be sent before the SSH session 5961 key is renegotiated. This defaults to 1 GB. 5962 :param rekey_seconds: (optional) 5963 The maximum time in seconds before the SSH session key is 5964 renegotiated. This defaults to 1 hour. 5965 :param connect_timeout: (optional) 5966 The maximum time in seconds allowed to complete an outbound 5967 SSH connection. This includes the time to establish the TCP 5968 connection and the time to perform the initial SSH protocol 5969 handshake, key exchange, and authentication. This is disabled 5970 by default, relying on the system's default TCP connect timeout 5971 and AsyncSSH's login timeout. 5972 :param login_timeout: (optional) 5973 The maximum time in seconds allowed for authentication to 5974 complete, defaulting to 2 minutes. Setting this to 0 will 5975 disable the login timeout. 5976 5977 .. note:: This timeout only applies after the SSH TCP 5978 connection is established. To set a timeout 5979 which includes establishing the TCP connection, 5980 use the `connect_timeout` argument above. 5981 :param keepalive_interval: (optional) 5982 The time in seconds to wait before sending a keepalive message 5983 if no data has been received from the server. This defaults to 5984 0, which disables sending these messages. 5985 :param keepalive_count_max: (optional) 5986 The maximum number of keepalive messages which will be sent 5987 without getting a response before disconnecting from the 5988 server. This defaults to 3, but only applies when 5989 keepalive_interval is non-zero. 5990 :param command: (optional) 5991 The default remote command to execute on client sessions. 5992 An interactive shell is started if no command or subsystem is 5993 specified. 5994 :param subsystem: (optional) 5995 The default remote subsystem to start on client sessions. 5996 :param env: (optional) 5997 The default environment variables to set for client sessions. 5998 Keys and values passed in here will be converted to Unicode 5999 strings encoded as UTF-8 (ISO 10646) for transmission. 6000 6001 .. note:: Many SSH servers restrict which environment 6002 variables a client is allowed to set. The 6003 server's configuration may need to be edited 6004 before environment variables can be 6005 successfully set in the remote environment. 6006 :param send_env: (optional) 6007 A list of environment variable names to pull from 6008 `os.environ` and set by default for client sessions. Wildcards 6009 patterns using `'*'` and `'?'` are allowed, and all variables 6010 with matching names will be sent with whatever value is set in 6011 the local environment. If a variable is present in both env 6012 and send_env, the value from env will be used. 6013 :param request_pty: (optional) 6014 Whether or not to request a pseudo-terminal (PTY) by default for 6015 client sessions. This defaults to `True`, which means to request 6016 a PTY whenever the `term_type` is set. Other possible values 6017 include `False` to never request a PTY, `'force'` to always 6018 request a PTY even without `term_type` being set, or `'auto'` 6019 to request a TTY when `term_type` is set but only when starting 6020 an interactive shell. 6021 :param term_type: (optional) 6022 The default terminal type to set for client sessions. 6023 :param term_size: (optional) 6024 The terminal width and height in characters and optionally 6025 the width and height in pixels to set for client sessions. 6026 :param term_modes: (optional) 6027 POSIX terminal modes to set for client sessions, where keys are 6028 taken from :ref:`POSIX terminal modes <PTYModes>` with values 6029 defined in section 8 of :rfc:`RFC 4254 <4254#section-8>`. 6030 :param x11_forwarding: (optional) 6031 Whether or not to request X11 forwarding for client sessions, 6032 defaulting to `False`. If set to `True`, X11 forwarding will be 6033 requested and a failure will raise :exc:`ChannelOpenError`. It 6034 can also be set to `'ignore_failure'` to attempt X11 forwarding 6035 but ignore failures. 6036 :param x11_display: (optional) 6037 The display that X11 connections should be forwarded to, 6038 defaulting to the value in the environment variable `DISPLAY`. 6039 :param x11_auth_path: (optional) 6040 The path to the Xauthority file to read X11 authentication 6041 data from, defaulting to the value in the environment variable 6042 `XAUTHORITY` or the file :file:`.Xauthority` in the user's 6043 home directory if that's not set. 6044 :param x11_single_connection: (optional) 6045 Whether or not to limit X11 forwarding to a single connection, 6046 defaulting to `False`. 6047 :param encoding: (optional) 6048 The default Unicode encoding to use for data exchanged on client 6049 sessions. 6050 :param errors: (optional) 6051 The default error handling strategy to apply on Unicode 6052 encode/decode errors. 6053 :param window: (optional) 6054 The default receive window size to set for client sessions. 6055 :param max_pktsize: (optional) 6056 The default maximum packet size to set for client sessions. 6057 :param config: (optional) 6058 Paths to OpenSSH client configuration files to load. This 6059 configuration will be used as a fallback to override the 6060 defaults for settings which are not explcitly specified using 6061 AsyncSSH's configuration options. 6062 6063 .. note:: Specifying configuration files when creating an 6064 :class:`SSHClientConnectionOptions` object will 6065 cause the config file to be read and parsed at 6066 the time of creation of the object, including 6067 evaluation of any conditional blocks. If you want 6068 the config to be parsed for every new connection, 6069 this argument should be added to the connect or 6070 listen calls instead. However, if you want to 6071 save the parsing overhead and your configuration 6072 doesn't depend on conditions that would change 6073 between calls, this argument may be an option. 6074 :param options: (optional) 6075 A previous set of options to use as the base to incrementally 6076 build up a configuration. When an option is not explicitly 6077 specified, its value will be pulled from this options object 6078 (if present) before falling back to the default value. 6079 :type client_factory: `callable` 6080 :type proxy_command: `str` or `list` of `str` 6081 :type known_hosts: *see* :ref:`SpecifyingKnownHosts` 6082 :type host_key_alias: `str` 6083 :type server_host_key_algs: `str` or `list` of `str` 6084 :type x509_trusted_certs: *see* :ref:`SpecifyingCertificates` 6085 :type x509_trusted_cert_paths: `list` of `str` 6086 :type x509_purposes: *see* :ref:`SpecifyingX509Purposes` 6087 :type username: `str` 6088 :type password: `str` 6089 :type client_host_keysign: `bool` or `str` 6090 :type client_host_keys: 6091 *see* :ref:`SpecifyingPrivateKeys` or :ref:`SpecifyingPublicKeys` 6092 :type client_host_certs: *see* :ref:`SpecifyingCertificates` 6093 :type client_host: `str` 6094 :type client_username: `str` 6095 :type client_keys: *see* :ref:`SpecifyingPrivateKeys` 6096 :type client_certs: *see* :ref:`SpecifyingCertificates` 6097 :type passphrase: `str` 6098 :type ignore_encrypted: `bool` 6099 :type host_based_auth: `bool` 6100 :type public_key_auth: `bool` 6101 :type kbdint_auth: `bool` 6102 :type password_auth: `bool` 6103 :type gss_host: `str` 6104 :type gss_kex: `bool` 6105 :type gss_auth: `bool` 6106 :type gss_delegate_creds: `bool` 6107 :type preferred_auth: `str` or `list` of `str` 6108 :type disable_trivial_auth: `bool` 6109 :type agent_path: `str` or :class:`SSHServerConnection` 6110 :type agent_identities: 6111 *see* :ref:`SpecifyingPublicKeys` and :ref:`SpecifyingCertificates` 6112 :type agent_forwarding: `bool` 6113 :type pkcs11_provider: `str` 6114 :type pkcs11_pin: `str` 6115 :type client_version: `str` 6116 :type kex_algs: `str` or `list` of `str` 6117 :type encryption_algs: `str` or `list` of `str` 6118 :type mac_algs: `str` or `list` of `str` 6119 :type compression_algs: `str` or `list` of `str` 6120 :type signature_algs: `str` or `list` of `str` 6121 :type rekey_bytes: *see* :ref:`SpecifyingByteCounts` 6122 :type rekey_seconds: *see* :ref:`SpecifyingTimeIntervals` 6123 :type connect_timeout: *see* :ref:`SpecifyingTimeIntervals` 6124 :type login_timeout: *see* :ref:`SpecifyingTimeIntervals` 6125 :type keepalive_interval: *see* :ref:`SpecifyingTimeIntervals` 6126 :type keepalive_count_max: `int` 6127 :type command: `str` 6128 :type subsystem: `str` 6129 :type env: `dict` 6130 :type send_env: `str` or `list` of `str` 6131 :type request_pty: `bool`, `'force'`, or `'auto'` 6132 :type term_type: `str` 6133 :type term_size: `tuple` of 2 or 4 `int` values 6134 :type term_modes: `dict` 6135 :type x11_forwarding: `bool` or `'ignore_failure'` 6136 :type x11_display: `str` 6137 :type x11_auth_path: `str` 6138 :type x11_single_connection: `bool` 6139 :type encoding: `str` 6140 :type errors: `str` 6141 :type window: `int` 6142 :type max_pktsize: `int` 6143 :type config: `list` of `str` 6144 :type options: :class:`SSHClientConnectionOptions` 6145 6146 """ 6147 6148 # pylint: disable=arguments-differ 6149 def prepare(self, last_config=None, config=(), reload=False, 6150 client_factory=None, client_version=(), host='', port=(), 6151 tunnel=(), proxy_command=(), family=(), local_addr=(), 6152 tcp_keepalive=(), kex_algs=(), encryption_algs=(), mac_algs=(), 6153 compression_algs=(), signature_algs=(), host_based_auth=(), 6154 public_key_auth=(), kbdint_auth=(), password_auth=(), 6155 x509_trusted_certs=(), x509_trusted_cert_paths=(), 6156 x509_purposes='secureShellServer', rekey_bytes=(), 6157 rekey_seconds=(), connect_timeout=(), 6158 login_timeout=_DEFAULT_LOGIN_TIMEOUT, keepalive_interval=(), 6159 keepalive_count_max=(), known_hosts=(), host_key_alias=None, 6160 server_host_key_algs=(), username=(), password=None, 6161 client_host_keysign=(), client_host_keys=None, 6162 client_host_certs=(), client_host=None, client_username=(), 6163 client_keys=(), client_certs=(), passphrase=None, 6164 ignore_encrypted=False, gss_host=(), gss_kex=(), gss_auth=(), 6165 gss_delegate_creds=(), preferred_auth=(), 6166 disable_trivial_auth=False, agent_path=(), 6167 agent_identities=(), agent_forwarding=(), pkcs11_provider=(), 6168 pkcs11_pin=None, command=(), subsystem=None, env=(), 6169 send_env=(), request_pty=(), term_type=None, term_size=None, 6170 term_modes=None, x11_forwarding=(), x11_display=None, 6171 x11_auth_path=None, x11_single_connection=False, 6172 encoding='utf-8', errors='strict', window=_DEFAULT_WINDOW, 6173 max_pktsize=_DEFAULT_MAX_PKTSIZE): 6174 """Prepare client connection configuration options""" 6175 6176 try: 6177 local_username = getpass.getuser() 6178 except KeyError: 6179 raise ValueError('Unknown local username: set one of ' 6180 'LOGNAME, USER, LNAME, or USERNAME in ' 6181 'the environment') from None 6182 6183 if config == () and not last_config: 6184 default_config = Path('~', '.ssh', 'config').expanduser() 6185 config = [default_config] if os.access(default_config, 6186 os.R_OK) else [] 6187 6188 config = SSHClientConfig.load(last_config, config, reload, 6189 local_username, username, host, port) 6190 6191 if x509_trusted_certs == (): 6192 default_x509_certs = Path('~', '.ssh', 'ca-bundle.crt').expanduser() 6193 6194 if os.access(default_x509_certs, os.R_OK): 6195 x509_trusted_certs = str(default_x509_certs) 6196 6197 if x509_trusted_cert_paths == (): 6198 default_x509_cert_path = Path('~', '.ssh', 'crt').expanduser() 6199 6200 if default_x509_cert_path.is_dir(): 6201 x509_trusted_cert_paths = [str(default_x509_cert_path)] 6202 6203 if connect_timeout == (): 6204 connect_timeout = config.get('ConnectTimeout', None) 6205 6206 if keepalive_interval == (): 6207 keepalive_interval = config.get('ServerAliveInterval', 6208 _DEFAULT_KEEPALIVE_INTERVAL) 6209 6210 if keepalive_count_max == (): 6211 keepalive_count_max = config.get('ServerAliveCountMax', 6212 _DEFAULT_KEEPALIVE_COUNT_MAX) 6213 6214 super().prepare(config, client_factory or SSHClient, client_version, 6215 host, port, tunnel, passphrase, proxy_command, family, 6216 local_addr, tcp_keepalive, kex_algs, encryption_algs, 6217 mac_algs, compression_algs, signature_algs, 6218 host_based_auth, public_key_auth, kbdint_auth, 6219 password_auth, x509_trusted_certs, 6220 x509_trusted_cert_paths, x509_purposes, rekey_bytes, 6221 rekey_seconds, connect_timeout, login_timeout, 6222 keepalive_interval, keepalive_count_max) 6223 6224 if known_hosts == (): 6225 known_hosts = (config.get('UserKnownHostsFile', []) + \ 6226 config.get('GlobalKnownHostsFile', [])) or () 6227 6228 self.known_hosts = known_hosts 6229 self.host_key_alias = host_key_alias or config.get('HostKeyAlias') 6230 6231 self.server_host_key_algs = server_host_key_algs 6232 6233 # Just validate the input here -- the actual server host key 6234 # selection is done later, after the known_hosts lookup is done. 6235 _select_host_key_algs(server_host_key_algs, 6236 config.get('HostKeyAlgorithms', ()), []) 6237 6238 if username == (): 6239 username = config.get('User', local_username) 6240 6241 self.username = saslprep(username) 6242 self.password = password 6243 6244 if client_host_keysign == (): 6245 client_host_keysign = config.get('EnableSSHKeySign', False) 6246 6247 if client_host_keysign: 6248 client_host_keysign = find_keysign(client_host_keysign) 6249 6250 if client_host_keys: 6251 client_host_keys = load_public_keys(client_host_keys) 6252 else: 6253 client_host_keys = load_default_host_public_keys() 6254 else: 6255 client_host_keys = load_keypairs(client_host_keys, passphrase, 6256 client_host_certs) 6257 6258 if client_username == (): 6259 client_username = local_username 6260 6261 self.client_host_keysign = client_host_keysign 6262 self.client_host_keys = client_host_keys 6263 self.client_host = client_host 6264 self.client_username = saslprep(client_username) 6265 6266 if gss_kex == (): 6267 gss_kex = config.get('GSSAPIKeyExchange', True) 6268 6269 if gss_auth == (): 6270 gss_auth = config.get('GSSAPIAuthentication', True) 6271 6272 if gss_delegate_creds == (): 6273 gss_delegate_creds = config.get('GSSAPIDelegateCredentials', False) 6274 6275 self.gss_host = gss_host 6276 self.gss_kex = gss_kex 6277 self.gss_auth = gss_auth 6278 self.gss_delegate_creds = gss_delegate_creds 6279 6280 if preferred_auth == (): 6281 preferred_auth = config.get('PreferredAuthentications', ()) 6282 6283 if isinstance(preferred_auth, str): 6284 preferred_auth = preferred_auth.split(',') 6285 6286 self.preferred_auth = preferred_auth 6287 6288 self.disable_trivial_auth = disable_trivial_auth 6289 6290 if agent_path == (): 6291 agent_path = config.get('IdentityAgent', ()) 6292 6293 if agent_path == (): 6294 agent_path = os.environ.get('SSH_AUTH_SOCK', ()) 6295 6296 if agent_path: 6297 agent_path = str(Path(agent_path).expanduser()) 6298 6299 if pkcs11_provider == (): 6300 pkcs11_provider = config.get('PKCS11Provider') 6301 6302 self.agent_path = None 6303 self.pkcs11_provider = None 6304 self.pkcs11_pin = None 6305 6306 if client_keys == (): 6307 client_keys = config.get('IdentityFile', ()) 6308 6309 if client_certs == (): 6310 client_certs = config.get('CertificateFile', ()) 6311 6312 identities_only = config.get('IdentitiesOnly') 6313 6314 if agent_identities == (): 6315 if identities_only: 6316 agent_identities = client_keys 6317 else: 6318 agent_identities = None 6319 6320 if agent_identities: 6321 self.agent_identities = load_identities(agent_identities, 6322 identities_only) 6323 elif agent_identities == (): 6324 self.agent_identities = load_default_identities() 6325 else: 6326 self.agent_identities = None 6327 6328 if client_keys: 6329 self.client_keys = load_keypairs(client_keys, passphrase, 6330 client_certs, identities_only, 6331 ignore_encrypted) 6332 else: 6333 if client_keys == (): 6334 client_keys = load_default_keypairs(passphrase, client_certs) 6335 6336 self.client_keys = client_keys 6337 6338 if client_keys is not None: 6339 self.agent_path = agent_path 6340 self.pkcs11_provider = pkcs11_provider 6341 self.pkcs11_pin = pkcs11_pin 6342 6343 if agent_forwarding == (): 6344 agent_forwarding = config.get('ForwardAgent', False) 6345 6346 self.agent_forward_path = agent_path if agent_forwarding else None 6347 6348 if command == (): 6349 command = config.get('RemoteCommand') 6350 6351 if env == (): 6352 env = config.get('SetEnv') 6353 6354 if send_env == (): 6355 send_env = config.get('SendEnv') 6356 6357 if request_pty == (): 6358 request_pty = config.get('RequestTTY', True) 6359 6360 if x11_forwarding == (): 6361 x11_forwarding = config.get('ForwardX11Trusted') and \ 6362 'ignore_failure' 6363 6364 self.command = command 6365 self.subsystem = subsystem 6366 self.env = env 6367 self.send_env = send_env 6368 self.request_pty = request_pty 6369 self.term_type = term_type 6370 self.term_size = term_size 6371 self.term_modes = term_modes 6372 self.x11_forwarding = x11_forwarding 6373 self.x11_display = x11_display 6374 self.x11_auth_path = x11_auth_path 6375 self.x11_single_connection = x11_single_connection 6376 self.encoding = encoding 6377 self.errors = errors 6378 self.window = window 6379 self.max_pktsize = max_pktsize 6380 6381 6382class SSHServerConnectionOptions(SSHConnectionOptions): 6383 """SSH server connection options 6384 6385 The following options are available to control the acceptance 6386 of SSH server connections: 6387 6388 :param server_factory: 6389 A `callable` which returns an :class:`SSHServer` object that will 6390 be created for each new connection. 6391 :param proxy_command: (optional) 6392 A string or list of strings specifying a command and arguments 6393 to run when using :func:`connect_reverse` to make a reverse 6394 direction connection to an SSH client. Data will be forwarded 6395 to this process over stdin/stdout instead of opening a TCP 6396 connection. If specified as a string, standard shell quoting 6397 will be applied when splitting the command and its arguments. 6398 :param server_host_keys: (optional) 6399 A list of private keys and optional certificates which can be 6400 used by the server as a host key. Either this argument or 6401 `gss_host` must be specified. If this is not specified, 6402 only GSS-based key exchange will be supported. 6403 :param server_host_certs: (optional) 6404 A list of optional certificates which can be paired with the 6405 provided server host keys. 6406 :param passphrase: (optional) 6407 The passphrase to use to decrypt server host keys when loading 6408 them, if they are encrypted. If this is not specified, only 6409 unencrypted server host keys can be loaded. If the keys passed 6410 into server_host_keys are already loaded, this argument is 6411 ignored. 6412 :param known_client_hosts: (optional) 6413 A list of client hosts which should be trusted to perform 6414 host-based client authentication. If this is not specified, 6415 host-based client authentication will be not be performed. 6416 :param trust_client_host: (optional) 6417 Whether or not to use the hostname provided by the client 6418 when performing host-based authentication. By default, the 6419 client-provided hostname is not trusted and is instead 6420 determined by doing a reverse lookup of the IP address the 6421 client connected from. 6422 :param authorized_client_keys: (optional) 6423 A list of authorized user and CA public keys which should be 6424 trusted for certifcate-based client public key authentication. 6425 :param x509_trusted_certs: (optional) 6426 A list of certificates which should be trusted for X.509 client 6427 certificate authentication. If this argument is explicitly set 6428 to `None`, X.509 client certificate authentication will not 6429 be performed. 6430 6431 .. note:: X.509 certificates to trust can also be provided 6432 through an :ref:`authorized_keys <AuthorizedKeys>` 6433 file if they are converted into OpenSSH format. 6434 This allows their trust to be limited to only 6435 specific client IPs or user names and allows 6436 SSH functions to be restricted when these 6437 certificates are used. 6438 :param x509_trusted_cert_paths: (optional) 6439 A list of path names to "hash directories" containing certificates 6440 which should be trusted for X.509 client certificate authentication. 6441 Each certificate should be in a separate file with a name of the 6442 form *hash.number*, where *hash* is the OpenSSL hash value of the 6443 certificate subject name and *number* is an integer counting up 6444 from zero if multiple certificates have the same hash. 6445 :param x509_purposes: (optional) 6446 A list of purposes allowed in the ExtendedKeyUsage of a 6447 certificate used for X.509 client certificate authentication, 6448 defulting to 'secureShellClient'. If this argument is explicitly 6449 set to `None`, the client certificate's ExtendedKeyUsage will 6450 not be checked. 6451 :param host_based_auth: (optional) 6452 Whether or not to allow host-based authentication. By default, 6453 host-based authentication is enabled if known client host keys 6454 are specified or if callbacks to validate client host keys 6455 are made available. 6456 :param public_key_auth: (optional) 6457 Whether or not to allow public key authentication. By default, 6458 public key authentication is enabled if authorized client keys 6459 are specified or if callbacks to validate client keys are made 6460 available. 6461 :param kbdint_auth: (optional) 6462 Whether or not to allow keyboard-interactive authentication. By 6463 default, keyboard-interactive authentication is enabled if the 6464 callbacks to generate challenges are made available. 6465 :param password_auth: (optional) 6466 Whether or not to allow password authentication. By default, 6467 password authentication is enabled if callbacks to validate a 6468 password are made available. 6469 :param gss_host: (optional) 6470 The principal name to use for the host in GSS key exchange and 6471 authentication. If not specified, the value returned by 6472 :func:`socket.gethostname` will be used if it is a fully qualified 6473 name. Otherwise, the value used by :func:`socket.getfqdn` will be 6474 used. If this argument is explicitly set to `None`, GSS 6475 key exchange and authentication will not be performed. 6476 :param gss_kex: (optional) 6477 Whether or not to allow GSS key exchange. By default, GSS 6478 key exchange is enabled. 6479 :param gss_auth: (optional) 6480 Whether or not to allow GSS authentication. By default, GSS 6481 authentication is enabled. 6482 :param allow_pty: (optional) 6483 Whether or not to allow allocation of a pseudo-tty in sessions, 6484 defaulting to `True` 6485 :param line_editor: (optional) 6486 Whether or not to enable input line editing on sessions which 6487 have a pseudo-tty allocated, defaulting to `True` 6488 :param line_history: (int) 6489 The number of lines of input line history to store in the 6490 line editor when it is enabled, defaulting to 1000 6491 :param max_line_length: (int) 6492 The maximum number of characters allowed in an input line when 6493 the line editor is enabled, defaulting to 1024 6494 :param rdns_lookup: (optional) 6495 Whether or not to perform reverse DNS lookups on the client's 6496 IP address to enable hostname-based matches in authorized key 6497 file "from" options and "Match Host" config options, defaulting 6498 to `False`. 6499 :param x11_forwarding: (optional) 6500 Whether or not to allow forwarding of X11 connections back 6501 to the client when the client supports it, defaulting to `False` 6502 :param x11_auth_path: (optional) 6503 The path to the Xauthority file to write X11 authentication 6504 data to, defaulting to the value in the environment variable 6505 `XAUTHORITY` or the file :file:`.Xauthority` in the user's 6506 home directory if that's not set 6507 :param agent_forwarding: (optional) 6508 Whether or not to allow forwarding of ssh-agent requests back 6509 to the client when the client supports it, defaulting to `True` 6510 :param process_factory: (optional) 6511 A `callable` or coroutine handler function which takes an AsyncSSH 6512 :class:`SSHServerProcess` argument that will be called each time a 6513 new shell, exec, or subsystem other than SFTP is requested by the 6514 client. If set, this takes precedence over the `session_factory` 6515 argument. 6516 :param session_factory: (optional) 6517 A `callable` or coroutine handler function which takes AsyncSSH 6518 stream objects for stdin, stdout, and stderr that will be called 6519 each time a new shell, exec, or subsystem other than SFTP is 6520 requested by the client. If not specified, sessions are rejected 6521 by default unless the :meth:`session_requested() 6522 <SSHServer.session_requested>` method is overridden on the 6523 :class:`SSHServer` object returned by `server_factory` to make 6524 this decision. 6525 :param encoding: (optional) 6526 The Unicode encoding to use for data exchanged on sessions on 6527 this server, defaulting to UTF-8 (ISO 10646) format. If `None` 6528 is passed in, the application can send and receive raw bytes. 6529 :param errors: (optional) 6530 The error handling strategy to apply on Unicode encode/decode 6531 errors of data exchanged on sessions on this server, defaulting 6532 to 'strict'. 6533 :param sftp_factory: (optional) 6534 A `callable` which returns an :class:`SFTPServer` object that 6535 will be created each time an SFTP session is requested by the 6536 client, or `True` to use the base :class:`SFTPServer` class 6537 to handle SFTP requests. If not specified, SFTP sessions are 6538 rejected by default. 6539 :param allow_scp: (optional) 6540 Whether or not to allow incoming scp requests to be accepted. 6541 This option can only be used in conjunction with `sftp_factory`. 6542 If not specified, scp requests will be passed as regular 6543 commands to the `process_factory` or `session_factory`. 6544 to the client when the client supports it, defaulting to `True` 6545 :param window: (optional) 6546 The receive window size for sessions on this server 6547 :param max_pktsize: (optional) 6548 The maximum packet size for sessions on this server 6549 :param server_version: (optional) 6550 An ASCII string to advertise to SSH clients as the version of 6551 this server, defaulting to `'AsyncSSH'` and its version number. 6552 :param kex_algs: (optional) 6553 A list of allowed key exchange algorithms in the SSH handshake, 6554 taken from :ref:`key exchange algorithms <KexAlgs>` 6555 :param encryption_algs: (optional) 6556 A list of encryption algorithms to use during the SSH handshake, 6557 taken from :ref:`encryption algorithms <EncryptionAlgs>` 6558 :param mac_algs: (optional) 6559 A list of MAC algorithms to use during the SSH handshake, taken 6560 from :ref:`MAC algorithms <MACAlgs>` 6561 :param compression_algs: (optional) 6562 A list of compression algorithms to use during the SSH handshake, 6563 taken from :ref:`compression algorithms <CompressionAlgs>`, or 6564 `None` to disable compression 6565 :param signature_algs: (optional) 6566 A list of public key signature algorithms to use during the SSH 6567 handshake, taken from :ref:`signature algorithms <SignatureAlgs>` 6568 :param rekey_bytes: (optional) 6569 The number of bytes which can be sent before the SSH session 6570 key is renegotiated, defaulting to 1 GB 6571 :param rekey_seconds: (optional) 6572 The maximum time in seconds before the SSH session key is 6573 renegotiated, defaulting to 1 hour 6574 :param connect_timeout: (optional) 6575 The maximum time in seconds allowed to complete an outbound 6576 SSH connection. This includes the time to establish the TCP 6577 connection and the time to perform the initial SSH protocol 6578 handshake, key exchange, and authentication. This is disabled 6579 by default, relying on the system's default TCP connect timeout 6580 and AsyncSSH's login timeout. 6581 :param login_timeout: (optional) 6582 The maximum time in seconds allowed for authentication to 6583 complete, defaulting to 2 minutes. Setting this to 0 6584 will disable the login timeout. 6585 6586 .. note:: This timeout only applies after the SSH TCP 6587 connection is established. To set a timeout 6588 which includes establishing the TCP connection, 6589 use the `connect_timeout` argument above. 6590 :param keepalive_interval: (optional) 6591 The time in seconds to wait before sending a keepalive message 6592 if no data has been received from the client. This defaults to 6593 0, which disables sending these messages. 6594 :param keepalive_count_max: (optional) 6595 The maximum number of keepalive messages which will be sent 6596 without getting a response before disconnecting a client. 6597 This defaults to 3, but only applies when keepalive_interval is 6598 non-zero. 6599 :param tcp_keepalive: (optional) 6600 Whether or not to enable keepalive probes at the TCP level to 6601 detect broken connections, defaulting to `True` 6602 :param config: (optional) 6603 Paths to OpenSSH server configuration files to load. This 6604 configuration will be used as a fallback to override the 6605 defaults for settings which are not explcitly specified using 6606 AsyncSSH's configuration options. 6607 6608 .. note:: Specifying configuration files when creating an 6609 :class:`SSHServerConnectionOptions` object will 6610 cause the config file to be read and parsed at 6611 the time of creation of the object, including 6612 evaluation of any conditional blocks. If you want 6613 the config to be parsed for every new connection, 6614 this argument should be added to the connect or 6615 listen calls instead. However, if you want to 6616 save the parsing overhead and your configuration 6617 doesn't depend on conditions that would change 6618 between calls, this argument may be an option. 6619 :param options: (optional) 6620 A previous set of options to use as the base to incrementally 6621 build up a configuration. When an option is not explicitly 6622 specified, its value will be pulled from this options object 6623 (if present) before falling back to the default value. 6624 :type server_factory: `callable` 6625 :type proxy_command: `str` or `list` of `str` 6626 :type family: `socket.AF_UNSPEC`, `socket.AF_INET`, or `socket.AF_INET6` 6627 :type server_host_keys: *see* :ref:`SpecifyingPrivateKeys` 6628 :type server_host_certs: *see* :ref:`SpecifyingCertificates` 6629 :type passphrase: `str` 6630 :type known_client_hosts: *see* :ref:`SpecifyingKnownHosts` 6631 :type trust_client_host: `bool` 6632 :type authorized_client_keys: *see* :ref:`SpecifyingAuthorizedKeys` 6633 :type x509_trusted_certs: *see* :ref:`SpecifyingCertificates` 6634 :type x509_trusted_cert_paths: `list` of `str` 6635 :type x509_purposes: *see* :ref:`SpecifyingX509Purposes` 6636 :type host_based_auth: `bool` 6637 :type public_key_auth: `bool` 6638 :type kbdint_auth: `bool` 6639 :type password_auth: `bool` 6640 :type gss_host: `str` 6641 :type gss_kex: `bool` 6642 :type gss_auth: `bool` 6643 :type allow_pty: `bool` 6644 :type line_editor: `bool` 6645 :type line_history: `int` 6646 :type max_line_length: `int` 6647 :type rdns_lookup: `bool` 6648 :type x11_forwarding: `bool` 6649 :type x11_auth_path: `str` 6650 :type agent_forwarding: `bool` 6651 :type process_factory: `callable` 6652 :type session_factory: `callable` 6653 :type encoding: `str` 6654 :type errors: `str` 6655 :type sftp_factory: `callable` 6656 :type allow_scp: `bool` 6657 :type window: `int` 6658 :type max_pktsize: `int` 6659 :type server_version: `str` 6660 :type kex_algs: `str` or `list` of `str` 6661 :type encryption_algs: `str` or `list` of `str` 6662 :type mac_algs: `str` or `list` of `str` 6663 :type compression_algs: `str` or `list` of `str` 6664 :type signature_algs: `str` or `list` of `str` 6665 :type rekey_bytes: *see* :ref:`SpecifyingByteCounts` 6666 :type rekey_seconds: *see* :ref:`SpecifyingTimeIntervals` 6667 :type connect_timeout: *see* :ref:`SpecifyingTimeIntervals` 6668 :type login_timeout: *see* :ref:`SpecifyingTimeIntervals` 6669 :type keepalive_interval: *see* :ref:`SpecifyingTimeIntervals` 6670 :type keepalive_count_max: `int` 6671 :type config: `list` of `str` 6672 :type options: :class:`SSHServerConnectionOptions` 6673 6674 """ 6675 6676 # pylint: disable=arguments-differ 6677 def prepare(self, last_config=None, config=(), reload=False, 6678 accept_addr='', accept_port=0, username='', client_host=None, 6679 client_addr='', server_factory=None, server_version=(), 6680 host='', port=(), tunnel=(), proxy_command=(), family=(), 6681 local_addr=(), tcp_keepalive=(), kex_algs=(), 6682 encryption_algs=(), mac_algs=(), compression_algs=(), 6683 signature_algs=(), host_based_auth=(), public_key_auth=(), 6684 kbdint_auth=(), password_auth=(), x509_trusted_certs=(), 6685 x509_trusted_cert_paths=(), x509_purposes='secureShellClient', 6686 rekey_bytes=(), rekey_seconds=(), connect_timeout=None, 6687 login_timeout=(), keepalive_interval=(), 6688 keepalive_count_max=(), server_host_keys=(), 6689 server_host_certs=(), passphrase=None, 6690 known_client_hosts=None, trust_client_host=False, 6691 authorized_client_keys=(), gss_host=(), gss_kex=(), 6692 gss_auth=(), allow_pty=(), line_editor=True, 6693 line_history=_DEFAULT_LINE_HISTORY, 6694 max_line_length=_DEFAULT_MAX_LINE_LENGTH, rdns_lookup=(), 6695 x11_forwarding=False, x11_auth_path=None, agent_forwarding=(), 6696 process_factory=None, session_factory=None, encoding='utf-8', 6697 errors='strict', sftp_factory=None, allow_scp=False, 6698 window=_DEFAULT_WINDOW, max_pktsize=_DEFAULT_MAX_PKTSIZE): 6699 """Prepare server connection configuration options""" 6700 6701 config = SSHServerConfig.load(last_config, config, reload, 6702 accept_addr, accept_port, username, 6703 client_host, client_addr) 6704 6705 if login_timeout == (): 6706 login_timeout = config.get('LoginGraceTime', 6707 _DEFAULT_LOGIN_TIMEOUT) 6708 6709 if keepalive_interval == (): 6710 keepalive_interval = config.get('ClientAliveInterval', 6711 _DEFAULT_KEEPALIVE_INTERVAL) 6712 6713 if keepalive_count_max == (): 6714 keepalive_count_max = config.get('ClientAliveCountMax', 6715 _DEFAULT_KEEPALIVE_COUNT_MAX) 6716 6717 super().prepare(config, server_factory or SSHServer, server_version, 6718 host, port, tunnel, passphrase, proxy_command, family, 6719 local_addr, tcp_keepalive, kex_algs, encryption_algs, 6720 mac_algs, compression_algs, signature_algs, 6721 host_based_auth, public_key_auth, kbdint_auth, 6722 password_auth, x509_trusted_certs, 6723 x509_trusted_cert_paths, x509_purposes, 6724 rekey_bytes, rekey_seconds, connect_timeout, 6725 login_timeout, keepalive_interval, keepalive_count_max) 6726 6727 if server_host_keys == (): 6728 server_host_keys = config.get('HostKey') 6729 6730 if server_host_certs == (): 6731 server_host_certs = config.get('HostCertificate', ()) 6732 6733 server_keys = load_keypairs(server_host_keys, passphrase, 6734 server_host_certs) 6735 6736 self.server_host_keys = OrderedDict() 6737 6738 for keypair in server_keys: 6739 for alg in keypair.host_key_algorithms: 6740 if alg in self.server_host_keys: 6741 raise ValueError('Multiple keys of type %s found' % 6742 alg.decode('ascii')) 6743 6744 self.server_host_keys[alg] = keypair 6745 6746 self.known_client_hosts = known_client_hosts 6747 self.trust_client_host = trust_client_host 6748 6749 if authorized_client_keys == () and reload: 6750 authorized_client_keys = config.get('AuthorizedKeysFile') 6751 6752 if isinstance(authorized_client_keys, (str, list)): 6753 self.authorized_client_keys = \ 6754 read_authorized_keys(authorized_client_keys) 6755 else: 6756 self.authorized_client_keys = authorized_client_keys 6757 6758 if gss_host == (): 6759 gss_host = socket.gethostname() 6760 6761 if '.' not in gss_host: 6762 gss_host = socket.getfqdn() 6763 6764 if gss_kex == (): 6765 gss_kex = config.get('GSSAPIKeyExchange', True) 6766 6767 if gss_auth == (): 6768 gss_auth = config.get('GSSAPIAuthentication', True) 6769 6770 self.gss_host = gss_host 6771 self.gss_kex = gss_kex 6772 self.gss_auth = gss_auth 6773 6774 if not server_keys and not gss_host: 6775 raise ValueError('No server host keys provided') 6776 6777 if allow_pty == (): 6778 allow_pty = config.get('PermitTTY', True) 6779 6780 if agent_forwarding == (): 6781 agent_forwarding = config.get('AllowAgentForwarding', True) 6782 6783 if rdns_lookup == (): 6784 rdns_lookup = config.get('UseDNS', False) 6785 6786 self.allow_pty = allow_pty 6787 self.line_editor = line_editor 6788 self.line_history = line_history 6789 self.max_line_length = max_line_length 6790 self.rdns_lookup = rdns_lookup 6791 self.x11_forwarding = x11_forwarding 6792 self.x11_auth_path = x11_auth_path 6793 self.agent_forwarding = agent_forwarding 6794 self.process_factory = process_factory 6795 self.session_factory = session_factory 6796 self.encoding = encoding 6797 self.errors = errors 6798 self.sftp_factory = SFTPServer if sftp_factory is True else sftp_factory 6799 self.allow_scp = allow_scp 6800 self.window = window 6801 self.max_pktsize = max_pktsize 6802 6803 6804@async_context_manager 6805async def connect(host, port=(), *, tunnel=(), family=(), flags=0, 6806 local_addr=None, config=(), options=None, **kwargs): 6807 """Make an SSH client connection 6808 6809 This function is a coroutine which can be run to create an outbound SSH 6810 client connection to the specified host and port. 6811 6812 When successful, the following steps occur: 6813 6814 1. The connection is established and an instance of 6815 :class:`SSHClientConnection` is created to represent it. 6816 2. The `client_factory` is called without arguments and should 6817 return an instance of :class:`SSHClient` or a subclass. 6818 3. The client object is tied to the connection and its 6819 :meth:`connection_made() <SSHClient.connection_made>` method 6820 is called. 6821 4. The SSH handshake and authentication process is initiated, 6822 calling methods on the client object if needed. 6823 5. When authentication completes successfully, the client's 6824 :meth:`auth_completed() <SSHClient.auth_completed>` method is 6825 called. 6826 6. The coroutine returns the :class:`SSHClientConnection`. At 6827 this point, the connection is ready for sessions to be opened 6828 or port forwarding to be set up. 6829 6830 If an error occurs, it will be raised as an exception and the partially 6831 open connection and client objects will be cleaned up. 6832 6833 :param host: 6834 The hostname or address to connect to. 6835 :param port: (optional) 6836 The port number to connect to. If not specified, the default 6837 SSH port is used. 6838 :param tunnel: (optional) 6839 An existing SSH client connection that this new connection should 6840 be tunneled over. If set, a direct TCP/IP tunnel will be opened 6841 over this connection to the requested host and port rather than 6842 connecting directly via TCP. A string of the form 6843 [user@]host[:port] may also be specified, in which case a 6844 connection will first be made to that host and it will then be 6845 used as a tunnel. 6846 :param family: (optional) 6847 The address family to use when creating the socket. By default, 6848 the address family is automatically selected based on the host. 6849 :param flags: (optional) 6850 The flags to pass to getaddrinfo() when looking up the host address 6851 :param local_addr: (optional) 6852 The host and port to bind the socket to before connecting 6853 :param config: (optional) 6854 Paths to OpenSSH client configuration files to load. This 6855 configuration will be used as a fallback to override the 6856 defaults for settings which are not explcitly specified using 6857 AsyncSSH's configuration options. If no paths are specified, 6858 an attempt will be made to load the configuration from the file 6859 :file:`.ssh/config`. If this argument is explicitly set to 6860 `None`, no OpenSSH configuration files will be loaded. See 6861 :ref:`SupportedClientConfigOptions` for details on what 6862 configuration options are currently supported. 6863 :param options: (optional) 6864 Options to use when establishing the SSH client connection. These 6865 options can be specified either through this parameter or as direct 6866 keyword arguments to this function. 6867 :type host: `str` 6868 :type port: `int` 6869 :type tunnel: :class:`SSHClientConnection` or `str` 6870 :type family: `socket.AF_UNSPEC`, `socket.AF_INET`, or `socket.AF_INET6` 6871 :type flags: flags to pass to :meth:`getaddrinfo() <socket.getaddrinfo>` 6872 :type local_addr: tuple of `str` and `int` 6873 :type config: `list` of `str` 6874 :type options: :class:`SSHClientConnectionOptions` 6875 6876 :returns: :class:`SSHClientConnection` 6877 6878 """ 6879 6880 def conn_factory(): 6881 """Return an SSH client connection factory""" 6882 6883 return SSHClientConnection(loop, options, wait='auth') 6884 6885 loop = asyncio.get_event_loop() 6886 6887 options = SSHClientConnectionOptions(options, config=config, host=host, 6888 port=port, tunnel=tunnel, 6889 family=family, local_addr=local_addr, 6890 **kwargs) 6891 6892 return await asyncio.wait_for( 6893 _connect(options, loop, flags, conn_factory, 6894 'Opening SSH connection to'), 6895 timeout=options.connect_timeout) 6896 6897 6898@async_context_manager 6899async def connect_reverse(host, port=(), *, tunnel=(), family=(), flags=0, 6900 local_addr=None, config=(), options=None, **kwargs): 6901 """Create a reverse direction SSH connection 6902 6903 This function is a coroutine which behaves similar to :func:`connect`, 6904 making an outbound TCP connection to a remote server. However, instead 6905 of starting up an SSH client which runs on that outbound connection, 6906 this function starts up an SSH server, expecting the remote system to 6907 start up a reverse-direction SSH client. 6908 6909 Arguments to this function are the same as :func:`connect`, except 6910 that the `options` are of type :class:`SSHServerConnectionOptions` 6911 instead of :class:`SSHClientConnectionOptions`. 6912 6913 :param host: 6914 The hostname or address to connect to. 6915 :param port: (optional) 6916 The port number to connect to. If not specified, the default 6917 SSH port is used. 6918 :param tunnel: (optional) 6919 An existing SSH client connection that this new connection should 6920 be tunneled over. If set, a direct TCP/IP tunnel will be opened 6921 over this connection to the requested host and port rather than 6922 connecting directly via TCP. A string of the form 6923 [user@]host[:port] may also be specified, in which case a 6924 connection will first be made to that host and it will then be 6925 used as a tunnel. 6926 :param family: (optional) 6927 The address family to use when creating the socket. By default, 6928 the address family is automatically selected based on the host. 6929 :param flags: (optional) 6930 The flags to pass to getaddrinfo() when looking up the host address 6931 :param local_addr: (optional) 6932 The host and port to bind the socket to before connecting 6933 :param config: (optional) 6934 Paths to OpenSSH server configuration files to load. This 6935 configuration will be used as a fallback to override the 6936 defaults for settings which are not explcitly specified using 6937 AsyncSSH's configuration options. By default, no OpenSSH 6938 configuration files will be loaded. See 6939 :ref:`SupportedServerConfigOptions` for details on what 6940 configuration options are currently supported. 6941 :param options: (optional) 6942 Options to use when starting the reverse-direction SSH server. 6943 These options can be specified either through this parameter 6944 or as direct keyword arguments to this function. 6945 :type host: `str` 6946 :type port: `int` 6947 :type tunnel: :class:`SSHClientConnection` or `str` 6948 :type family: `socket.AF_UNSPEC`, `socket.AF_INET`, or `socket.AF_INET6` 6949 :type flags: flags to pass to :meth:`getaddrinfo() <socket.getaddrinfo>` 6950 :type local_addr: tuple of `str` and `int` 6951 :type config: `list` of `str` 6952 :type options: :class:`SSHServerConnectionOptions` 6953 6954 :returns: :class:`SSHServerConnection` 6955 6956 """ 6957 6958 def conn_factory(): 6959 """Return an SSH client connection factory""" 6960 6961 return SSHServerConnection(loop, options, wait='auth') 6962 6963 loop = asyncio.get_event_loop() 6964 6965 options = SSHServerConnectionOptions(options, config=config, host=host, 6966 port=port, tunnel=tunnel, 6967 family=family, local_addr=local_addr, 6968 **kwargs) 6969 6970 return await asyncio.wait_for( 6971 _connect(options, loop, flags, conn_factory, 6972 'Opening reverse SSH connection to'), 6973 timeout=options.connect_timeout) 6974 6975 6976@async_context_manager 6977async def listen(host='', port=(), tunnel=(), family=(), 6978 flags=socket.AI_PASSIVE, backlog=100, reuse_address=None, 6979 reuse_port=None, acceptor=None, error_handler=None, 6980 config=(), options=None, **kwargs): 6981 """Start an SSH server 6982 6983 This function is a coroutine which can be run to create an SSH server 6984 listening on the specified host and port. The return value is an 6985 :class:`SSHAcceptor` which can be used to shut down the listener. 6986 6987 :param host: (optional) 6988 The hostname or address to listen on. If not specified, listeners 6989 are created for all addresses. 6990 :param port: (optional) 6991 The port number to listen on. If not specified, the default 6992 SSH port is used. 6993 :param tunnel: (optional) 6994 An existing SSH client connection that this new listener should 6995 be forwarded over. If set, a remote TCP/IP listener will be 6996 opened on this connection on the requested host and port rather 6997 than listening directly via TCP. A string of the form 6998 [user@]host[:port] may also be specified, in which case a 6999 connection will first be made to that host and it will then be 7000 used as a tunnel. 7001 :param family: (optional) 7002 The address family to use when creating the server. By default, 7003 the address families are automatically selected based on the host. 7004 :param flags: (optional) 7005 The flags to pass to getaddrinfo() when looking up the host 7006 :param backlog: (optional) 7007 The maximum number of queued connections allowed on listeners 7008 :param reuse_address: (optional) 7009 Whether or not to reuse a local socket in the TIME_WAIT state 7010 without waiting for its natural timeout to expire. If not 7011 specified, this will be automatically set to `True` on UNIX. 7012 :param reuse_port: (optional) 7013 Whether or not to allow this socket to be bound to the same 7014 port other existing sockets are bound to, so long as they all 7015 set this flag when being created. If not specified, the 7016 default is to not allow this. This option is not supported 7017 on Windows or Python versions prior to 3.4.4. 7018 :param acceptor: (optional) 7019 A `callable` or coroutine which will be called when the 7020 SSH handshake completes on an accepted connection, taking 7021 the :class:`SSHServerConnection` as an argument. 7022 :param error_handler: (optional) 7023 A `callable` which will be called whenever the SSH handshake 7024 fails on an accepted connection. It is called with the failed 7025 :class:`SSHServerConnection` and an exception object describing 7026 the failure. If not specified, failed handshakes result in the 7027 connection object being silently cleaned up. 7028 :param config: (optional) 7029 Paths to OpenSSH server configuration files to load. This 7030 configuration will be used as a fallback to override the 7031 defaults for settings which are not explcitly specified using 7032 AsyncSSH's configuration options. By default, no OpenSSH 7033 configuration files will be loaded. See 7034 :ref:`SupportedServerConfigOptions` for details on what 7035 configuration options are currently supported. 7036 :param options: (optional) 7037 Options to use when accepting SSH server connections. These 7038 options can be specified either through this parameter or 7039 as direct keyword arguments to this function. 7040 :type protocol_factory: `callable` 7041 :type host: `str` 7042 :type port: `int` 7043 :type tunnel: :class:`SSHClientConnection` or `str` 7044 :type family: `socket.AF_UNSPEC`, `socket.AF_INET`, or `socket.AF_INET6` 7045 :type flags: flags to pass to :meth:`getaddrinfo() <socket.getaddrinfo>` 7046 :type backlog: `int` 7047 :type reuse_address: `bool` 7048 :type reuse_port: `bool` 7049 :type config: `list` of `str` 7050 :type options: :class:`SSHServerConnectionOptions` 7051 7052 :returns: :class:`SSHAcceptor` 7053 7054 """ 7055 7056 def conn_factory(): 7057 """Return an SSH client connection factory""" 7058 7059 return SSHServerConnection(loop, options, acceptor, error_handler) 7060 7061 loop = asyncio.get_event_loop() 7062 7063 options = SSHServerConnectionOptions(options, config=config, host=host, 7064 port=port, tunnel=tunnel, 7065 family=family, **kwargs) 7066 7067 # pylint: disable=attribute-defined-outside-init 7068 options.proxy_command = None 7069 7070 return await asyncio.wait_for( 7071 _listen(options, loop, flags, backlog, reuse_address, 7072 reuse_port, conn_factory, 'Creating SSH listener on'), 7073 timeout=options.connect_timeout) 7074 7075 7076@async_context_manager 7077async def listen_reverse(host='', port=(), *, tunnel=(), family=(), 7078 flags=socket.AI_PASSIVE, backlog=100, 7079 reuse_address=None, reuse_port=None, 7080 acceptor=None, error_handler=None, config=(), 7081 options=None, **kwargs): 7082 """Create a reverse-direction SSH listener 7083 7084 This function is a coroutine which behaves similar to :func:`listen`, 7085 creating a listener which accepts inbound connections on the specified 7086 host and port. However, instead of starting up an SSH server on each 7087 inbound connection, it starts up a reverse-direction SSH client, 7088 expecting the remote system making the connection to start up a 7089 reverse-direction SSH server. 7090 7091 Arguments to this function are the same as :func:`listen`, except 7092 that the `options` are of type :class:`SSHClientConnectionOptions` 7093 instead of :class:`SSHServerConnectionOptions`. 7094 7095 The return value is an :class:`SSHAcceptor` which can be used to 7096 shut down the reverse listener. 7097 7098 :param host: (optional) 7099 The hostname or address to listen on. If not specified, listeners 7100 are created for all addresses. 7101 :param port: (optional) 7102 The port number to listen on. If not specified, the default 7103 SSH port is used. 7104 :param tunnel: (optional) 7105 An existing SSH client connection that this new listener should 7106 be forwarded over. If set, a remote TCP/IP listener will be 7107 opened on this connection on the requested host and port rather 7108 than listening directly via TCP. A string of the form 7109 [user@]host[:port] may also be specified, in which case a 7110 connection will first be made to that host and it will then be 7111 used as a tunnel. 7112 :param family: (optional) 7113 The address family to use when creating the server. By default, 7114 the address families are automatically selected based on the host. 7115 :param flags: (optional) 7116 The flags to pass to getaddrinfo() when looking up the host 7117 :param backlog: (optional) 7118 The maximum number of queued connections allowed on listeners 7119 :param reuse_address: (optional) 7120 Whether or not to reuse a local socket in the TIME_WAIT state 7121 without waiting for its natural timeout to expire. If not 7122 specified, this will be automatically set to `True` on UNIX. 7123 :param reuse_port: (optional) 7124 Whether or not to allow this socket to be bound to the same 7125 port other existing sockets are bound to, so long as they all 7126 set this flag when being created. If not specified, the 7127 default is to not allow this. This option is not supported 7128 on Windows or Python versions prior to 3.4.4. 7129 :param acceptor: (optional) 7130 A `callable` or coroutine which will be called when the 7131 SSH handshake completes on an accepted connection, taking 7132 the :class:`SSHClientConnection` as an argument. 7133 :param error_handler: (optional) 7134 A `callable` which will be called whenever the SSH handshake 7135 fails on an accepted connection. It is called with the failed 7136 :class:`SSHClientConnection` and an exception object describing 7137 the failure. If not specified, failed handshakes result in the 7138 connection object being silently cleaned up. 7139 :param config: (optional) 7140 Paths to OpenSSH client configuration files to load. This 7141 configuration will be used as a fallback to override the 7142 defaults for settings which are not explcitly specified using 7143 AsyncSSH's configuration options. If no paths are specified, 7144 an attempt will be made to load the configuration from the file 7145 :file:`.ssh/config`. If this argument is explicitly set to 7146 `None`, no OpenSSH configuration files will be loaded. See 7147 :ref:`SupportedClientConfigOptions` for details on what 7148 configuration options are currently supported. 7149 :param options: (optional) 7150 Options to use when starting reverse-direction SSH clients. 7151 These options can be specified either through this parameter 7152 or as direct keyword arguments to this function. 7153 :type client_factory: `callable` 7154 :type host: `str` 7155 :type port: `int` 7156 :type tunnel: :class:`SSHClientConnection` or `str` 7157 :type family: `socket.AF_UNSPEC`, `socket.AF_INET`, or `socket.AF_INET6` 7158 :type flags: flags to pass to :meth:`getaddrinfo() <socket.getaddrinfo>` 7159 :type backlog: `int` 7160 :type reuse_address: `bool` 7161 :type reuse_port: `bool` 7162 :type config: `list` of `str` 7163 :type options: :class:`SSHClientConnectionOptions` 7164 7165 :returns: :class:`SSHAcceptor` 7166 7167 """ 7168 7169 def conn_factory(): 7170 """Return an SSH client connection factory""" 7171 7172 return SSHClientConnection(loop, options, acceptor, error_handler) 7173 7174 loop = asyncio.get_event_loop() 7175 7176 options = SSHClientConnectionOptions(options, config=config, host=host, 7177 port=port, tunnel=tunnel, 7178 family=family, **kwargs) 7179 7180 # pylint: disable=attribute-defined-outside-init 7181 options.proxy_command = None 7182 7183 return await asyncio.wait_for( 7184 _listen(options, loop, flags, backlog, 7185 reuse_address, reuse_port, conn_factory, 7186 'Creating reverse direction SSH listener on'), 7187 timeout=options.connect_timeout) 7188 7189 7190async def create_connection(client_factory, host, port=(), **kwargs): 7191 """Create an SSH client connection 7192 7193 This is a coroutine which wraps around :func:`connect`, providing 7194 backward compatibility with older AsyncSSH releases. The only 7195 differences are that the `client_factory` argument is the first 7196 positional argument in this call rather than being a keyword argument 7197 or specified via an :class:`SSHClientConnectionOptions` object and 7198 the return value is a tuple of an :class:`SSHClientConnection` and 7199 :class:`SSHClient` rather than just the connection, mirroring 7200 :meth:`asyncio.BaseEventLoop.create_connection`. 7201 7202 :returns: An :class:`SSHClientConnection` and :class:`SSHClient` 7203 7204 """ 7205 7206 conn = await connect(host, port, client_factory=client_factory, **kwargs) 7207 7208 return conn, conn.get_owner() 7209 7210 7211@async_context_manager 7212async def create_server(server_factory, host='', port=(), **kwargs): 7213 """Create an SSH server 7214 7215 This is a coroutine which wraps around :func:`listen`, providing 7216 backward compatibility with older AsyncSSH releases. The only 7217 difference is that the `server_factory` argument is the first 7218 positional argument in this call rather than being a keyword argument 7219 or specified via an :class:`SSHServerConnectionOptions` object, 7220 mirroring :meth:`asyncio.BaseEventLoop.create_server`. 7221 7222 """ 7223 7224 return await listen(host, port, server_factory=server_factory, **kwargs) 7225 7226 7227async def get_server_host_key(host, port=(), *, tunnel=(), proxy_command=(), 7228 family=(), flags=0, local_addr=None, 7229 client_version=(), kex_algs=(), 7230 server_host_key_algs=(), config=(), 7231 options=None): 7232 """Retrieve an SSH server's host key 7233 7234 This is a coroutine which can be run to connect to an SSH server and 7235 return the server host key presented during the SSH handshake. 7236 7237 A list of server host key algorithms can be provided to specify 7238 which host key types the server is allowed to choose from. If the 7239 key exchange is successful, the server host key sent during the 7240 handshake is returned. 7241 7242 .. note:: Not all key exchange methods involve the server 7243 presenting a host key. If something like GSS key 7244 exchange is used without a server host key, this 7245 method may return `None` even when the handshake 7246 completes. 7247 7248 :param host: 7249 The hostname or address to connect to 7250 :param port: (optional) 7251 The port number to connect to. If not specified, the default 7252 SSH port is used. 7253 :param tunnel: (optional) 7254 An existing SSH client connection that this new connection should 7255 be tunneled over. If set, a direct TCP/IP tunnel will be opened 7256 over this connection to the requested host and port rather than 7257 connecting directly via TCP. A string of the form 7258 [user@]host[:port] may also be specified, in which case a 7259 connection will first be made to that host and it will then be 7260 used as a tunnel. 7261 :param proxy_command: (optional) 7262 A string or list of strings specifying a command and arguments 7263 to run to make a connection to the SSH server. Data will be 7264 forwarded to this process over stdin/stdout instead of opening a 7265 TCP connection. If specified as a string, standard shell quoting 7266 will be applied when splitting the command and its arguments. 7267 :param family: (optional) 7268 The address family to use when creating the socket. By default, 7269 the address family is automatically selected based on the host. 7270 :param flags: (optional) 7271 The flags to pass to getaddrinfo() when looking up the host address 7272 :param local_addr: (optional) 7273 The host and port to bind the socket to before connecting 7274 :param client_version: (optional) 7275 An ASCII string to advertise to the SSH server as the version of 7276 this client, defaulting to `'AsyncSSH'` and its version number. 7277 :param kex_algs: (optional) 7278 A list of allowed key exchange algorithms in the SSH handshake, 7279 taken from :ref:`key exchange algorithms <KexAlgs>` 7280 :param server_host_key_algs: (optional) 7281 A list of server host key algorithms to allow during the SSH 7282 handshake, taken from :ref:`server host key algorithms 7283 <PublicKeyAlgs>`. 7284 :param config: (optional) 7285 Paths to OpenSSH client configuration files to load. This 7286 configuration will be used as a fallback to override the 7287 defaults for settings which are not explcitly specified using 7288 AsyncSSH's configuration options. If no paths are specified, 7289 an attempt will be made to load the configuration from the file 7290 :file:`.ssh/config`. If this argument is explicitly set to 7291 `None`, no OpenSSH configuration files will be loaded. See 7292 :ref:`SupportedClientConfigOptions` for details on what 7293 configuration options are currently supported. 7294 :param options: (optional) 7295 Options to use when establishing the SSH client connection used 7296 to retrieve the server host key. These options can be specified 7297 either through this parameter or as direct keyword arguments to 7298 this function. 7299 :type host: `str` 7300 :type port: `int` 7301 :type tunnel: :class:`SSHClientConnection` or `str` 7302 :type proxy_command: `str` or `list` of `str` 7303 :type family: `socket.AF_UNSPEC`, `socket.AF_INET`, or `socket.AF_INET6` 7304 :type flags: flags to pass to :meth:`getaddrinfo() <socket.getaddrinfo>` 7305 :type local_addr: tuple of `str` and `int` 7306 :type client_version: `str` 7307 :type kex_algs: `str` or `list` of `str` 7308 :type server_host_key_algs: `str` or `list` of `str` 7309 :type config: `list` of `str` 7310 :type options: :class:`SSHClientConnectionOptions` 7311 7312 :returns: An :class:`SSHKey` public key or `None` 7313 7314 """ 7315 7316 def conn_factory(): 7317 """Return an SSH client connection factory""" 7318 7319 return SSHClientConnection(loop, options, wait='kex') 7320 7321 loop = asyncio.get_event_loop() 7322 7323 options = SSHClientConnectionOptions( 7324 options, config=config, host=host, port=port, tunnel=tunnel, 7325 proxy_command=proxy_command, family=family, local_addr=local_addr, 7326 known_hosts=None, server_host_key_algs=server_host_key_algs, 7327 x509_trusted_certs=None, x509_trusted_cert_paths=None, 7328 x509_purposes='any', gss_host=None, kex_algs=kex_algs, 7329 client_version=client_version) 7330 7331 conn = await asyncio.wait_for( 7332 _connect(options, loop, flags, conn_factory, 7333 'Fetching server host key from'), 7334 timeout=options.connect_timeout) 7335 7336 server_host_key = conn.get_server_host_key() 7337 7338 conn.abort() 7339 7340 await conn.wait_closed() 7341 7342 return server_host_key 7343