1.. toctree:: 2 :hidden: 3 4 changes 5 contributing 6 api 7 8.. currentmodule:: asyncssh 9 10.. include:: ../README.rst 11 12.. _ClientExamples: 13 14Client Examples 15=============== 16 17Simple client 18------------- 19 20The following code shows an example of a simple SSH client which logs into 21localhost and lists files in a directory named 'abc' under the user's home 22directory. The username provided is the logged in user, and the user's 23default SSH client keys or certificates are presented during authentication. 24The server's host key is checked against the user's SSH known_hosts file and 25the connection will fail if there's no entry for localhost there or if the 26key doesn't match. 27 28 .. include:: ../examples/simple_client.py 29 :literal: 30 :start-line: 22 31 32This example only uses the output on stdout, but output on stderr is also 33collected as another attribute in the returned :class:`SSHCompletedProcess` 34object. 35 36To check against a different set of server host keys, they can be provided 37in the known_hosts argument when the connection is opened: 38 39 .. code:: 40 41 async with asyncssh.connect('localhost', known_hosts='my_known_hosts') as conn: 42 43 44Server host key checking can be disabled by setting the known_hosts 45argument to ``None``, but that's not recommended as it makes the 46connection vulnerable to a man-in-the-middle attack. 47 48To log in as a different remote user, the username argument can be 49provided: 50 51 .. code:: 52 53 async with asyncssh.connect('localhost', username='user123') as conn: 54 55To use a different set of client keys for authentication, they can be 56provided in the client_keys argument: 57 58 .. code:: 59 60 async with asyncssh.connect('localhost', client_keys=['my_ssh_key']) as conn: 61 62Password authentication can be used by providing a password argument: 63 64 .. code:: 65 66 async with asyncssh.connect('localhost', password='secretpw') as conn: 67 68Any of the arguments above can be combined together as needed. If client 69keys and a password are both provided, either may be used depending 70on what forms of authentication the server supports and whether the 71authentication with them is successful. 72 73Callback example 74---------------- 75 76AsyncSSH also provides APIs that use callbacks rather than "await" and "async 77with". Here's the example above written using custom :class:`SSHClient` and 78:class:`SSHClientSession` subclasses: 79 80 .. include:: ../examples/callback_client.py 81 :literal: 82 :start-line: 22 83 84In cases where you don't need to customize callbacks on the SSHClient class, 85this code can be simplified somewhat to: 86 87 .. include:: ../examples/callback_client2.py 88 :literal: 89 :start-line: 22 90 91If you need to distinguish output going to stdout vs. stderr, that's easy to 92do with the following change: 93 94 .. include:: ../examples/callback_client3.py 95 :literal: 96 :start-line: 22 97 98Interactive input 99----------------- 100 101The following example demonstrates sending interactive input to a remote 102process. It executes the calculator program ``bc`` and performs some basic 103math calculations. Note that it uses the :meth:`create_process 104<SSHClientConnection.create_process>` method rather than the :meth:`run 105<SSHClientConnection.run>` method. This starts the process but doesn't wait 106for it to exit, allowing interaction with it. 107 108 .. include:: ../examples/math_client.py 109 :literal: 110 :start-line: 22 111 112When run, this program should produce the following output: 113 114 .. code:: 115 116 2+2 = 4 117 1*2*3*4 = 24 118 2^32 = 4294967296 119 120I/O redirection 121--------------- 122 123The following example shows how to pass a fixed input string to a remote 124process and redirect the resulting output to the local file '/tmp/stdout'. 125Input lines containing 1, 2, and 3 are passed into the 'tail -r' command 126and the output written to '/tmp/stdout' should contain the reversed lines 1273, 2, and 1: 128 129 .. include:: ../examples/redirect_input.py 130 :literal: 131 :start-line: 22 132 133The ``stdin``, ``stdout``, and ``stderr`` arguments support redirecting 134to a variety of locations include local files, pipes, and sockets as 135well as :class:`SSHReader` or :class:`SSHWriter` objects associated with 136other remote SSH processes. Here's an example of piping stdout from a 137local process to a remote process: 138 139 .. include:: ../examples/redirect_local_pipe.py 140 :literal: 141 :start-line: 22 142 143Here's an example of piping one remote process to another: 144 145 .. include:: ../examples/redirect_remote_pipe.py 146 :literal: 147 :start-line: 22 148 149In this example both remote processes are running on the same SSH 150connection, but this redirection can just as easily be used between 151SSH sessions associated with connections going to different servers. 152 153Checking exit status 154-------------------- 155 156The following example shows how to test the exit status of a remote process: 157 158 .. include:: ../examples/check_exit_status.py 159 :literal: 160 :start-line: 22 161 162If an exit signal is received, the exit status will be set to -1 and exit 163signal information is provided in the ``exit_signal`` attribute of the 164returned :class:`SSHCompletedProcess`. 165 166If the ``check`` argument in :meth:`run <SSHClientConnection.run>` is set 167to ``True``, any abnormal exit will raise a :exc:`ProcessError` exception 168instead of returning an :class:`SSHCompletedProcess`. 169 170Running multiple clients 171------------------------ 172 173The following example shows how to run multiple clients in parallel and 174process the results when all of them have completed: 175 176 .. include:: ../examples/gather_results.py 177 :literal: 178 :start-line: 22 179 180Results could be processed as they became available by setting up a 181loop which repeatedly called :func:`asyncio.wait` instead of calling 182:func:`asyncio.gather`. 183 184Setting environment variables 185----------------------------- 186 187The following example demonstrates setting environment variables 188for the remote session and displaying them by executing the 'env' 189command. 190 191 .. include:: ../examples/set_environment.py 192 :literal: 193 :start-line: 22 194 195Any number of environment variables can be passed in the dictionary 196given to :meth:`create_session() <SSHClientConnection.create_session>`. 197Note that SSH servers may restrict which environment variables (if any) 198are accepted, so this feature may require setting options on the SSH 199server before it will work. 200 201Setting terminal information 202---------------------------- 203 204The following example demonstrates setting the terminal type and size 205passed to the remote session. 206 207 .. include:: ../examples/set_terminal.py 208 :literal: 209 :start-line: 22 210 211Note that this will cause AsyncSSH to request a pseudo-tty from the 212server. When a pseudo-tty is used, the server will no longer send output 213going to stderr with a different data type. Instead, it will be mixed 214with output going to stdout (unless it is redirected elsewhere by the 215remote command). 216 217Port forwarding 218--------------- 219 220The following example demonstrates the client setting up a local TCP 221listener on port 8080 and requesting that connections which arrive on 222that port be forwarded across SSH to the server and on to port 80 on 223``www.google.com``: 224 225 .. include:: ../examples/local_forwarding_client.py 226 :literal: 227 :start-line: 22 228 229To listen on a dynamically assigned port, the client can pass in ``0`` 230as the listening port. If the listener is successfully opened, the selected 231port will be available via the :meth:`get_port() <SSHListener.get_port>` 232method on the returned listener object: 233 234 .. include:: ../examples/local_forwarding_client2.py 235 :literal: 236 :start-line: 22 237 238The client can also request remote port forwarding from the server. The 239following example shows the client requesting that the server listen on 240port 8080 and that connections arriving there be forwarded across SSH 241and on to port 80 on ``localhost``: 242 243 .. include:: ../examples/remote_forwarding_client.py 244 :literal: 245 :start-line: 22 246 247To limit which connections are accepted or dynamically select where to 248forward traffic to, the client can implement their own session factory and 249call :meth:`forward_connection() <SSHClientConnection.forward_connection>` 250on the connections they wish to forward and raise an error on those they 251wish to reject: 252 253 .. include:: ../examples/remote_forwarding_client2.py 254 :literal: 255 :start-line: 22 256 257Just as with local listeners, the client can request remote port forwarding 258from a dynamic port by passing in ``0`` as the listening port and then call 259:meth:`get_port() <SSHListener.get_port>` on the returned listener to 260determine which port was selected. 261 262Direct TCP connections 263---------------------- 264 265The client can also ask the server to open a TCP connection and directly 266send and receive data on it by using the :meth:`create_connection() 267<SSHClientConnection.create_connection>` method on the 268:class:`SSHClientConnection` object. In this example, a connection is 269attempted to port 80 on ``www.google.com`` and an HTTP HEAD request is 270sent for the document root. 271 272Note that unlike sessions created with :meth:`create_session() 273<SSHClientConnection.create_session>`, the I/O on these connections defaults 274to sending and receiving bytes rather than strings, allowing arbitrary 275binary data to be exchanged. However, this can be changed by setting 276the encoding to use when the connection is created. 277 278 .. include:: ../examples/direct_client.py 279 :literal: 280 :start-line: 22 281 282To use the streams API to open a direct connection, you can use 283:meth:`open_connection <SSHClientConnection.open_connection>` instead of 284:meth:`create_connection <SSHClientConnection.create_connection>`: 285 286 .. include:: ../examples/stream_direct_client.py 287 :literal: 288 :start-line: 22 289 290Forwarded TCP connections 291------------------------- 292 293The client can also directly process data from incoming TCP connections 294received on the server. The following example demonstrates the client 295requesting that the server listen on port 8888 and forward any received 296connections back to it over SSH. It then has a simple handler which 297echoes any data it receives back to the sender. 298 299As in the direct TCP connection example above, the default would be to 300send and receive bytes on this connection rather than strings, but here 301we set the encoding explicitly so all data is sent and received as strings: 302 303 .. include:: ../examples/listening_client.py 304 :literal: 305 :start-line: 22 306 307To use the streams API to open a listening connection, you can use 308:meth:`start_server <SSHClientConnection.start_server>` instead 309of :meth:`create_server <SSHClientConnection.create_server>`: 310 311 .. include:: ../examples/stream_listening_client.py 312 :literal: 313 :start-line: 22 314 315SFTP client 316----------- 317 318AsyncSSH also provides SFTP support. The following code shows an example 319of starting an SFTP client and requesting the download of a file: 320 321 .. include:: ../examples/sftp_client.py 322 :literal: 323 :start-line: 22 324 325To recursively download a directory, preserving access and modification 326times and permissions on the files, the preserve and recurse arguments 327can be included: 328 329 .. code:: 330 331 await sftp.get('example_dir', preserve=True, recurse=True) 332 333Wild card pattern matching is supported by the :meth:`mget <SFTPClient.mget>`, 334:meth:`mput <SFTPClient.mput>`, and :meth:`mcopy <SFTPClient.mcopy>` methods. 335The following downloads all files with extension "txt": 336 337 .. code:: 338 339 await sftp.mget('*.txt') 340 341See the :class:`SFTPClient` documentation for the full list of available 342actions. 343 344SCP client 345---------- 346 347AsyncSSH also supports SCP. The following code shows an example of 348downloading a file via SCP: 349 350 .. include:: ../examples/scp_client.py 351 :literal: 352 :start-line: 22 353 354To upload a file to a remote system, host information can be specified for 355the destination instead of the source: 356 357 .. code:: 358 359 await asyncssh.scp('example.txt', 'localhost:') 360 361If the destination path includes a file name, that name will be used instead 362of the original file name when performing the copy. For instance: 363 364 .. code:: 365 366 await asyncssh.scp('example.txt', 'localhost:example2.txt') 367 368If the destination path refers to a directory, the origin file name 369will be preserved, but it will be copied into the requested directory. 370 371Wild card patterns are also supported on local source paths. For instance, 372the following copies all files with extension "txt": 373 374 .. code:: 375 376 await asyncssh.scp('*.txt', 'localhost:') 377 378When copying files from a remote system, any wild card expansion is the 379responsibility of the remote SCP program or the shell which starts it. 380 381Similar to SFTP, SCP also supports options for recursively copying a 382directory and preserving modification times and permissions on files 383using the preserve and recurse arguments: 384 385 .. code:: 386 387 await asyncssh.scp('example_dir', 'localhost:', preserve=True, recurse=True) 388 389In addition to the ``'host:path'`` syntax for source and destination paths, 390a tuple of the form ``(host, path)`` is also supported. A non-default port 391can be specified by replacing ``host`` with ``(host, port)``, resulting in 392something like: 393 394 .. code:: 395 396 await asyncssh.scp((('localhost', 8022), 'example.txt'), '.') 397 398An already open :class:`SSHClientConnection` can also be passed as the host: 399 400 .. code:: 401 402 async with asyncssh.connect('localhost') as conn: 403 await asyncssh.scp((conn, 'example.txt'), '.') 404 405Multiple file patterns can be copied to the same destination by making the 406source path argument a list. Source paths in this list can be a mixture 407of local and remote file references and the destination path can be 408local or remote, but one or both of source and destination must be remote. 409Local to local copies are not supported. 410 411See the :func:`scp` function documentation for the complete list of 412available options. 413 414.. _ServerExamples: 415 416Server Examples 417=============== 418 419Simple server 420------------- 421 422The following code shows an example of a simple SSH server which listens 423for connections on port 8022, does password authentication, and prints 424a message when users authenticate successfully and start a shell. 425 426 .. include:: ../examples/simple_server.py 427 :literal: 428 :start-line: 22 429 430To authenticate with SSH client keys or certificates, the server would 431look something like the following. Client and certificate authority 432keys for each user need to be placed in a file matching the username in 433a directory called ``authorized_keys``. 434 435 .. include:: ../examples/simple_keyed_server.py 436 :literal: 437 :start-line: 30 438 439It is also possible to use a single authorized_keys file for all users. 440This is common when using certificates, as AsyncSSH can automatically 441enforce that the certificates presented have a principal in them which 442matches the username. In this case, a custom :class:`SSHServer` subclass 443is no longer required, and so the :func:`listen` function can be used in 444place of :func:`create_server`. 445 446 .. include:: ../examples/simple_cert_server.py 447 :literal: 448 :start-line: 29 449 450Simple server with input 451------------------------ 452 453The following example demonstrates reading input in a server session. 454It adds a column of numbers, displaying the total when it receives EOF. 455 456 .. include:: ../examples/math_server.py 457 :literal: 458 :start-line: 29 459 460Callback example 461---------------- 462 463Here's an example of the server above written using callbacks in 464custom :class:`SSHServer` and :class:`SSHServerSession` subclasses. 465 466 .. include:: ../examples/callback_math_server.py 467 :literal: 468 :start-line: 29 469 470I/O redirection 471--------------- 472 473The following shows an example of I/O redirection on the server side, 474executing a process on the server with input and output redirected 475back to the SSH client: 476 477 .. include:: ../examples/redirect_server.py 478 :literal: 479 :start-line: 29 480 481Serving multiple clients 482------------------------ 483 484The following is a slightly more complicated example showing how a 485server can manage multiple simultaneous clients. It implements a 486basic chat service, where clients can send messages to one other. 487 488 .. include:: ../examples/chat_server.py 489 :literal: 490 :start-line: 29 491 492Line editing 493------------ 494 495When SSH clients request a pseudo-terminal, they generally default to 496sending input a character at a time and expect the remote system to 497provide character echo and line editing. To better support interactive 498applications like the one above, AsyncSSH defaults to providing basic 499line editing for server sessions which request a pseudo-terminal. 500 501When thise line editor is enabled, it defaults to delivering input to 502the application a line at a time. Applications can switch between line 503and character at a time input using the :meth:`set_line_mode() 504<SSHLineEditorChannel.set_line_mode>` method. Also, when in line 505mode, applications can enable or disable echoing of input using the 506:meth:`set_echo() <SSHLineEditorChannel.set_echo>` method. The 507following code provides an example of this. 508 509 .. include:: ../examples/editor.py 510 :literal: 511 :start-line: 29 512 513Getting environment variables 514----------------------------- 515 516The following example demonstrates reading environment variables set 517by the client. It will show all of the variables set by the client, 518or return an error if none are set. Note that SSH clients may restrict 519which environment variables (if any) are sent by default, so you may 520need to set options in the client to get it to do so. 521 522 .. include:: ../examples/show_environment.py 523 :literal: 524 :start-line: 29 525 526Getting terminal information 527---------------------------- 528 529The following example demonstrates reading the client's terminal 530type and window size, and handling window size changes during a 531session. 532 533 .. include:: ../examples/show_terminal.py 534 :literal: 535 :start-line: 29 536 537Port forwarding 538--------------- 539 540The following example demonstrates a server accepting port forwarding 541requests from clients, but only when they are destined to port 80. When 542such a connection is received, a connection is attempted to the requested 543host and port and data is bidirectionally forwarded over SSH from the 544client to this destination. Requests by the client to connect to any 545other port are rejected. 546 547 .. include:: ../examples/local_forwarding_server.py 548 :literal: 549 :start-line: 29 550 551The server can also support forwarding inbound TCP connections back to 552the client. The following example demonstrates a server which will accept 553requests like this from clients, but only to listen on port 8080. When 554such a connection is received, the client is notified and data is 555bidirectionally forwarded from the incoming connection over SSH to the 556client. 557 558 .. include:: ../examples/remote_forwarding_server.py 559 :literal: 560 :start-line: 29 561 562Direct TCP connections 563---------------------- 564 565The server can also accept direct TCP connection requests from the client 566and process the data on them itself. The following example demonstrates a 567server which accepts requests to port 7 (the "echo" port) for any host and 568echoes the data itself rather than forwarding the connection: 569 570 .. include:: ../examples/direct_server.py 571 :literal: 572 :start-line: 29 573 574Here's an example of this server written using the streams API. In this 575case, :meth:`connection_requested() <SSHServer.connection_requested>` 576returns a handler coroutine instead of a session object. When a new 577direct TCP connection is opened, the handler coroutine is called with 578AsyncSSH stream objects which can be used to perform I/O on the tunneled 579connection. 580 581 .. include:: ../examples/stream_direct_server.py 582 :literal: 583 :start-line: 29 584 585SFTP server 586----------- 587 588The following example shows how to start an SFTP server with default 589behavior: 590 591 .. include:: ../examples/simple_sftp_server.py 592 :literal: 593 :start-line: 29 594 595A subclass of :class:`SFTPServer` can be provided as the value of the SFTP 596factory to override specific behavior. For example, the following code 597remaps path names so that each user gets access to only their own individual 598directory under ``/tmp/sftp``: 599 600 .. include:: ../examples/chroot_sftp_server.py 601 :literal: 602 :start-line: 29 603 604More complex path remapping can be performed by implementing the 605:meth:`map_path <SFTPServer.map_path>` and 606:meth:`reverse_map_path <SFTPServer.reverse_map_path>` methods. Individual 607SFTP actions can also be overridden as needed. See the :class:`SFTPServer` 608documentation for the full list of methods to override. 609 610SCP server 611---------- 612 613The above server examples can be modified to also support SCP by simply 614adding ``allow_scp=True`` alongside the specification of the ``sftp_factory`` 615in the :func:`listen` call. This will use the same :class:`SFTPServer` 616instance when performing file I/O for both SFTP and SCP requests. For 617instance: 618 619 .. include:: ../examples/simple_scp_server.py 620 :literal: 621 :start-line: 29 622 623Reverse Direction Example 624========================= 625 626One of the unique capabilities of AsyncSSH is its ability to support 627"reverse direction" SSH connections, using the functions 628:func:`connect_reverse` and :func:`listen_reverse`. This can be 629helpful when implementing protocols such as "NETCONF Call Home", 630described in :rfc:`8071`. When using this capability, the SSH protocol 631doesn't change, but the roles at the TCP level about which side acts 632as a TCP client and server are reversed, with the TCP client taking 633on the role of the SSH server and the TCP server taking on the role of 634the SSH client once the connection is established. 635 636For these examples to run, the following files must be created: 637 638 * The file ``client_host_key`` must exist on the client and contain an 639 SSH private key for the client to use to authenticate itself as a 640 host to the server. An SSH certificate can optionally be provided 641 in ``client_host_key-cert.pub``. 642 * The file ``trusted_server_keys`` must exist on the client and contain 643 a list of trusted server keys or a ``cert-authority`` entry with a 644 public key trusted to sign server keys if certificates are used. This 645 file should be in "authorized_keys" format. 646 * The file ``server_key`` must exist on the server and contain an SSH 647 private key for the server to use to authenticate itself to the 648 client. An SSH certificate can optionally be provided in 649 ``server_key-cert.pub``. 650 * The file ``trusted_client_host_keys`` must exist on the server and 651 contain a list of trusted client host keys or a ``@cert-authority`` 652 entry with a public key trusted to sign client host keys if 653 certificates are used. This file should be in "known_hosts" format. 654 655Reverse Direction Client 656------------------------ 657 658The following example shows a reverse-direction SSH client which will run 659arbitrary shell commands given to it by the server it connects to: 660 661 .. include:: ../examples/reverse_client.py 662 :literal: 663 :start-line: 32 664 665Reverse Direction Server 666------------------------ 667 668Here is the corresponding server which makes requests to run the commands: 669 670 .. include:: ../examples/reverse_server.py 671 :literal: 672 :start-line: 32 673