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