非常教程

Erlang 20参考手册

ssh

2.入门 | 2. Getting Started

2.1一般信息

下面的示例使用效用函数ssh:start/0来启动所有需要的应用程序(cryptopublic_key,和ssh)。所有示例都在Erlang shell或bash shell中运行,使用openssh来说明如何使用该ssh应用程序。这些示例otptest以本地网络上的用户身份运行,用户有权登录ssh到主机的tarlop

如果没有其他说明,则推定otptest用户在tarlopauthorized_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)

Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1