Examples — ssh v6.0.2

Search documentation of ssh

# Examples

Copy Markdown View Source

The following examples use the utility function `ssh:start/0` to start all needed applications (`crypto`, `public_key`, and `ssh`). All examples are run in an Erlang shell, or in a bash shell, using OpenSSH to illustrate how the `ssh` application can be used. The examples are run as the user `otptest` on a local network where the user is authorized to log in over `ssh` to the host ssh.example.com.

If nothing else is stated, it is presumed that the `otptest` user has an entry in the authorized_keys file of ssh.example.com (allowed to log in over `ssh` without entering a password). Also, ssh.example.com is a known host in the `known_hosts` file of the user `otptest`. This means that host-verification can be done without user-interaction.

## Using the Erlang ssh Terminal Client

The user `otptest`, which has bash as default shell, uses the `ssh:shell/1` client to connect to the OpenSSH daemon running on a host called ssh.example.com:

    1> ssh:start().
    ok
    2> {ok, S} = ssh:shell("ssh.example.com").
    otptest@ssh.example.com:> pwd
    /home/otptest
    otptest@ssh.example.com:> exit
    logout
    3>

## Running an Erlang ssh Daemon

The `system_dir` option must be a directory containing a host key file and it defaults to `/etc/ssh`. For details, see Section Configuration in ssh.

#### Note

Normally, the `/etc/ssh` directory is only readable by root.

The option `user_dir` defaults to directory `~/.ssh`.

Step 1. To run the example without root privileges, generate new host keys and user keys:

    $bash> ssh-keygen -t rsa -f /tmp/ssh_daemon/ssh_host_rsa_key
    [...]
    $bash> ssh-keygen -t rsa -f /tmp/otptest_user/.ssh/id_rsa
    [...]

Step 2. Create the file `/tmp/otptest_user/.ssh/authorized_keys` and add the content of `/tmp/otptest_user/.ssh/id_rsa.pub`.

Step 3. Start the Erlang `ssh` daemon:

    1> ssh:start().
    ok
    2> {ok, Sshd} = ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
                                      {user_dir, "/tmp/otptest_user/.ssh"},
                                      {shell, {shell, start, []}},
                                      {exec, erlang_eval}]).
    {ok,<0.54.0>}
    3>

Step 4. Use the OpenSSH client from a shell to connect to the Erlang `ssh` daemon:

    $bash> ssh ssh.example.com -p 8989  -i /tmp/otptest_user/.ssh/id_rsa \
                      -o UserKnownHostsFile=/tmp/otptest_user/.ssh/known_hosts
    The authenticity of host 'ssh.example.com' can't be established.
    RSA key fingerprint is 14:81:80:50:b1:1f:57:dd:93:a8:2d:2f:dd:90:ae:a8.
    Are you sure you want to continue connecting (yes/no)? yes
    Warning: Permanently added 'ssh.example.com' (RSA) to the list of known hosts.
    Eshell V5.10  (abort with ^G)
    1>

There are two ways of shutting down an `ssh` daemon, see Step 5a and Step 5b.

Step 5a. Shut down the Erlang `ssh` daemon so that it stops the listener but leaves existing connections, started by the listener, operational:

    3> ssh:stop_listener(Sshd).
    ok
    4>

Step 5b. Shut down the Erlang `ssh` daemon so that it stops the listener and all connections started by the listener:

    3> ssh:stop_daemon(Sshd).
    ok
    4>

## One-Time Execution

### Erlang Client Contacting OpenSSH Server

In the following example, the Erlang shell is the client process that receives the channel replies as Erlang messages.

Do an one-time execution of a remote OS command ("pwd") over `ssh` to the ssh server of the OS at the host "ssh.example.com":

    1> ssh:start().
    ok
    2> {ok, ConnectionRef} = ssh:connect("ssh.example.com", 22, []).
    {ok,<0.57.0>}
    3> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
    {ok,0}
    4> success = ssh_connection:exec(ConnectionRef, ChannelId, "pwd", infinity).
    5> flush(). % Get all pending messages. NOTE: ordering may vary!
    Shell got {ssh_cm,<0.57.0>,{data,0,0,<<"/home/otptest\n">>}}
    Shell got {ssh_cm,<0.57.0>,{eof,0}}
    Shell got {ssh_cm,<0.57.0>,{exit_status,0,0}}
    Shell got {ssh_cm,<0.57.0>,{closed,0}}
    ok
    6> ssh:connection_info(ConnectionRef, channels).
    {channels,[]}
    7>

See `ssh_connection` and `ssh_connection:exec/4` for finding documentation of the channel messages.

To collect the channel messages in a program, use `receive...end` instead of `flush/1`:

    5> receive
    5>     {ssh_cm, ConnectionRef, {data, ChannelId, Type, Result}} when Type == 0 ->
    5>         {ok,Result};
    5>     {ssh_cm, ConnectionRef, {data, ChannelId, Type, Result}} when Type == 1 ->
    5>         {error,Result}
    5> end.
    {ok,<<"/home/otptest\n">>}
    6>

#### Note

Only the exec channel is closed after the one-time execution. The connection is still up and can handle previously opened channels. It is also possible to open a new channel:

    % try to open a new channel to check if the ConnectionRef is still open
    7> {ok, NewChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
    {ok,1}
    8>

To close the connection, call the function `ssh:close(ConnectionRef)`. As an alternative, set the option `{idle_time, 1}` when opening the connection. This will cause the connection to be closed automatically when there are no channels open for the specified time period, in this case 1 ms.

### OpenSSH and Erlang Clients to Erlang Daemon

An Erlang SSH daemon could be called for one-time execution of a "command". The "command" must be as if entered into the erlang shell, that is a sequence of Erlang expressions ended by a period (.). Variables bound in that sequence will keep their bindings throughout the expression sequence. The bindings are disposed when the result is returned.

Here is an example of a suitable expression sequence:

    A=1, B=2, 3 == (A + B).

It evaluates to `true` if submitted to the Erlang daemon started in Step 3 above:

    $bash> ssh ssh.example.com -p 8989 "A=1, B=2, 3 == (A + B)."
    true
    $bash>

The same example but now using the Erlang ssh client to contact the Erlang server:

    1> {ok, ConnectionRef} = ssh:connect("ssh.example.com", 8989, []).
    {ok,<0.216.0>}
    2> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
    {ok,0}
    3> success = ssh_connection:exec(ConnectionRef, ChannelId,
                                     "A=1, B=2, 3 == (A + B).",
                                     infinity).
    success
    4> flush().
    Shell got {ssh_cm,<0.216.0>,{data,0,0,<<"true">>}}
    Shell got {ssh_cm,<0.216.0>,{exit_status,0,0}}
    Shell got {ssh_cm,<0.216.0>,{eof,0}}
    Shell got {ssh_cm,<0.216.0>,{closed,0}}
    ok
    5>

#### Note

Erlang shell specific functions and control sequences like for example `h().` are not supported.

### I/O from a function called in an Erlang ssh daemon

Output to stdout on the server side is also displayed as well as the resulting term from the function call:

    $bash> ssh ssh.example.com -p 8989 'io:format("Hello!~n~nHow are ~p?~n",[you]).'
    Hello!

    How are you?
    ok
    $bash>

And similar for reading from stdin. As an example we use `io:read/1` which displays the argument as a prompt on stdout, reads a term from stdin and returns it in an ok-tuple:

    $bash> ssh ssh.example.com -p 8989 'io:read("write something: ").'
    write something: [a,b,c].
    {ok,[a,b,c]}
    $bash>

The same example but using the Erlang ssh client:

    Eshell V10.5.2  (abort with ^G)
    1> ssh:start().
    ok
    2> {ok, ConnectionRef} = ssh:connect(loopback, 8989, []).
    {ok,<0.92.0>}
    3> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
    {ok,0}
    4> success = ssh_connection:exec(ConnectionRef, ChannelId,
                                     "io:read(\"write something: \").",
                                     infinity).
    success
    5> flush().
    Shell got {ssh_cm,<0.92.0>,{data,0,0,<<"write something: ">>}}
    ok
    % All data is sent as binaries with string contents:
    6> ok = ssh_connection:send(ConnectionRef, ChannelId, <<"[a,b,c].">>).
    ok
    7> flush().
    ok
    %% Nothing is received, because the io:read/1
    %% requires the input line to end with a newline.

    %% Send a newline (it could have been included in the last send):
    8> ssh_connection:send(ConnectionRef, ChannelId, <<"\n">>).
    ok
    9> flush().
    Shell got {ssh_cm,<0.92.0>,{data,0,0,<<"{ok,[a,b,c]}">>}}
    Shell got {ssh_cm,<0.92.0>,{exit_status,0,0}}
    Shell got {ssh_cm,<0.92.0>,{eof,0}}
    Shell got {ssh_cm,<0.92.0>,{closed,0}}
    ok
    10>

### Configuring the server's (daemon's) command execution

By default, one-time execution of commands is disabled — clients receive `"Prohibited."` on stderr with exit status 255. The daemon started in Step 3 above explicitly enables the built-in Erlang term evaluator with `{exec, erlang_eval}`.

To tailor the input language or restrict the possible functions to call, install an alternative evaluator with `{exec, {direct, Fun}}`. See ssh:daemon/2,3 and exec_daemon_option() for details.

Start the daemon with a reference to a `fun()` that handles the evaluation:

    1> ssh:start().
    ok
    2> MyEvaluator = fun("1") -> {ok, some_value};
                        ("2") -> {ok, some_other_value};
                        ("3") -> {ok, V} = io:read("input erlang term>> "),
                                 {ok, V};
                        (Err) -> {error,{bad_input,Err}}
                     end.
    3> {ok, Sshd} = ssh:daemon(1234, [{system_dir, "/tmp/ssh_daemon"},
                                      {user_dir, "/tmp/otptest_user/.ssh"},
                                      {exec, {direct,MyEvaluator}}
                                     ]).
    {ok,<0.275.0>}
    4>

The `fun()` in the exec option could take up to three arguments (`Cmd`, `User` and `ClientAddress`). See the exec_daemon_option() for the details.

And call it:

    $bash> ssh localhost -p 1234 1
    some_value
    $bash> ssh localhost -p 1234 2
    some_other_value
    # I/O works:
    $bash> ssh localhost -p 1234 3
    input erlang term>> abc.
    abc
    # Check that Erlang evaluation is disabled:
    $bash> ssh localhost -p 1234 1+ 2.
    **Error** {bad_input,"1+ 2."}
    $bash>

#### Note

Spaces are preserved and no `.` is needed at the end — that was required by the default evaluator.

The error return in the Erlang client (The text as data type 1 and exit_status 255):

    2> {ok, ConnectionRef} = ssh:connect(loopback, 1234, []).
    {ok,<0.92.0>}
    3> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
    {ok,0}
    4> success = ssh_connection:exec(ConnectionRef, ChannelId, "1+ 2.", infinity).
    success
    5> flush().
    Shell got {ssh_cm,<0.106.0>,{data,0,1,<<"**Error** {bad_input,\"1+ 2.\"}">>}}
    Shell got {ssh_cm,<0.106.0>,{exit_status,0,255}}
    Shell got {ssh_cm,<0.106.0>,{eof,0}}
    Shell got {ssh_cm,<0.106.0>,{closed,0}}
    ok
    6>

#### Note

The `{direct, ...}` wrapper is required to distinguish from a legacy evaluator interface that lacks I/O support. The legacy form is undocumented and discouraged.

## SFTP Server

The SFTP subsystem is not enabled by default. To start an SSH daemon with SFTP, configure the `subsystems` option explicitly:

    1> ssh:start().
    ok
    2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
                         {user_dir, "/tmp/otptest_user/.ssh"},
                         {subsystems, [ssh_sftpd:subsystem_spec([
                                                {cwd, "/tmp/sftp/example"},
                                                {max_handles, 1000},  % default
                                                {max_path, 4096}      % default
                                               ])
                                      ]}]).
    {ok,<0.54.0>}
    3>

Run the OpenSSH SFTP client:

    $bash> sftp -oPort=8989 -o IdentityFile=/tmp/otptest_user/.ssh/id_rsa \
                -o UserKnownHostsFile=/tmp/otptest_user/.ssh/known_hosts ssh.example.com
    Connecting to ssh.example.com...
    sftp> pwd
    Remote working directory: /tmp/sftp/example
    sftp>

## SFTP Client

#### Note

This assumes the remote SSH server has the SFTP subsystem enabled. OpenSSH enables it by default.

Fetch a file with the Erlang SFTP client:

    1> ssh:start().
    ok
    2> {ok, ChannelPid, Connection} = ssh_sftp:start_channel("ssh.example.com", []).
    {ok,<0.57.0>,<0.51.0>}
    3> ssh_sftp:read_file(ChannelPid, "/home/otptest/test.txt").
    {ok,<<"This is a test file\n">>}

## SFTP Client with TAR Compression

### Basic example

This is an example of writing and then reading a tar file:

    {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write]),
    ok = erl_tar:add(HandleWrite, .... ),
    ok = erl_tar:add(HandleWrite, .... ),
    ...
    ok = erl_tar:add(HandleWrite, .... ),
    ok = erl_tar:close(HandleWrite),

    %% And for reading
    {ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read]),
    {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]),
    ok = erl_tar:close(HandleRead),

### Example with encryption

The previous Basic example can be extended with encryption and decryption as follows:

    %% First three parameters depending on which crypto type we select:
    Key = <<"This is a 256 bit key. abcdefghi">>,
    Ivec0 = crypto:strong_rand_bytes(16),
    DataSize = 1024,  % DataSize rem 16 = 0 for aes_cbc

    %% Initialization of the CryptoState, in this case it is the Ivector.
    InitFun = fun() -> {ok, Ivec0, DataSize} end,

    %% How to encrypt:
    EncryptFun =
        fun(PlainBin,Ivec) ->
            EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, PlainBin),
            {ok, EncryptedBin, crypto:next_iv(aes_cbc,EncryptedBin)}
        end,

    %% What to do with the very last block:
    CloseFun =
        fun(PlainBin, Ivec) ->
            EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec,
                                                pad(16,PlainBin) %% Last chunk
                                               ),
           {ok, EncryptedBin}
        end,

    Cw = {InitFun,EncryptFun,CloseFun},
    {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write,{crypto,Cw}]),
    ok = erl_tar:add(HandleWrite, .... ),
    ok = erl_tar:add(HandleWrite, .... ),
    ...
    ok = erl_tar:add(HandleWrite, .... ),
    ok = erl_tar:close(HandleWrite),

    %% And for decryption (in this crypto example we could use the same InitFun
    %% as for encryption):
    DecryptFun =
        fun(EncryptedBin,Ivec) ->
            PlainBin = crypto:block_decrypt(aes_cbc256, Key, Ivec, EncryptedBin),
           {ok, PlainBin, crypto:next_iv(aes_cbc,EncryptedBin)}
        end,

    Cr = {InitFun,DecryptFun},
    {ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read,{crypto,Cr}]),
    {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]),
    ok = erl_tar:close(HandleRead),

## Creating a Subsystem

A small `ssh` subsystem that echoes N bytes can be implemented as shown in the following example:

    -module(ssh_echo_server).
    -behaviour(ssh_server_channel).
    -record(state, {
        n,
        id,
        cm
    }).
    -export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).

    init([N]) ->
        {ok, #state{n = N}}.

    handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
        {ok, State#state{id = ChannelId,
                         cm = ConnectionManager}}.

    handle_ssh_msg({ssh_cm, CM, {data, ChannelId, 0, Data}}, #state{n = N} = State) ->
        M = N - byte_size(Data),
        case M > 0 of
            true ->
                ssh_connection:send(CM, ChannelId, Data),
                {ok, State#state{n = M}};
            false ->
                <<SendData:N/binary, _/binary>> = Data,
                ssh_connection:send(CM, ChannelId, SendData),
                ssh_connection:send_eof(CM, ChannelId),
                {stop, ChannelId, State}
        end;
    handle_ssh_msg({ssh_cm, _ConnectionManager,
                    {data, _ChannelId, 1, Data}}, State) ->
        logger:notice(" ~p~n", [binary_to_list(Data)]),
        {ok, State};

    handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
        {ok, State};

    handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
        %% Ignore signals according to RFC 4254 section 6.9.
        {ok, State};

    handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, _Error, _}},
                   State) ->
        {stop, ChannelId, State};

    handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, _Status}}, State) ->
        {stop, ChannelId, State}.

    terminate(_Reason, _State) ->
        ok.

The subsystem can be run on the host ssh.example.com with the generated keys, as described in Section Running an Erlang ssh Daemon:

    1> ssh:start().
    ok
    2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
                         {user_dir, "/tmp/otptest_user/.ssh"},
                         {subsystems, [{"echo_n", {ssh_echo_server, [10]}}]}]).
    {ok,<0.54.0>}
    3>

Then connect from an Erlang client:

    1> ssh:start().
    ok
    2> {ok, ConnectionRef} = ssh:connect("ssh.example.com", 8989,
                                        [{user_dir, "/tmp/otptest_user/.ssh"}]).
     {ok,<0.57.0>}
    3> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
    4> success = ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity).
    5> ok = ssh_connection:send(ConnectionRef, ChannelId, "0123456789", infinity).
    6> flush().
    {ssh_cm, <0.57.0>, {data, 0, 0, <<"0123456789">>}}
    {ssh_cm, <0.57.0>, {eof, 0}}
    {ssh_cm, <0.57.0>, {closed, 0}}
    7> {error, closed} = ssh_connection:send(ConnectionRef, ChannelId, "10", infinity).

See also `ssh_client_channel`.

← Previous Page SSH Protocol Introduction
Next Page → Terminology