%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2009-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %%% @doc SSH/SFTP client module. %%% %%%
ct_ssh uses the OTP ssh application and more detailed information %%% about e.g. functions, types and options can be found in the %%% documentation for this application.
%%% %%%The Server
argument in the SFTP functions should
%%% only be used for SFTP sessions that have been started on existing
%%% SSH connections (i.e. when the original connection type is
%%% ssh
). Whenever the connection type is
%%% sftp
, use the SSH connection reference only.
The following options are valid for specifying an SSH/SFTP %%% connection (i.e. may be used as config elements):
%%% %%%%%% %%% [{ConnType, Addr}, %%% {port, Port}, %%% {user, UserName} %%% {password, Pwd} %%% {user_dir, String} %%% {public_key_alg, PubKeyAlg} %%% {connect_timeout, Timeout} %%% {key_cb, KeyCallbackMod}] %%%%%% %%%
ConnType = ssh | sftp
.
Please see ssh(3) for other types.
%%% %%%All timeout parameters in ct_ssh functions are values in %%% milliseconds.
%%% %%% @type connection() = handle() | ct:target_name() %%% @type handle() = ct_gen_conn:handle(). Handle for a specific %%% SSH/SFTP connection. %%% @type ssh_sftp_return() = term(). A return value from an ssh_sftp function. -module(ct_ssh). %% SSH Functions -export([connect/1, connect/2, connect/3, disconnect/1, session_open/1, session_open/2, session_close/2, send/3, send/4, send/5, receive_response/2, receive_response/3, receive_response/4, send_and_receive/3, send_and_receive/4, send_and_receive/5, send_and_receive/6, exec/2, exec/3, exec/4, subsystem/3, subsystem/4, shell/2, shell/3]). %% STFP Functions -export([sftp_connect/1, read_file/2, write_file/3, list_dir/2, open/3, opendir/2, close/2, read/3, pread/4, aread/3, apread/4, write/3, pwrite/4, awrite/3, apwrite/4, position/3, read_file_info/2, get_file_info/2, read_link_info/2, write_file_info/3, read_link/2, make_symlink/3, rename/3, delete/2, make_dir/2, del_dir/2, read_file/3, write_file/4, list_dir/3, open/4, opendir/3, close/3, read/4, pread/5, aread/4, apread/5, write/4, pwrite/5, awrite/4, apwrite/5, position/4, read_file_info/3, get_file_info/3, read_link_info/3, write_file_info/4, read_link/3, make_symlink/4, rename/4, delete/3, make_dir/3, del_dir/3]). %% Callbacks -export([init/3, handle_msg/2, reconnect/2, terminate/2, close/1]). -define(DEFAULT_TIMEOUT, 10000). -record(state, {ssh_ref, conn_type, target}). -type handle() :: pid(). %%%----------------------------------------------------------------- %%%------------------------ SSH COMMANDS --------------------------- %%%----------------------------------------------------------------- %%% @spec connect(KeyOrName) -> {ok,Handle} | {error,Reason} %%% @equiv connect(KeyOrName,host,[]) connect(KeyOrName) -> connect(KeyOrName, host). %%%----------------------------------------------------------------- %%% @spec connect(KeyOrName,ConnType) -> {ok,Handle} | {error,Reason} %%% @equiv connect(KeyOrName,ConnType,[]) connect(KeyOrName, ConnType) when is_atom(ConnType) -> connect(KeyOrName, ConnType, []); %%%----------------------------------------------------------------- %%% @spec connect(KeyOrName,ExtraOpts) -> {ok,Handle} | {error,Reason} %%% @equiv connect(KeyOrName,host,ExtraOpts) connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) -> connect(KeyOrName, host, ExtraOpts). %%%----------------------------------------------------------------- %%% @spec connect(KeyOrName,ConnType,ExtraOpts) -> %%% {ok,Handle} | {error,Reason} %%% KeyOrName = Key | Name %%% Key = atom() %%% Name = ct:target_name() %%% ConnType = ssh | sftp | host %%% ExtraOpts = ssh_connect_options() %%% Handle = handle() %%% Reason = term() %%% %%% @doc Open an SSH or SFTP connection using the information %%% associated withKeyOrName
.
%%%
%%% If Name
(an alias name for Key
),
%%% is used to identify the connection, this name may
%%% be used as connection reference for subsequent calls.
%%% It's only possible to have one open connection at a time
%%% associated with Name
. If Key
is
%%% used, the returned handle must be used for subsequent calls
%%% (multiple connections may be opened using the config
%%% data specified by Key
). See
ConnType
will always override the type
%%% specified in the address tuple in the configuration data (and
%%% in ExtraOpts
). So it is possible to for example
%%% open an sftp connection directly using data originally
%%% specifying an ssh connection. The value host
%%% means the connection type specified by the host option
%%% (either in the configuration data or in ExtraOpts
)
%%% will be used.
ExtraOpts
(optional) are extra SSH options
%%% to be added to the config data for KeyOrName
.
%%% The extra options will override any existing options with the
%%% same key in the config data. For details on valid SSH
%%% options, see the documentation for the OTP ssh application.
Command
. A session
%%% channel is opened automatically for the request.
%%% Data
is received from the server as a result
%%% of the command.
exec(SSH, Command, Timeout) when is_list(Command) ->
exec(SSH, undefined, Command, Timeout);
%%%-----------------------------------------------------------------
%%% @spec exec(SSH,ChannelId,Command) -> {ok,Data} | {error,Reason}
%%% @equiv exec(SSH,ChannelId,Command,DefaultTimeout)
exec(SSH, ChannelId, Command) when is_integer(ChannelId) ->
exec(SSH, ChannelId, Command, ?DEFAULT_TIMEOUT).
%%%-----------------------------------------------------------------
%%% @spec exec(SSH,ChannelId,Command,Timeout) -> {ok,Data} | {error,Reason}
%%% SSH = connection()
%%% ChannelId = integer()
%%% Command = string()
%%% Timeout = integer()
%%% Data = list()
%%% Reason = term()
%%%
%%% @doc Requests server to perform Command
. A previously
%%% opened session channel is used for the request.
%%% Data
is received from the server as a result
%%% of the command.
exec(SSH, ChannelId, Command, Timeout) ->
call(SSH, {exec,ChannelId,Command,Timeout}).
%%%-----------------------------------------------------------------
%%% @spec receive_response(SSH,ChannelId) -> {ok,Data} | {error,Reason}
%%% @equiv receive_response(SSH,ChannelId,close)
receive_response(SSH, ChannelId) ->
receive_response(SSH, ChannelId, close, ?DEFAULT_TIMEOUT).
%%%-----------------------------------------------------------------
%%% @spec receive_response(SSH,ChannelId,End) -> {ok,Data} | {error,Reason}
%%% @equiv receive_response(SSH,ChannelId,End,DefaultTimeout)
receive_response(SSH, ChannelId, End) when is_function(End) ->
receive_response(SSH, ChannelId, End, ?DEFAULT_TIMEOUT);
%%%-----------------------------------------------------------------
%%% @spec receive_response(SSH,ChannelId,Timeout) -> {ok,Data} | {error,Reason}
%%% @equiv receive_response(SSH,ChannelId,close,Timeout)
receive_response(SSH, ChannelId, Timeout) when is_integer(Timeout) ->
receive_response(SSH, ChannelId, close, Timeout).
%%%-----------------------------------------------------------------
%%% @spec receive_response(SSH,ChannelId,End,Timeout) ->
%%% {ok,Data} | {timeout,Data} | {error,Reason}
%%% SSH = connection()
%%% ChannelId = integer()
%%% End = Fun | close | timeout
%%% Timeout = integer()
%%% Data = list()
%%% Reason = term()
%%%
%%% @doc Receives expected data from server on the specified
%%% session channel.
%%%
%%% If End == close
, data is returned
%%% to the caller when the channel is closed by the
%%% server. If a timeout occurs before this happens,
%%% the function returns {timeout,Data}
%%% (where Data
is the data received so far).
%%% If End == timeout
, a timeout is expected
%%% and {ok,Data}
is returned both in the case
%%% of a timeout and when the channel is closed. If
%%% End
is a fun, this fun will be
%%% called with one argument - the data value in a received
%%% ssh_cm
message (see ssh_connection(3)). The
%%% fun should return true
to end the receiving
%%% operation (and have the so far collected data returned), or
%%% false
to wait for more data from the server.
%%% (Note that even if a fun is supplied, the function returns
%%% immediately if the server closes the channel).
See receive_response/4
for details on the
%%% End
argument.
Server
identifies the new session and must be
%%% specified whenever SFTP requests are to be sent.
sftp_connect(SSH) ->
call(SSH, sftp_connect).
%%%-----------------------------------------------------------------
%%% @spec read_file(SSH, File) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read_file(SSH, File) ->
call(SSH, {read_file,sftp,File}).
%%%-----------------------------------------------------------------
%%% @spec read_file(SSH, Server, File) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read_file(SSH, Server, File) ->
call(SSH, {read_file,Server,File}).
%%%-----------------------------------------------------------------
%%% @spec write_file(SSH, File, Iolist) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
write_file(SSH, File, Iolist) ->
call(SSH, {write_file,sftp,File,Iolist}).
%%%-----------------------------------------------------------------
%%% @spec write_file(SSH, Server, File, Iolist) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
write_file(SSH, Server, File, Iolist) ->
call(SSH, {write_file,Server,File,Iolist}).
%%%-----------------------------------------------------------------
%%% @spec list_dir(SSH, Path) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
list_dir(SSH, Path) ->
call(SSH, {list_dir,sftp,Path}).
%%%-----------------------------------------------------------------
%%% @spec list_dir(SSH, Server, Path) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
list_dir(SSH, Server, Path) ->
call(SSH, {list_dir,Server,Path}).
%%%-----------------------------------------------------------------
%%% @spec open(SSH, File, Mode) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
open(SSH, File, Mode) ->
call(SSH, {open,sftp,File,Mode}).
%%%-----------------------------------------------------------------
%%% @spec open(SSH, Server, File, Mode) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
open(SSH, Server, File, Mode) ->
call(SSH, {open,Server,File,Mode}).
%%%-----------------------------------------------------------------
%%% @spec opendir(SSH, Path) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
opendir(SSH, Path) ->
call(SSH, {opendir,sftp,Path}).
%%%-----------------------------------------------------------------
%%% @spec opendir(SSH, Server, Path) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
opendir(SSH, Server, Path) ->
call(SSH, {opendir,Server,Path}).
%%%-----------------------------------------------------------------
%%% @spec close(SSH, Handle) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
close(SSH, Handle) ->
call(SSH, {close,sftp,Handle}).
%%%-----------------------------------------------------------------
%%% @spec close(SSH, Server, Handle) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
close(SSH, Server, Handle) ->
call(SSH, {close,Server,Handle}).
%%%-----------------------------------------------------------------
%%% @spec read(SSH, Handle, Len) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read(SSH, Handle, Len) ->
call(SSH, {read,sftp,Handle,Len}).
%%%-----------------------------------------------------------------
%%% @spec read(SSH, Server, Handle, Len) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read(SSH, Server, Handle, Len) ->
call(SSH, {read,Server,Handle,Len}).
%%%-----------------------------------------------------------------
%%% @spec pread(SSH, Handle, Position, Length) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
pread(SSH, Handle, Position, Length) ->
call(SSH, {pread,sftp,Handle,Position,Length}).
%%%-----------------------------------------------------------------
%%% @spec pread(SSH, Server, Handle, Position, Length) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
pread(SSH, Server, Handle, Position, Length) ->
call(SSH, {pread,Server,Handle,Position,Length}).
%%%-----------------------------------------------------------------
%%% @spec aread(SSH, Handle, Len) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
aread(SSH, Handle, Len) ->
call(SSH, {aread,sftp,Handle,Len}).
%%%-----------------------------------------------------------------
%%% @spec aread(SSH, Server, Handle, Len) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
aread(SSH, Server, Handle, Len) ->
call(SSH, {aread,Server,Handle,Len}).
%%%-----------------------------------------------------------------
%%% @spec apread(SSH, Handle, Position, Length) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
apread(SSH, Handle, Position, Length) ->
call(SSH, {apread,sftp,Handle,Position,Length}).
%%%-----------------------------------------------------------------
%%% @spec apread(SSH, Server, Handle, Position, Length) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
apread(SSH, Server, Handle, Position, Length) ->
call(SSH, {apread,Server,Handle,Position,Length}).
%%%-----------------------------------------------------------------
%%% @spec write(SSH, Handle, Data) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
write(SSH, Handle, Data) ->
call(SSH, {write,sftp,Handle,Data}).
%%%-----------------------------------------------------------------
%%% @spec write(SSH, Server, Handle, Data) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
write(SSH, Server, Handle, Data) ->
call(SSH, {write,Server,Handle,Data}).
%%%-----------------------------------------------------------------
%%% @spec pwrite(SSH, Handle, Position, Data) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
pwrite(SSH, Handle, Position, Data) ->
call(SSH, {pwrite,sftp,Handle,Position,Data}).
%%%-----------------------------------------------------------------
%%% @spec pwrite(SSH, Server, Handle, Position, Data) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
pwrite(SSH, Server, Handle, Position, Data) ->
call(SSH, {pwrite,Server,Handle,Position,Data}).
%%%-----------------------------------------------------------------
%%% @spec awrite(SSH, Handle, Data) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
awrite(SSH, Handle, Data) ->
call(SSH, {awrite,sftp,Handle, Data}).
%%%-----------------------------------------------------------------
%%% @spec awrite(SSH, Server, Handle, Data) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
awrite(SSH, Server, Handle, Data) ->
call(SSH, {awrite,Server,Handle, Data}).
%%%-----------------------------------------------------------------
%%% @spec apwrite(SSH, Handle, Position, Data) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
apwrite(SSH, Handle, Position, Data) ->
call(SSH, {apwrite,sftp,Handle,Position,Data}).
%%%-----------------------------------------------------------------
%%% @spec apwrite(SSH, Server, Handle, Position, Data) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
apwrite(SSH, Server, Handle, Position, Data) ->
call(SSH, {apwrite,Server,Handle,Position,Data}).
%%%-----------------------------------------------------------------
%%% @spec position(SSH, Handle, Location) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
position(SSH, Handle, Location) ->
call(SSH, {position,sftp,Handle,Location}).
%%%-----------------------------------------------------------------
%%% @spec position(SSH, Server, Handle, Location) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
position(SSH, Server, Handle, Location) ->
call(SSH, {position,Server,Handle,Location}).
%%%-----------------------------------------------------------------
%%% @spec read_file_info(SSH, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read_file_info(SSH, Name) ->
call(SSH, {read_file_info,sftp,Name}).
%%%-----------------------------------------------------------------
%%% @spec read_file_info(SSH, Server, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read_file_info(SSH, Server, Name) ->
call(SSH, {read_file_info,Server,Name}).
%%%-----------------------------------------------------------------
%%% @spec get_file_info(SSH, Handle) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
get_file_info(SSH, Handle) ->
call(SSH, {get_file_info,sftp,Handle}).
%%%-----------------------------------------------------------------
%%% @spec get_file_info(SSH, Server, Handle) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
get_file_info(SSH, Server, Handle) ->
call(SSH, {get_file_info,Server,Handle}).
%%%-----------------------------------------------------------------
%%% @spec read_link_info(SSH, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read_link_info(SSH, Name) ->
call(SSH, {read_link_info,sftp,Name}).
%%%-----------------------------------------------------------------
%%% @spec read_link_info(SSH, Server, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read_link_info(SSH, Server, Name) ->
call(SSH, {read_link_info,Server,Name}).
%%%-----------------------------------------------------------------
%%% @spec write_file_info(SSH, Name, Info) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
write_file_info(SSH, Name, Info) ->
call(SSH, {write_file_info,sftp,Name,Info}).
%%%-----------------------------------------------------------------
%%% @spec write_file_info(SSH, Server, Name, Info) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
write_file_info(SSH, Server, Name, Info) ->
call(SSH, {write_file_info,Server,Name,Info}).
%%%-----------------------------------------------------------------
%%% @spec read_link(SSH, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read_link(SSH, Name) ->
call(SSH, {read_link,sftp,Name}).
%%%-----------------------------------------------------------------
%%% @spec read_link(SSH, Server, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
read_link(SSH, Server, Name) ->
call(SSH, {read_link,Server,Name}).
%%%-----------------------------------------------------------------
%%% @spec make_symlink(SSH, Name, Target) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
make_symlink(SSH, Name, Target) ->
call(SSH, {make_symlink,sftp,Name,Target}).
%%%-----------------------------------------------------------------
%%% @spec make_symlink(SSH, Server, Name, Target) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
make_symlink(SSH, Server, Name, Target) ->
call(SSH, {make_symlink,Server,Name,Target}).
%%%-----------------------------------------------------------------
%%% @spec rename(SSH, OldName, NewName) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
rename(SSH, OldName, NewName) ->
call(SSH, {rename,sftp,OldName,NewName}).
%%%-----------------------------------------------------------------
%%% @spec rename(SSH, Server, OldName, NewName) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
rename(SSH, Server, OldName, NewName) ->
call(SSH, {rename,Server,OldName,NewName}).
%%%-----------------------------------------------------------------
%%% @spec delete(SSH, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
delete(SSH, Name) ->
call(SSH, {delete,sftp,Name}).
%%%-----------------------------------------------------------------
%%% @spec delete(SSH, Server, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
delete(SSH, Server, Name) ->
call(SSH, {delete,Server,Name}).
%%%-----------------------------------------------------------------
%%% @spec make_dir(SSH, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
make_dir(SSH, Name) ->
call(SSH, {make_dir,sftp,Name}).
%%%-----------------------------------------------------------------
%%% @spec make_dir(SSH, Server, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
make_dir(SSH, Server, Name) ->
call(SSH, {make_dir,Server,Name}).
%%%-----------------------------------------------------------------
%%% @spec del_dir(SSH, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
del_dir(SSH, Name) ->
call(SSH, {del_dir,sftp,Name}).
%%%-----------------------------------------------------------------
%%% @spec del_dir(SSH, Server, Name) -> Result
%%% SSH = connection()
%%% Result = ssh_sftp_return() | {error,Reason}
%%% Reason = term()
%%% @doc For info and other types, see ssh_sftp(3).
del_dir(SSH, Server, Name) ->
call(SSH, {del_dir,Server,Name}).
%%%=================================================================
%%% Callback functions
%%%=================================================================
%% @hidden
init(KeyOrName, {ConnType,Addr,Port}, AllOpts) ->
User = proplists:get_value(user, AllOpts),
Password = case proplists:get_value(password, AllOpts) of
undefined -> "";
Pwd -> Pwd
end,
AllOpts1 = case proplists:get_value(connect_timeout, AllOpts) of
undefined ->
[{connect_timeout,?DEFAULT_TIMEOUT}|AllOpts];
_ ->
AllOpts
end,
Options =
lists:foldl(fun({ssh,_},Opts) -> Opts;
({sftp,_},Opts) -> Opts;
({port,_},Opts) -> Opts;
({silently_accept_hosts,_},Opts) -> Opts;
({user_interaction,_},Opts) -> Opts;
(Opt={Key,_},Opts) ->
case lists:keymember(Key, 1, Opts) of
true -> Opts;
false -> [Opt|Opts]
end;
(_,Opts) -> Opts
end, [], AllOpts1),
FinalOptions = [{silently_accept_hosts,true},
{user_interaction,false} | Options],
_ = crypto:start(),
_ = ssh:start(),
Result = case ConnType of
ssh ->
ssh:connect(Addr, Port, FinalOptions);
sftp ->
ssh_sftp:start_channel(Addr, Port, FinalOptions)
end,
case Result of
Error = {error,_} ->
Error;
Ok ->
SSHRef = element(2, Ok),
try_log(heading(init,KeyOrName),
"Opened ~w connection:\n"
"Host: ~p (~p)\nUser: ~p\nPassword: ~p\n",
[ConnType,Addr,Port,User,lists:duplicate(length(Password),$*)]),
{ok,SSHRef,#state{ssh_ref=SSHRef, conn_type=ConnType,
target=KeyOrName}}
end.
%% @hidden
handle_msg(sftp_connect, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(sftp_connect,Target), "SSH Ref: ~p", [SSHRef]),
{ssh_sftp:start_channel(SSHRef),State};
handle_msg({session_open,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(session_open,Target), "SSH Ref: ~p, Timeout: ~p",
[SSHRef,TO]),
{ssh_connection:session_channel(SSHRef, TO),State};
handle_msg({session_close,Chn}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(session_close,Target), "SSH Ref: ~p, Chn: ~p", [SSHRef,Chn]),
{ssh_connection:close(SSHRef, Chn),State};
handle_msg({exec,Chn,Command,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
Chn1 =
if Chn == undefined ->
try_log(heading(exec,Target),
"Opening channel for exec, SSH Ref: ~p", [SSHRef]),
case ssh_connection:session_channel(SSHRef, TO) of
{ok,C} -> C;
CErr -> CErr
end;
true ->
Chn
end,
case Chn1 of
{error,_} = ChnError ->
log(heading(exec,Target), "Opening channel failed: ~p", [ChnError]),
{ChnError,State};
_ ->
try_log(heading(exec,Target),
"SSH Ref: ~p, Chn: ~p, Command: ~p, Timeout: ~p",
[SSHRef,Chn1,Command,TO]),
case ssh_connection:exec(SSHRef, Chn1, Command, TO) of
success ->
Result = do_recv_response(SSHRef, Chn1, [], close, TO),
ssh_connection:close(SSHRef, Chn1),
{Result,State};
Other ->
{{error,Other},State}
end
end;
handle_msg({receive_response,Chn,End,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(receive_response,Target),
"SSH Ref: ~p, Chn: ~p, Timeout: ~p", [SSHRef,Chn,TO]),
Result = do_recv_response(SSHRef, Chn, [], End, TO),
{Result,State};
handle_msg({send,Chn,Type,Data,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(send,Target),
"SSH Ref: ~p, Chn: ~p, Type: ~p, Timeout: ~p~n"
"Data: ~p", [SSHRef,Chn,Type,TO,Data]),
Result = ssh_connection:send(SSHRef, Chn, Type, Data, TO),
{Result,State};
handle_msg({send_and_receive,Chn,Type,Data,End,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(send_and_receive,Target),
"SSH Ref: ~p, Chn: ~p, Type: ~p, Timeout: ~p~n"
"Data: ~p", [SSHRef,Chn,Type,TO,Data]),
case ssh_connection:send(SSHRef, Chn, Type, Data, TO) of
ok ->
Result = do_recv_response(SSHRef, Chn, [], End, TO),
{Result,State};
Error ->
{Error,State}
end;
handle_msg({subsystem,Chn,Subsystem,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(subsystem,Target),
"SSH Ref: ~p, Chn: ~p, Subsys: ~p, Timeout: ~p",
[SSHRef,Chn,Subsystem,TO]),
Result = ssh_connection:subsystem(SSHRef, Chn, Subsystem, TO),
{Result,State};
handle_msg({shell,Chn,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(shell,Target),
"SSH Ref: ~p, Chn: ~p, Timeout: ~p",
[SSHRef,Chn,TO]),
Result = ssh_connection:shell(SSHRef, Chn),
{Result,State};
%% --- SFTP Commands ---
handle_msg({read_file,Srv,File}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_file(ref(Srv,SSHRef), File),S};
handle_msg({write_file,Srv,File,Iolist}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write_file(ref(Srv,SSHRef), File, Iolist),S};
handle_msg({list_dir,Srv,Path}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:list_dir(ref(Srv,SSHRef), Path),S};
handle_msg({open,Srv,File,Mode}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:open(ref(Srv,SSHRef), File, Mode),S};
handle_msg({opendir,Srv,Path}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:opendir(ref(Srv,SSHRef), Path),S};
handle_msg({close,Srv,Handle}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:close(ref(Srv,SSHRef), Handle),S};
handle_msg({read,Srv,Handle,Len}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read(ref(Srv,SSHRef), Handle, Len),S};
handle_msg({pread,Srv,Handle,Position,Length}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:pread(ref(Srv,SSHRef),Handle,Position,Length),S};
handle_msg({aread,Srv,Handle,Len}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:aread(ref(Srv,SSHRef), Handle, Len),S};
handle_msg({apread,Srv,Handle,Position,Length}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:apread(ref(Srv,SSHRef), Handle, Position, Length),S};
handle_msg({write,Srv,Handle,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write(ref(Srv,SSHRef), Handle, Data),S};
handle_msg({pwrite,Srv,Handle,Position,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:pwrite(ref(Srv,SSHRef), Handle, Position, Data),S};
handle_msg({awrite,Srv,Handle,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:awrite(ref(Srv,SSHRef), Handle, Data),S};
handle_msg({apwrite,Srv,Handle,Position,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:apwrite(ref(Srv,SSHRef), Handle, Position, Data),S};
handle_msg({position,Srv,Handle,Location}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:position(ref(Srv,SSHRef), Handle, Location),S};
handle_msg({read_file_info,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_file_info(ref(Srv,SSHRef), Name),S};
handle_msg({get_file_info,Srv,Handle}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:get_file_info(ref(Srv,SSHRef), Handle),S};
handle_msg({read_link_info,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_link_info(ref(Srv,SSHRef), Name),S};
handle_msg({write_file_info,Srv,Name,Info}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write_file_info(ref(Srv,SSHRef), Name, Info),S};
handle_msg({read_link,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_link(ref(Srv,SSHRef), Name),S};
handle_msg({make_symlink,Srv,Name,Target}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:make_symlink(ref(Srv,SSHRef), Name, Target),S};
handle_msg({rename,Srv,OldName,NewName}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:rename(ref(Srv,SSHRef), OldName, NewName),S};
handle_msg({delete,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:delete(ref(Srv,SSHRef), Name),S};
handle_msg({make_dir,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:make_dir(ref(Srv,SSHRef), Name),S};
handle_msg({del_dir,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
"SSH Ref: ~p, Server: ~p~nCmd: ~p",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:del_dir(ref(Srv,SSHRef), Name),S}.
%% @hidden
reconnect(_Addr,_State) ->
{error,no_reconnection_of_ssh}.
%% @hidden
close(SSHRef) ->
disconnect(SSHRef).
%% @hidden
terminate(SSHRef, State) ->
case State#state.conn_type of
ssh ->
try_log(heading(disconnect_ssh,State#state.target),
"SSH Ref: ~p",[SSHRef], 5000),
ssh:close(SSHRef);
sftp ->
try_log(heading(disconnect_sftp,State#state.target),
"SFTP Ref: ~p",[SSHRef], 5000),
ssh_sftp:stop_channel(SSHRef)
end.
%%%=================================================================
%%% Internal functions
%%%-----------------------------------------------------------------
%%%
do_recv_response(SSH, Chn, Data, End, Timeout) ->
receive
{ssh_cm, SSH, {open,Chn,RemoteChn,{session}}} ->
debug("RECVD open"),
{ok,{open,Chn,RemoteChn,{session}}};
{ssh_cm, SSH, {closed,Chn}} ->
ssh_connection:close(SSH, Chn),
debug("CLSD~n~p ~p", [SSH,Chn]),
{ok,Data};
{ssh_cm, SSH, {data,Chn,_,NewData}} ->
ssh_connection:adjust_window(SSH, Chn, size(NewData)),
debug("RECVD~n~p", [binary_to_list(NewData)]),
DataAcc = Data ++ binary_to_list(NewData),
if is_function(End) ->
case End(DataAcc) of
true ->
{ok,DataAcc};
false ->
do_recv_response(SSH, Chn, DataAcc, End, Timeout)
end;
true ->
do_recv_response(SSH, Chn, DataAcc, End, Timeout)
end;
{ssh_cm, SSH, {eof,Chn}} ->
debug("RECVD EOF~n~p ~p", [SSH,Chn]),
{ok,Data};
{ssh_cm, SSH, {exit_signal,Chn,Signal,Err,_Lang}} ->
debug("RECVD exit_signal~n~p ~p~n~p ~p", [SSH,Chn,Signal,Err]),
do_recv_response(SSH, Chn, Data, End, Timeout);
%% {ok,{exit_signal,Chn,Signal,Err,_Lang}};
{ssh_cm, SSH, {exit_status,Chn,Status}} ->
debug("RECVD exit_status~n~p ~p~n~p", [SSH,Chn,Status]),
do_recv_response(SSH, Chn, Data, End, Timeout);
%% {ok,{exit_status,Chn,_Status}};
%% --- INTERACTIVE MESSAGES - NOT HANDLED ---
%%
%% {ssh_cm, SSH, {subsystem,Chn,WantReply,Name}} ->
%% debug("RECVD SUBS WNTRPLY~n~p ~p~n~p~n~p",
%% [SSH,Chn,WantReply]),
%% ssh_connection:reply_request(SSH, WantReply, success, Chn),
%% do_recv_response(SSH, Chn, Data, End, Timeout);
%% {ssh_cm, SSH, {shell,WantReply}} ->
%% debug("RECVD SHELL WNTRPLY~n~p ~p~n~p~n~p",
%% [SSH,Chn,WantReply]),
%% ssh_connection:reply_request(SSH, WantReply, success, Chn),
%% do_recv_response(SSH,Chn,Data,End,Timeout);
%% {ssh_cm, SSH, {pty,Chn,WantReply,Pty}} ->
%% debug("RECVD PTY WNTRPLY~n~p ~p~n~p~n~p",
%% [SSH,Chn,WantReply,Pty]),
%% ssh_connection:reply_request(SSH, WantReply, success, Chn),
%% do_recv_response(SSH, Chn, Data, End, Timeout);
%% {ssh_cm, SSH, WCh={window_change,_Chn,_Width,_Height,_PixWidth,_PixHeight}} ->
%% debug("RECVD WINCH"),
%% {ok,WCh};
Other ->
debug("UNEXPECTED MESSAGE~n~p ~p~n~p", [SSH,Chn,Other]),
do_recv_response(SSH, Chn, Data, End, Timeout)
after Timeout ->
case End of
timeout ->
{ok,Data};
_ ->
{timeout,Data}
end
end.
%%%-----------------------------------------------------------------
%%%
get_handle(SSH) when is_pid(SSH) ->
{ok,SSH};
get_handle(SSH) ->
case ct_util:get_connection(SSH, ?MODULE) of
{ok,{Pid,_}} ->
{ok,Pid};
{error,no_registered_connection} ->
connect(SSH);
Error ->
Error
end.
%%%-----------------------------------------------------------------
%%%
call(SSH, Msg) ->
call(SSH, Msg, infinity).
call(SSH, Msg, Timeout) ->
case get_handle(SSH) of
{ok,Pid} ->
ct_gen_conn:call(Pid, Msg, Timeout);
Error ->
Error
end.
%%%-----------------------------------------------------------------
%%%
ref(sftp, SSHRef) -> SSHRef;
ref(Server, _) -> Server.
%%%-----------------------------------------------------------------
%%%
mod(Cmd) ->
[Op,_Server|Args] = tuple_to_list(Cmd),
list_to_tuple([Op|Args]).
%%%-----------------------------------------------------------------
%%%
heading(Function, Ref) ->
io_lib:format("ct_ssh:~w ~p",[Function,Ref]).
%%%-----------------------------------------------------------------
%%%
log(Heading, Str, Args) ->
ct_gen_conn:log(Heading, Str, Args).
%%%-----------------------------------------------------------------
%%%
try_log(Heading, Str, Args) ->
try_log(Heading, Str, Args, infinity).
try_log(Heading, Str, Args, Timeout) ->
case ct_util:is_silenced(ssh, Timeout) of
true ->
ok;
false ->
ct_gen_conn:log(Heading, Str, Args);
_Error ->
ok
end.
%%%-----------------------------------------------------------------
%%%
debug(Str) ->
debug(Str, []).
debug(_Str, _Args) ->
%% io:format("~n--- ct_ssh debug ---~n" ++ _Str ++ "~n", _Args),
ok.