aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssh/src/ssh.erl
blob: e2a289d73778f9eef580216c4533a10cf3be526b (plain) (tree)
1
2
3
4
5
 
                   
  
                                                        
  










                                                                           
  








                            
                                                  
                                        
 


                                         

                                      
                       
                              

                                                            

                                  
 
                











                                   

                



                                                  
                                                                      

                                                                       
  
                                                        


                                                                      
                     

              





                                                     

                                                                      
                                     
  
                                          




                                                                      
                                                                                 
 



                                                                                                           
 


                                                                      


                                                         
 


                                                                  

                           

                                                                            
                     
                                                               
                                                                                                  


                                                                                           

               
 



                                                             
 



                                                                      

                                   




                                                                               
                               
                                                                                        



                                                                                           

                                                           

                                                                   
                                                                    
               

        
                                                                      
                         

                                         
                                                                      
                       
                                               

                                                                      
                                                             
  

                                                                      
                                          
                                                                   

                                                                      
                                                                        
  

                                                                      
                                                  
                                                                           

                                                                      



                                                                                                         
             
 
                                                             
                     
                                                                      


                     
 





                                                             
 


                                                                              

                                                                      
                                                                  
 


                                                         

                                                                
                                                                                                  



                                              




                                                                      
                                        
                                                                 
  
                                              
                                                               
                                                                      


                                         


                                                         

                                                                      
                                      

                                                                       
  
                                                                 
                
                                                                      


                                       


                                                                
 
                                                                      


                                                                      
 








                                                                      

                                     

                                       


                                              

                                            
 
                             






                                                                              
                                            



                                                                  

                 


                     

                                                                      
                                           
                                                                      
                       


                                                                      

                                                                      













                                                                 

        


















                                                                   




                                              
 
















                                                                    
 






                                                    

        
 
                                   
                     




                                          






                                                        

                                                                 



                                                
                                                                                          











                                                              
                                                                                            




                         
                                          
                  
           
                                                 


                                   
                                     



                                
                                          

                                                                      
                                                                       
                                                      
                       

                                                                      

                  
                                        
            



                                                            
                                                                 
                    
                                                
                                                
                                        
                                  
                                                                      

                                      
                          



                                            
              
                                                             
                                                                 

                                                
                          
                                                                      
                              

                         


               


                                                                                  







                                                         
                                        
       

                                                    






                                                             






                                                                          
 
                      
                                        
                                
                                                                                           
%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-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%
%%

%%

-module(ssh).

-include("ssh.hrl").
-include("ssh_connect.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/file.hrl").

-export([start/0, start/1, stop/0,
	 connect/2, connect/3, connect/4,
	 close/1, connection_info/2,
	 channel_info/3,
	 daemon/1, daemon/2, daemon/3,
	 daemon_info/1,
	 default_algorithms/0,
	 stop_listener/1, stop_listener/2,  stop_listener/3,
	 stop_daemon/1, stop_daemon/2, stop_daemon/3,
	 shell/1, shell/2, shell/3
	]).

%%% Type exports
-export_type([ssh_daemon_ref/0,
              ssh_connection_ref/0,
	      ssh_channel_id/0,
              role/0,
              subsystem_spec/0,
              subsystem_name/0,
              channel_callback/0,
              channel_init_args/0,
              algs_list/0,
              alg_entry/0,
              simple_algs/0,
              double_algs/0
	     ]).

-opaque ssh_daemon_ref()     :: daemon_ref() .
-opaque ssh_connection_ref() :: connection_ref() .
-opaque ssh_channel_id()     :: channel_id().

%%--------------------------------------------------------------------
-spec start() -> ok | {error, term()}.
-spec start(permanent | transient | temporary) -> ok | {error, term()}.
%%
%% Description: Starts the ssh application. Default type
%% is temporary. see application(3)
%%--------------------------------------------------------------------
start() ->
    start(temporary).

start(Type) ->
    case application:ensure_all_started(ssh, Type) of
        {ok, _} ->
            ok;
        Other ->
            Other
    end.

%%--------------------------------------------------------------------
-spec stop() -> ok | {error, term()}.
%%
%% Description: Stops the ssh application.
%%--------------------------------------------------------------------
stop() ->
    application:stop(ssh).

%%--------------------------------------------------------------------
-spec connect(inet:socket(), proplists:proplist()) -> ok_error(connection_ref()).

-spec connect(inet:socket(), proplists:proplist(), timeout()) -> ok_error(connection_ref())
           ; (string(), inet:port_number(), proplists:proplist()) -> ok_error(connection_ref()).

-spec connect(string(), inet:port_number(), proplists:proplist(), timeout()) -> ok_error(connection_ref()).

%%
%% Description: Starts an ssh connection.
%%--------------------------------------------------------------------
connect(Socket, UserOptions) when is_port(Socket),
                                  is_list(UserOptions) ->
    connect(Socket, UserOptions, infinity).

connect(Socket, UserOptions, Timeout) when is_port(Socket),
                                           is_list(UserOptions) ->
    case ssh_options:handle_options(client, UserOptions) of
	{error, Error} ->
	    {error, Error};
	Options ->
            case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
		ok ->
		    {ok, {Host,_Port}} = inet:sockname(Socket),
		    Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options),
		    ssh_connection_handler:start_connection(client, Socket, Opts, Timeout);
		{error,SockError} ->
		    {error,SockError}
	    end
    end;

connect(Host, Port, UserOptions) when is_integer(Port),
                                      Port>0,
                                      is_list(UserOptions) ->
    connect(Host, Port, UserOptions, infinity).

connect(Host, Port, UserOptions, Timeout) when is_integer(Port),
                                               Port>0,
                                               is_list(UserOptions) ->
    case ssh_options:handle_options(client, UserOptions) of
	{error, _Reason} = Error ->
	    Error;
        Options ->
	    {_, Transport, _} = TransportOpts = ?GET_OPT(transport, Options),
	    ConnectionTimeout = ?GET_OPT(connect_timeout, Options),
            SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)],
	    try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of
		{ok, Socket} ->
		    Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options),
		    ssh_connection_handler:start_connection(client, Socket, Opts, Timeout);
		{error, Reason} ->
		    {error, Reason}
	    catch
		exit:{function_clause, _F} ->
                    io:format('function_clause ~p~n',[_F]),
		    {error, {options, {transport, TransportOpts}}};
		exit:badarg ->
		    {error, {options, {socket_options, SocketOpts}}}
	    end
    end.

%%--------------------------------------------------------------------
-spec close(pid()) -> ok.
%%
%% Description: Closes an ssh connection.
%%--------------------------------------------------------------------
close(ConnectionRef) ->
    ssh_connection_handler:stop(ConnectionRef).

%%--------------------------------------------------------------------
-spec connection_info(pid(), [atom()]) -> [{atom(), term()}].
%%
%% Description: Retrieves information about a connection.
%%--------------------------------------------------------------------
connection_info(ConnectionRef, Options) ->
    ssh_connection_handler:connection_info(ConnectionRef, Options).

%%--------------------------------------------------------------------
-spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}].
%%
%% Description: Retrieves information about a connection.
%%--------------------------------------------------------------------
channel_info(ConnectionRef, ChannelId, Options) ->
    ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options).

%%--------------------------------------------------------------------
-spec daemon(inet:port_number()) ->  ok_error(daemon_ref()).
-spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(daemon_ref()).
-spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(daemon_ref())
           ;(socket, inet:socket(), proplists:proplist()) -> ok_error(daemon_ref())
            .

%% Description: Starts a server listening for SSH connections
%% on the given port.
%%--------------------------------------------------------------------
daemon(Port) ->
    daemon(Port, []).


daemon(Port, UserOptions) when is_integer(Port), Port >= 0 ->
    daemon(any, Port, UserOptions);

daemon(Socket, UserOptions) when is_port(Socket) ->
    daemon(socket, Socket, UserOptions).


daemon(Host0, Port, UserOptions0) ->
    {Host, UserOptions} = handle_daemon_args(Host0, UserOptions0),
    start_daemon(Host, Port, ssh_options:handle_options(server, UserOptions)).

%%--------------------------------------------------------------------
-spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ).

daemon_info(Pid) ->
    case catch ssh_system_sup:acceptor_supervisor(Pid) of
	AsupPid when is_pid(AsupPid) ->
	    [{ListenAddr,Port,Profile}] =
		[{LA,Prt,Prf} || {{ssh_acceptor_sup,LA,Prt,Prf},
			 _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)],
	    {ok, [{port,Port},
                  {listen_address,ListenAddr},
                  {profile,Profile}
                 ]};
	_ ->
	    {error,bad_daemon_ref}
    end.

%%--------------------------------------------------------------------
-spec stop_listener(daemon_ref()) -> ok.
-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok.
%%
%% Description: Stops the listener, but leaves
%% existing connections started by the listener up and running.
%%--------------------------------------------------------------------
stop_listener(SysSup) ->
    ssh_system_sup:stop_listener(SysSup).
stop_listener(Address, Port) ->
    stop_listener(Address, Port, ?DEFAULT_PROFILE).
stop_listener(Address, Port, Profile) ->
    ssh_system_sup:stop_listener(Address, Port, Profile).

%%--------------------------------------------------------------------
-spec stop_daemon(daemon_ref()) -> ok.
-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok.
-spec stop_daemon(inet:ip_address(), inet:port_number(), atom()) -> ok.
%%
%% Description: Stops the listener and all connections started by
%% the listener.
%%--------------------------------------------------------------------
stop_daemon(SysSup) ->
    ssh_system_sup:stop_system(SysSup).
stop_daemon(Address, Port) ->
    ssh_system_sup:stop_system(Address, Port, ?DEFAULT_PROFILE).
stop_daemon(Address, Port, Profile) ->
    ssh_system_sup:stop_system(Address, Port, Profile).

%%--------------------------------------------------------------------
-spec shell(inet:socket() | string()) ->  _.
-spec shell(inet:socket() | string(), proplists:proplist()) ->  _.
-spec shell(string(), inet:port_number(), proplists:proplist()) ->  _.

%%   Host = string()
%%   Port = integer()
%%   Options = [{Option, Value}]
%%
%% Description: Starts an interactive shell to an SSH server on the
%% given <Host>. The function waits for user input,
%% and will not return until the remote shell is ended.(e.g. on
%% exit from the shell)
%%--------------------------------------------------------------------
shell(Socket) when is_port(Socket) ->
    shell(Socket, []);
shell(Host) ->
    shell(Host, ?SSH_DEFAULT_PORT, []).

shell(Socket, Options) when is_port(Socket) ->
    start_shell( connect(Socket, Options) );
shell(Host, Options) ->
    shell(Host, ?SSH_DEFAULT_PORT, Options).

shell(Host, Port, Options) ->
    start_shell( connect(Host, Port, Options) ).


start_shell({ok, ConnectionRef}) ->
    case ssh_connection:session_channel(ConnectionRef, infinity) of
	{ok,ChannelId}  ->
	    success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []),
	    Args = [{channel_cb, ssh_shell},
		    {init_args,[ConnectionRef, ChannelId]},
		    {cm, ConnectionRef}, {channel_id, ChannelId}],
	    {ok, State} = ssh_channel:init([Args]),
	    ssh_channel:enter_loop(State);
	Error ->
	    Error
    end;
start_shell(Error) ->
    Error.

%%--------------------------------------------------------------------
-spec default_algorithms() -> algs_list() .
%%--------------------------------------------------------------------
default_algorithms() ->
    ssh_transport:default_algorithms().

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
handle_daemon_args(Host, UserOptions0) ->
    case Host of
        socket ->
            {Host, UserOptions0};
        any ->
            {ok, Host0} = inet:gethostname(),
            Inet = proplists:get_value(inet, UserOptions0, inet),
            {Host0, [Inet | UserOptions0]};
        {_,_,_,_} ->
            {Host, [inet, {ip,Host} | UserOptions0]};
        {_,_,_,_,_,_,_,_} ->
            {Host, [inet6, {ip,Host} | UserOptions0]};
        _ ->
            error(badarg)
    end.

%%%----------------------------------------------------------------
valid_socket_to_use(Socket, {tcp,_,_}) ->
    %% Is this tcp-socket a valid socket?
    case {is_tcp_socket(Socket),
          {ok,[{active,false}]} == inet:getopts(Socket, [active])
         }
    of
        {true, true} ->
            ok;
        {true, false} ->
            {error, not_passive_mode};
        _ ->
            {error, not_tcp_socket}
    end;

valid_socket_to_use(_, {L4,_,_}) ->
    {error, {unsupported,L4}}.


is_tcp_socket(Socket) ->
    case inet:getopts(Socket, [delay_send]) of
        {ok,[_]} -> true;
        _ -> false
    end.

%%%----------------------------------------------------------------
start_daemon(_, _, {error,Error}) ->
    {error,Error};

start_daemon(socket, Socket, Options) ->
    case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
        ok ->
            try
                do_start_daemon(Socket, Options)
            catch
                throw:bad_fd -> {error,bad_fd};
                throw:bad_socket -> {error,bad_socket};
                _C:_E -> {error,{cannot_start_daemon,_C,_E}}
            end;
        {error,SockError} ->
            {error,SockError}
    end;

start_daemon(Host, Port, Options) ->
    try
        do_start_daemon(Host, Port, Options)
    catch
        throw:bad_fd -> {error,bad_fd};
        throw:bad_socket -> {error,bad_socket};
        _C:_E -> {error,{cannot_start_daemon,_C,_E}}
    end.


do_start_daemon(Socket, Options) ->
    {ok, {IP,Port}} =
	try {ok,_} = inet:sockname(Socket)
	catch
	    _:_ -> throw(bad_socket)
	end,
    Host = fmt_host(IP),
    Opts = ?PUT_INTERNAL_OPT([{asocket, Socket},
                              {asock_owner,self()},
                              {address, Host},
                              {port, Port},
                              {role, server}], Options),
    
    Profile = ?GET_OPT(profile, Options),
    case ssh_system_sup:system_supervisor(Host, Port, Profile) of
	undefined ->
	    try sshd_sup:start_child(Opts) of
		{error, {already_started, _}} ->
		    {error, eaddrinuse};
		Result = {ok,_} ->
		    call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, Result);
		Result = {error, _} ->
		    Result
	    catch
		exit:{noproc, _} ->
		    {error, ssh_not_started}
	    end;
	Sup  ->
	    AccPid = ssh_system_sup:acceptor_supervisor(Sup),
	    case ssh_acceptor_sup:start_child(AccPid, Opts) of
		{error, {already_started, _}} ->
		    {error, eaddrinuse};
		{ok, _} ->
		    call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, {ok,Sup});
		Other ->
		    Other
	    end
    end.

do_start_daemon(Host0, Port0, Options0) ->
    {Host,Port1} =
	try
	    case ?GET_SOCKET_OPT(fd, Options0) of
		undefined ->
		    {Host0,Port0};
		Fd when Port0==0 ->
		    find_hostport(Fd)
	    end
	catch
	    _:_ -> throw(bad_fd)
	end,
    {Port, WaitRequestControl, Options1} =
	case Port1 of
	    0 -> %% Allocate the socket here to get the port number...
		{ok,LSock} = ssh_acceptor:callback_listen(0, Options0),
		{ok,{_,LPort}} = inet:sockname(LSock),
		{LPort,
		 LSock,
		 ?PUT_INTERNAL_OPT({lsocket,{LSock,self()}}, Options0)
		};
	    _ ->
		{Port1, false, Options0}
	end,
    Options = ?PUT_INTERNAL_OPT([{address, Host},
                                 {port, Port},
                                 {role, server}], Options1),
    Profile = ?GET_OPT(profile, Options0),
    case ssh_system_sup:system_supervisor(Host, Port, Profile) of
	undefined ->
	    try sshd_sup:start_child(Options) of
		{error, {already_started, _}} ->
		    {error, eaddrinuse};
		Result = {ok,_} ->
		    sync_request_control(WaitRequestControl, Options),
		    Result;
		Result = {error, _} ->
		    Result
	    catch
		exit:{noproc, _} ->
		    {error, ssh_not_started}
	    end;
	Sup ->
	    AccPid = ssh_system_sup:acceptor_supervisor(Sup),
	    case ssh_acceptor_sup:start_child(AccPid, Options) of
		{error, {already_started, _}} ->
		    {error, eaddrinuse};
		{ok, _} ->
		    sync_request_control(WaitRequestControl, Options),
		    {ok, Sup};
		Other ->
		    Other
	    end
    end.

call_ssh_acceptor_handle_connection(Host, Port, Options, Socket, DefaultResult) ->
    {_, Callback, _} = ?GET_OPT(transport, Options),
    try ssh_acceptor:handle_connection(Callback, Host, Port, Options, Socket)
    of
        {error,Error} -> {error,Error};
        _ -> DefaultResult
    catch
        C:R -> {error,{could_not_start_connection,{C,R}}}
    end.
             

sync_request_control(false, _Options) ->
    ok;
sync_request_control(LSock, Options) ->
    {_, Callback, _} = ?GET_OPT(transport, Options),
    receive
	{request_control,LSock,ReqPid} ->
	    ok = Callback:controlling_process(LSock, ReqPid),
	    ReqPid ! {its_yours,LSock},
	    ok
    end.

find_hostport(Fd) ->
    %% Using internal functions inet:open/8 and inet:close/0.
    %% Don't try this at home unless you know what you are doing!
    {ok,S} = inet:open(Fd, {0,0,0,0}, 0, [], tcp, inet, stream, inet_tcp),
    {ok, HostPort} = inet:sockname(S),
    ok = inet:close(S),
    HostPort.

fmt_host({A,B,C,D}) ->
    lists:concat([A,".",B,".",C,".",D]);
fmt_host(T={_,_,_,_,_,_,_,_}) ->
    lists:flatten(string:join([io_lib:format("~.16B",[A]) || A <- tuple_to_list(T)], ":")).