2.入门 | 2. Getting Started
2 Getting Started
2.1一般信息
下面的示例使用效用函数ssh:start/0
来启动所有需要的应用程序(crypto
,public_key
,和ssh
)。所有示例都在Erlang shell或bash shell中运行,使用openssh
来说明如何使用该ssh
应用程序。这些示例otptest
以本地网络上的用户身份运行,用户有权登录ssh
到主机的tarlop
。
如果没有其他说明,则推定otptest
用户在tarlop
的authorized_keys
文件中有一个条目(允许在不输入密码的情况下登录)。另外,tarlop
是用户文件中的已知主机。这意味着主机验证可以在没有用户交互的情况下完成。sshknown_hostsotptest
2.2使用Erlangssh终端客户端
用户otptest
,它以bash作为默认shell,它使用ssh:shell/1
客户端连接到露台
运行在名为塔洛普
*
1> ssh:start().
ok
2> {ok, S} = ssh:shell("tarlop").
otptest@tarlop:> pwd
/home/otptest
otptest@tarlop:> exit
logout
3>
运行Erlangssh守护进程的2.3
该system_dir
选项必须是包含主机密钥文件的目录,默认为/etc/ssh
。有关详细信息,请参见中的配置文件ssh(6)
。
注
通常,/etc/ssh
目录只能通过根目录读取。
该选项user_dir
默认为目录users ~/.ssh
。
第一步。
若要在没有根权限的情况下运行该示例,请生成新键和主机键:
$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
[...]
第2步。
创建文件/tmp/otptest_user/.ssh/authorized_keys
并添加内容/tmp/otptest_user/.ssh/id_rsa.pub
。
第三步。
启动Erlangssh
守护进程:
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>
第四步。
使用露台
从shell连接到Erlang的客户端ssh
守护进程:
$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>
关闭ssh
守护进程有两种方法,请参阅步骤5a
和步骤5b
。
第5a步
关闭二郎ssh
守护进程使其停止侦听器,但保留由侦听器启动的现有连接,操作如下:
3> ssh:stop_listener(Sshd).
ok
4>
第5b步
关闭二郎ssh
守护进程,以便它停止侦听器和监听器启动的所有连接:
3> ssh:stop_daemon(Sshd)
ok
4>
2.4一次性执行
在下面的示例中,Erlangshell是接收通道回复的客户端进程。
注
本例中接收到的消息数量取决于运行ssh
守护程序的机器上使用哪个操作系统和哪个shell 。另见ssh_connection:exec/4
。
对远程命令执行一次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>
注意,只有通道关闭。连接仍然处于正常状态,可以处理其他通道:
6> {ok, NewChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
{ok,1}
...
2.5 SFTP服务器
启动Erlangssh
带有SFTP子系统的守护进程:
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>
运行OpenSSHSFTP客户端:
$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客户端
使用ErlangSFTP客户端获取一个文件:
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客户端的TAR压缩和加密
下面是编写并读取tar文件的示例:
{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),
前面的写入和读取示例可以通过加密和解密进行扩展,如下所示:
%% 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创建子系统
小的ssh
可以实现回显N个字节的子系统,如以下示例所示:
-module(ssh_echo_server).
-behaviour(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.
子系统可以在主机上运行。塔洛普
使用生成的密钥,如节中所述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).
另见ssh_channel(3)
。