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