diff options
Diffstat (limited to 'lib/ssh/src')
| -rw-r--r-- | lib/ssh/src/ssh.erl | 3 | ||||
| -rw-r--r-- | lib/ssh/src/ssh.hrl | 4 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_acceptor.erl | 16 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_acceptor_sup.erl | 5 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_channel_sup.erl | 11 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_cli.erl | 265 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_connection.erl | 28 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 108 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_connection_sup.erl | 5 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_options.erl | 16 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_subsystem_sup.erl | 8 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_sup.erl | 15 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_system_sup.erl | 13 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_transport.erl | 5 | ||||
| -rw-r--r-- | lib/ssh/src/sshc_sup.erl | 14 | ||||
| -rw-r--r-- | lib/ssh/src/sshd_sup.erl | 6 | 
16 files changed, 308 insertions, 214 deletions
| diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 032d87bdad..25d537c624 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -184,7 +184,6 @@ channel_info(ConnectionRef, ChannelId, Options) ->  daemon(Port) ->      daemon(Port, []). -  daemon(Socket, UserOptions) when is_port(Socket) ->      try          #{} = Options = ssh_options:handle_options(server, UserOptions), @@ -267,8 +266,6 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535,  daemon(_, _, _) ->      {error, badarg}. - -  %%--------------------------------------------------------------------  -spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ). diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 3dee1c5521..8d950eea3c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -35,6 +35,8 @@  -define(DEFAULT_TRANSPORT,  {tcp, gen_tcp, tcp_closed} ). +-define(DEFAULT_SHELL, {shell, start, []} ). +  -define(MAX_RND_PADDING_LEN, 15).  -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). @@ -112,7 +114,7 @@                               | {mac, double_algs()}                               | {compression, double_algs()} .  -type simple_algs()         :: list( atom() ) . --type double_algs()         :: list( {client2serverlist,simple_algs()} | {server2client,simple_algs()} ) +-type double_algs()         :: list( {client2server,simple_algs()} | {server2client,simple_algs()} )                               | simple_algs() .  -type options() :: #{socket_options   := socket_options(), diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index d66a34c58a..27d4242dd4 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -86,7 +86,8 @@ acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) ->                      acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout);                  {error,_} -> % Not open, a restart -                    {ok,NewLSock} = listen(Port, Opts), +                    %% Allow gen_tcp:listen to fail 4 times if eaddrinuse: +                    {ok,NewLSock} = try_listen(Port, Opts, 4),                      proc_lib:init_ack(Parent, {ok, self()}),                      Opts1 = ?DELETE_INTERNAL_OPT(lsocket, Opts),                      {_, Callback, _} =  ?GET_OPT(transport, Opts1), @@ -98,6 +99,19 @@ acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) ->      end. +try_listen(Port, Opts, NtriesLeft) -> +    try_listen(Port, Opts, 1, NtriesLeft). + +try_listen(Port, Opts, N, Nmax) -> +    case listen(Port, Opts) of +        {error,eaddrinuse} when N<Nmax -> +            timer:sleep(10*N), % Sleep 10, 20, 30,... ms +            try_listen(Port, Opts, N+1, Nmax); +        Other -> +            Other +    end. + +  request_ownership(LSock, SockOwner) ->      SockOwner ! {request_control,LSock,self()},      receive diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index a24664793b..fc564a359b 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -86,10 +86,7 @@ child_spec(Address, Port, Profile, Options) ->      Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT),      #{id       => id(Address, Port, Profile),        start    => {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, -      restart  => transient, -      shutdown => 5500, %brutal_kill, -      type     => worker, -      modules  => [ssh_acceptor] +      restart  => transient % because a crashed listener could be replaced by a new one       }.  id(Address, Port, Profile) -> diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl index 6b01dc334d..8444533fd1 100644 --- a/lib/ssh/src/ssh_channel_sup.erl +++ b/lib/ssh/src/ssh_channel_sup.erl @@ -26,7 +26,7 @@  -behaviour(supervisor). --export([start_link/1, start_child/2]). +-export([start_link/1, start_child/5]).  %% Supervisor callback  -export([init/1]). @@ -37,7 +37,14 @@  start_link(Args) ->      supervisor:start_link(?MODULE, [Args]). -start_child(Sup, ChildSpec) -> +start_child(Sup, Callback, Id, Args, Exec) -> +    ChildSpec = +        #{id       => make_ref(), +          start    => {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]}, +          restart  => temporary, +          type     => worker, +          modules  => [ssh_channel] +         },      supervisor:start_child(Sup, ChildSpec).  %%%========================================================================= diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 62854346b0..783f2f80c0 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -118,31 +118,52 @@ handle_ssh_msg({ssh_cm, ConnectionHandler,      write_chars(ConnectionHandler, ChannelId, Chars),      {ok, State#state{pty = Pty, buf = NewBuf}}; -handle_ssh_msg({ssh_cm, ConnectionHandler, -	    {shell, ChannelId, WantReply}}, State) -> +handle_ssh_msg({ssh_cm, ConnectionHandler,  {shell, ChannelId, WantReply}}, State) ->      NewState = start_shell(ConnectionHandler, State), -    ssh_connection:reply_request(ConnectionHandler, WantReply, -				 success, ChannelId), +    ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),      {ok, NewState#state{channel = ChannelId,  			cm = ConnectionHandler}}; -handle_ssh_msg({ssh_cm, ConnectionHandler, -		{exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined} = State) -> -    {Reply, Status} = exec(Cmd), -    write_chars(ConnectionHandler, -		ChannelId, io_lib:format("~p\n", [Reply])), -    ssh_connection:reply_request(ConnectionHandler, WantReply, -				 success, ChannelId), -    ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), -    ssh_connection:send_eof(ConnectionHandler, ChannelId), -    {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}}; -handle_ssh_msg({ssh_cm, ConnectionHandler, -		{exec, ChannelId, WantReply, Cmd}}, State) -> -    NewState = start_shell(ConnectionHandler, Cmd, State), -    ssh_connection:reply_request(ConnectionHandler, WantReply, -				 success, ChannelId), -    {ok, NewState#state{channel = ChannelId, -			cm = ConnectionHandler}}; +handle_ssh_msg({ssh_cm, ConnectionHandler,  {exec, ChannelId, WantReply, Cmd}}, S0) -> +    case +        case S0#state.exec of +            {direct,F} -> +                %% Exec called and a Fun or MFA is defined to use.  The F returns the +                %% value to return. +                exec_direct(ConnectionHandler, F, Cmd); + +            undefined when S0#state.shell == ?DEFAULT_SHELL -> +                %% Exec called and the shell is the default shell (= Erlang shell). +                %% To be exact, eval the term as an Erlang term (but not using the +                %% ?DEFAULT_SHELL directly). This disables banner, prompts and such. +                exec_in_erlang_default_shell(Cmd); + +            undefined -> +                %% Exec called, but the a shell other than the default shell is defined. +                %% No new exec shell is defined, so don't execute! +                %% We don't know if it is intended to use the new shell or not. +                {"Prohibited.", 255, 1}; + +            _ -> +                %% Exec called and a Fun or MFA is defined to use.  The F communicates via +                %% standard io:write/read. +                %% Kept for compatibility. +                S1 = start_exec_shell(ConnectionHandler, Cmd, S0), +                ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId), +                {ok, S1} +        end +    of +        {Reply, Status, Type} -> +            write_chars(ConnectionHandler, ChannelId, Type, Reply), +            ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId), +            ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), +            ssh_connection:send_eof(ConnectionHandler, ChannelId), +            {stop, ChannelId, S0#state{channel = ChannelId, cm = ConnectionHandler}}; +             +        {ok, S} -> +            {ok, S#state{channel = ChannelId, +                         cm = ConnectionHandler}} +    end;  handle_ssh_msg({ssh_cm, _ConnectionHandler, {eof, _ChannelId}}, State) ->      {ok, State}; @@ -249,35 +270,7 @@ to_group(Data, Group) ->      end,      to_group(Tail, Group). -exec(Cmd) -> -    case eval(parse(scan(Cmd))) of -	{error, _} -> -	    {Cmd, 0}; %% This should be an external call -	Term -> -	    Term -    end. - -scan(Cmd) -> -    erl_scan:string(Cmd).  - -parse({ok, Tokens, _}) -> -    erl_parse:parse_exprs(Tokens); -parse(Error) -> -    Error. - -eval({ok, Expr_list}) -> -    case (catch erl_eval:exprs(Expr_list, - 			       erl_eval:new_bindings())) of - 	{value, Value, _NewBindings} -> - 	    {Value, 0}; - 	{'EXIT', {Error, _}} ->  - 	    {Error, -1}; - 	Error ->  - 	    {Error, -1} -    end; -eval(Error) -> -    {Error, -1}. - +%%--------------------------------------------------------------------  %%% io_request, handle io requests from the user process,  %%% Note, this is not the real I/O-protocol, but the mockup version  %%% used between edlin and a user_driver. The protocol tags are @@ -453,11 +446,14 @@ move_cursor(From, To, #ssh_pty{width=Width, term=Type}) ->  %% %%% make sure that there is data to send  %% %%% before calling ssh_connection:send  write_chars(ConnectionHandler, ChannelId, Chars) -> +    write_chars(ConnectionHandler, ChannelId, ?SSH_EXTENDED_DATA_DEFAULT, Chars). + +write_chars(ConnectionHandler, ChannelId, Type, Chars) ->      case has_chars(Chars) of          false -> ok;          true -> ssh_connection:send(ConnectionHandler,                                      ChannelId, -                                    ?SSH_EXTENDED_DATA_DEFAULT, +                                    Type,                                      Chars)      end. @@ -493,53 +489,130 @@ bin_to_list(L) when is_list(L) ->  bin_to_list(I) when is_integer(I) ->      I. + +%%--------------------------------------------------------------------  start_shell(ConnectionHandler, State) -> -    Shell = State#state.shell, -    ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler, -						  [peer, user]), -    ShellFun = case is_function(Shell) of -		   true -> -		       User = proplists:get_value(user, ConnectionInfo), -		       case erlang:fun_info(Shell, arity) of -			   {arity, 1} -> -			       fun() -> Shell(User) end; -			   {arity, 2} -> -			       {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), -			       fun() -> Shell(User, PeerAddr) end; -			   _ -> -			       Shell -		       end; -		   _ -> -		       Shell -	       end, -    Echo = get_echo(State#state.pty), -    Group = group:start(self(), ShellFun, [{echo, Echo}]), -    State#state{group = Group, buf = empty_buf()}. - -start_shell(_ConnectionHandler, Cmd, #state{exec={M, F, A}} = State) -> -    Group = group:start(self(), {M, F, A++[Cmd]}, [{echo, false}]), -    State#state{group = Group, buf = empty_buf()}; -start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function(Shell) -> - -    ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler, -						 [peer, user]), -    User = proplists:get_value(user, ConnectionInfo), -    ShellFun =  -	case erlang:fun_info(Shell, arity) of -	    {arity, 1} -> -		fun() -> Shell(Cmd) end; -	    {arity, 2} -> -		fun() -> Shell(Cmd, User) end; -	    {arity, 3} -> -		{_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), -		fun() -> Shell(Cmd, User, PeerAddr) end; -	    _ -> -		Shell -	end, -    Echo = get_echo(State#state.pty), -    Group = group:start(self(), ShellFun, [{echo,Echo}]), -    State#state{group = Group, buf = empty_buf()}. +    ShellSpawner = +        case State#state.shell of +            Shell when is_function(Shell, 1) -> +                [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]), +                fun() -> Shell(User) end; +            Shell when is_function(Shell, 2) -> +                ConnectionInfo = +                    ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]), +                User = proplists:get_value(user, ConnectionInfo), +                {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), +                fun() -> Shell(User, PeerAddr) end; +            {_,_,_} = Shell -> +                Shell +        end, +    State#state{group = group:start(self(), ShellSpawner, [{echo, get_echo(State#state.pty)}]), +                buf = empty_buf()}. + +%%-------------------------------------------------------------------- +start_exec_shell(ConnectionHandler, Cmd, State) -> +    ExecShellSpawner = +        case State#state.exec of +            ExecShell when is_function(ExecShell, 1) -> +                fun() -> ExecShell(Cmd) end; +            ExecShell when is_function(ExecShell, 2) -> +                [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]), +                fun() -> ExecShell(Cmd, User) end; +            ExecShell when is_function(ExecShell, 3) -> +                ConnectionInfo = +                    ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]), +                User = proplists:get_value(user, ConnectionInfo), +                {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), +                fun() -> ExecShell(Cmd, User, PeerAddr) end; +            {M,F,A} -> +                {M, F, A++[Cmd]} +        end, +    State#state{group = group:start(self(), ExecShellSpawner, [{echo,false}]), +                buf = empty_buf()}. + +%%-------------------------------------------------------------------- +exec_in_erlang_default_shell(Cmd) -> +    case eval(parse(scan(Cmd))) of +	{ok, Term} -> +            {io_lib:format("~p\n", [Term]), 0, 0}; +        {error, Error} when is_atom(Error) -> +            {io_lib:format("Error in ~p: ~p\n", [Cmd,Error]), -1, 1}; +        _ -> +            {io_lib:format("Error: ~p\n", [Cmd]), -1, 1} +    end. + +scan(Cmd) -> +    erl_scan:string(Cmd).  + +parse({ok, Tokens, _}) -> +    erl_parse:parse_exprs(Tokens); +parse(Error) -> +    Error. + +eval({ok, Expr_list}) -> +    case (catch erl_eval:exprs(Expr_list, +                              erl_eval:new_bindings())) of +        {value, Value, _NewBindings} -> +            {ok, Value}; +        {'EXIT', {Error, _}} ->  +            {error, Error}; +        {error, Error} ->  +            {error, Error}; +        Error ->  +            {error, Error} +    end; +eval({error,Error}) -> +    {error, Error}; +eval(Error) -> +    {error, Error}. + +%%-------------------------------------------------------------------- +exec_direct(ConnectionHandler, ExecSpec, Cmd) -> +    try +        case ExecSpec of +            _ when is_function(ExecSpec, 1) -> +                ExecSpec(Cmd); +            _ when is_function(ExecSpec, 2) -> +                [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]), +                ExecSpec(Cmd, User); +            _ when is_function(ExecSpec, 3) -> +                ConnectionInfo = +                    ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]), +                User = proplists:get_value(user, ConnectionInfo), +                {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), +                ExecSpec(Cmd, User, PeerAddr) +        end +    of +        Reply -> +            return_direct_exec_reply(Reply, Cmd) +    catch +        C:Error -> +            {io_lib:format("Error in \"~s\": ~p ~p~n", [Cmd,C,Error]), -1, 1} +    end. + + + +return_direct_exec_reply(Reply, Cmd) -> +    case fmt_exec_repl(Reply) of +        {ok,S} -> +            {S, 0, 0}; +        {error,S} -> +            {io_lib:format("Error in \"~s\": ~s~n", [Cmd,S]), -1, 1} +    end. + +fmt_exec_repl({T,A}) when T==ok ; T==error -> +    try +        {T, io_lib:format("~s",[A])} +    catch +        error:badarg -> +            {T, io_lib:format("~p", [A])}; +        C:Err -> +            {error, io_lib:format("~p:~p~n",[C,Err])} +    end; +fmt_exec_repl(Other) -> +    {error, io_lib:format("Bad exec-plugin return: ~p",[Other])}. +%%--------------------------------------------------------------------  % Pty can be undefined if the client never sets any pty options before  % starting the shell.  get_echo(undefined) -> diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 7e9ee78fd2..946ae2967b 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -812,22 +812,20 @@ start_channel(Cb, Id, Args, SubSysSup, Opts) ->      start_channel(Cb, Id, Args, SubSysSup, undefined, Opts).  start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> -    ChildSpec = child_spec(Cb, Id, Args, Exec),      ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), -    assert_limit_num_channels_not_exceeded(ChannelSup, Opts), -    ssh_channel_sup:start_child(ChannelSup, ChildSpec). +    case max_num_channels_not_exceeded(ChannelSup, Opts) of +        true -> +            ssh_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); +        false -> +	    throw(max_num_channels_exceeded) +    end. -assert_limit_num_channels_not_exceeded(ChannelSup, Opts) -> +max_num_channels_not_exceeded(ChannelSup, Opts) ->      MaxNumChannels = ?GET_OPT(max_channels, Opts),      NumChannels = length([x || {_,_,worker,[ssh_channel]} <-   				   supervisor:which_children(ChannelSup)]), -    if  -	%% Note that NumChannels is BEFORE starting a new one -	NumChannels < MaxNumChannels -> -	    ok; -	true -> -	    throw(max_num_channels_exceeded) -    end. +    %% Note that NumChannels is BEFORE starting a new one +    NumChannels < MaxNumChannels.  %%--------------------------------------------------------------------  %%% Internal functions @@ -874,14 +872,6 @@ check_subsystem(SsName, Options) ->  	    Value      end. -child_spec(Callback, Id, Args, Exec) -> -    Name = make_ref(), -    StartFunc = {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]}, -    Restart = temporary,  -    Shutdown = 3600, -    Type = worker, -    {Name, StartFunc, Restart, Shutdown, Type, [ssh_channel]}. -  start_cli(#connection{cli_spec = no_cli}, _) ->      {error, cli_disabled};  start_cli(#connection{options = Options, diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 0ca960ef96..852e70d9e2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1168,23 +1168,30 @@ handle_event({call,From}, stop, StateName, D0) ->      {Repls,D} = send_replies(Replies, D0),      {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}}; -  handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) ->      {keep_state_and_data, [postpone]};  handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0)     when ?CONNECTED(StateName) -> -    D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0), -    %% Note reply to channel will happen later when reply is recived from peer on the socket -    start_channel_request_timer(ChannelId, From, Timeout), -    {keep_state, cache_request_idle_timer_check(D)}; +    case handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0) of +        {error,Error} -> +            {keep_state, D0, {reply,From,{error,Error}}}; +        D -> +            %% Note reply to channel will happen later when reply is recived from peer on the socket +            start_channel_request_timer(ChannelId, From, Timeout), +            {keep_state, cache_request_idle_timer_check(D)} +    end;  handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0)     when ?CONNECTED(StateName) -> -    D = handle_request(ChannelId, Type, Data, true, From, D0), -    %% Note reply to channel will happen later when reply is recived from peer on the socket -    start_channel_request_timer(ChannelId, From, Timeout), -    {keep_state, cache_request_idle_timer_check(D)}; +    case handle_request(ChannelId, Type, Data, true, From, D0) of +        {error,Error} -> +            {keep_state, D0, {reply,From,{error,Error}}}; +        D -> +            %% Note reply to channel will happen later when reply is recived from peer on the socket +            start_channel_request_timer(ChannelId, From, Timeout), +            {keep_state, cache_request_idle_timer_check(D)} +    end;  handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0)     when ?CONNECTED(StateName) -> @@ -1442,38 +1449,43 @@ handle_event(Type, Ev, StateName, D) ->  -spec terminate(any(),  		state_name(),  		#data{} -	       ) -> finalize_termination_result() . +	       ) -> term().  %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  terminate(normal, StateName, State) -> -    finalize_termination(StateName, State); +    stop_subsystem(State), +    close_transport(State);  terminate({shutdown,{init,Reason}}, StateName, State) ->      error_logger:info_report(io_lib:format("Erlang ssh in connection handler init: ~p~n",[Reason])), -    finalize_termination(StateName, State); +    stop_subsystem(State), +    close_transport(State);  terminate(shutdown, StateName, State0) ->      %% Terminated by supervisor      State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, -					 description = "Application shutdown"}, -		     State0), -    finalize_termination(StateName, State); - -%% terminate({shutdown,Msg}, StateName, State0) when is_record(Msg,ssh_msg_disconnect)-> -%%     State = send_msg(Msg, State0), -%%     finalize_termination(StateName, Msg, State); +                                         description = "Application shutdown"}, +                     State0), +    close_transport(State);  terminate({shutdown,_R}, StateName, State) -> -    finalize_termination(StateName, State); +    %% Internal termination +    stop_subsystem(State), +    close_transport(State); + +terminate(kill, StateName, State) -> +    stop_subsystem(State), +    close_transport(State);  terminate(Reason, StateName, State0) ->      %% Others, e.g  undef, {badmatch,_}      log_error(Reason),      State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, -					   description = "Internal error"}, +                                         description = "Internal error"},  		     State0), -    finalize_termination(StateName, State). +    stop_subsystem(State), +    close_transport(State).  %%-------------------------------------------------------------------- @@ -1548,21 +1560,25 @@ start_the_connection_child(UserPid, Role, Socket, Options0) ->  %%--------------------------------------------------------------------  %% Stopping --type finalize_termination_result() :: ok . - -finalize_termination(_StateName, #data{transport_cb = Transport, -				       connection_state = Connection, -				       socket = Socket}) -> -    case Connection of -	#connection{system_supervisor = SysSup, -		    sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) -> -	    ssh_system_sup:stop_subsystem(SysSup, SubSysSup); -	_ -> -	    do_nothing -    end, -    (catch Transport:close(Socket)), + +stop_subsystem(#data{connection_state = +                         #connection{system_supervisor = SysSup, +                                     sub_system_supervisor = SubSysSup}}) when is_pid(SubSysSup) -> +    ssh_system_sup:stop_subsystem(SysSup, SubSysSup); +stop_subsystem(_) ->      ok. + +close_transport(#data{transport_cb = Transport, +                      socket = Socket}) -> +    try +        Transport:close(Socket) +    of +        _ -> ok +    catch +        _:_ -> ok +    end. +  %%--------------------------------------------------------------------  %% "Invert" the Role  peer_role(client) -> server; @@ -1774,21 +1790,31 @@ is_usable_user_pubkey(A, Ssh) ->  %%%----------------------------------------------------------------  handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) ->      case ssh_channel:cache_lookup(cache(D), ChannelId) of -	#channel{remote_id = Id} = Channel -> +	#channel{remote_id = Id, +                 sent_close = false} = Channel ->  	    update_sys(cache(D), Channel, Type, ChannelPid),  	    send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data),  		     add_request(WantReply, ChannelId, From, D)); -	undefined -> -	    D + +        _ when WantReply==true -> +            {error,closed}; + +        _ -> +            D      end.  handle_request(ChannelId, Type, Data, WantReply, From, D) ->      case ssh_channel:cache_lookup(cache(D), ChannelId) of -	#channel{remote_id = Id} -> +	#channel{remote_id = Id, +                 sent_close = false} ->  	    send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data),  		     add_request(WantReply, ChannelId, From, D)); -	undefined -> -	    D + +	_ when WantReply==true -> +            {error,closed}; +         +        _ -> +            D      end.  %%%---------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl index 60ee8b7c73..2e8450090a 100644 --- a/lib/ssh/src/ssh_connection_sup.erl +++ b/lib/ssh/src/ssh_connection_sup.erl @@ -52,10 +52,7 @@ init(_) ->                  },      ChildSpecs = [#{id       => undefined, % As simple_one_for_one is used.                      start    => {ssh_connection_handler, start_link, []}, -                    restart  => temporary, -                    shutdown => 4000, -                    type     => worker, -                    modules  => [ssh_connection_handler] +                    restart  => temporary % because there is no way to restart a crashed connection                     }                   ],      {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 68c99743ee..c05293d1ae 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -268,17 +268,19 @@ default(server) ->             },        {shell, def} => -          #{default => {shell, start, []}, +          #{default => ?DEFAULT_SHELL,              chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A);                        (V) -> check_function1(V) orelse check_function2(V)                     end,              class => user_options             }, -      {exec, def} =>                 % FIXME: need some archeology.... +      {exec, def} =>            #{default => undefined, -            chk => fun({M,F,_}) -> is_atom(M) andalso is_atom(F); -                      (V) -> is_function(V) +            chk => fun({direct, V}) ->  check_function1(V) orelse check_function2(V) orelse check_function3(V); +                      %% Compatibility (undocumented): +                      ({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A); +                      (V) -> check_function1(V) orelse check_function2(V) orelse check_function3(V)                     end,              class => user_options             }, @@ -439,6 +441,12 @@ default(client) ->              class => user_options             }, +      {save_accepted_host, def} => +          #{default => true, +            chk => fun erlang:is_boolean/1, +            class => user_options +           }, +        {pref_public_key_algs, def} =>            #{default => ssh_transport:default_algorithms(public_key),              chk => fun check_pref_public_key_algs/1, diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index 8db051095c..77da240a66 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -74,18 +74,14 @@ ssh_connection_child_spec(Role, Address, Port, _Profile, Options) ->      #{id       => id(Role, ssh_connection_sup, Address, Port),        start    => {ssh_connection_sup, start_link, [Options]},        restart  => temporary, -      shutdown => 5000, -      type     => supervisor, -      modules  => [ssh_connection_sup] +      type     => supervisor       }.  ssh_channel_child_spec(Role, Address, Port, _Profile, Options) ->      #{id       => id(Role, ssh_channel_sup, Address, Port),        start    => {ssh_channel_sup, start_link, [Options]},        restart  => temporary, -      shutdown => infinity, -      type     => supervisor, -      modules  => [ssh_channel_sup] +      type     => supervisor       }.  id(Role, Sup, Address, Port) -> diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index eaec7a54e4..8183016ba5 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -36,15 +36,14 @@ init(_) ->                   intensity =>   10,                   period    => 3600                  }, -    ChildSpecs = [#{id       => Module, -                    start    => {Module, start_link, []}, -                    restart  => permanent, -                    shutdown => 4000, %brutal_kill, -                    type     => supervisor, -                    modules  => [Module] +    ChildSpecs = [#{id       => sshd_sup, +                    start    => {sshd_sup, start_link, []}, +                    type     => supervisor +                   }, +                  #{id       => sshc_sup, +                    start    => {sshc_sup, start_link, []}, +                    type     => supervisor                     } -                  || Module <- [sshd_sup, -                                sshc_sup]                   ],      {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index e70abf59c2..469f9560e9 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -63,9 +63,7 @@ init([Address, Port, Profile, Options]) ->                  [#{id       => id(ssh_acceptor_sup, Address, Port, Profile),                     start    => {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]},                     restart  => transient, -                   shutdown => infinity, -                   type     => supervisor, -                   modules  => [ssh_acceptor_sup] +                   type     => supervisor                    }];              _ ->                  [] @@ -90,11 +88,11 @@ stop_listener(Address, Port, Profile) ->  stop_system(SysSup) -> -    spawn(fun() -> sshd_sup:stop_child(SysSup) end), +    catch sshd_sup:stop_child(SysSup),      ok.  stop_system(Address, Port, Profile) -> -    spawn(fun() -> sshd_sup:stop_child(Address, Port, Profile) end), +    catch sshd_sup:stop_child(Address, Port, Profile),      ok. @@ -124,9 +122,8 @@ start_subsystem(SystemSup, Role, Address, Port, Profile, Options) ->          #{id       => make_ref(),            start    => {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]},            restart  => temporary, -          shutdown => infinity, -          type     => supervisor, -          modules  => [ssh_subsystem_sup]}, +          type     => supervisor +         },      supervisor:start_child(SystemSup, SubsystemSpec).  stop_subsystem(SystemSup, SubSys) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index ad9efc4755..975053d301 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -889,10 +889,13 @@ known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}  	{_,true} ->  	    ok;  	{_,false} -> +            DoAdd = ?GET_OPT(save_accepted_host, Opts),  	    case accepted_host(Ssh, PeerName, Public, Opts) of -		true -> +		true when DoAdd == true ->  		    {_,R} = add_host_key(KeyCb, PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]),                      R; +		true when DoAdd == false -> +                    ok;  		false ->  		    {error, rejected_by_user};                  {error,E} -> diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 133b2c6450..f4b39dbbdc 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -27,7 +27,7 @@  -behaviour(supervisor). --export([start_link/0, start_child/1, stop_child/1]). +-export([start_link/0, start_child/1]).  %% Supervisor callback  -export([init/1]). @@ -43,13 +43,6 @@ start_link() ->  start_child(Args) ->      supervisor:start_child(?MODULE, Args). -stop_child(Client) -> -    spawn(fun() ->  -		  ClientSup = whereis(?SSHC_SUP), -		  supervisor:terminate_child(ClientSup, Client) -	  end), -    ok. -  %%%=========================================================================  %%%  Supervisor callback  %%%========================================================================= @@ -60,10 +53,7 @@ init(_) ->                  },      ChildSpecs = [#{id       => undefined, % As simple_one_for_one is used.                      start    => {ssh_connection_handler, start_link, []}, -                    restart  => temporary, -                    shutdown => 4000, -                    type     => worker, -                    modules  => [ssh_connection_handler] +                    restart  => temporary % because there is no way to restart a crashed connection                     }                   ],      {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index c23e65d955..779a861a54 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -90,10 +90,8 @@ init(_) ->  child_spec(Address, Port, Profile, Options) ->      #{id       => id(Address, Port, Profile),        start    => {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, -      restart  => temporary, -      shutdown => infinity, -      type     => supervisor, -      modules  => [ssh_system_sup] +      restart  => temporary,  +      type     => supervisor       }.  id(Address, Port, Profile) -> | 
