% %% %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 . 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)], ":")).