diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/ssh.erl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_client_channel.erl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connect.hrl | 3 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection.erl | 222 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 116 | ||||
-rw-r--r-- | lib/ssh/src/ssh_server_channel.erl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftp.erl | 376 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftpd.erl | 12 |
8 files changed, 629 insertions, 106 deletions
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index ff5aee14d7..32f10c797d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -66,6 +66,8 @@ cipher_alg/0, mac_alg/0, compression_alg/0, + host/0, + open_socket/0, ip_port/0 ]). diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl index f985d8e273..3bd1e1fdf1 100644 --- a/lib/ssh/src/ssh_client_channel.erl +++ b/lib/ssh/src/ssh_client_channel.erl @@ -52,7 +52,7 @@ -callback handle_msg(Msg ::term(), State :: term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, +-callback handle_ssh_msg(ssh_connection:event(), State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 9a060b8304..d6b50613f9 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -263,11 +263,8 @@ -record(connection, { requests = [], %% [{ChannelId, Pid}...] awaiting reply on request, channel_cache, - port_bindings, channel_id_seed, cli_spec, - address, - port, options, exec, system_supervisor, diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 83f85b1d8e..c5316bf133 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -60,13 +60,121 @@ request_failure_msg/0, request_success_msg/1, - bind/4, unbind/3, unbind_channel/2, - bound_channel/3, encode_ip/1 + encode_ip/1 ]). -type connection_ref() :: ssh:connection_ref(). -type channel_id() :: ssh:channel_id(). +-type req_status() :: success | failure . +-type reason() :: closed | timeout . + +-type result() :: req_status() | {error, reason()} . + +-type ssh_data_type_code() :: non_neg_integer(). % Only 0 and 1 are used + + +%%% The SSH Connection Protocol + +-export_type([event/0, + channel_msg/0, + want_reply/0, + data_ch_msg/0, + eof_ch_msg/0, + signal_ch_msg/0, + exit_signal_ch_msg/0, + exit_status_ch_msg/0, + closed_ch_msg/0, + env_ch_msg/0, + pty_ch_msg/0, + shell_ch_msg/0, + window_change_ch_msg/0, + exec_ch_msg/0 + ]). + +-type event() :: {ssh_cm, ssh:connection_ref(), channel_msg()}. +-type channel_msg() :: data_ch_msg() + | eof_ch_msg() + | closed_ch_msg() + | pty_ch_msg() + | env_ch_msg() + | shell_ch_msg() + | exec_ch_msg() + | signal_ch_msg() + | window_change_ch_msg() + | exit_status_ch_msg() + | exit_signal_ch_msg() + . + +-type want_reply() :: boolean(). + +-type data_ch_msg() :: {data, + ssh:channel_id(), + ssh_data_type_code(), + Data :: binary() + } . +-type eof_ch_msg() :: {eof, + ssh:channel_id() + } . +-type signal_ch_msg() :: {signal, + ssh:channel_id(), + SignalName :: string() + } . +-type exit_signal_ch_msg() :: {exit_signal, ssh:channel_id(), + ExitSignal :: string(), + ErrorMsg :: string(), + LanguageString :: string()} . +-type exit_status_ch_msg() :: {exit_status, + ssh:channel_id(), + ExitStatus :: non_neg_integer() + } . +-type closed_ch_msg() :: {closed, + ssh:channel_id() + } . +-type env_ch_msg() :: {env, + ssh:channel_id(), + want_reply(), + Var :: string(), + Value :: string() + } . +-type pty_ch_msg() :: {pty, + ssh:channel_id(), + want_reply(), + {Terminal :: string(), + CharWidth :: non_neg_integer(), + RowHeight :: non_neg_integer(), + PixelWidth :: non_neg_integer(), + PixelHeight :: non_neg_integer(), + TerminalModes :: [term_mode()] + } + } . + +-type term_mode() :: {Opcode :: atom() | byte(), + Value :: non_neg_integer()} . + +-type shell_ch_msg() :: {shell, + ssh:channel_id(), + want_reply() + } . +-type window_change_ch_msg() :: {window_change, + ssh:channel_id(), + CharWidth :: non_neg_integer(), + RowHeight :: non_neg_integer(), + PixelWidth :: non_neg_integer(), + PixelHeight :: non_neg_integer() + } . +-type exec_ch_msg() :: {exec, + ssh:channel_id(), + want_reply(), + Command :: string() + } . + +%%% This function is soley to convince all +%%% checks that the type event() exists... +-export([dummy/1]). +-spec dummy(event()) -> false. +dummy(_) -> false. + %%-------------------------------------------------------------------- %%% API %%-------------------------------------------------------------------- @@ -77,14 +185,21 @@ %% application, a system command, or some built-in subsystem. %% -------------------------------------------------------------------- --spec session_channel(connection_ref(), timeout()) -> - {ok, channel_id()} | {error, timeout | closed}. +-spec session_channel(ConnectionRef, Timeout) -> Result when + ConnectionRef :: ssh:connection_ref(), + Timeout :: timeout(), + Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, Timeout) -> session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). --spec session_channel(connection_ref(), integer(), integer(), timeout()) -> - {ok, channel_id()} | {error, timeout | closed}. + +-spec session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> Result when + ConnectionRef :: ssh:connection_ref(), + InitialWindowSize :: pos_integer(), + MaxPacketSize :: pos_integer(), + Timeout :: timeout(), + Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>, @@ -100,8 +215,11 @@ session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> %% Description: Will request that the server start the %% execution of the given command. %%-------------------------------------------------------------------- --spec exec(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec exec(ConnectionRef, ChannelId, Command, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Command :: string(), + Timeout :: timeout(). exec(ConnectionHandler, ChannelId, Command, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec", @@ -112,8 +230,10 @@ exec(ConnectionHandler, ChannelId, Command, TimeOut) -> %% defined in /etc/passwd in UNIX systems) be started at the other %% end. %%-------------------------------------------------------------------- --spec shell(connection_ref(), channel_id()) -> - ok | success | failure | {error, timeout}. +-spec shell(ConnectionRef, ChannelId) -> Result when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Result :: ok | success | failure | {error, timeout} . shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, @@ -122,8 +242,11 @@ shell(ConnectionHandler, ChannelId) -> %% %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- --spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Subsystem :: string(), + Timeout :: timeout(). subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), @@ -134,12 +257,13 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> %%-------------------------------------------------------------------- -spec send(connection_ref(), channel_id(), iodata()) -> ok | {error, timeout | closed}. + send(ConnectionHandler, ChannelId, Data) -> send(ConnectionHandler, ChannelId, 0, Data, infinity). --spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> - ok | {error, timeout | closed}. +-spec send(connection_ref(), channel_id(), iodata(), timeout()) -> ok | {error, reason()}; + (connection_ref(), channel_id(), ssh_data_type_code(), iodata()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> send(ConnectionHandler, ChannelId, 0, Data, TimeOut); @@ -151,14 +275,15 @@ send(ConnectionHandler, ChannelId, Type, Data) -> send(ConnectionHandler, ChannelId, Type, Data, infinity). --spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> - ok | {error, timeout | closed}. +-spec send(connection_ref(), channel_id(), ssh_data_type_code(), iodata(), timeout()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). %%-------------------------------------------------------------------- --spec send_eof(connection_ref(), channel_id()) -> ok | {error, closed}. +-spec send_eof(ConnectionRef, ChannelId) -> ok | {error, closed} when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Sends eof on the channel <ChannelId>. @@ -167,7 +292,10 @@ send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- --spec adjust_window(connection_ref(), channel_id(), integer()) -> ok. +-spec adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + NumOfBytes :: integer(). %% %% %% Description: Adjusts the ssh flowcontrol window. @@ -176,8 +304,12 @@ adjust_window(ConnectionHandler, Channel, Bytes) -> ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes). %%-------------------------------------------------------------------- --spec setenv(connection_ref(), channel_id(), string(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Var :: string(), + Value :: string(), + Timeout :: timeout(). %% %% %% Description: Environment variables may be passed to the shell/command to be @@ -189,7 +321,9 @@ setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) -> %%-------------------------------------------------------------------- --spec close(connection_ref(), channel_id()) -> ok. +-spec close(ConnectionRef, ChannelId) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Sends a close message on the channel <ChannelId>. @@ -198,7 +332,11 @@ close(ConnectionHandler, ChannelId) -> ssh_connection_handler:close(ConnectionHandler, ChannelId). %%-------------------------------------------------------------------- --spec reply_request(connection_ref(), boolean(), success | failure, channel_id()) -> ok. +-spec reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok when + ConnectionRef :: ssh:connection_ref(), + WantReply :: boolean(), + Status :: req_status(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Send status replies to requests that want such replies. @@ -211,15 +349,20 @@ reply_request(_,false, _, _) -> %%-------------------------------------------------------------------- %% Description: Sends a ssh connection protocol pty_req. %%-------------------------------------------------------------------- --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> - success | failure | {error, timeout}. +-spec ptty_alloc(ConnectionRef, ChannelId, Options) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Options :: proplists:proplist(). ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Options :: proplists:proplist(), + Timeout :: timeout(). ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> TermData = backwards_compatible(Options0, []), % FIXME @@ -252,6 +395,10 @@ signal(ConnectionHandler, Channel, Sig) -> "signal", false, [?string(Sig)], 0). +-spec exit_status(ConnectionRef, ChannelId, Status) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Status :: integer(). exit_status(ConnectionHandler, Channel, Status) -> ssh_connection_handler:request(ConnectionHandler, Channel, "exit-status", false, [?uint32(Status)], 0). @@ -713,29 +860,6 @@ request_success_msg(Data) -> %%%---------------------------------------------------------------- %%% %%% -bind(IP, Port, ChannelPid, Connection) -> - Binds = [{{IP, Port}, ChannelPid} - | lists:keydelete({IP, Port}, 1, - Connection#connection.port_bindings)], - Connection#connection{port_bindings = Binds}. - -unbind(IP, Port, Connection) -> - Connection#connection{ - port_bindings = - lists:keydelete({IP, Port}, 1, - Connection#connection.port_bindings)}. -unbind_channel(ChannelPid, Connection) -> - Binds = [{Bind, ChannelP} || {Bind, ChannelP} - <- Connection#connection.port_bindings, - ChannelP =/= ChannelPid], - Connection#connection{port_bindings = Binds}. - -bound_channel(IP, Port, Connection) -> - case lists:keysearch({IP, Port}, 1, Connection#connection.port_bindings) of - {value, {{IP, Port}, ChannelPid}} -> ChannelPid; - _ -> undefined - end. - encode_ip(Addr) when is_tuple(Addr) -> case catch inet_parse:ntoa(Addr) of {'EXIT',_} -> false; diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8f32966a12..e984cbb21b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -386,16 +386,24 @@ init_connection_handler(Role, Socket, Opts) -> D); {stop, Error} -> - Sups = ?GET_INTERNAL_OPT(supervisors, Opts), - C = #connection{system_supervisor = proplists:get_value(system_sup, Sups), - sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), - connection_supervisor = proplists:get_value(connection_sup, Sups) - }, + D = try + %% Only servers have supervisorts defined in Opts + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + #connection{system_supervisor = proplists:get_value(system_sup, Sups), + sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), + connection_supervisor = proplists:get_value(connection_sup, Sups) + } + of + C -> + #data{connection_state=C} + catch + _:_ -> + #data{connection_state=#connection{}} + end, gen_statem:enter_loop(?MODULE, [], {init_error,Error}, - #data{connection_state=C, - socket=Socket}) + D#data{socket=Socket}) end. @@ -406,7 +414,6 @@ init([Role,Socket,Opts]) -> {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), C = #connection{channel_cache = ssh_client_channel:cache_create(), channel_id_seed = 0, - port_bindings = [], requests = [], options = Opts}, D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), @@ -1550,7 +1557,7 @@ terminate({shutdown,"Connection closed"}, _StateName, D) -> terminate({shutdown,{init,Reason}}, StateName, D) -> %% Error in initiation. "This error should not occur". - log(error, D, io_lib:format("Shutdown in init (StateName=~p): ~p~n",[StateName,Reason])), + log(error, D, "Shutdown in init (StateName=~p): ~p~n", [StateName,Reason]), stop_subsystem(D), close_transport(D); @@ -1952,12 +1959,12 @@ send_disconnect(Code, Reason, DetailedText, Module, Line, StateName, D0) -> call_disconnectfun_and_log_cond(LogMsg, DetailedText, Module, Line, StateName, D) -> case disconnect_fun(LogMsg, D) of void -> - log(info, D, - io_lib:format("~s~n" - "State = ~p~n" - "Module = ~p, Line = ~p.~n" - "Details:~n ~s~n", - [LogMsg, StateName, Module, Line, DetailedText])); + log(info, D, + "~s~n" + "State = ~p~n" + "Module = ~p, Line = ~p.~n" + "Details:~n ~s~n", + [LogMsg, StateName, Module, Line, DetailedText]); _ -> ok end. @@ -2021,6 +2028,9 @@ fold_keys(Keys, Fun, Extra) -> end, [], Keys). %%%---------------------------------------------------------------- +log(Tag, D, Format, Args) -> + log(Tag, D, io_lib:format(Format,Args)). + log(Tag, D, Reason) -> case atom_to_list(Tag) of % Dialyzer-technical reasons... "error" -> do_log(error_msg, Reason, D); @@ -2028,36 +2038,56 @@ log(Tag, D, Reason) -> "info" -> do_log(info_msg, Reason, D) end. -do_log(F, Reason, #data{ssh_params = #ssh{role = Role} = S - }) -> - VSN = - case application:get_key(ssh,vsn) of - {ok,Vsn} -> Vsn; - undefined -> "" - end, - PeerVersion = - case Role of - server -> S#ssh.c_version; - client -> S#ssh.s_version - end, - CryptoInfo = - try - [{_,_,CI}] = crypto:info_lib(), - <<"(",CI/binary,")">> + +do_log(F, Reason0, #data{ssh_params = S}) -> + Reason = + try io_lib:format("~s",[Reason0]) + of _ -> Reason0 catch - _:_ -> "" - end, - Other = - case Role of - server -> "Client"; - client -> "Server" + _:_ -> io_lib:format("~p",[Reason0]) end, - error_logger:F("Erlang SSH ~p ~s ~s.~n" - "~s: ~p~n" - "~s~n", - [Role, VSN, CryptoInfo, - Other, PeerVersion, - Reason]). + case S of + #ssh{role = Role} when Role==server ; + Role==client -> + {PeerRole,PeerVersion} = + case Role of + server -> {"Client", S#ssh.c_version}; + client -> {"Server", S#ssh.s_version} + end, + error_logger:F("Erlang SSH ~p ~s ~s.~n" + "~s: ~p~n" + "~s~n", + [Role, + ssh_log_version(), crypto_log_info(), + PeerRole, PeerVersion, + Reason]); + _ -> + error_logger:F("Erlang SSH ~s ~s.~n" + "~s~n", + [ssh_log_version(), crypto_log_info(), + Reason]) + end. + +crypto_log_info() -> + try + [{_,_,CI}] = crypto:info_lib(), + case crypto:info_fips() of + enabled -> + <<"(",CI/binary,". FIPS enabled)">>; + not_enabled -> + <<"(",CI/binary,". FIPS available but not enabled)">>; + _ -> + <<"(",CI/binary,")">> + end + catch + _:_ -> "" + end. + +ssh_log_version() -> + case application:get_key(ssh,vsn) of + {ok,Vsn} -> Vsn; + undefined -> "" + end. %%%---------------------------------------------------------------- not_connected_filter({connection_reply, _Data}) -> true; diff --git a/lib/ssh/src/ssh_server_channel.erl b/lib/ssh/src/ssh_server_channel.erl index 555080e9ee..1905c40c98 100644 --- a/lib/ssh/src/ssh_server_channel.erl +++ b/lib/ssh/src/ssh_server_channel.erl @@ -37,7 +37,7 @@ -callback handle_msg(Msg ::term(), State :: term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, +-callback handle_ssh_msg(ssh_connection:event(), State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 1b2ba5a50b..4b6e187c3a 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -92,24 +92,63 @@ -define(XF(S), S#state.xf). -define(REQID(S), S#state.req_id). +-type sftp_option() :: {timeout, timeout()} + | {sftp_vsn, pos_integer()} + | {window_size, pos_integer()} + | {packet_size, pos_integer()} . + +-type reason() :: atom() | string() | tuple() . + %%==================================================================== %% API %%==================================================================== + + +%%%================================================================ +%%% + +%%%---------------------------------------------------------------- +%%% start_channel/1 + start_channel(Cm) when is_pid(Cm) -> start_channel(Cm, []); + start_channel(Socket) when is_port(Socket) -> start_channel(Socket, []); -start_channel(Host) when is_list(Host) -> + +start_channel(Host) -> start_channel(Host, []). + +%%%---------------------------------------------------------------- +%%% start_channel/2 + +%%% -spec:s are as if Dialyzer handled signatures for separate +%%% function clauses. + +-spec start_channel(ssh:open_socket(), + [ssh:client_options() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()}; + + (ssh:connection_ref(), + [sftp_option()] + ) + -> {ok,pid()} | {ok,pid(),ssh:connection_ref()} | {error,reason()}; + + (ssh:host(), + [ssh:client_options() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()} . + start_channel(Socket, UserOptions) when is_port(Socket) -> - {SshOpts, _ChanOpts, SftpOpts} = handle_options(UserOptions), + {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: proplists:get_value(connect_timeout, SshOpts, proplists:get_value(timeout, SftpOpts, infinity)), case ssh:connect(Socket, SshOpts, Timeout) of {ok,Cm} -> - case start_channel(Cm, UserOptions) of + case start_channel(Cm, ChanOpts ++ SftpOpts) of {ok, Pid} -> {ok, Pid, Cm}; Error -> @@ -144,6 +183,16 @@ start_channel(Cm, UserOptions) when is_pid(Cm) -> start_channel(Host, UserOptions) -> start_channel(Host, 22, UserOptions). + +%%%---------------------------------------------------------------- +%%% start_channel/3 + +-spec start_channel(ssh:host(), + inet:port_number(), + [ssh:client_option() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()}. + start_channel(Host, Port, UserOptions) -> {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: @@ -168,6 +217,15 @@ start_channel(Host, Port, UserOptions) -> Error end. +%%% Helper for start_channel + +wait_for_version_negotiation(Pid, Timeout) -> + call(Pid, wait_for_version_negotiation, Timeout). + +%%%---------------------------------------------------------------- +-spec stop_channel(ChannelPid) -> ok when + ChannelPid :: pid(). + stop_channel(Pid) -> case is_process_alive(Pid) of true -> @@ -185,20 +243,63 @@ stop_channel(Pid) -> ok end. -wait_for_version_negotiation(Pid, Timeout) -> - call(Pid, wait_for_version_negotiation, Timeout). - +%%%---------------------------------------------------------------- +-spec open(ChannelPid, Name, Mode) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Name :: string(), + Mode :: [read | write | append | binary | raw], + Handle :: term(), + Error :: {error, reason()} . open(Pid, File, Mode) -> open(Pid, File, Mode, ?FILEOP_TIMEOUT). +-spec open(ChannelPid, Name, Mode, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Name :: string(), + Mode :: [read | write | append | binary | raw], + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . open(Pid, File, Mode, FileOpTimeout) -> call(Pid, {open, false, File, Mode}, FileOpTimeout). + +-type tar_crypto_spec() :: encrypt_spec() | decrypt_spec() . + +-type encrypt_spec() :: {init_fun(), crypto_fun(), final_fun()} . +-type decrypt_spec() :: {init_fun(), crypto_fun()} . + +-type init_fun() :: fun(() -> {ok,crypto_state()}) + | fun(() -> {ok,crypto_state(),chunk_size()}) . + +-type crypto_fun() :: fun((TextIn::binary(), crypto_state()) -> crypto_result()) . +-type crypto_result() :: {ok,TextOut::binary(),crypto_state()} + | {ok,TextOut::binary(),crypto_state(),chunk_size()} . + +-type final_fun() :: fun((FinalTextIn::binary(),crypto_state()) -> {ok,FinalTextOut::binary()}) . + +-type chunk_size() :: undefined | pos_integer(). +-type crypto_state() :: any() . + + +-spec open_tar(ChannelPid, Path, Mode) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Mode :: [read | write | {crypto, tar_crypto_spec()} ], + Handle :: term(), + Error :: {error, reason()} . open_tar(Pid, File, Mode) -> open_tar(Pid, File, Mode, ?FILEOP_TIMEOUT). +-spec open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Mode :: [read | write | {crypto, tar_crypto_spec()} ], + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . open_tar(Pid, File, Mode, FileOpTimeout) -> case {lists:member(write,Mode), lists:member(read,Mode), - Mode -- [read,write]} of + Mode -- [write,read]} of {true,false,[]} -> {ok,Handle} = open(Pid, File, [write], FileOpTimeout), erl_tar:init(Pid, write, @@ -264,13 +365,33 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> end. +-spec opendir(ChannelPid, Path) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Handle :: term(), + Error :: {error, reason()} . opendir(Pid, Path) -> opendir(Pid, Path, ?FILEOP_TIMEOUT). +-spec opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . opendir(Pid, Path, FileOpTimeout) -> call(Pid, {opendir, false, Path}, FileOpTimeout). +-spec close(ChannelPid, Handle) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Error :: {error, reason()} . close(Pid, Handle) -> close(Pid, Handle, ?FILEOP_TIMEOUT). +-spec close(ChannelPid, Handle, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Timeout :: timeout(), + Error :: {error, reason()} . close(Pid, Handle, FileOpTimeout) -> call(Pid, {close,false,Handle}, FileOpTimeout). @@ -279,47 +400,149 @@ readdir(Pid,Handle) -> readdir(Pid,Handle, FileOpTimeout) -> call(Pid, {readdir,false,Handle}, FileOpTimeout). +-spec pread(ChannelPid, Handle, Position, Len) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Data :: string() | binary(), + Error :: {error, reason()}. pread(Pid, Handle, Offset, Len) -> pread(Pid, Handle, Offset, Len, ?FILEOP_TIMEOUT). + +-spec pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Timeout :: timeout(), + Data :: string() | binary(), + Error :: {error, reason()}. pread(Pid, Handle, Offset, Len, FileOpTimeout) -> call(Pid, {pread,false,Handle, Offset, Len}, FileOpTimeout). + +-spec read(ChannelPid, Handle, Len) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Data :: string() | binary(), + Error :: {error, reason()}. read(Pid, Handle, Len) -> read(Pid, Handle, Len, ?FILEOP_TIMEOUT). + +-spec read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Timeout :: timeout(), + Data :: string() | binary(), + Error :: {error, reason()}. read(Pid, Handle, Len, FileOpTimeout) -> call(Pid, {read,false,Handle, Len}, FileOpTimeout). + %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec apread(ChannelPid, Handle, Position, Len) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Error :: {error, reason()}, + N :: term() . apread(Pid, Handle, Offset, Len) -> call(Pid, {pread,true,Handle, Offset, Len}, infinity). %% TODO this ought to be a cast! +-spec aread(ChannelPid, Handle, Len) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Error :: {error, reason()}, + N :: term() . aread(Pid, Handle, Len) -> call(Pid, {read,true,Handle, Len}, infinity). + +-spec pwrite(ChannelPid, Handle, Position, Data) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: iolist(), + Error :: {error, reason()}. pwrite(Pid, Handle, Offset, Data) -> pwrite(Pid, Handle, Offset, Data, ?FILEOP_TIMEOUT). + +-spec pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: iolist(), + Timeout :: timeout(), + Error :: {error, reason()}. pwrite(Pid, Handle, Offset, Data, FileOpTimeout) -> call(Pid, {pwrite,false,Handle,Offset,Data}, FileOpTimeout). + +-spec write(ChannelPid, Handle, Data) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: iodata(), + Error :: {error, reason()}. write(Pid, Handle, Data) -> write(Pid, Handle, Data, ?FILEOP_TIMEOUT). + +-spec write(ChannelPid, Handle, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: iodata(), + Timeout :: timeout(), + Error :: {error, reason()}. write(Pid, Handle, Data, FileOpTimeout) -> call(Pid, {write,false,Handle,Data}, FileOpTimeout). %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: binary(), + Error :: {error, reason()}, + N :: term() . apwrite(Pid, Handle, Offset, Data) -> call(Pid, {pwrite,true,Handle,Offset,Data}, infinity). %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec awrite(ChannelPid, Handle, Data) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: binary(), + Error :: {error, reason()}, + N :: term() . awrite(Pid, Handle, Data) -> call(Pid, {write,true,Handle,Data}, infinity). +-spec position(ChannelPid, Handle, Location) -> {ok, NewPosition} | Error when + ChannelPid :: pid(), + Handle :: term(), + Location :: Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof, + Offset :: integer(), + NewPosition :: integer(), + Error :: {error, reason()}. position(Pid, Handle, Pos) -> position(Pid, Handle, Pos, ?FILEOP_TIMEOUT). + +-spec position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition} | Error when + ChannelPid :: pid(), + Handle :: term(), + Location :: Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof, + Timeout :: timeout(), + Offset :: integer(), + NewPosition :: integer(), + Error :: {error, reason()}. position(Pid, Handle, Pos, FileOpTimeout) -> call(Pid, {position, Handle, Pos}, FileOpTimeout). @@ -328,8 +551,21 @@ real_path(Pid, Path) -> real_path(Pid, Path, FileOpTimeout) -> call(Pid, {real_path, false, Path}, FileOpTimeout). + +-spec read_file_info(ChannelPid, Name) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_file_info(Pid, Name) -> read_file_info(Pid, Name, ?FILEOP_TIMEOUT). + +-spec read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_file_info(Pid, Name, FileOpTimeout) -> call(Pid, {read_file_info,false,Name}, FileOpTimeout). @@ -338,18 +574,57 @@ get_file_info(Pid, Handle) -> get_file_info(Pid, Handle, FileOpTimeout) -> call(Pid, {get_file_info,false,Handle}, FileOpTimeout). + +-spec write_file_info(ChannelPid, Name, FileInfo) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. write_file_info(Pid, Name, Info) -> write_file_info(Pid, Name, Info, ?FILEOP_TIMEOUT). + +-spec write_file_info(ChannelPid, Name, FileInfo, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Timeout :: timeout(), + Error :: {error, reason()}. write_file_info(Pid, Name, Info, FileOpTimeout) -> call(Pid, {write_file_info,false,Name, Info}, FileOpTimeout). + +-spec read_link_info(ChannelPid, Name) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_link_info(Pid, Name) -> read_link_info(Pid, Name, ?FILEOP_TIMEOUT). + +-spec read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Timeout :: timeout(), + Error :: {error, reason()}. read_link_info(Pid, Name, FileOpTimeout) -> call(Pid, {read_link_info,false,Name}, FileOpTimeout). + +-spec read_link(ChannelPid, Name) -> {ok, Target} | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Error :: {error, reason()}. read_link(Pid, LinkName) -> read_link(Pid, LinkName, ?FILEOP_TIMEOUT). + +-spec read_link(ChannelPid, Name, Timeout) -> {ok, Target} | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Timeout :: timeout(), + Error :: {error, reason()}. read_link(Pid, LinkName, FileOpTimeout) -> case call(Pid, {read_link,false,LinkName}, FileOpTimeout) of {ok, [{Name, _Attrs}]} -> @@ -358,28 +633,79 @@ read_link(Pid, LinkName, FileOpTimeout) -> ErrMsg end. +-spec make_symlink(ChannelPid, Name, Target) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Error :: {error, reason()} . make_symlink(Pid, Name, Target) -> make_symlink(Pid, Name, Target, ?FILEOP_TIMEOUT). +-spec make_symlink(ChannelPid, Name, Target, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . make_symlink(Pid, Name, Target, FileOpTimeout) -> call(Pid, {make_symlink,false, Name, Target}, FileOpTimeout). + +-spec rename(ChannelPid, OldName, NewName) -> ok | Error when + ChannelPid :: pid(), + OldName :: string(), + NewName :: string(), + Error :: {error, reason()}. rename(Pid, FromFile, ToFile) -> rename(Pid, FromFile, ToFile, ?FILEOP_TIMEOUT). + +-spec rename(ChannelPid, OldName, NewName, Timeout) -> ok | Error when + ChannelPid :: pid(), + OldName :: string(), + NewName :: string(), + Timeout :: timeout(), + Error :: {error, reason()}. rename(Pid, FromFile, ToFile, FileOpTimeout) -> call(Pid, {rename,false,FromFile, ToFile}, FileOpTimeout). +-spec delete(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . delete(Pid, Name) -> delete(Pid, Name, ?FILEOP_TIMEOUT). +-spec delete(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . delete(Pid, Name, FileOpTimeout) -> call(Pid, {delete,false,Name}, FileOpTimeout). +-spec make_dir(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . make_dir(Pid, Name) -> make_dir(Pid, Name, ?FILEOP_TIMEOUT). +-spec make_dir(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . make_dir(Pid, Name, FileOpTimeout) -> call(Pid, {make_dir,false,Name}, FileOpTimeout). +-spec del_dir(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . del_dir(Pid, Name) -> del_dir(Pid, Name, ?FILEOP_TIMEOUT). +-spec del_dir(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . del_dir(Pid, Name, FileOpTimeout) -> call(Pid, {del_dir,false,Name}, FileOpTimeout). @@ -396,9 +722,21 @@ recv_window(Pid, FileOpTimeout) -> call(Pid, recv_window, FileOpTimeout). +-spec list_dir(ChannelPid, Path) -> {ok,FileNames} | Error when + ChannelPid :: pid(), + Path :: string(), + FileNames :: [FileName], + FileName :: string(), + Error :: {error, reason()} . list_dir(Pid, Name) -> list_dir(Pid, Name, ?FILEOP_TIMEOUT). - +-spec list_dir(ChannelPid, Path, Timeout) -> {ok,FileNames} | Error when + ChannelPid :: pid(), + Path :: string(), + Timeout :: timeout(), + FileNames :: [FileName], + FileName :: string(), + Error :: {error, reason()} . list_dir(Pid, Name, FileOpTimeout) -> case opendir(Pid, Name, FileOpTimeout) of {ok,Handle} -> @@ -429,9 +767,20 @@ do_list_dir(Pid, Handle, FileOpTimeout, Acc) -> end. +-spec read_file(ChannelPid, File) -> {ok, Data} | Error when + ChannelPid :: pid(), + File :: string(), + Data :: binary(), + Error :: {error, reason()}. read_file(Pid, Name) -> read_file(Pid, Name, ?FILEOP_TIMEOUT). +-spec read_file(ChannelPid, File, Timeout) -> {ok, Data} | Error when + ChannelPid :: pid(), + File :: string(), + Data :: binary(), + Timeout :: timeout(), + Error :: {error, reason()}. read_file(Pid, Name, FileOpTimeout) -> case open(Pid, Name, [read, binary], FileOpTimeout) of {ok, Handle} -> @@ -453,9 +802,20 @@ read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, Acc) -> Error end. +-spec write_file(ChannelPid, File, Data) -> ok | Error when + ChannelPid :: pid(), + File :: string(), + Data :: iodata(), + Error :: {error, reason()}. write_file(Pid, Name, List) -> write_file(Pid, Name, List, ?FILEOP_TIMEOUT). +-spec write_file(ChannelPid, File, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + File :: string(), + Data :: iodata(), + Timeout :: timeout(), + Error :: {error, reason()}. write_file(Pid, Name, List, FileOpTimeout) when is_list(List) -> write_file(Pid, Name, list_to_binary(List), FileOpTimeout); write_file(Pid, Name, Bin, FileOpTimeout) -> diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 5ec12e2d04..bf921f0ff3 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -58,7 +58,17 @@ %%==================================================================== %% API %%==================================================================== --spec subsystem_spec(list()) -> subsystem_spec(). +-spec subsystem_spec(Options) -> Spec when + Options :: [ {cwd, string()} | + {file_handler, CallbackModule::string()} | + {max_files, integer()} | + {root, string()} | + {sftpd_vsn, integer()} + ], + Spec :: {Name, {CbMod,Options}}, + Name :: string(), + CbMod :: atom() . + subsystem_spec(Options) -> {"sftp", {?MODULE, Options}}. |