2 Getting Started

2.1  General Information

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 tarlop.

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

2.2  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 tarlop:

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

2.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 Files in ssh(6).

Note

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

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

Step 1. To run the example without root privileges, generate new keys and host 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"}]).
      {ok,<0.54.0>}
      3>

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

      $bash> ssh tarlop -p 8989  -i /tmp/otptest_user/.ssh/id_rsa\
             -o UserKnownHostsFile=/tmp/otptest_user/.ssh/known_hosts
      The authenticity of host 'tarlop' 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 'tarlop' (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>

2.4  One-Time Execution

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

Note

The number of received messages in this example depends on which OS and which shell that is used on the machine running the ssh daemon. See also ssh_connection:exec/4.

Do a one-time execution of a remote command over ssh:

      1>  ssh:start().
      ok
      2> {ok, ConnectionRef} = ssh:connect("tarlop", 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().
      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>

Notice that only the channel is closed. The connection is still up and can handle other channels:

      6> {ok, NewChannelId} =  ssh_connection:session_channel(ConnectionRef, infinity).
        {ok,1}
	...

2.5  SFTP Server

Start the Erlang ssh daemon with the SFTP subsystem:

      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"}])
                                        ]}]).
      {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 tarlop
      Connecting to tarlop...
      sftp> pwd
      Remote working directory: /tmp/sftp/example
      sftp>

2.6  SFTP Client

Fetch a file with the Erlang SFTP client:

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

2.7  SFTP Client with TAR Compression and Encryption

Example of writing and then reading a tar file follows:

      {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),

The previous write and read 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,Cw}]),
{ok,NameValueList} = erl_tar:extract(HandleRead,[memory]),
ok = erl_tar:close(HandleRead),

2.8  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). % replaces ssh_daemon_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 - 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) ->
    error_logger:format(standard_error, " ~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 tarlop 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>
   1> ssh:start().
   ok
   2>{ok, ConnectionRef} = ssh:connect("tarlop", 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_msg, <0.57.0>, {data, 0, 1, "0123456789"}}
   {ssh_msg, <0.57.0>, {eof, 0}}
   {ssh_msg, <0.57.0>, {closed, 0}}
   7> {error, closed} = ssh_connection:send(ConnectionRef, ChannelId, "10", infinity).

See also ssh_client_channel(3) (replaces ssh_channel(3)).