Monday, December 23, 2013

Erlang client SCP

Okay, I needed to send some data to a unit using Secure Copy (SCP), from an Erlang program.
Well it turned out Erlang did not have a SCP implementation as part of the ssh module, only sftp, but that was not supported by the receiving device, only SCP.

Hmmm, okay I could write the data and use the os:cmd to transfer the file, but where is the fun in that.

So I decided to implement a simple SCP, the mechanics of SCP is described brilliantly here: "How the SCP protocol works".

So here are some code snippets, which shows my take on this in Erlang.

First you need to establish a SSH connection to the receiving side, this is done with the ssh module fro my case like something like this, your connection parameters will vary:
 
 crypto:start(),
 ssh:start(), 
 {ok, ConnectionRef} = ssh:connect("localhost",10022,
   [{user,"root"},
    {user_dir,"/usr/local/etc/ssh"},
    {rsa_pass_phrase,"whatever"}]
   , 30000),

Now you have the ConnectionRef, that is needed. With this you can open a secure channel like this:

 {ok, ChannelId} = 
  ssh_connection:session_channel(ConnectionRef, 30000),

And execute an SCP command on the remote side using this channel:

success = ssh_connection:exec(ConnectionRef
                              ChannelId,
                    "scp -tq /tmp", 30000),

The last parameter to the remote scp command is the directory, where your file ends up.
Now I just call:
 wait_for_response(ConnectionRef,ChannelId,FileName,Content,0);

Where FileName parameter is the name of the file ending up at the remote side, and files content is in Content parameter. The wait_for_response implements the SCP protocol like this:

wait_for_response(ConnectionRef,ChannelId,FileName,Content,C) ->
  receive
    {ssh_cm, ConnectionRef, Msg} ->
      case Msg of
        {closed, ChannelId} ->
          ssh_connection:close(ConnectionRef, ChannelId),ok;
        {eof, ChannelId} -> 
          ssh_connection:close(ConnectionRef, ChannelId),
          {error, <<"Error: EOF">>};
        {exit_signal, ChannelId, ExitSignal
         ErrorMsg, _LanguageString} ->
          ssh_connection:close(ConnectionRef, ChannelId),
          {error, list_to_binary(io_lib:format(
                    "Remote SCP exit signal: ~p : ~p"
                    [ExitSignal,ErrorMsg]))};
        {exit_status,ChannelId,ExitStatus} ->
          ssh_connection:close(ConnectionRef, ChannelId),
          case ExitStatus of
            0 -> ok;
            _ -> {error,
                  list_to_binary(io_lib:format(
                     "Remote SCP exit status: ~p",[ExitStatus]))}
          end;
        {data,ChannelId,_Type,<<0>>} ->
          SendResponse
          case C of 
            0 -> %%ok send header
              Header
                list_to_binary(lists:flatten(
                  io_lib:format("~s ~p ~s~n",
                  ["C0644", size(Content), FileName]))),
                ssh_connection:send(ConnectionRef,
                                    ChannelId
                                    Header);
            1 -> %%ok send file
              ssh_connection:send(ConnectionRef
                                  ChannelId
                                  Content),
              ssh_connection:send(ConnectionRef
                                  ChannelId,
                                  <<"\0">>);
            2 -> %%ok file transferred
              ssh_connection:send_eof(ConnectionRef, ChannelId)
          end,
          case SendResponse of
            ok -> %%recurse
              wait_for_response(ConnectionRef,
                                ChannelId,
                                FileName,
                                Content,C+1);
            _ -> SendResponse
          end
      end
  end.  


Notice that all files are ends up with permission 0644 as per the Header:
io_lib:format("~s ~p ~s~n",["C0644", size(Content), FileName])
and how the parameter C holds the protocol state.

And don't for get to close your connection when done:  ssh:close(ConnectionRef)

I might someday get the time to write a feature complete SCP module for Erlang, But till then, these snippets might help some of you.