diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/Makefile | 44 | ||||
-rw-r--r-- | lib/ssh/src/ssh.app.src | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 195 | ||||
-rw-r--r-- | lib/ssh/src/ssh.hrl | 281 | ||||
-rw-r--r-- | lib/ssh/src/ssh_acceptor_sup.erl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_channel.erl | 388 | ||||
-rw-r--r-- | lib/ssh/src/ssh_cli.erl | 21 | ||||
-rw-r--r-- | lib/ssh/src/ssh_client_channel.erl | 456 | ||||
-rw-r--r-- | lib/ssh/src/ssh_client_key_api.erl | 35 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connect.hrl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection.erl | 128 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 63 | ||||
-rw-r--r-- | lib/ssh/src/ssh_daemon_channel.erl | 36 | ||||
-rw-r--r-- | lib/ssh/src/ssh_file.erl | 21 | ||||
-rw-r--r-- | lib/ssh/src/ssh_info.erl | 8 | ||||
-rw-r--r-- | lib/ssh/src/ssh_options.erl | 27 | ||||
-rw-r--r-- | lib/ssh/src/ssh_server_channel.erl | 55 | ||||
-rw-r--r-- | lib/ssh/src/ssh_server_channel_sup.erl (renamed from lib/ssh/src/ssh_channel_sup.erl) | 8 | ||||
-rw-r--r-- | lib/ssh/src/ssh_server_key_api.erl | 12 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftp.erl | 18 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftpd.erl | 18 | ||||
-rw-r--r-- | lib/ssh/src/ssh_shell.erl | 21 | ||||
-rw-r--r-- | lib/ssh/src/ssh_subsystem_sup.erl | 12 |
23 files changed, 1120 insertions, 737 deletions
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index bcd13213b3..5e4efb6b99 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -40,42 +40,44 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssh-$(VSN) # Behaviour (api) modules are first so they are compiled when # the compiler reaches a callback module using them. BEHAVIOUR_MODULES= \ - ssh_sftpd_file_api \ - ssh_channel \ - ssh_daemon_channel \ ssh_client_key_api \ - ssh_server_key_api + ssh_daemon_channel \ + ssh_server_channel \ + ssh_server_key_api \ + ssh_sftpd_file_api \ + ssh_channel \ + ssh_client_channel MODULES= \ ssh \ - ssh_sup \ - sshc_sup \ - sshd_sup \ - ssh_options \ - ssh_connection_sup \ - ssh_connection \ - ssh_connection_handler \ - ssh_dbg \ - ssh_shell \ - ssh_system_sup \ - ssh_subsystem_sup \ - ssh_channel_sup \ - ssh_acceptor_sup \ ssh_acceptor \ + ssh_acceptor_sup \ ssh_app \ ssh_auth\ ssh_bits \ ssh_cli \ + ssh_connection \ + ssh_connection_handler \ + ssh_connection_sup \ + ssh_dbg \ ssh_file \ - ssh_io \ ssh_info \ + ssh_io \ ssh_message \ ssh_no_io \ + ssh_options \ + ssh_server_channel_sup \ ssh_sftp \ ssh_sftpd \ ssh_sftpd_file\ + ssh_shell \ + ssh_subsystem_sup \ + ssh_sup \ + ssh_system_sup \ ssh_transport \ - ssh_xfer + ssh_xfer \ + sshc_sup \ + sshd_sup HRL_FILES = @@ -169,7 +171,7 @@ $(EBIN)/ssh_connection_handler.$(EMULATOR): ssh_connection_handler.erl ssh.hrl \ $(EBIN)/ssh_shell.$(EMULATOR): ssh_shell.erl ssh_connect.hrl $(EBIN)/ssh_system_sup.$(EMULATOR): ssh_system_sup.erl ssh.hrl $(EBIN)/ssh_subsystem_sup.$(EMULATOR): ssh_subsystem_sup.erl -$(EBIN)/ssh_channel_sup.$(EMULATOR): ssh_channel_sup.erl +$(EBIN)/ssh_server_channel_sup.$(EMULATOR): ssh_server_channel_sup.erl $(EBIN)/ssh_acceptor_sup.$(EMULATOR): ssh_acceptor_sup.erl ssh.hrl $(EBIN)/ssh_acceptor.$(EMULATOR): ssh_acceptor.erl ssh.hrl $(EBIN)/ssh_app.$(EMULATOR): ssh_app.erl @@ -208,8 +210,10 @@ $(EBIN)/ssh_transport.$(EMULATOR): ssh_transport.erl \ ssh_transport.hrl ssh.hrl $(EBIN)/ssh_xfer.$(EMULATOR): ssh_xfer.erl ssh.hrl ssh_xfer.hrl $(EBIN)/ssh_sftpd_file_api.$(EMULATOR): ssh_sftpd_file_api.erl +$(EBIN)/ssh_client_channel.$(EMULATOR): ssh_client_channel.erl ssh_connect.hrl $(EBIN)/ssh_channel.$(EMULATOR): ssh_channel.erl ssh_connect.hrl $(EBIN)/ssh_daemon_channel.$(EMULATOR): ssh_daemon_channel.erl +$(EBIN)/ssh_server_channel.$(EMULATOR): ssh_server_channel.erl $(EBIN)/ssh_client_key_api.$(EMULATOR): ssh_client_key_api.erl \ ../../public_key/include/public_key.hrl \ ../../public_key/include/OTP-PUB-KEY.hrl \ diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 4a22322333..410061cded 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -12,9 +12,9 @@ ssh_message, ssh_bits, ssh_cli, + ssh_client_channel, ssh_client_key_api, ssh_channel, - ssh_channel_sup, ssh_connection, ssh_connection_handler, ssh_connection_sup, @@ -27,6 +27,8 @@ ssh_io, ssh_info, ssh_no_io, + ssh_server_channel, + ssh_server_channel_sup, ssh_server_key_api, ssh_sftp, ssh_sftpd, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 25d537c624..7ddb1ca5be 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -41,35 +41,51 @@ shell/1, shell/2, shell/3 ]). +%%% "Deprecated" types export: +-export_type([ssh_daemon_ref/0, ssh_connection_ref/0, ssh_channel_id/0]). +-opaque ssh_daemon_ref() :: daemon_ref(). +-opaque ssh_connection_ref() :: connection_ref(). +-opaque ssh_channel_id() :: channel_id(). + + %%% Type exports --export_type([ssh_daemon_ref/0, - ssh_connection_ref/0, - ssh_channel_id/0, +-export_type([daemon_ref/0, + connection_ref/0, + channel_id/0, + client_options/0, client_option/0, + daemon_options/0, daemon_option/0, + common_options/0, role/0, subsystem_spec/0, - subsystem_name/0, - channel_callback/0, - channel_init_args/0, algs_list/0, + double_algs/1, + modify_algs_list/0, alg_entry/0, - simple_algs/0, - double_algs/0 + kex_alg/0, + pubkey_alg/0, + cipher_alg/0, + mac_alg/0, + compression_alg/0, + ip_port/0 ]). --opaque ssh_daemon_ref() :: daemon_ref() . --opaque ssh_connection_ref() :: connection_ref() . --opaque ssh_channel_id() :: channel_id(). + +-opaque daemon_ref() :: pid() . +-opaque channel_id() :: non_neg_integer(). +-type connection_ref() :: pid(). % should be -opaque, but that gives problems %%-------------------------------------------------------------------- --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) %%-------------------------------------------------------------------- +-spec start() -> ok | {error, term()}. + start() -> start(temporary). +-spec start(Type) -> ok | {error, term()} when + Type :: permanent | transient | temporary . + start(Type) -> case application:ensure_all_started(ssh, Type) of {ok, _} -> @@ -79,30 +95,32 @@ start(Type) -> end. %%-------------------------------------------------------------------- --spec stop() -> ok | {error, term()}. -%% %% Description: Stops the ssh application. %%-------------------------------------------------------------------- +-spec stop() -> ok | {error, term()}. + stop() -> application:stop(ssh). %%-------------------------------------------------------------------- --spec connect(inet:socket(), proplists:proplist()) -> ok_error(connection_ref()). +%% Description: Starts an ssh connection. +%%-------------------------------------------------------------------- +-spec connect(OpenTcpSocket, Options) -> {ok,connection_ref()} | {error,term()} when + OpenTcpSocket :: open_socket(), + Options :: client_options(). --spec connect(inet:socket(), proplists:proplist(), timeout()) -> ok_error(connection_ref()) - ; (string(), inet:port_number(), proplists:proplist()) -> ok_error(connection_ref()). +connect(OpenTcpSocket, Options) when is_port(OpenTcpSocket), + is_list(Options) -> + connect(OpenTcpSocket, Options, infinity). --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). +-spec connect(open_socket(), client_options(), timeout()) -> + {ok,connection_ref()} | {error,term()} + ; (host(), inet:port_number(), client_options()) -> + {ok,connection_ref()} | {error,term()}. -connect(Socket, UserOptions, Timeout) when is_port(Socket), - is_list(UserOptions) -> +connect(Socket, UserOptions, NegotiationTimeout) when is_port(Socket), + is_list(UserOptions) -> case ssh_options:handle_options(client, UserOptions) of {error, Error} -> {error, Error}; @@ -111,16 +129,23 @@ connect(Socket, UserOptions, Timeout) when is_port(Socket), ok -> {ok, {Host,_Port}} = inet:sockname(Socket), Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), - ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); + ssh_connection_handler:start_connection(client, Socket, Opts, NegotiationTimeout); {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, Options) when is_integer(Port), + Port>0, + is_list(Options) -> + connect(Host, Port, Options, infinity). + + +-spec connect(Host, Port, Options, NegotiationTimeout) -> {ok,connection_ref()} | {error,term()} when + Host :: host(), + Port :: inet:port_number(), + Options :: client_options(), + NegotiationTimeout :: timeout(). connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), Port>0, @@ -148,7 +173,8 @@ connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), end. %%-------------------------------------------------------------------- --spec close(pid()) -> ok. +-spec close(ConnectionRef) -> ok | {error,term()} when + ConnectionRef :: connection_ref() . %% %% Description: Closes an ssh connection. %%-------------------------------------------------------------------- @@ -156,15 +182,25 @@ 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 connection_info(ConnectionRef, Keys) -> ConnectionInfo when + ConnectionRef :: connection_ref(), + Keys :: [client_version | server_version | user | peer | sockname], + ConnectionInfo :: [{client_version, Version} + | {server_version, Version} + | {user,string()} + | {peer, {inet:hostname(), ip_port()}} + | {sockname, ip_port()} + ], + Version :: {ProtocolVersion, VersionString::string()}, + ProtocolVersion :: {Major::pos_integer(), Minor::non_neg_integer()} . + +connection_info(Connection, Options) -> + ssh_connection_handler:connection_info(Connection, Options). %%-------------------------------------------------------------------- --spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}]. +-spec channel_info(connection_ref(), channel_id(), [atom()]) -> proplists:proplist(). %% %% Description: Retrieves information about a connection. %%-------------------------------------------------------------------- @@ -172,18 +208,17 @@ 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. %%-------------------------------------------------------------------- +-spec daemon(inet:port_number()) -> {ok,daemon_ref()} | {error,term()}. + daemon(Port) -> daemon(Port, []). + +-spec daemon(inet:port_number()|open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}. + daemon(Socket, UserOptions) when is_port(Socket) -> try #{} = Options = ssh_options:handle_options(server, UserOptions), @@ -226,6 +261,10 @@ daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 -> daemon(any, Port, UserOptions). +-spec daemon(any | inet:ip_address(), inet:port_number(), daemon_options()) -> {ok,daemon_ref()} | {error,term()} + ;(socket, open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()} + . + daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535, Host0 == any ; Host0 == loopback ; is_tuple(Host0) -> try @@ -267,7 +306,12 @@ daemon(_, _, _) -> {error, badarg}. %%-------------------------------------------------------------------- --spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ). +-spec daemon_info(Daemon) -> {ok, DaemonInfo} | {error,term()} when + Daemon :: daemon_ref(), + DaemonInfo :: [ {ip, inet:ip_address()} + | {port, inet:port_number()} + | {profile, term()} + ]. daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of @@ -290,16 +334,23 @@ daemon_info(Pid) -> 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. %%-------------------------------------------------------------------- +-spec stop_listener(daemon_ref()) -> ok. + stop_listener(SysSup) -> ssh_system_sup:stop_listener(SysSup). + + +-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. + stop_listener(Address, Port) -> stop_listener(Address, Port, ?DEFAULT_PROFILE). + + +-spec stop_listener(any|inet:ip_address(), inet:port_number(), term()) -> ok. + stop_listener(any, Port, Profile) -> map_ip(fun(IP) -> ssh_system_sup:stop_listener(IP, Port, Profile) @@ -310,17 +361,23 @@ stop_listener(Address, Port, Profile) -> end, {address,Address}). %%-------------------------------------------------------------------- --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. %%-------------------------------------------------------------------- +-spec stop_daemon(DaemonRef::daemon_ref()) -> ok. + stop_daemon(SysSup) -> ssh_system_sup:stop_system(SysSup). + + +-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok. + stop_daemon(Address, Port) -> stop_daemon(Address, Port, ?DEFAULT_PROFILE). + + +-spec stop_daemon(any|inet:ip_address(), inet:port_number(), atom()) -> ok. + stop_daemon(any, Port, Profile) -> map_ip(fun(IP) -> ssh_system_sup:stop_system(IP, Port, Profile) @@ -331,33 +388,37 @@ stop_daemon(Address, Port, Profile) -> end, {address,Address}). %%-------------------------------------------------------------------- --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) %%-------------------------------------------------------------------- +-spec shell(open_socket() | host()) -> _. + shell(Socket) when is_port(Socket) -> shell(Socket, []); shell(Host) -> shell(Host, ?SSH_DEFAULT_PORT, []). + +-spec shell(open_socket() | host(), client_options()) -> _. + shell(Socket, Options) when is_port(Socket) -> start_shell( connect(Socket, Options) ); shell(Host, Options) -> shell(Host, ?SSH_DEFAULT_PORT, Options). + +-spec shell(Host, Port, Options) -> _ when + Host :: host(), + Port :: inet:port_number(), + Options :: client_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} -> @@ -365,11 +426,17 @@ start_shell({ok, ConnectionRef}) -> 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); + {ok, State} = ssh_client_channel:init([Args]), + try + ssh_client_channel:enter_loop(State) + catch + exit:normal -> + ok + end; Error -> Error end; + start_shell(Error) -> Error. @@ -380,7 +447,7 @@ default_algorithms() -> ssh_transport:default_algorithms(). %%-------------------------------------------------------------------- --spec chk_algos_opts(list(any())) -> algs_list() . +-spec chk_algos_opts(client_options()|daemon_options()) -> internal_options() | {error,term()}. %%-------------------------------------------------------------------- chk_algos_opts(Opts) -> case lists:foldl( diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 0e118ac13f..a3d9a1b1cb 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -98,35 +98,267 @@ %% Types --type role() :: client | server . --type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} . --type daemon_ref() :: pid() . +-type role() :: client | server . + +-type host() :: string() | inet:ip_address() | loopback . +-type open_socket() :: gen_tcp:socket(). + +-type subsystem_spec() :: {Name::string(), mod_args()} . + +-type algs_list() :: list( alg_entry() ). +-type alg_entry() :: {kex, [kex_alg()]} + | {public_key, [pubkey_alg()]} + | {cipher, double_algs(cipher_alg())} + | {mac, double_algs(mac_alg())} + | {compression, double_algs(compression_alg())} . + +-type kex_alg() :: 'diffie-hellman-group-exchange-sha1' | + 'diffie-hellman-group-exchange-sha256' | + 'diffie-hellman-group1-sha1' | + 'diffie-hellman-group14-sha1' | + 'diffie-hellman-group14-sha256' | + 'diffie-hellman-group16-sha512' | + 'diffie-hellman-group18-sha512' | + 'ecdh-sha2-nistp256' | + 'ecdh-sha2-nistp384' | + 'ecdh-sha2-nistp521' + . + +-type pubkey_alg() :: 'ecdsa-sha2-nistp256' | + 'ecdsa-sha2-nistp384' | + 'ecdsa-sha2-nistp521' | + 'rsa-sha2-256' | + 'rsa-sha2-512' | + 'ssh-dss' | + 'ssh-rsa' + . + +-type cipher_alg() :: '3des-cbc' | + 'AEAD_AES_128_GCM' | + 'AEAD_AES_256_GCM' | + 'aes128-cbc' | + 'aes128-ctr' | + '[email protected]' | + 'aes192-ctr' | + 'aes256-ctr' | + . + +-type mac_alg() :: 'AEAD_AES_128_GCM' | + 'AEAD_AES_256_GCM' | + 'hmac-sha1' | + 'hmac-sha2-256' | + 'hmac-sha2-512' + . + +-type compression_alg() :: 'none' | + 'zlib' | + . + +-type double_algs(AlgType) :: list( {client2server,[AlgType]} | {server2client,[AlgType]} ) + | [AlgType]. + +-type modify_algs_list() :: list( {append,algs_list()} | {prepend,algs_list()} | {rm,algs_list()} ) . + +-type internal_options() :: ssh_options:private_options(). +-type socket_options() :: [gen_tcp:connect_option() | gen_tcp:listen_option()]. + +-type client_options() :: [ client_option() ] . +-type daemon_options() :: [ daemon_option() ]. + + +-type common_options() :: [ common_option() ]. +-type common_option() :: + user_dir_common_option() + | profile_common_option() + | max_idle_time_common_option() + | key_cb_common_option() + | disconnectfun_common_option() + | unexpectedfun_common_option() + | ssh_msg_debug_fun_common_option() + | rekey_limit_common_option() + | id_string_common_option() + | preferred_algorithms_common_option() + | modify_algorithms_common_option() + | auth_methods_common_option() + | inet_common_option() + | fd_common_option() + . + +-define(COMMON_OPTION, common_option()). + + +-type user_dir_common_option() :: {user_dir, false | string()}. +-type profile_common_option() :: {profile, atom() }. +-type max_idle_time_common_option() :: {idle_time, timeout()}. +-type rekey_limit_common_option() :: {rekey_limit, non_neg_integer() }. + +-type key_cb_common_option() :: {key_cb, Module::atom() | {Module::atom(),Opts::[term()]} } . +-type disconnectfun_common_option() :: + {disconnectfun, fun((Reason::term()) -> void | any()) }. +-type unexpectedfun_common_option() :: + {unexpectedfun, fun((Message::term(),{Host::term(),Port::term()}) -> report | skip ) }. +-type ssh_msg_debug_fun_common_option() :: + {ssh_msg_debug_fun, fun((ssh:connection_ref(),AlwaysDisplay::boolean(),Msg::binary(),LanguageTag::binary()) -> any()) } . + +-type id_string_common_option() :: {id_string, string() | random | {random,Nmin::pos_integer(),Nmax::pos_integer()} }. +-type preferred_algorithms_common_option():: {preferred_algorithms, algs_list()}. +-type modify_algorithms_common_option() :: {modify_algorithms, modify_algs_list()}. +-type auth_methods_common_option() :: {auth_methods, string() }. + +-type inet_common_option() :: {inet, inet | inet6} . +-type fd_common_option() :: {fd, gen_tcp:socket()} . + + +-type opaque_common_options() :: + {transport, {atom(),atom(),atom()} } + | {vsn, {non_neg_integer(),non_neg_integer()} } + | {tstflg, list(term())} + | {user_dir_fun, fun()} + | {max_random_length_padding, non_neg_integer()} . + + + +-type client_option() :: + pref_public_key_algs_client_option() + | pubkey_passphrase_client_options() + | host_accepting_client_options() + | authentication_client_options() + | diffie_hellman_group_exchange_client_option() + | connect_timeout_client_option() + | recv_ext_info_client_option() + | opaque_client_options() + | gen_tcp:connect_option() + | ?COMMON_OPTION . + +-type opaque_client_options() :: + {keyboard_interact_fun, fun((term(),term(),term()) -> term())} + | opaque_common_options(). + +-type pref_public_key_algs_client_option() :: {pref_public_key_algs, [pubkey_alg()] } . + +-type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()} + | {rsa_pass_phrase, string()} + | {ecdsa_pass_phrase, string()} . + +-type host_accepting_client_options() :: + {silently_accept_hosts, accept_hosts()} + | {user_interaction, boolean()} + | {save_accepted_host, boolean()} + | {quiet_mode, boolean()} . + +-type accept_hosts() :: boolean() + | accept_callback() + | {HashAlgoSpec::fp_digest_alg(), accept_callback()}. + +-type fp_digest_alg() :: 'md5' | + 'sha' | + 'sha224' | + 'sha256' | + 'sha384' | + 'sha512' + . + +-type accept_callback() :: fun((PeerName::string(), fingerprint() ) -> boolean()) . +-type fingerprint() :: string() | [string()]. + +-type authentication_client_options() :: + {user, string()} + | {password, string()} . + +-type diffie_hellman_group_exchange_client_option() :: + {dh_gex_limits, {Min::pos_integer(), I::pos_integer(), Max::pos_integer()} } . + +-type connect_timeout_client_option() :: {connect_timeout, timeout()} . + +-type recv_ext_info_client_option() :: {recv_ext_info, boolean()} . + + + +-type daemon_option() :: + subsystem_daemon_option() + | shell_daemon_option() + | exec_daemon_option() + | ssh_cli_daemon_option() + | authentication_daemon_options() + | diffie_hellman_group_exchange_daemon_option() + | negotiation_timeout_daemon_option() + | hardening_daemon_options() + | callbacks_daemon_options() + | send_ext_info_daemon_option() + | opaque_daemon_options() + | gen_tcp:listen_option() + | ?COMMON_OPTION . --type subsystem_spec() :: {subsystem_name(), {channel_callback(), channel_init_args()}} . --type subsystem_name() :: string() . --type channel_callback() :: atom() . --type channel_init_args() :: list() . +-type subsystem_daemon_option() :: {subsystems, subsystem_spec()}. --type algs_list() :: list( alg_entry() ). --type alg_entry() :: {kex, simple_algs()} - | {public_key, simple_algs()} - | {cipher, double_algs()} - | {mac, double_algs()} - | {compression, double_algs()} . --type simple_algs() :: list( atom() ) . --type double_algs() :: list( {client2server,simple_algs()} | {server2client,simple_algs()} ) - | simple_algs() . +-type shell_daemon_option() :: {shell, mod_fun_args() | 'shell_fun/1'() | 'shell_fun/2'() }. +-type 'shell_fun/1'() :: fun((User::string()) -> pid()) . +-type 'shell_fun/2'() :: fun((User::string(), PeerAddr::inet:ip_address()) -> pid()). --type options() :: #{socket_options := socket_options(), - internal_options := internal_options(), - option_key() => any() - }. +-type exec_daemon_option() :: {exec, 'exec_fun/1'() | 'exec_fun/2'() | 'exec_fun/3'() }. --type socket_options() :: proplists:proplist(). --type internal_options() :: #{option_key() => any()}. +-type 'exec_fun/1'() :: fun((Cmd::string()) -> exec_result()) . +-type 'exec_fun/2'() :: fun((Cmd::string(), User::string()) -> exec_result()) . +-type 'exec_fun/3'() :: fun((Cmd::string(), User::string(), ClientAddr::ip_port()) -> exec_result()) . +-type exec_result() :: {ok,Result::term()} | {error,Reason::term()} . --type option_key() :: atom(). +-type ssh_cli_daemon_option() :: {ssh_cli, mod_args() | no_cli }. +-type send_ext_info_daemon_option() :: {send_ext_info, boolean()} . + +-type authentication_daemon_options() :: + {system_dir, string()} + | {auth_method_kb_interactive_data, prompt_texts() } + | {user_passwords, [{UserName::string(),Pwd::string()}]} + | {password, string()} + | {pwdfun, pwdfun_2() | pwdfun_4()} . + +-type prompt_texts() :: + kb_int_tuple() + | kb_int_fun_3() + . + +-type kb_int_fun_3() :: fun((Peer::ip_port(), User::string(), Service::string()) -> kb_int_tuple()). +-type kb_int_tuple() :: {Name::string(), Instruction::string(), Prompt::string(), Echo::boolean()}. + +-type pwdfun_2() :: fun((User::string(), Password::string()) -> boolean()) . +-type pwdfun_4() :: fun((User::string(), + Password::string(), + PeerAddress::ip_port(), + State::any()) -> + boolean() | disconnect | {boolean(),NewState::any()} + ) . + +-type diffie_hellman_group_exchange_daemon_option() :: + {dh_gex_groups, [explicit_group()] | explicit_group_file() | ssh_moduli_file()} + | {dh_gex_limits, {Min::pos_integer(), Max::pos_integer()} } . + +-type explicit_group() :: {Size::pos_integer(),G::pos_integer(),P::pos_integer()} . +-type explicit_group_file() :: {file,string()} . +-type ssh_moduli_file() :: {ssh_moduli_file,string()}. + +-type negotiation_timeout_daemon_option() :: {negotiation_timeout, timeout()} . + +-type hardening_daemon_options() :: + {max_sessions, pos_integer()} + | {max_channels, pos_integer()} + | {parallel_login, boolean()} + | {minimal_remote_max_packet_size, pos_integer()}. + +-type callbacks_daemon_options() :: + {failfun, fun((User::string(), PeerAddress::inet:ip_address(), Reason::term()) -> _)} + | {connectfun, fun((User::string(), PeerAddress::inet:ip_address(), Method::string()) ->_)} . + +-type opaque_daemon_options() :: + {infofun, fun()} + | opaque_common_options(). + +-type ip_port() :: {inet:ip_address(), inet:port_number()} . + +-type mod_args() :: {Module::atom(), Args::list()} . +-type mod_fun_args() :: {Module::atom(), Function::atom(), Args::list()} . %% Records @@ -134,8 +366,7 @@ { role :: client | role(), peer :: undefined | - {inet:hostname(), - {inet:ip_address(),inet:port_number()}}, %% string version of peer address + {inet:hostname(),ip_port()}, %% string version of peer address local, %% Local sockname. Need this AFTER a socket is closed by i.e. a crash diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index fc564a359b..10fd4452bf 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -36,8 +36,6 @@ -define(DEFAULT_TIMEOUT, 50000). --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - %%%========================================================================= %%% API %%%========================================================================= diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index b90e571448..81c495a815 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -50,407 +50,43 @@ {ok, NewState :: term()} | {error, Reason :: term()}. -callback handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. + {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, +-callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), + {stop, ChannelId::ssh:channel_id(), State::term()}. --behaviour(gen_server). - %%% API -export([start/4, start/5, start_link/4, start_link/5, call/2, call/3, cast/2, reply/2, enter_loop/1]). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - -%% Internal application API --export([cache_create/0, cache_lookup/2, cache_update/2, - cache_delete/1, cache_delete/2, cache_foldl/3, - cache_info/2, cache_find/2, - get_print_info/1]). - --export([dbg_trace/3]). - --record(state, { - cm, - channel_cb, - channel_state, - channel_id, - close_sent = false - }). - %%==================================================================== %% API %%==================================================================== call(ChannelPid, Msg) -> - call(ChannelPid, Msg, infinity). + ssh_client_channel:call(ChannelPid, Msg). call(ChannelPid, Msg, TimeOute) -> - try gen_server:call(ChannelPid, Msg, TimeOute) of - Result -> - Result - catch - exit:{noproc, _} -> - {error, closed}; - exit:{normal, _} -> - {error, closed}; - exit:{shutdown, _} -> - {error, closed}; - exit:{{shutdown, _}, _} -> - {error, closed}; - exit:{timeout, _} -> - {error, timeout} - end. + ssh_client_channel:call(ChannelPid, Msg, TimeOute). cast(ChannelPid, Msg) -> - gen_server:cast(ChannelPid, Msg). - + ssh_client_channel:cast(ChannelPid, Msg). reply(From, Msg) -> - gen_server:reply(From, Msg). + ssh_client_channel:reply(From, Msg). -%%==================================================================== -%% Internal application API -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- start(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> - start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + ssh_client_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs). start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - Options = [{channel_cb, CallBack}, - {channel_id, ChannelId}, - {init_args, CbInitArgs}, - {cm, ConnectionManager}, - {exec, Exec}], - gen_server:start(?MODULE, [Options], []). + ssh_client_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> - start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + ssh_client_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs). start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - Options = [{channel_cb, CallBack}, - {channel_id, ChannelId}, - {init_args, CbInitArgs}, - {cm, ConnectionManager}, - {exec, Exec}], - gen_server:start_link(?MODULE, [Options], []). + ssh_client_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). enter_loop(State) -> - gen_server:enter_loop(?MODULE, [], State). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Options]) -> - Cb = proplists:get_value(channel_cb, Options), - ConnectionManager = proplists:get_value(cm, Options), - ChannelId = proplists:get_value(channel_id, Options), - process_flag(trap_exit, true), - try Cb:init(channel_cb_init_args(Options)) of - {ok, ChannelState} -> - State = #state{cm = ConnectionManager, - channel_cb = Cb, - channel_id = ChannelId, - channel_state = ChannelState}, - self() ! {ssh_channel_up, ChannelId, ConnectionManager}, - {ok, State}; - {ok, ChannelState, Timeout} -> - State = #state{cm = ConnectionManager, - channel_cb = Cb, - channel_id = ChannelId, - channel_state = ChannelState}, - self() ! {ssh_channel_up, ChannelId, ConnectionManager}, - {ok, State, Timeout}; - {stop, Why} -> - {stop, Why} - catch - _:Reason -> - {stop, Reason} - end. - -channel_cb_init_args(Options) -> - case proplists:get_value(exec, Options) of - undefined -> - proplists:get_value(init_args, Options); - Exec -> - proplists:get_value(init_args, Options) ++ [Exec] - end. - -%%-------------------------------------------------------------------- -%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%%-------------------------------------------------------------------- -handle_call(get_print_info, _From, State) -> - Reply = - {{State#state.cm, - State#state.channel_id}, - io_lib:format('CB=~p',[State#state.channel_cb]) - }, - {reply, Reply, State}; - -handle_call(Request, From, #state{channel_cb = Module, - channel_state = ChannelState} = State) -> - try Module:handle_call(Request, From, ChannelState) of - Result -> - handle_cb_result(Result, State) - catch - error:{undef, _} -> - {noreply, State} - end. - - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast(Msg, #state{channel_cb = Module, - channel_state = ChannelState} = State) -> - - try Module:handle_cast(Msg, ChannelState) of - Result -> - handle_cb_result(Result, State) - catch - error:{undef, _} -> - {noreply, State} - end. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info({ssh_cm, ConnectionManager, {closed, _ChannelId}}, - #state{cm = ConnectionManager, - close_sent = true} = State) -> - {stop, normal, State}; -handle_info({ssh_cm, ConnectionManager, {closed, ChannelId}}, - #state{cm = ConnectionManager, - close_sent = false} = State) -> - %% To be on the safe side, i.e. the manager has already been terminated. - (catch ssh_connection:close(ConnectionManager, ChannelId)), - {stop, normal, State#state{close_sent = true}}; - -handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager, - channel_cb = Module, - channel_state = ChannelState0} = State) -> - case Module:handle_ssh_msg(Msg, ChannelState0) of - {ok, ChannelState} -> - adjust_window(Msg), - {noreply, State#state{channel_state = ChannelState}}; - {ok, ChannelState, Timeout} -> - adjust_window(Msg), - {noreply, State#state{channel_state = ChannelState}, Timeout}; - {stop, ChannelId, ChannelState} -> - catch ssh_connection:close(ConnectionManager, ChannelId), - {stop, normal, State#state{close_sent = true, - channel_state = ChannelState}} - end; - -handle_info(Msg, #state{cm = ConnectionManager, channel_cb = Module, - channel_state = ChannelState0} = State) -> - case Module:handle_msg(Msg, ChannelState0) of - {ok, ChannelState} -> - {noreply, State#state{channel_state = ChannelState}}; - {ok, ChannelState, Timeout} -> - {noreply, State#state{channel_state = ChannelState}, Timeout}; - {stop, Reason, ChannelState} when is_atom(Reason)-> - {stop, Reason, State#state{close_sent = true, - channel_state = ChannelState}}; - {stop, ChannelId, ChannelState} -> - Reason = - case Msg of - {'EXIT', _Pid, shutdown} -> - shutdown; - _ -> - normal - end, - (catch ssh_connection:close(ConnectionManager, ChannelId)), - {stop, Reason, State#state{close_sent = true, - channel_state = ChannelState}} - end. - -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -terminate(Reason, #state{cm = ConnectionManager, - channel_id = ChannelId, - close_sent = false} = State) -> - catch ssh_connection:close(ConnectionManager, ChannelId), - terminate(Reason, State#state{close_sent = true}); -terminate(_, #state{channel_cb = Cb, channel_state = ChannelState}) -> - catch Cb:terminate(Cb, ChannelState), - ok. - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(OldVsn, #state{channel_cb = Module, - channel_state = ChannelState0} = State, Extra) -> - {ok, ChannelState} = Module:code_change(OldVsn, ChannelState0, Extra), - {ok, State#state{channel_state = ChannelState}}. - -%%==================================================================== -%% Internal application API -%%==================================================================== -cache_create() -> - ets:new(cm_tab, [set,{keypos, #channel.local_id}]). - -cache_lookup(Cache, Key) -> - case ets:lookup(Cache, Key) of - [Channel] -> - Channel; - [] -> - undefined - end. - -cache_update(Cache, #channel{local_id = Id} = Entry) when Id =/= undefined -> - ets:insert(Cache, Entry). - -cache_delete(Cache, Key) -> - ets:delete(Cache, Key). - -cache_delete(Cache) -> - ets:delete(Cache). - -cache_foldl(Fun, Acc, Cache) -> - ets:foldl(Fun, Acc, Cache). - -cache_info(num_entries, Cache) -> - proplists:get_value(size, ets:info(Cache)). - -cache_find(ChannelPid, Cache) -> - case ets:match_object(Cache, #channel{user = ChannelPid}) of - [] -> - undefined; - [Channel] -> - Channel - end. - -get_print_info(Pid) -> - call(Pid, get_print_info, 1000). - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -handle_cb_result({reply, Reply, ChannelState}, State) -> - {reply, Reply, State#state{channel_state = ChannelState}}; -handle_cb_result({reply, Reply, ChannelState, Timeout}, State) -> - {reply, Reply,State#state{channel_state = ChannelState}, Timeout}; -handle_cb_result({noreply, ChannelState}, State) -> - {noreply, State#state{channel_state = ChannelState}}; -handle_cb_result({noreply, ChannelState, Timeout}, State) -> - {noreply, State#state{channel_state = ChannelState}, Timeout}; -handle_cb_result({stop, Reason, Reply, ChannelState}, State) -> - {stop, Reason, Reply, State#state{channel_state = ChannelState}}; -handle_cb_result({stop, Reason, ChannelState}, State) -> - {stop, Reason, State#state{channel_state = ChannelState}}. - -adjust_window({ssh_cm, ConnectionManager, - {data, ChannelId, _, Data}}) -> - ssh_connection:adjust_window(ConnectionManager, ChannelId, size(Data)); -adjust_window(_) -> - ok. - - -%%%################################################################ -%%%# -%%%# Tracing -%%%# - -dbg_trace(points, _, _) -> [terminate, channels, channel_events]; - - -dbg_trace(flags, channels, A) -> [c] ++ dbg_trace(flags, terminate, A); -dbg_trace(on, channels, A) -> dbg:tp(?MODULE, init, 1, x), - dbg_trace(on, terminate, A); -dbg_trace(off, channels, A) -> dbg:ctpg(?MODULE, init, 1), - dbg_trace(off, terminate, A); -dbg_trace(format, channels, {call, {?MODULE,init, [[KVs]]}}) -> - ["Server Channel Starting:\n", - io_lib:format("Connection: ~p, ChannelId: ~p, CallBack: ~p\nCallBack init args = ~p", - [proplists:get_value(K,KVs) || K <- [cm, channel_id, channel_cb]] - ++ [channel_cb_init_args(KVs)]) - ]; -dbg_trace(format, channels, {return_from, {?MODULE,init,1}, {stop,Reason}}) -> - ["Server Channel Start FAILED!\n", - io_lib:format("Reason = ~p", [Reason]) - ]; -dbg_trace(format, channels, F) -> - dbg_trace(format, terminate, F); - - -dbg_trace(flags, terminate, _) -> [c]; -dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); -dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); -dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> - ["Server Channel Terminating:\n", - io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) - ]; - -dbg_trace(flags, channel_events, _) -> [c]; -dbg_trace(on, channel_events, _) -> dbg:tp(?MODULE, handle_call, 3, x), - dbg:tp(?MODULE, handle_cast, 2, x), - dbg:tp(?MODULE, handle_info, 2, x); -dbg_trace(off, channel_events, _) -> dbg:ctpg(?MODULE, handle_call, 3), - dbg:ctpg(?MODULE, handle_cast, 2), - dbg:ctpg(?MODULE, handle_info, 2); -dbg_trace(format, channel_events, {call, {?MODULE,handle_call, [Call,From,State]}}) -> - [hdr("is called", State), - io_lib:format("From: ~p~nCall: ~p~n", [From, Call]) - ]; -dbg_trace(format, channel_events, {return_from, {?MODULE,handle_call,3}, Ret}) -> - ["Server Channel call returned:\n", - io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) - ]; -dbg_trace(format, channel_events, {call, {?MODULE,handle_cast, [Cast,State]}}) -> - [hdr("got cast", State), - io_lib:format("Cast: ~p~n", [Cast]) - ]; -dbg_trace(format, channel_events, {return_from, {?MODULE,handle_cast,2}, Ret}) -> - ["Server Channel cast returned:\n", - io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) - ]; -dbg_trace(format, channel_events, {call, {?MODULE,handle_info, [Info,State]}}) -> - [hdr("got info", State), - io_lib:format("Info: ~p~n", [Info]) - ]; -dbg_trace(format, channel_events, {return_from, {?MODULE,handle_info,2}, Ret}) -> - ["Server Channel info returned:\n", - io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) - ]. - -hdr(Title, S) -> - io_lib:format("Server Channel (Id=~p, CB=~p) ~s:\n", [S#state.channel_id, S#state.channel_cb, Title]). - -?wr_record(state). - - + ssh_client_channel:enter_loop(State). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 26c7cb45aa..fcc1d3d59f 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -25,12 +25,12 @@ -module(ssh_cli). --behaviour(ssh_daemon_channel). +-behaviour(ssh_server_channel). -include("ssh.hrl"). -include("ssh_connect.hrl"). -%% ssh_channel callbacks +%% ssh_server_channel callbacks -export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). -export([dbg_trace/3]). @@ -47,23 +47,8 @@ }). %%==================================================================== -%% ssh_channel callbacks +%% ssh_server_channel callbacks %%==================================================================== --spec init(Args :: term()) -> - {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | - {stop, Reason :: term()} | ignore. - --spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | - term()), - State :: term()) -> - term(). - --spec handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. --spec handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, - State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), - State::term()}. %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl new file mode 100644 index 0000000000..f20007baaf --- /dev/null +++ b/lib/ssh/src/ssh_client_channel.erl @@ -0,0 +1,456 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_client_channel). + +-include("ssh.hrl"). +-include("ssh_connect.hrl"). + +-callback init(Args :: term()) -> + {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | + {stop, Reason :: term()} | ignore. +-callback handle_call(Request :: term(), From :: {pid(), Tag :: term()}, + State :: term()) -> + {reply, Reply :: term(), NewState :: term()} | + {reply, Reply :: term(), NewState :: term(), timeout() | hibernate} | + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate} | + {stop, Reason :: term(), Reply :: term(), NewState :: term()} | + {stop, Reason :: term(), NewState :: term()}. +-callback handle_cast(Request :: term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate} | + {stop, Reason :: term(), NewState :: term()}. + +-callback terminate(Reason :: (normal | shutdown | {shutdown, term()} | + term()), + State :: term()) -> + term(). +-callback code_change(OldVsn :: (term() | {down, term()}), State :: term(), + Extra :: term()) -> + {ok, NewState :: term()} | {error, Reason :: term()}. + +-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()}, + State::term()) -> {ok, State::term()} | + {stop, ChannelId::ssh:channel_id(), + State::term()}. +-behaviour(gen_server). + +%%% API +-export([start/4, start/5, start_link/4, start_link/5, call/2, call/3, + cast/2, reply/2, enter_loop/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%% Internal application API +-export([cache_create/0, cache_lookup/2, cache_update/2, + cache_delete/1, cache_delete/2, cache_foldl/3, + cache_info/2, cache_find/2, + get_print_info/1]). + +-export([dbg_trace/3]). + +-record(state, { + cm, + channel_cb, + channel_state, + channel_id, + close_sent = false + }). + +%%==================================================================== +%% API +%%==================================================================== + +call(ChannelPid, Msg) -> + call(ChannelPid, Msg, infinity). + +call(ChannelPid, Msg, TimeOute) -> + try gen_server:call(ChannelPid, Msg, TimeOute) of + Result -> + Result + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{shutdown, _} -> + {error, closed}; + exit:{{shutdown, _}, _} -> + {error, closed}; + exit:{timeout, _} -> + {error, timeout} + end. + +cast(ChannelPid, Msg) -> + gen_server:cast(ChannelPid, Msg). + + +reply(From, Msg) -> + gen_server:reply(From, Msg). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- +start(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> + start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + +start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> + Options = [{channel_cb, CallBack}, + {channel_id, ChannelId}, + {init_args, CbInitArgs}, + {cm, ConnectionManager}, + {exec, Exec}], + gen_server:start(?MODULE, [Options], []). + +start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> + start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + +start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> + Options = [{channel_cb, CallBack}, + {channel_id, ChannelId}, + {init_args, CbInitArgs}, + {cm, ConnectionManager}, + {exec, Exec}], + gen_server:start_link(?MODULE, [Options], []). + +enter_loop(State) -> + gen_server:enter_loop(?MODULE, [], State). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([Options]) -> + Cb = proplists:get_value(channel_cb, Options), + ConnectionManager = proplists:get_value(cm, Options), + ChannelId = proplists:get_value(channel_id, Options), + process_flag(trap_exit, true), + try Cb:init(channel_cb_init_args(Options)) of + {ok, ChannelState} -> + State = #state{cm = ConnectionManager, + channel_cb = Cb, + channel_id = ChannelId, + channel_state = ChannelState}, + self() ! {ssh_channel_up, ChannelId, ConnectionManager}, + {ok, State}; + {ok, ChannelState, Timeout} -> + State = #state{cm = ConnectionManager, + channel_cb = Cb, + channel_id = ChannelId, + channel_state = ChannelState}, + self() ! {ssh_channel_up, ChannelId, ConnectionManager}, + {ok, State, Timeout}; + {stop, Why} -> + {stop, Why} + catch + _:Reason -> + {stop, Reason} + end. + +channel_cb_init_args(Options) -> + case proplists:get_value(exec, Options) of + undefined -> + proplists:get_value(init_args, Options); + Exec -> + proplists:get_value(init_args, Options) ++ [Exec] + end. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call(get_print_info, _From, State) -> + Reply = + {{State#state.cm, + State#state.channel_id}, + io_lib:format('CB=~p',[State#state.channel_cb]) + }, + {reply, Reply, State}; + +handle_call(Request, From, #state{channel_cb = Module, + channel_state = ChannelState} = State) -> + try Module:handle_call(Request, From, ChannelState) of + Result -> + handle_cb_result(Result, State) + catch + error:{undef, _} -> + {noreply, State} + end. + + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast(Msg, #state{channel_cb = Module, + channel_state = ChannelState} = State) -> + + try Module:handle_cast(Msg, ChannelState) of + Result -> + handle_cb_result(Result, State) + catch + error:{undef, _} -> + {noreply, State} + end. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info({ssh_cm, ConnectionManager, {closed, _ChannelId}}, + #state{cm = ConnectionManager, + close_sent = true} = State) -> + {stop, normal, State}; +handle_info({ssh_cm, ConnectionManager, {closed, ChannelId}}, + #state{cm = ConnectionManager, + close_sent = false} = State) -> + %% To be on the safe side, i.e. the manager has already been terminated. + (catch ssh_connection:close(ConnectionManager, ChannelId)), + {stop, normal, State#state{close_sent = true}}; + +handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager, + channel_cb = Module, + channel_state = ChannelState0} = State) -> + case Module:handle_ssh_msg(Msg, ChannelState0) of + {ok, ChannelState} -> + adjust_window(Msg), + {noreply, State#state{channel_state = ChannelState}}; + {ok, ChannelState, Timeout} -> + adjust_window(Msg), + {noreply, State#state{channel_state = ChannelState}, Timeout}; + {stop, ChannelId, ChannelState} -> + catch ssh_connection:close(ConnectionManager, ChannelId), + {stop, normal, State#state{close_sent = true, + channel_state = ChannelState}} + end; + +handle_info(Msg, #state{cm = ConnectionManager, channel_cb = Module, + channel_state = ChannelState0} = State) -> + case Module:handle_msg(Msg, ChannelState0) of + {ok, ChannelState} -> + {noreply, State#state{channel_state = ChannelState}}; + {ok, ChannelState, Timeout} -> + {noreply, State#state{channel_state = ChannelState}, Timeout}; + {stop, Reason, ChannelState} when is_atom(Reason)-> + {stop, Reason, State#state{close_sent = true, + channel_state = ChannelState}}; + {stop, ChannelId, ChannelState} -> + Reason = + case Msg of + {'EXIT', _Pid, shutdown} -> + shutdown; + _ -> + normal + end, + (catch ssh_connection:close(ConnectionManager, ChannelId)), + {stop, Reason, State#state{close_sent = true, + channel_state = ChannelState}} + end. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(Reason, #state{cm = ConnectionManager, + channel_id = ChannelId, + close_sent = false} = State) -> + catch ssh_connection:close(ConnectionManager, ChannelId), + terminate(Reason, State#state{close_sent = true}); +terminate(_, #state{channel_cb = Cb, channel_state = ChannelState}) -> + catch Cb:terminate(Cb, ChannelState), + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(OldVsn, #state{channel_cb = Module, + channel_state = ChannelState0} = State, Extra) -> + {ok, ChannelState} = Module:code_change(OldVsn, ChannelState0, Extra), + {ok, State#state{channel_state = ChannelState}}. + +%%==================================================================== +%% Internal application API +%%==================================================================== +cache_create() -> + ets:new(cm_tab, [set,{keypos, #channel.local_id}]). + +cache_lookup(Cache, Key) -> + case ets:lookup(Cache, Key) of + [Channel] -> + Channel; + [] -> + undefined + end. + +cache_update(Cache, #channel{local_id = Id} = Entry) when Id =/= undefined -> + ets:insert(Cache, Entry). + +cache_delete(Cache, Key) -> + ets:delete(Cache, Key). + +cache_delete(Cache) -> + ets:delete(Cache). + +cache_foldl(Fun, Acc, Cache) -> + ets:foldl(Fun, Acc, Cache). + +cache_info(num_entries, Cache) -> + proplists:get_value(size, ets:info(Cache)). + +cache_find(ChannelPid, Cache) -> + case ets:match_object(Cache, #channel{user = ChannelPid}) of + [] -> + undefined; + [Channel] -> + Channel + end. + +get_print_info(Pid) -> + call(Pid, get_print_info, 1000). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +handle_cb_result({reply, Reply, ChannelState}, State) -> + {reply, Reply, State#state{channel_state = ChannelState}}; +handle_cb_result({reply, Reply, ChannelState, Timeout}, State) -> + {reply, Reply,State#state{channel_state = ChannelState}, Timeout}; +handle_cb_result({noreply, ChannelState}, State) -> + {noreply, State#state{channel_state = ChannelState}}; +handle_cb_result({noreply, ChannelState, Timeout}, State) -> + {noreply, State#state{channel_state = ChannelState}, Timeout}; +handle_cb_result({stop, Reason, Reply, ChannelState}, State) -> + {stop, Reason, Reply, State#state{channel_state = ChannelState}}; +handle_cb_result({stop, Reason, ChannelState}, State) -> + {stop, Reason, State#state{channel_state = ChannelState}}. + +adjust_window({ssh_cm, ConnectionManager, + {data, ChannelId, _, Data}}) -> + ssh_connection:adjust_window(ConnectionManager, ChannelId, size(Data)); +adjust_window(_) -> + ok. + + +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +dbg_trace(points, _, _) -> [terminate, channels, channel_events]; + + +dbg_trace(flags, channels, A) -> [c] ++ dbg_trace(flags, terminate, A); +dbg_trace(on, channels, A) -> dbg:tp(?MODULE, init, 1, x), + dbg_trace(on, terminate, A); +dbg_trace(off, channels, A) -> dbg:ctpg(?MODULE, init, 1), + dbg_trace(off, terminate, A); +dbg_trace(format, channels, {call, {?MODULE,init, [[KVs]]}}) -> + ["Server Channel Starting:\n", + io_lib:format("Connection: ~p, ChannelId: ~p, CallBack: ~p\nCallBack init args = ~p", + [proplists:get_value(K,KVs) || K <- [cm, channel_id, channel_cb]] + ++ [channel_cb_init_args(KVs)]) + ]; +dbg_trace(format, channels, {return_from, {?MODULE,init,1}, {stop,Reason}}) -> + ["Server Channel Start FAILED!\n", + io_lib:format("Reason = ~p", [Reason]) + ]; +dbg_trace(format, channels, F) -> + dbg_trace(format, terminate, F); + + +dbg_trace(flags, terminate, _) -> [c]; +dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x); +dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2); +dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> + ["Server Channel Terminating:\n", + io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) + ]; + +dbg_trace(flags, channel_events, _) -> [c]; +dbg_trace(on, channel_events, _) -> dbg:tp(?MODULE, handle_call, 3, x), + dbg:tp(?MODULE, handle_cast, 2, x), + dbg:tp(?MODULE, handle_info, 2, x); +dbg_trace(off, channel_events, _) -> dbg:ctpg(?MODULE, handle_call, 3), + dbg:ctpg(?MODULE, handle_cast, 2), + dbg:ctpg(?MODULE, handle_info, 2); +dbg_trace(format, channel_events, {call, {?MODULE,handle_call, [Call,From,State]}}) -> + [hdr("is called", State), + io_lib:format("From: ~p~nCall: ~p~n", [From, Call]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_call,3}, Ret}) -> + ["Server Channel call returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]; +dbg_trace(format, channel_events, {call, {?MODULE,handle_cast, [Cast,State]}}) -> + [hdr("got cast", State), + io_lib:format("Cast: ~p~n", [Cast]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_cast,2}, Ret}) -> + ["Server Channel cast returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]; +dbg_trace(format, channel_events, {call, {?MODULE,handle_info, [Info,State]}}) -> + [hdr("got info", State), + io_lib:format("Info: ~p~n", [Info]) + ]; +dbg_trace(format, channel_events, {return_from, {?MODULE,handle_info,2}, Ret}) -> + ["Server Channel info returned:\n", + io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) + ]. + +hdr(Title, S) -> + io_lib:format("Server Channel (Id=~p, CB=~p) ~s:\n", [S#state.channel_id, S#state.channel_cb, Title]). + +?wr_record(state). + + diff --git a/lib/ssh/src/ssh_client_key_api.erl b/lib/ssh/src/ssh_client_key_api.erl index 6e994ff292..d0d8ab25d6 100644 --- a/lib/ssh/src/ssh_client_key_api.erl +++ b/lib/ssh/src/ssh_client_key_api.erl @@ -23,26 +23,25 @@ -include_lib("public_key/include/public_key.hrl"). -include("ssh.hrl"). --export_type([algorithm/0]). - --type algorithm() :: 'ssh-rsa' - | 'ssh-dss' - | 'ecdsa-sha2-nistp256' - | 'ecdsa-sha2-nistp384' - | 'ecdsa-sha2-nistp521' - . - --callback is_host_key(PublicKey :: public_key:public_key(), - Host :: string(), - Algorithm :: algorithm(), - ConnectOptions :: proplists:proplist()) -> +-export_type([client_key_cb_options/0]). + +-type client_key_cb_options() :: [{key_cb_private,term()} | ssh:client_option()]. + +-callback is_host_key(Key :: public_key:public_key(), + Host :: string(), + Algorithm :: ssh:pubkey_alg(), + Options :: client_key_cb_options() + ) -> boolean(). --callback user_key(Algorithm :: algorithm(), - ConnectOptions :: proplists:proplist()) -> - {ok, PrivateKey::public_key:private_key()} | {error, term()}. +-callback user_key(Algorithm :: ssh:pubkey_alg(), + Options :: client_key_cb_options() + ) -> + {ok, PrivateKey :: public_key:private_key()} | {error, string()}. --callback add_host_key(Host :: string(), PublicKey :: public_key:public_key(), - Options :: proplists:proplist()) -> +-callback add_host_key(Host :: string(), + PublicKey :: public_key:public_key(), + Options :: client_key_cb_options() + ) -> ok | {error, Error::term()}. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index a8de5f9a2f..3c61638285 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -22,10 +22,6 @@ %%% Description : SSH connection protocol --type channel_id() :: pos_integer(). --type connection_ref() :: pid(). - - -define(DEFAULT_PACKET_SIZE, 65536). -define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE). diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 2b8780a991..ed03b4e2ed 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -64,29 +64,32 @@ bound_channel/3, encode_ip/1 ]). +-type connection_ref() :: ssh:connection_ref(). +-type channel_id() :: ssh:channel_id(). + %%-------------------------------------------------------------------- %%% API %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec session_channel(connection_ref(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. --spec session_channel(connection_ref(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. - %% Description: Opens a channel for a ssh session. A session is a %% remote execution of a program. The program may be a shell, an %% application, a system command, or some built-in subsystem. %% -------------------------------------------------------------------- +-spec session_channel(connection_ref(), timeout()) -> + {ok, channel_id()} | {error, timeout | closed}. + session_channel(ConnectionHandler, Timeout) -> - session_channel(ConnectionHandler, - ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, - Timeout). + session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). -session_channel(ConnectionHandler, InitialWindowSize, - MaxPacketSize, Timeout) -> +-spec session_channel(connection_ref(), integer(), integer(), timeout()) -> + {ok, channel_id()} | {error, timeout | closed}. + +session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>, - InitialWindowSize, - MaxPacketSize, Timeout) of + InitialWindowSize, + MaxPacketSize, Timeout) of {open, Channel} -> {ok, Channel}; Error -> @@ -94,55 +97,63 @@ session_channel(ConnectionHandler, InitialWindowSize, end. %%-------------------------------------------------------------------- --spec exec(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. - %% 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}. + exec(ConnectionHandler, ChannelId, Command, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec", true, [?string(Command)], TimeOut). %%-------------------------------------------------------------------- --spec shell(connection_ref(), channel_id()) -> _. - %% Description: Will request that the user's default shell (typically %% defined in /etc/passwd in UNIX systems) be started at the other %% end. %%-------------------------------------------------------------------- +-spec shell(connection_ref(), channel_id()) -> + ok | success | failure | {error, timeout}. + shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "shell", false, <<>>, 0). %%-------------------------------------------------------------------- --spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. %% %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- +-spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> + success | failure | {error, timeout | closed}. + subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "subsystem", true, [?string(SubSystem)], TimeOut). %%-------------------------------------------------------------------- --spec send(connection_ref(), channel_id(), iodata()) -> - ok | {error, closed}. --spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> - ok | {error, timeout} | {error, closed}. --spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> - ok | {error, timeout} | {error, closed}. -%% -%% %% Description: Sends channel data. %%-------------------------------------------------------------------- +-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}. + send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> send(ConnectionHandler, ChannelId, 0, Data, TimeOut); + send(ConnectionHandler, ChannelId, Data, infinity) -> send(ConnectionHandler, ChannelId, 0, Data, infinity); + send(ConnectionHandler, ChannelId, Type, Data) -> send(ConnectionHandler, ChannelId, Type, Data, infinity). + + +-spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> + ok | {error, timeout | closed}. + send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). @@ -156,7 +167,7 @@ send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- --spec adjust_window(connection_ref(), channel_id(), integer()) -> ok | {error, closed}. +-spec adjust_window(connection_ref(), channel_id(), integer()) -> ok. %% %% %% Description: Adjusts the ssh flowcontrol window. @@ -198,17 +209,18 @@ reply_request(_,false, _, _) -> ok. %%-------------------------------------------------------------------- --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> - success | failiure | {error, closed}. --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> - success | failiure | {error, timeout} | {error, closed}. - -%% -%% %% Description: Sends a ssh connection protocol pty_req. %%-------------------------------------------------------------------- +-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> + success | failure | {error, timeout}. + 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}. + ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> TermData = backwards_compatible(Options0, []), % FIXME {Width, PixWidth} = pty_default_dimensions(width, TermData), @@ -259,7 +271,7 @@ channel_data(ChannelId, DataType, Data, Connection, From) when is_list(Data)-> channel_data(ChannelId, DataType, Data, #connection{channel_cache = Cache} = Connection, From) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of + case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id, sent_close = false} = Channel0 -> {SendList, Channel} = update_send_window(Channel0#channel{flow_control = From}, DataType, @@ -291,9 +303,9 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, #connection{channel_cache = Cache} = Connection0, _) -> #channel{remote_id = undefined} = Channel = - ssh_channel:cache_lookup(Cache, ChannelId), + ssh_client_channel:cache_lookup(Cache, ChannelId), - ssh_channel:cache_update(Cache, Channel#channel{ + ssh_client_channel:cache_update(Cache, Channel#channel{ remote_id = RemoteId, recv_packet_size = max(32768, % rfc4254/5.2 min(PacketSz, Channel#channel.recv_packet_size) @@ -307,8 +319,8 @@ handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId, description = Descr, lang = Lang}, #connection{channel_cache = Cache} = Connection0, _) -> - Channel = ssh_channel:cache_lookup(Cache, ChannelId), - ssh_channel:cache_delete(Cache, ChannelId), + Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), + ssh_client_channel:cache_delete(Cache, ChannelId), reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang}); handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _) -> @@ -323,10 +335,10 @@ handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _) - handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, #connection{channel_cache = Cache} = Connection0, _) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of + case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{sent_close = Closed, remote_id = RemoteId, flow_control = FlowControl} = Channel -> - ssh_channel:cache_delete(Cache, ChannelId), + ssh_client_channel:cache_delete(Cache, ChannelId), {CloseMsg, Connection} = reply_msg(Channel, Connection0, {closed, ChannelId}), ConnReplyMsgs = @@ -367,7 +379,7 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, bytes_to_add = Add}, #connection{channel_cache = Cache} = Connection, _) -> #channel{send_window_size = Size, remote_id = RemoteId} = - Channel0 = ssh_channel:cache_lookup(Cache, ChannelId), + Channel0 = ssh_client_channel:cache_lookup(Cache, ChannelId), {SendList, Channel} = %% TODO: Datatype 0 ? update_send_window(Channel0#channel{send_window_size = Size + Add}, @@ -443,7 +455,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, ?BOOLEAN(_Core), ?DEC_BIN(Err, _ErrLen), ?DEC_BIN(Lang, _LangLen)>> = Data, - Channel = ssh_channel:cache_lookup(Cache, ChannelId), + Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), RemoteId = Channel#channel.remote_id, {Reply, Connection} = reply_msg(Channel, Connection0, {exit_signal, ChannelId, @@ -488,7 +500,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, <<?DEC_BIN(SsName,_SsLen)>> = Data, #channel{remote_id = RemoteId} = Channel0 = - ssh_channel:cache_lookup(Cache, ChannelId), + ssh_client_channel:cache_lookup(Cache, ChannelId), ReplyMsg = {subsystem, ChannelId, WantReply, binary_to_list(SsName)}, @@ -496,7 +508,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, {ok, Pid} = start_subsystem(SsName, Connection, Channel0, ReplyMsg), erlang:monitor(process, Pid), Channel = Channel0#channel{user = Pid}, - ssh_channel:cache_update(Cache, Channel), + ssh_client_channel:cache_update(Cache, Channel), Reply = {connection_reply, channel_success_msg(RemoteId)}, {[Reply], Connection} @@ -576,7 +588,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, want_reply = WantReply}, #connection{channel_cache = Cache} = Connection, _) -> if WantReply == true -> - case ssh_channel:cache_lookup(Cache, ChannelId) of + case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = RemoteId} -> FailMsg = channel_failure_msg(RemoteId), {[{connection_reply, FailMsg}], Connection}; @@ -619,14 +631,14 @@ handle_msg(#ssh_msg_disconnect{code = Code, %%% handle_stop(#connection{channel_cache = Cache} = Connection0) -> {Connection, Replies} = - ssh_channel:cache_foldl( + ssh_client_channel:cache_foldl( fun(Channel, {Connection1, Acc}) -> {Reply, Connection2} = reply_msg(Channel, Connection1, {closed, Channel#channel.local_id}), {Connection2, Reply ++ Acc} end, {Connection0, []}, Cache), - ssh_channel:cache_delete(Cache), + ssh_client_channel:cache_delete(Cache), {Replies, Connection}. %%%---------------------------------------------------------------- @@ -767,7 +779,7 @@ setup_session(#connection{channel_cache = Cache, send_buf = queue:new(), remote_id = RemoteId }, - ssh_channel:cache_update(Cache, Channel), + ssh_client_channel:cache_update(Cache, Channel), OpenConfMsg = channel_open_confirmation_msg(RemoteId, NewChannelID, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE), @@ -810,14 +822,14 @@ start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), case max_num_channels_not_exceeded(ChannelSup, Opts) of true -> - ssh_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); + ssh_server_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec); false -> throw(max_num_channels_exceeded) end. max_num_channels_not_exceeded(ChannelSup, Opts) -> MaxNumChannels = ?GET_OPT(max_channels, Opts), - NumChannels = length([x || {_,_,worker,[ssh_channel]} <- + NumChannels = length([x || {_,_,worker,[ssh_server_channel]} <- supervisor:which_children(ChannelSup)]), %% Note that NumChannels is BEFORE starting a new one NumChannels < MaxNumChannels. @@ -856,7 +868,7 @@ update_send_window(#channel{send_buf = SendBuffer} = Channel, DataType, Data, do_update_send_window(Channel0, Cache) -> {SendMsgs, Channel} = get_window(Channel0, []), - ssh_channel:cache_update(Cache, Channel), + ssh_client_channel:cache_update(Cache, Channel), {SendMsgs, Channel}. get_window(#channel{send_window_size = 0 @@ -907,13 +919,13 @@ flow_control(Channel, Cache) -> flow_control([window_adjusted], Channel, Cache). flow_control([], Channel, Cache) -> - ssh_channel:cache_update(Cache, Channel), + ssh_client_channel:cache_update(Cache, Channel), []; flow_control([_|_], #channel{flow_control = From, send_buf = Buffer} = Channel, Cache) when From =/= undefined -> case queue:is_empty(Buffer) of true -> - ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), + ssh_client_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), [{flow_control, Cache, Channel, From, ok}]; false -> [] @@ -1157,14 +1169,14 @@ backwards_compatible([Value| Rest], Acc) -> handle_cli_msg(C0, ChId, Reply0) -> Cache = C0#connection.channel_cache, - Ch0 = ssh_channel:cache_lookup(Cache, ChId), + Ch0 = ssh_client_channel:cache_lookup(Cache, ChId), case Ch0#channel.user of undefined -> case (catch start_cli(C0, ChId)) of {ok, Pid} -> erlang:monitor(process, Pid), Ch = Ch0#channel{user = Pid}, - ssh_channel:cache_update(Cache, Ch), + ssh_client_channel:cache_update(Cache, Ch), reply_msg(Ch, C0, Reply0); _Other -> Reply = {connection_reply, channel_failure_msg(Ch0#channel.remote_id)}, @@ -1182,10 +1194,10 @@ handle_cli_msg(C0, ChId, Reply0) -> %%% channel_data_reply_msg(ChannelId, Connection, DataType, Data) -> - case ssh_channel:cache_lookup(Connection#connection.channel_cache, ChannelId) of + case ssh_client_channel:cache_lookup(Connection#connection.channel_cache, ChannelId) of #channel{recv_window_size = Size} = Channel -> WantedSize = Size - size(Data), - ssh_channel:cache_update(Connection#connection.channel_cache, + ssh_client_channel:cache_update(Connection#connection.channel_cache, Channel#channel{recv_window_size = WantedSize}), reply_msg(Channel, Connection, {data, ChannelId, DataType, Data}); undefined -> @@ -1194,7 +1206,7 @@ channel_data_reply_msg(ChannelId, Connection, DataType, Data) -> reply_msg(ChId, C, Reply) when is_integer(ChId) -> - reply_msg(ssh_channel:cache_lookup(C#connection.channel_cache, ChId), C, Reply); + reply_msg(ssh_client_channel:cache_lookup(C#connection.channel_cache, ChId), C, Reply); reply_msg(Channel, Connection, {open, _} = Reply) -> request_reply_or_data(Channel, Connection, Reply); diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 033f11f4a1..57641cf74c 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -60,6 +60,9 @@ get_print_info/1 ]). +-type connection_ref() :: ssh:connection_ref(). +-type channel_id() :: ssh:channel_id(). + %%% Behaviour callbacks -export([init/1, callback_mode/0, handle_event/4, terminate/3, format_status/2, code_change/4]). @@ -88,8 +91,8 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec start_link(role(), - inet:socket(), - ssh_options:options() + gen_tcp:socket(), + internal_options() ) -> {ok, pid()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . start_link(Role, Socket, Options) -> @@ -118,8 +121,8 @@ stop(ConnectionHandler)-> %%-------------------------------------------------------------------- -spec start_connection(role(), - inet:socket(), - ssh_options:options(), + gen_tcp:socket(), + internal_options(), timeout() ) -> {ok, connection_ref()} | {error, term()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -359,7 +362,7 @@ alg(ConnectionHandler) -> | undefined, % ex: tcp_closed ssh_params :: #ssh{} | undefined, - socket :: inet:socket() + socket :: gen_tcp:socket() | undefined, decrypted_data_buffer = <<>> :: binary() | undefined, @@ -370,7 +373,6 @@ alg(ConnectionHandler) -> | undefined, last_size_rekey = 0 :: non_neg_integer(), event_queue = [] :: list(), -% opts :: ssh_options:options(), inet_initial_recbuf_size :: pos_integer() | undefined }). @@ -380,8 +382,8 @@ alg(ConnectionHandler) -> %%==================================================================== %%-------------------------------------------------------------------- -spec init_connection_handler(role(), - inet:socket(), - ssh_options:options() + gen_tcp:socket(), + internal_options() ) -> no_return(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . init_connection_handler(Role, Socket, Opts) -> @@ -412,7 +414,7 @@ init([Role,Socket,Opts]) -> case inet:peername(Socket) of {ok, PeerAddr} -> {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), - C = #connection{channel_cache = ssh_channel:cache_create(), + C = #connection{channel_cache = ssh_client_channel:cache_create(), channel_id_seed = 0, port_bindings = [], requests = [], @@ -1107,13 +1109,13 @@ handle_event(cast, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTED(StateName) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_window_pending = Pending, recv_packet_size = PktSize} = Channel when (WinSize-Bytes) >= 2*PktSize -> %% The peer can send at least two more *full* packet, no hurry. - ssh_channel:cache_update(cache(D), + ssh_client_channel:cache_update(cache(D), Channel#channel{recv_window_pending = Pending + Bytes}), keep_state_and_data; @@ -1121,7 +1123,7 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTE recv_window_pending = Pending, remote_id = Id} = Channel -> %% Now we have to update the window - we can't receive so many more pkts - ssh_channel:cache_update(cache(D), + ssh_client_channel:cache_update(cache(D), Channel#channel{recv_window_size = WinSize + Bytes + Pending, recv_window_pending = 0}), @@ -1133,7 +1135,7 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTE end; handle_event(cast, {reply_request,success,ChannelId}, StateName, D) when ?CONNECTED(StateName) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = RemoteId} -> Msg = ssh_connection:channel_success_msg(RemoteId), update_inet_buffers(D#data.socket), @@ -1176,7 +1178,7 @@ handle_event({call,From}, {connection_info, Options}, _, D) -> {keep_state_and_data, [{reply,From,Info}]}; handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{} = Channel -> Info = fold_keys(Options, fun chann_info/2, Channel), {keep_state_and_data, [{reply,From,Info}]}; @@ -1186,14 +1188,14 @@ handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) -> handle_event({call,From}, {info, all}, _, D) -> - Result = ssh_channel:cache_foldl(fun(Channel, Acc) -> + Result = ssh_client_channel:cache_foldl(fun(Channel, Acc) -> [Channel | Acc] end, [], cache(D)), {keep_state_and_data, [{reply, From, {ok,Result}}]}; handle_event({call,From}, {info, ChannelPid}, _, D) -> - Result = ssh_channel:cache_foldl( + Result = ssh_client_channel:cache_foldl( fun(Channel, Acc) when Channel#channel.user == ChannelPid -> [Channel | Acc]; (_, Acc) -> @@ -1239,7 +1241,7 @@ handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) handle_event({call,From}, {eof, ChannelId}, StateName, D0) when ?CONNECTED(StateName) -> - case ssh_channel:cache_lookup(cache(D0), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id, sent_close = false} -> D = send_msg(ssh_connection:channel_eof_msg(Id), D0), {keep_state, D, [{reply,From,ok}]}; @@ -1257,7 +1259,7 @@ handle_event({call,From}, InitialWindowSize, MaxPacketSize, Data), D1), - ssh_channel:cache_update(cache(D2), + ssh_client_channel:cache_update(cache(D2), #channel{type = Type, sys = "none", user = ChannelPid, @@ -1272,7 +1274,7 @@ handle_event({call,From}, handle_event({call,From}, {send_window, ChannelId}, StateName, D) when ?CONNECTED(StateName) -> - Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of + Reply = case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{send_window_size = WinSize, send_packet_size = Packsize} -> {ok, {WinSize, Packsize}}; @@ -1283,7 +1285,7 @@ handle_event({call,From}, {send_window, ChannelId}, StateName, D) handle_event({call,From}, {recv_window, ChannelId}, StateName, D) when ?CONNECTED(StateName) -> - Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of + Reply = case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_packet_size = Packsize} -> {ok, {WinSize, Packsize}}; @@ -1294,10 +1296,10 @@ handle_event({call,From}, {recv_window, ChannelId}, StateName, D) handle_event({call,From}, {close, ChannelId}, StateName, D0) when ?CONNECTED(StateName) -> - case ssh_channel:cache_lookup(cache(D0), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id} = Channel -> D1 = send_msg(ssh_connection:channel_close_msg(Id), D0), - ssh_channel:cache_update(cache(D1), Channel#channel{sent_close = true}), + ssh_client_channel:cache_update(cache(D1), Channel#channel{sent_close = true}), {keep_state, cache_request_idle_timer_check(D1), [{reply,From,ok}]}; undefined -> {keep_state_and_data, [{reply,From,ok}]} @@ -1535,7 +1537,6 @@ terminate(shutdown, _StateName, D0) -> D = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, description = "Terminated (shutdown) by supervisor"}, D0), - stop_subsystem(D), close_transport(D); terminate(kill, _StateName, D) -> @@ -1858,7 +1859,7 @@ is_usable_user_pubkey(A, Ssh) -> %%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = Id, sent_close = false} = Channel -> update_sys(cache(D), Channel, Type, ChannelPid), @@ -1873,7 +1874,7 @@ handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> end. handle_request(ChannelId, Type, Data, WantReply, From, D) -> - case ssh_channel:cache_lookup(cache(D), ChannelId) of + case ssh_client_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = Id, sent_close = false} -> send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data), @@ -1889,10 +1890,10 @@ handle_request(ChannelId, Type, Data, WantReply, From, D) -> %%%---------------------------------------------------------------- handle_channel_down(ChannelPid, D) -> Cache = cache(D), - ssh_channel:cache_foldl( + ssh_client_channel:cache_foldl( fun(#channel{user=U, local_id=Id}, Acc) when U == ChannelPid -> - ssh_channel:cache_delete(Cache, Id), + ssh_client_channel:cache_delete(Cache, Id), Acc; (_,Acc) -> Acc @@ -1901,7 +1902,7 @@ handle_channel_down(ChannelPid, D) -> update_sys(Cache, Channel, Type, ChannelPid) -> - ssh_channel:cache_update(Cache, + ssh_client_channel:cache_update(Cache, Channel#channel{sys = Type, user = ChannelPid}). add_request(false, _ChannelId, _From, State) -> @@ -1978,7 +1979,7 @@ conn_info(sockname, #data{ssh_params=S}) -> S#ssh.local; %% dbg options ( = not documented): conn_info(socket, D) -> D#data.socket; conn_info(chan_ids, D) -> - ssh_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) -> + ssh_client_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) -> [Id | Acc] end, [], cache(D)). @@ -2069,7 +2070,7 @@ get_repl({channel_data,Pid,Data}, Acc) -> get_repl({channel_request_reply,From,Data}, {CallRepls,S}) -> {[{reply,From,Data}|CallRepls], S}; get_repl({flow_control,Cache,Channel,From,Msg}, {CallRepls,S}) -> - ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), + ssh_client_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), {[{reply,From,Msg}|CallRepls], S}; get_repl({flow_control,From,Msg}, {CallRepls,S}) -> {[{reply,From,Msg}|CallRepls], S}; @@ -2145,7 +2146,7 @@ cache_init_idle_timer(D) -> cache_check_set_idle_timer(D = #data{idle_timer_ref = undefined, idle_timer_value = IdleTime}) -> %% No timer set - shall we set one? - case ssh_channel:cache_info(num_entries, cache(D)) of + case ssh_client_channel:cache_info(num_entries, cache(D)) of 0 when IdleTime == infinity -> %% No. Meaningless to set a timer that fires in an infinite time... D; diff --git a/lib/ssh/src/ssh_daemon_channel.erl b/lib/ssh/src/ssh_daemon_channel.erl index 6ca93eff44..fdb6c10971 100644 --- a/lib/ssh/src/ssh_daemon_channel.erl +++ b/lib/ssh/src/ssh_daemon_channel.erl @@ -25,7 +25,7 @@ -module(ssh_daemon_channel). -%% API to special server side channel that can be pluged into the erlang ssh daemeon +%% API to server side channel that can be pluged into the erlang ssh daemeon -callback init(Args :: term()) -> {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | {stop, Reason :: term()} | ignore. @@ -36,34 +36,20 @@ term(). -callback handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, + {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. +-callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), + {stop, ChannelId::ssh:channel_id(), State::term()}. -%%% API --export([start/4, start/5, start_link/4, start_link/5, enter_loop/1]). - -%% gen_server callbacks --export([init/1, terminate/2]). - -start(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> - ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). - -start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). - -start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> - ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). +%%% Internal API +-export([start_link/5, + get_print_info/1 + ]). start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> - ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). + ssh_server_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). -enter_loop(State) -> - ssh_channel:enter_loop(State). -init(Args) -> - ssh_channel:init(Args). -terminate(Reason, State) -> - ssh_channel:terminate(Reason, State). +get_print_info(Pid) -> + ssh_server_channel:get_print_info(Pid). diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 33792da38f..9cab2fe0bd 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -45,27 +45,6 @@ %%% API -%%% client --spec add_host_key(string(), - public_key:public_key(), - proplists:proplist()) -> ok | {error,term()}. - --spec is_host_key(public_key:public_key(), - string(), - ssh_client_key_api:algorithm(), - proplists:proplist()) -> boolean(). - --spec user_key(ssh_client_key_api:algorithm(), - proplists:proplist()) -> {ok, public_key:private_key()} | {error,term()}. - -%%% server --spec host_key(ssh_server_key_api:algorithm(), - proplists:proplist()) -> {ok, public_key:private_key()} | {error,term()}. - --spec is_auth_key(public_key:public_key(), - string(), proplists:proplist()) -> boolean(). - - %% Used by server host_key(Algorithm, Opts) -> File = file_name(system, file_base_name(Algorithm), Opts), diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl index d464def6fa..ee244f1432 100644 --- a/lib/ssh/src/ssh_info.erl +++ b/lib/ssh/src/ssh_info.erl @@ -140,15 +140,15 @@ print_system_sup({{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, Pid, superv -print_channels({{server,ssh_channel_sup,_,_},Pid,supervisor,[ssh_channel_sup]}) when is_pid(Pid) -> +print_channels({{server,ssh_server_channel_sup,_,_},Pid,supervisor,[ssh_server_channel_sup]}) when is_pid(Pid) -> Children = supervisor:which_children(Pid), - ChannelPids = [P || {R,P,worker,[ssh_channel]} <- Children, + ChannelPids = [P || {R,P,worker,[ssh_server_channel]} <- Children, is_pid(P), is_reference(R)], case ChannelPids of [] -> io_lib:format(?INDENT?INDENT"No channels~n",[]); [Ch1Pid|_] -> - {{ConnManager,_}, _Str} = ssh_channel:get_print_info(Ch1Pid), + {{ConnManager,_}, _Str} = ssh_server_channel:get_print_info(Ch1Pid), {{_,Remote},_} = ssh_connection_handler:get_print_info(ConnManager), [io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p~n",[fmt_host_port(Remote),ConnManager]), lists:map(fun print_ch/1, ChannelPids) @@ -159,7 +159,7 @@ print_channels({{server,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_s print_ch(Pid) -> try - {{ConnManager,ChannelID}, Str} = ssh_channel:get_print_info(Pid), + {{ConnManager,ChannelID}, Str} = ssh_server_channel:get_print_info(Pid), {_LocalRemote,StrM} = ssh_connection_handler:get_print_info(ConnManager), io_lib:format(?INDENT?INDENT?INDENT"ch ~p ~p: ~s ~s~n",[ChannelID, Pid, StrM, Str]) catch diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index c05293d1ae..4dd9082250 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -32,7 +32,7 @@ handle_options/2 ]). --export_type([options/0 +-export_type([private_options/0 ]). %%%================================================================ @@ -47,16 +47,23 @@ default => any() }. +-type option_key() :: atom(). + -type option_declarations() :: #{ {option_key(),def} := option_declaration() }. -type error() :: {error,{eoptions,any()}} . +-type private_options() :: #{socket_options := socket_options(), + internal_options := internal_options(), + option_key() => any() + }. + %%%================================================================ %%% %%% Get an option %%% --spec get_value(option_class(), option_key(), options(), +-spec get_value(option_class(), option_key(), private_options(), atom(), non_neg_integer()) -> any() | no_return(). get_value(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> @@ -69,7 +76,7 @@ get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). --spec get_value(option_class(), option_key(), options(), fun(() -> any()), +-spec get_value(option_class(), option_key(), private_options(), fun(() -> any()), atom(), non_neg_integer()) -> any() | no_return(). get_value(socket_options, Key, Opts, DefFun, _CallerMod, _CallerLine) when is_map(Opts) -> @@ -91,8 +98,8 @@ get_value(Class, Key, Opts, _DefFun, _CallerMod, _CallerLine) -> %%% Put an option %%% --spec put_value(option_class(), option_in(), options(), - atom(), non_neg_integer()) -> options(). +-spec put_value(option_class(), option_in(), private_options(), + atom(), non_neg_integer()) -> private_options(). put_value(user_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> put_user_value(KeyVal, Opts); @@ -131,8 +138,8 @@ put_socket_value(A, SockOpts) when is_atom(A) -> %%% Delete an option %%% --spec delete_key(option_class(), option_key(), options(), - atom(), non_neg_integer()) -> options(). +-spec delete_key(option_class(), option_key(), private_options(), + atom(), non_neg_integer()) -> private_options(). delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> InternalOpts = maps:get(internal_options,Opts), @@ -144,9 +151,7 @@ delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opt %%% Initialize the options %%% --spec handle_options(role(), proplists:proplist()) -> options() | error() . - --spec handle_options(role(), proplists:proplist(), options()) -> options() | error() . +-spec handle_options(role(), client_options()|daemon_options()) -> private_options() | error() . handle_options(Role, PropList0) -> handle_options(Role, PropList0, #{socket_options => [], @@ -155,7 +160,7 @@ handle_options(Role, PropList0) -> }). handle_options(Role, PropList0, Opts0) when is_map(Opts0), - is_list(PropList0) -> + is_list(PropList0) -> PropList1 = proplists:unfold(PropList0), try OptionDefinitions = default(Role), diff --git a/lib/ssh/src/ssh_server_channel.erl b/lib/ssh/src/ssh_server_channel.erl new file mode 100644 index 0000000000..f1c9a85639 --- /dev/null +++ b/lib/ssh/src/ssh_server_channel.erl @@ -0,0 +1,55 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-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% +%% + +%% +%% Description: a gen_server implementing a simple +%% terminal (using the group module) for a CLI +%% over SSH + +-module(ssh_server_channel). + +%% API to server side channel that can be pluged into the erlang ssh daemeon +-callback init(Args :: term()) -> + {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | + {stop, Reason :: term()} | ignore. + +-callback terminate(Reason :: (normal | shutdown | {shutdown, term()} | + term()), + State :: term()) -> + term(). + +-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()}, + State::term()) -> {ok, State::term()} | + {stop, ChannelId::ssh:channel_id(), + State::term()}. + +%%% Internal API +-export([start_link/5, + get_print_info/1 + ]). + +start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> + ssh_client_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). + + +get_print_info(Pid) -> + ssh_client_channel:get_print_info(Pid). diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_server_channel_sup.erl index 8444533fd1..70799db714 100644 --- a/lib/ssh/src/ssh_channel_sup.erl +++ b/lib/ssh/src/ssh_server_channel_sup.erl @@ -22,7 +22,7 @@ %%---------------------------------------------------------------------- %% Purpose: Ssh channel supervisor. %%---------------------------------------------------------------------- --module(ssh_channel_sup). +-module(ssh_server_channel_sup). -behaviour(supervisor). @@ -40,18 +40,16 @@ start_link(Args) -> start_child(Sup, Callback, Id, Args, Exec) -> ChildSpec = #{id => make_ref(), - start => {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]}, + start => {ssh_server_channel, start_link, [self(), Id, Callback, Args, Exec]}, restart => temporary, type => worker, - modules => [ssh_channel] + modules => [ssh_server_channel] }, supervisor:start_child(Sup, ChildSpec). %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - init(_Args) -> RestartStrategy = one_for_one, MaxR = 10, diff --git a/lib/ssh/src/ssh_server_key_api.erl b/lib/ssh/src/ssh_server_key_api.erl index 3f1b886fa7..a285bf9475 100644 --- a/lib/ssh/src/ssh_server_key_api.erl +++ b/lib/ssh/src/ssh_server_key_api.erl @@ -23,16 +23,18 @@ -include_lib("public_key/include/public_key.hrl"). -include("ssh.hrl"). --export_type([algorithm/0]). +-export_type([daemon_key_cb_options/0]). --type algorithm() :: ssh_client_key_api:algorithm(). +-type daemon_key_cb_options() :: [{key_cb_private,term()} | ssh:daemon_option()]. --callback host_key(Algorithm :: algorithm(), - DaemonOptions :: proplists:proplist()) -> +-callback host_key(Algorithm :: ssh:pubkey_alg(), + DaemonOptions :: daemon_key_cb_options() + ) -> {ok, PrivateKey :: public_key:private_key()} | {error, term()}. -callback is_auth_key(PublicKey :: public_key:public_key(), User :: string(), - DaemonOptions :: proplists:proplist()) -> + DaemonOptions :: daemon_key_cb_options() + ) -> boolean(). diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index f00c0aed1f..5984713ec9 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -24,7 +24,7 @@ -module(ssh_sftp). --behaviour(ssh_channel). +-behaviour(ssh_client_channel). -include_lib("kernel/include/file.hrl"). -include("ssh.hrl"). @@ -47,7 +47,7 @@ recv_window/1, list_dir/2, read_file/2, write_file/3, recv_window/2, list_dir/3, read_file/3, write_file/4]). -%% ssh_channel callbacks +%% ssh_client_channel callbacks -export([init/1, handle_call/3, handle_cast/2, code_change/3, handle_msg/2, handle_ssh_msg/2, terminate/2]). %% TODO: Should be placed elsewhere ssh_sftpd should not call functions in ssh_sftp! -export([info_to_attr/1, attr_to_info/1]). @@ -123,7 +123,7 @@ start_channel(Cm, UserOptions) when is_pid(Cm) -> {_SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), case ssh_xfer:attach(Cm, [], ChanOpts) of {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, + case ssh_client_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId, SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of @@ -151,7 +151,7 @@ start_channel(Host, Port, UserOptions) -> proplists:get_value(timeout, SftpOpts, infinity)), case ssh_xfer:connect(Host, Port, SshOpts, ChanOpts, Timeout) of {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,ChannelId,SftpOpts]) of + case ssh_client_channel:start(Cm, ChannelId, ?MODULE, [Cm,ChannelId,SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> @@ -825,7 +825,7 @@ handle_msg({ssh_channel_up, _, _}, #state{opts = Options, xf = Xf} = State) -> %% Version negotiation timed out handle_msg({timeout, undefined, From}, #state{xf = #ssh_xfer{channel = ChannelId}} = State) -> - ssh_channel:reply(From, {error, timeout}), + ssh_client_channel:reply(From, {error, timeout}), {stop, ChannelId, State}; handle_msg({timeout, Id, From}, #state{req_list = ReqList0} = State) -> @@ -834,7 +834,7 @@ handle_msg({timeout, Id, From}, #state{req_list = ReqList0} = State) -> {ok, State}; _ -> ReqList = lists:keydelete(Id, 1, ReqList0), - ssh_channel:reply(From, {error, timeout}), + ssh_client_channel:reply(From, {error, timeout}), {ok, State#state{req_list = ReqList}} end; @@ -882,7 +882,7 @@ handle_options([Opt|Rest], Sftp, Chan, Ssh) -> handle_options(Rest, Sftp, Chan, [Opt|Ssh]). call(Pid, Msg, TimeOut) -> - ssh_channel:call(Pid, {{timeout, TimeOut}, Msg}, infinity). + ssh_client_channel:call(Pid, {{timeout, TimeOut}, Msg}, infinity). handle_reply(State, <<?UINT32(Len),Reply:Len/binary,Rest/binary>>) -> do_handle_reply(State, Reply, Rest); @@ -901,7 +901,7 @@ do_handle_reply(#state{xf = Xf} = State, true -> ok end, - ssh_channel:reply(From, ok) + ssh_client_channel:reply(From, ok) end, State#state{xf = Xf#ssh_xfer{vsn = Version, ext = Ext}, rep_buf = Rest}; @@ -949,7 +949,7 @@ async_reply(ReqID, Reply, _From={To,_}, State) -> State. sync_reply(Reply, From, State) -> - catch (ssh_channel:reply(From, Reply)), + catch (ssh_client_channel:reply(From, Reply)), State. open2(OrigReqID,FileName,Handle,Mode,Async,From,State) -> diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 945e9f457b..cb2eab1fec 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -24,7 +24,7 @@ -module(ssh_sftpd). --behaviour(ssh_daemon_channel). +-behaviour(ssh_server_channel). -include_lib("kernel/include/file.hrl"). @@ -58,21 +58,7 @@ %%==================================================================== %% API %%==================================================================== --spec init(Args :: term()) -> - {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | - {stop, Reason :: term()} | ignore. - --spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | - term()), - State :: term()) -> - term(). - --spec handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. --spec handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, - State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), - State::term()}. +-spec subsystem_spec(list()) -> subsystem_spec(). subsystem_spec(Options) -> {"sftp", {?MODULE, Options}}. diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl index 085534592d..084daa6821 100644 --- a/lib/ssh/src/ssh_shell.erl +++ b/lib/ssh/src/ssh_shell.erl @@ -27,9 +27,9 @@ %%% As this is an user interactive client it behaves like a daemon %%% channel inspite of it being a client. --behaviour(ssh_daemon_channel). +-behaviour(ssh_server_channel). -%% ssh_channel callbacks +%% ssh_server_channel callbacks -export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]). %% Spawn export @@ -46,23 +46,8 @@ ). %%==================================================================== -%% ssh_channel callbacks +%% ssh_server_channel callbacks %%==================================================================== --spec init(Args :: term()) -> - {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | - {stop, Reason :: term()} | ignore. - --spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | - term()), - State :: term()) -> - term(). - --spec handle_msg(Msg ::term(), State :: term()) -> - {ok, State::term()} | {stop, ChannelId::integer(), State::term()}. --spec handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, - State::term()) -> {ok, State::term()} | - {stop, ChannelId::integer(), - State::term()}. %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index 77da240a66..f80be7f20b 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -48,7 +48,7 @@ connection_supervisor(SupPid) -> channel_supervisor(SupPid) -> Children = supervisor:which_children(SupPid), - ssh_channel_sup(Children). + ssh_server_channel_sup(Children). %%%========================================================================= %%% Supervisor callback @@ -78,8 +78,8 @@ ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> }. ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> - #{id => id(Role, ssh_channel_sup, Address, Port), - start => {ssh_channel_sup, start_link, [Options]}, + #{id => id(Role, ssh_server_channel_sup, Address, Port), + start => {ssh_server_channel_sup, start_link, [Options]}, restart => temporary, type => supervisor }. @@ -92,10 +92,10 @@ ssh_connection_sup([{_, Child, _, [ssh_connection_sup]} | _]) -> ssh_connection_sup([_ | Rest]) -> ssh_connection_sup(Rest). -ssh_channel_sup([{_, Child, _, [ssh_channel_sup]} | _]) -> +ssh_server_channel_sup([{_, Child, _, [ssh_server_channel_sup]} | _]) -> Child; -ssh_channel_sup([_ | Rest]) -> - ssh_channel_sup(Rest). +ssh_server_channel_sup([_ | Rest]) -> + ssh_server_channel_sup(Rest). |