The examples in the following sections use the utility function
The user otptest, that has bash as default shell, uses the ssh:shell/1 client to connect to the openssh daemon running on a host called tarlop. Note that currently this client is very simple and you should not be expected to be as fancy as the openssh client.
1> ssh:start().
ok
2> {ok, S} = ssh:shell("tarlop").
>pwd
/home/otptest
>exit
logout
3>
The option system_dir must be a directory containing a host
key file and it defaults to /etc/ssh. For details see section
Configuration Files in
Normally the /etc/ssh directory is only readable by root.
The option user_dir defaults to the users ~/.ssh directory
In the following example we generate new keys and host keys as to be able to run the example without having root privilages
$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
[...]
Create the file /tmp/otptest_user/.ssh/authrized_keys and add the content of /tmp/otptest_user/.ssh/id_rsa.pub Now we can do
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>
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
1: Stops the listener, but leaves existing connections started by the listener up and running.
3> ssh:stop_listener(Sshd).
ok
4>
2: Stops the listener and all connections started by the listener.
3> ssh:stop_daemon(Sshd)
ok
4>
In the following example the Erlang shell is the client process
that receives the channel replies.
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>
Note 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}
...
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>
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">>}
A very small SSH subsystem that echos N bytes could be implemented like this.
See also
-module(ssh_echo_server).
-behaviour(ssh_subsystem).
-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.
And run like this on the host tarlop with the keys generated in section 3.3
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).