1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
|
OVERVIEW
This is the low-level part of the SFTP client used in AXD 301 R10A.
Use it in any way you like.
I believe that the module ssh_transport contains the most interesting parts,
and the rest is probably less reusable.
If you decide to use it, I would appreciate feedback on any problems you come
across.
The IETF drafts in this archive are available from the IETF draft pages as
well (unless they have expired). The ...2.ps versions are really nice to have
in print, should you decide to get your hands dirty with SSH.
http://www.ietf.org/ids.by.wg/secsh.html
The SFTP API provided in ssh_ftp is a pure erlang translation of the draft
for SFTP, so read the draft to understand how to use it.
BASICS
In SSH-2, an SSH connection is set up as a transport layer (typically running
on top of TCP).
The transport layer provides
* Server authentication (public key or X.509 certificate)
* Privacy and integrity (by means of negotiated algorithms) for
data sent and received over the transport.
On top of the SSH transport, one SSH connection can be started. Most servers
will agree to start an SSH connection only in response to a successful
client (user) authentication.
The connection layer provides one or more channels, each carrying one of:
* An SSH subsystem, such as sftp.
* A terminal session.
* A "shell dialogue".
* A remote command execution (non-interactive).
* "(TCP) port forwarding".
REQUIREMENTS
OTP R9C
OpenSSL 0.9.7x (I have been using 0.9.7b).
(compiling the linked-in driver with gcc in Unices goes something like:
gcc -fpic -shared -o ../priv/lib/ssh_crypto_drv.so \
-I$SSL_ROOT/include -I$OTP_ROOT/LXA_11930_R9C_5/erts-5.3.4/src \
-L$SSL_ROOT/lib -lcrypto ssh_crypto_drv.c
)
ALREADY DONE
Client code for the transport layer, connection layer and sftp.
The transport layer implements the REQUIRED algorithms from the transport
draft, as well as
* the ssh-rss public-key algorithm for server authentication purposes
* the aes128-cbc block cipher algorithm
* the hmac-md5 authentication algorithm
The authentication module, ssh_userauth, implements the password authentication
algorithm.
The connection layer implements basic support to multiplex several channels
over an ssh connection.
The sftp module implements synchronous file operations (the protocol allows
for asynchronous operations, which could probably provide better performance).
The SFTP protocol version supported is 3. (This was also the case with OpenSsh
the last time I checked.)
Version 3 of the SFTP protocol is described in
draft-ietf-secsh-filexfer-02.txt.
Version 4 of the SFTP protocol is described in draft-ietf-secsh-filexfer-03.txt
and draft-ietf-secsh-filexfer-04.txt.
All of these documents have expired, but you can find expired drafts here:
http://www.watersprings.org/pub/id/index-wgs.html
TODO
Make the C stuff run on windows? Move it to the crypto module? (I would not
recommend the latter.)
Correct bugs. (Of course I've corrected the ones I've found.)
Probably clean up the APIs (in particular error codes and general behaviour).
The current state is to use set_active_once/1,2 with the transport and
connection layers. Although I would argue that it is sufficient, you might
disagree.
Tune buffer sizes to provide good performance without consuming too much
memory. (Especially, how big chunks to send and receive over SFTP. What send
and receive buffer sizes to use with the transport's TCP socket. Should
messages sent in the connection and transport layers be buffered until a
time or size limit is reached? Similarly, should nodelay be used with the
TCP socket?)
Implement server code (in the transport layer to begin with)?
Implement the diffie-hellman-group-exchange-sha1 key exchange algorithm,
described in draft-ietf-secsh-dh-group-exchange-04.txt.
Implement user authentication through public-key algorithms.
Implement server and user authentication through X.509 certificates.
Implement other versions of the SFTP protocol.
Build higher-level APIs for remote command execution, port forwarding etc.
EXAMPLES
% Open up an SSH transport towards the host 192.168.1.1, with no preference
% of algorithms, accepting any host key:
{ok, Ssh} = ssh_transport:open("192.168.1.1").
% Dump the state of the transport:
ssh_transport:dump_config(Ssh).
% Force re-exchange of session keys for the transport:
ssh_transport:exchange_keys(Ssh).
% (dump the config again and compare what's changed in a re-exchange)
%
% Start the service "ssh-connection" over the transport, by authenticating:
ssh_userauth:passwd(Ssh, "ericsson", "microsoftsupporter", "ssh-connection").
% (where "ericsson" is a user name and "microsoftsupporter" is the
% corresponding password)
% Shut down the transport:
ssh_transport:close(Ssh).
%
% Start an ssh connection on the host 192.168.1.1:
% (This is a higher level API than the previously used functions...)
{ok, SshConn} = ssh_connection:open("192.168.1.1"),
ssh_connection:user(SshConn, "ericsson", "microsoftsupporter").
% Execute a command on the server:
{ok, Channel} = ssh_connection:exec(SshConn, "ls"),
ssh_connection:set_active_once(SshConn, Channel),
receive
{ssh_connection, SshConn, Channel, Msg} ->
io:format("~s", [binary_to_list(Msg)])
after 5000 -> timeout
end,
ssh_connection:set_active_once(SshConn, Channel),
receive
SshConnectionClosingAfterExec ->
SshConnectionClosingAfterExec
end.
% Try out the sftp API: (it makes use of ssh_connection)
{ok, SftpSession} = ssh_ftp:start("192.168.1.1").
ssh_ftp:user(SftpSession, "ericsson", "myvoiceismypasswd").
ssh_ftp:realpath(SftpSession, ".").
{ok, DirHandle} = ssh_ftp:opendir(SftpSession, ".").
% (you may repeat the next command until the return value is
% {error, {eof, Description}})
ssh_ftp:readdir(SftpSession, DirHandle).
ssh_ftp:close(SftpSession, DirHandle).
ssh_ftp:stat(SftpSession, "existing_remote_file").
{ok, FileHandle} = ssh_ftp:open(SftpSession, "existing_remote_file").
%fstat is like stat, but for opened files
ssh_ftp:fstat(SftpSession, FileHandle).
ssh_ftp:read(SftpSession, FileHandle, 0, ChunkSize).
ssh_ftp:close(SftpSession, FileHandle).
{ok, WriteFileHandle} =
ssh_ftp:open(SftpSession, "new_remote_file", [write, creat, trunc],
ssh_ftp:empty_attrs()).
ssh_ftp:write(SftpSession, WriteFileHandle, 0, "This goes into the new file.").
ssh_ftp:close(SftpSession, WriteFileHandle).
{ok, ReadFileHandle} = ssh_ftp:open(SftpSession, "new_remote_file").
ssh_ftp:read(SftpSession, FileHandle, 0, 30).
ssh_ftp:close(SftpSession, FileHandle).
ssh_ftp:rename(SftpSession, "new_remote_file", "renamed_remote_file").
ssh_ftp:remove(SftpSession, "renamed_remote_file").
ssh_ftp:stop(SftpSession).
% To get the fingerprints of a host's keys:
ssh_transport:open(Host,
[{verifun, fun(_,_) -> false end},{pub_key_alg,["ssh-dss"]}]).
ssh_transport:open(Host,
[{verifun, fun(_,_) -> false end},{pub_key_alg,["ssh-rsa"]}]).
% The ability to provide a fun for verification of the server keys allows you
% to use e.g. some GUI callback to ask an operator.
% To start an SFTP session towards a known host (the fingerprint of the
% server's key is known):
Fingerprint = "76:7e:d7:44:06:46:92:ed:4b:75:b2:ed:1e:f8:b5:79",
ssh_ftp:start(Host,
[{verifun, fun("ssh-dss",Fingerprint) -> true; (_,_) -> false end},
{pub_key_alg,["ssh-dss"]}]).
|