2.入门 | 2. Getting Started

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)