% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% -module(ssh). -include("ssh.hrl"). -include("ssh_connect.hrl"). -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). -export([start/0, start/1, stop/0, connect/2, connect/3, connect/4, close/1, connection_info/2, channel_info/3, daemon/1, daemon/2, daemon/3, daemon_info/1, default_algorithms/0, stop_listener/1, stop_listener/2, stop_listener/3, stop_daemon/1, stop_daemon/2, stop_daemon/3, shell/1, shell/2, shell/3 ]). %%% Type exports -export_type([connection_ref/0, channel_id/0 ]). %%-------------------------------------------------------------------- -spec start() -> ok | {error, term()}. -spec start(permanent | transient | temporary) -> ok | {error, term()}. %% %% Description: Starts the ssh application. Default type %% is temporary. see application(3) %%-------------------------------------------------------------------- start() -> start(temporary). start(Type) -> case application:ensure_all_started(ssh, Type) of {ok, _} -> ok; Other -> Other end. %%-------------------------------------------------------------------- -spec stop() -> ok | {error, term()}. %% %% Description: Stops the ssh application. %%-------------------------------------------------------------------- stop() -> application:stop(ssh). %%-------------------------------------------------------------------- -spec connect(port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. -spec connect(port(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()} ; (string(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. -spec connect(string(), integer(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()}. %% %% Description: Starts an ssh connection. %%-------------------------------------------------------------------- connect(Socket, Options) -> connect(Socket, Options, infinity). connect(Socket, Options, Timeout) when is_port(Socket) -> case handle_options(Options) of {error, Error} -> {error, Error}; {_SocketOptions, SshOptions} -> case valid_socket_to_use(Socket, Options) of ok -> {ok, {Host,_Port}} = inet:sockname(Socket), Opts = [{user_pid,self()}, {host,fmt_host(Host)} | SshOptions], ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error,SockError} -> {error,SockError} end end; connect(Host, Port, Options) when is_integer(Port), Port>0 -> connect(Host, Port, Options, infinity). connect(Host, Port, Options, Timeout) -> case handle_options(Options) of {error, _Reason} = Error -> Error; {SocketOptions, SshOptions} -> {_, Transport, _} = TransportOpts = proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), ConnectionTimeout = proplists:get_value(connect_timeout, Options, infinity), try Transport:connect(Host, Port, [ {active, false} | SocketOptions], ConnectionTimeout) of {ok, Socket} -> Opts = [{user_pid,self()}, {host,Host} | SshOptions], ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error, Reason} -> {error, Reason} catch exit:{function_clause, _} -> {error, {options, {transport, TransportOpts}}}; exit:badarg -> {error, {options, {socket_options, SocketOptions}}} end end. %%-------------------------------------------------------------------- -spec close(pid()) -> ok. %% %% Description: Closes an ssh connection. %%-------------------------------------------------------------------- close(ConnectionRef) -> ssh_connection_handler:stop(ConnectionRef). %%-------------------------------------------------------------------- -spec connection_info(pid(), [atom()]) -> [{atom(), term()}]. %% %% Description: Retrieves information about a connection. %%-------------------------------------------------------------------- connection_info(ConnectionRef, Options) -> ssh_connection_handler:connection_info(ConnectionRef, Options). %%-------------------------------------------------------------------- -spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}]. %% %% Description: Retrieves information about a connection. %%-------------------------------------------------------------------- channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- -spec daemon(integer()) -> {ok, pid()} | {error, term()}. -spec daemon(integer()|port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. -spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. %% Description: Starts a server listening for SSH connections %% on the given port. %%-------------------------------------------------------------------- daemon(Port) -> daemon(Port, []). daemon(Port, Options) when is_integer(Port) -> daemon(any, Port, Options); daemon(Socket, Options0) when is_port(Socket) -> Options = daemon_shell_opt(Options0), start_daemon(Socket, Options). daemon(HostAddr, Port, Options0) -> Options1 = daemon_shell_opt(Options0), {Host, Inet, Options} = daemon_host_inet_opt(HostAddr, Options1), start_daemon(Host, Port, Options, Inet). %%-------------------------------------------------------------------- daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> [Port] = [Prt || {{ssh_acceptor_sup,_,Prt,_}, _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)], {ok, [{port,Port}]}; _ -> {error,bad_daemon_ref} end. %%-------------------------------------------------------------------- -spec stop_listener(pid()) -> ok. -spec stop_listener(inet:ip_address(), integer()) -> ok. %% %% Description: Stops the listener, but leaves %% existing connections started by the listener up and running. %%-------------------------------------------------------------------- stop_listener(SysSup) -> ssh_system_sup:stop_listener(SysSup). stop_listener(Address, Port) -> stop_listener(Address, Port, ?DEFAULT_PROFILE). stop_listener(Address, Port, Profile) -> ssh_system_sup:stop_listener(Address, Port, Profile). %%-------------------------------------------------------------------- -spec stop_daemon(pid()) -> ok. -spec stop_daemon(inet:ip_address(), integer()) -> ok. %% %% Description: Stops the listener and all connections started by %% the listener. %%-------------------------------------------------------------------- stop_daemon(SysSup) -> ssh_system_sup:stop_system(SysSup). stop_daemon(Address, Port) -> ssh_system_sup:stop_system(Address, Port, ?DEFAULT_PROFILE). stop_daemon(Address, Port, Profile) -> ssh_system_sup:stop_system(Address, Port, Profile). %%-------------------------------------------------------------------- -spec shell(port() | string()) -> _. -spec shell(port() | string(), proplists:proplist()) -> _. -spec shell(string(), integer(), proplists:proplist()) -> _. %% Host = string() %% Port = integer() %% Options = [{Option, Value}] %% %% Description: Starts an interactive shell to an SSH server on the %% given . The function waits for user input, %% and will not return until the remote shell is ended.(e.g. on %% exit from the shell) %%-------------------------------------------------------------------- shell(Socket) when is_port(Socket) -> shell(Socket, []); shell(Host) -> shell(Host, ?SSH_DEFAULT_PORT, []). shell(Socket, Options) when is_port(Socket) -> start_shell( connect(Socket, Options) ); shell(Host, Options) -> shell(Host, ?SSH_DEFAULT_PORT, Options). shell(Host, Port, Options) -> start_shell( connect(Host, Port, Options) ). start_shell({ok, ConnectionRef}) -> case ssh_connection:session_channel(ConnectionRef, infinity) of {ok,ChannelId} -> success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []), Args = [{channel_cb, ssh_shell}, {init_args,[ConnectionRef, ChannelId]}, {cm, ConnectionRef}, {channel_id, ChannelId}], {ok, State} = ssh_channel:init([Args]), ssh_channel:enter_loop(State); Error -> Error end; start_shell(Error) -> Error. %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- default_algorithms() -> ssh_transport:default_algorithms(). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- valid_socket_to_use(Socket, Options) -> case proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}) of {tcp,_,_} -> %% Is this tcp-socket a valid socket? case {is_tcp_socket(Socket), {ok,[{active,false}]} == inet:getopts(Socket, [active]) } of {true, true} -> ok; {true, false} -> {error, not_passive_mode}; _ -> {error, not_tcp_socket} end; {L4,_,_} -> {error, {unsupported,L4}} end. is_tcp_socket(Socket) -> case inet:getopts(Socket, [delay_send]) of {ok,[_]} -> true; _ -> false end. daemon_shell_opt(Options) -> case proplists:get_value(shell, Options) of undefined -> [{shell, {shell, start, []}} | Options]; _ -> Options end. daemon_host_inet_opt(HostAddr, Options1) -> case HostAddr of any -> {ok, Host0} = inet:gethostname(), {Host0, proplists:get_value(inet, Options1, inet), Options1}; {_,_,_,_} -> {HostAddr, inet, [{ip, HostAddr} | Options1]}; {_,_,_,_,_,_,_,_} -> {HostAddr, inet6, [{ip, HostAddr} | Options1]} end. start_daemon(Socket, Options) -> case handle_options(Options) of {error, Error} -> {error, Error}; {SocketOptions, SshOptions} -> case valid_socket_to_use(Socket, Options) of ok -> try do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions) catch throw:bad_fd -> {error,bad_fd}; throw:bad_socket -> {error,bad_socket}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end; {error,SockError} -> {error,SockError} end end. start_daemon(Host, Port, Options, Inet) -> case handle_options(Options) of {error, _Reason} = Error -> Error; {SocketOptions, SshOptions}-> try do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions]) catch throw:bad_fd -> {error,bad_fd}; throw:bad_socket -> {error,bad_socket}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end end. do_start_daemon(Socket, SshOptions, SocketOptions) -> {ok, {IP,Port}} = try {ok,_} = inet:sockname(Socket) catch _:_ -> throw(bad_socket) end, Host = fmt_host(IP), Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE), Opts = [{asocket, Socket}, {asock_owner,self()}, {address, Host}, {port, Port}, {role, server}, {socket_opts, SocketOptions}, {ssh_opts, SshOptions}], {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> %% It would proably make more sense to call the %% address option host but that is a too big change at the %% monent. The name is a legacy name! try sshd_sup:start_child(Opts) of {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, Result); Result = {error, _} -> Result catch exit:{noproc, _} -> {error, ssh_not_started} end; Sup -> AccPid = ssh_system_sup:acceptor_supervisor(Sup), case ssh_acceptor_sup:start_child(AccPid, Opts) of {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, {ok, Sup}); Other -> Other end end. do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> {Host,Port1} = try case proplists:get_value(fd, SocketOptions) of undefined -> {Host0,Port0}; Fd when Port0==0 -> find_hostport(Fd); _ -> {Host0,Port0} end catch _:_ -> throw(bad_fd) end, Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE), {Port, WaitRequestControl, Opts0} = case Port1 of 0 -> %% Allocate the socket here to get the port number... {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), {ok,LSock} = ssh_acceptor:callback_listen(Callback, 0, SocketOptions), {ok,{_,LPort}} = inet:sockname(LSock), {LPort, {LSock,Callback}, [{lsocket,LSock},{lsock_owner,self()}] }; _ -> {Port1, false, []} end, Opts = [{address, Host}, {port, Port}, {role, server}, {socket_opts, SocketOptions}, {ssh_opts, SshOptions} | Opts0], case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> %% It would proably make more sense to call the %% address option host but that is a too big change at the %% monent. The name is a legacy name! try sshd_sup:start_child(Opts) of {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> sync_request_control(WaitRequestControl), Result; Result = {error, _} -> Result catch exit:{noproc, _} -> {error, ssh_not_started} end; Sup -> AccPid = ssh_system_sup:acceptor_supervisor(Sup), case ssh_acceptor_sup:start_child(AccPid, Opts) of {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> sync_request_control(WaitRequestControl), {ok, Sup}; Other -> Other end end. call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, DefaultResult) -> try ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket) of {error,Error} -> {error,Error}; _ -> DefaultResult catch C:R -> {error,{could_not_start_connection,{C,R}}} end. sync_request_control(false) -> ok; sync_request_control({LSock,Callback}) -> receive {request_control,LSock,ReqPid} -> ok = Callback:controlling_process(LSock, ReqPid), ReqPid ! {its_yours,LSock}, ok end. find_hostport(Fd) -> %% Using internal functions inet:open/8 and inet:close/0. %% Don't try this at home unless you know what you are doing! {ok,S} = inet:open(Fd, {0,0,0,0}, 0, [], tcp, inet, stream, inet_tcp), {ok, HostPort} = inet:sockname(S), ok = inet:close(S), HostPort. handle_options(Opts) -> try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of {Inet, Ssh} -> {handle_ip(Inet), Ssh} catch throw:Error -> Error end. algs_compatibility(Os0) -> %% Take care of old options 'public_key_alg' and 'pref_public_key_algs' case proplists:get_value(public_key_alg, Os0) of undefined -> Os0; A when is_atom(A) -> %% Skip public_key_alg if pref_public_key_algs is defined: Os = lists:keydelete(public_key_alg, 1, Os0), case proplists:get_value(pref_public_key_algs,Os) of undefined when A == 'ssh-rsa' ; A==ssh_rsa -> [{pref_public_key_algs,['ssh-rsa','ssh-dss']} | Os]; undefined when A == 'ssh-dss' ; A==ssh_dsa -> [{pref_public_key_algs,['ssh-dss','ssh-rsa']} | Os]; undefined -> throw({error, {eoptions, {public_key_alg,A} }}); _ -> Os end; V -> throw({error, {eoptions, {public_key_alg,V} }}) end. handle_option([], SocketOptions, SshOptions) -> {SocketOptions, SshOptions}; handle_option([{system_dir, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{user_dir, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{user_dir_fun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{silently_accept_hosts, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{user_interaction, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{connect_timeout, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{user, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{dsa_pass_phrase, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{rsa_pass_phrase, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{password, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{user_passwords, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{pwdfun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{key_cb, {Module, Options}} | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option({key_cb, Module}), handle_ssh_priv_option({key_cb_private, Options}) | SshOptions]); handle_option([{key_cb, Module} | Rest], SocketOptions, SshOptions) -> handle_option([{key_cb, {Module, []}} | Rest], SocketOptions, SshOptions); handle_option([{keyboard_interact_fun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); %%Backwards compatibility handle_option([{allow_user_interaction, Value} | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option({user_interaction, Value}) | SshOptions]); handle_option([{infofun, _} = Opt | Rest],SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{connectfun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{disconnectfun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{unexpectedfun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{failfun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{ssh_msg_debug_fun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); %%Backwards compatibility should not be underscore between ip and v6 in API handle_option([{ip_v6_disabled, Value} | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option({ipv6_disabled, Value}) | SshOptions]); handle_option([{ipv6_disabled, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{transport, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{subsystems, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{ssh_cli, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{shell, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{exec, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{auth_methods, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{auth_method_kb_interactive_data, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{pref_public_key_algs, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{preferred_algorithms,_} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{dh_gex_groups,_} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{dh_gex_limits,_} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{rekey_limit, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{max_sessions, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{max_channels, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{negotiation_timeout, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{parallel_login, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); %% (Is handled by proplists:unfold above:) %% handle_option([parallel_login|Rest], SocketOptions, SshOptions) -> %% handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | SshOptions]); handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{id_string, _ID} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{profile, _ID} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{max_random_length_padding, _Bool} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{tstflg, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). handle_ssh_option({tstflg,_F} = Opt) -> Opt; handle_ssh_option({minimal_remote_max_packet_size, Value} = Opt) when is_integer(Value), Value >=0 -> Opt; handle_ssh_option({system_dir, Value} = Opt) when is_list(Value) -> check_dir(Opt); handle_ssh_option({user_dir, Value} = Opt) when is_list(Value) -> check_dir(Opt); handle_ssh_option({user_dir_fun, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_function(Value,2) -> Opt; handle_ssh_option({silently_accept_hosts, {DigestAlg,Value}} = Opt) when is_function(Value,2) -> Algs = if is_atom(DigestAlg) -> [DigestAlg]; is_list(DigestAlg) -> DigestAlg; true -> throw({error, {eoptions, Opt}}) end, case [A || A <- Algs, not lists:member(A, [md5, sha, sha224, sha256, sha384, sha512])] of [_|_] = UnSup1 -> throw({error, {{eoptions, Opt}, {not_fingerprint_algos,UnSup1}}}); [] -> CryptoHashAlgs = proplists:get_value(hashs, crypto:supports(), []), case [A || A <- Algs, not lists:member(A, CryptoHashAlgs)] of [_|_] = UnSup2 -> throw({error, {{eoptions, Opt}, {unsupported_algo,UnSup2}}}); [] -> Opt end end; handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> handle_pref_algs(Opt); handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> {dh_gex_groups, collect_per_size( lists:foldl( fun({N,G,P}, Acc) when is_integer(N),N>0, is_integer(G),G>0, is_integer(P),P>0 -> [{N,{G,P}} | Acc]; ({N,{G,P}}, Acc) when is_integer(N),N>0, is_integer(G),G>0, is_integer(P),P>0 -> [{N,{G,P}} | Acc]; ({N,GPs}, Acc) when is_list(GPs) -> lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, is_integer(Pi),Pi>0 -> [{N,{Gi,Pi}} | Acci] end, Acc, GPs) end, [], L0))}; handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0, Tag == file ; Tag == ssh_moduli_file -> {ok,GroupDefs} = case Tag of file -> file:consult(File); ssh_moduli_file -> case file:open(File,[read]) of {ok,D} -> try {ok,Moduli} = read_moduli_file(D, 1, []), file:close(D), {ok, Moduli} catch _:_ -> throw({error, {{eoptions, Opt}, "Bad format in file "++File}}) end; {error,enoent} -> throw({error, {{eoptions, Opt}, "File not found:"++File}}); {error,Error} -> throw({error, {{eoptions, Opt}, io_lib:format("Error reading file ~s: ~p",[File,Error])}}) end end, try handle_ssh_option({dh_gex_groups,GroupDefs}) catch _:_ -> throw({error, {{eoptions, Opt}, "Bad format in file: "++File}}) end; handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0, is_integer(Max), Max>=Min -> %% Server Opt; handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, is_integer(I), I>=Min, is_integer(Max), Max>=I -> %% Client Opt; handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 -> case handle_user_pref_pubkey_algs(Value, []) of {true, NewOpts} -> {pref_public_key_algs, NewOpts}; _ -> throw({error, {eoptions, Opt}}) end; handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> Opt; handle_ssh_option({max_sessions, Value} = Opt) when is_integer(Value), Value>0 -> Opt; handle_ssh_option({max_channels, Value} = Opt) when is_integer(Value), Value>0 -> Opt; handle_ssh_option({negotiation_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> Opt; handle_ssh_option({parallel_login, Value} = Opt) when Value==true ; Value==false -> Opt; handle_ssh_option({user, Value} = Opt) when is_list(Value) -> Opt; handle_ssh_option({dsa_pass_phrase, Value} = Opt) when is_list(Value) -> Opt; handle_ssh_option({rsa_pass_phrase, Value} = Opt) when is_list(Value) -> Opt; handle_ssh_option({password, Value} = Opt) when is_list(Value) -> Opt; handle_ssh_option({user_passwords, Value} = Opt) when is_list(Value)-> Opt; handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,2) -> Opt; handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,4) -> Opt; handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) -> Opt; handle_ssh_option({key_cb, {CallbackMod, CallbackOptions}} = Opt) when is_atom(CallbackMod), is_list(CallbackOptions) -> Opt; handle_ssh_option({keyboard_interact_fun, Value} = Opt) when is_function(Value,3) -> Opt; handle_ssh_option({compression, Value} = Opt) when is_atom(Value) -> Opt; handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module), is_atom(Function) -> Opt; handle_ssh_option({exec, Function} = Opt) when is_function(Function) -> Opt; handle_ssh_option({auth_methods, Value} = Opt) when is_list(Value) -> Opt; handle_ssh_option({auth_method_kb_interactive_data, {Name,Instruction,Prompt,Echo}} = Opt) when is_list(Name), is_list(Instruction), is_list(Prompt), is_boolean(Echo) -> Opt; handle_ssh_option({auth_method_kb_interactive_data, F} = Opt) when is_function(F,3) -> Opt; handle_ssh_option({infofun, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({connectfun, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({disconnectfun, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({unexpectedfun, Value} = Opt) when is_function(Value,2) -> Opt; handle_ssh_option({failfun, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({ssh_msg_debug_fun, Value} = Opt) when is_function(Value,4) -> Opt; handle_ssh_option({ipv6_disabled, Value} = Opt) when is_boolean(Value) -> throw({error, {{ipv6_disabled, Opt}, option_no_longer_valid_use_inet_option_instead}}); handle_ssh_option({transport, {Protocol, Cb, ClosTag}} = Opt) when is_atom(Protocol), is_atom(Cb), is_atom(ClosTag) -> Opt; handle_ssh_option({subsystems, Value} = Opt) when is_list(Value) -> Opt; handle_ssh_option({ssh_cli, {Cb, _}}= Opt) when is_atom(Cb) -> Opt; handle_ssh_option({ssh_cli, no_cli} = Opt) -> Opt; handle_ssh_option({shell, {Module, Function, _}} = Opt) when is_atom(Module), is_atom(Function) -> Opt; handle_ssh_option({shell, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({quiet_mode, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> Opt; handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> Opt; handle_ssh_option({id_string, random}) -> {id_string, {random,2,5}}; %% 2 - 5 random characters handle_ssh_option({id_string, ID} = Opt) when is_list(ID) -> Opt; handle_ssh_option({max_random_length_padding, Value} = Opt) when is_integer(Value), Value =< 255 -> Opt; handle_ssh_option({profile, Value} = Opt) when is_atom(Value) -> Opt; handle_ssh_option(Opt) -> throw({error, {eoptions, Opt}}). handle_ssh_priv_option({key_cb_private, Value} = Opt) when is_list(Value) -> Opt. handle_inet_option({active, _} = Opt) -> throw({error, {{eoptions, Opt}, "SSH has built in flow control, " "and active is handled internally, user is not allowed" "to specify this option"}}); handle_inet_option({inet, Value}) when (Value == inet) or (Value == inet6) -> Value; handle_inet_option({reuseaddr, _} = Opt) -> throw({error, {{eoptions, Opt},"Is set internally, user is not allowed" "to specify this option"}}); %% Option verified by inet handle_inet_option(Opt) -> Opt. %% Check preferred algs handle_pref_algs({preferred_algorithms,Algs}) -> try alg_duplicates(Algs, [], []) of [] -> {preferred_algorithms, [try ssh_transport:supported_algorithms(Key) of DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs) catch _:_ -> throw({error, {{eoptions, {preferred_algorithms,Key}}, "Bad preferred_algorithms key"}}) end || {Key,Vals} <- Algs] }; Dups -> throw({error, {{eoptions, {preferred_algorithms,Dups}}, "Duplicates found"}}) catch _:_ -> throw({error, {{eoptions, preferred_algorithms}, "Malformed"}}) end. alg_duplicates([{K,V}|KVs], Ks, Dups0) -> Dups = case lists:member(K,Ks) of true -> [K|Dups0]; false -> Dups0 end, case V--lists:usort(V) of [] -> alg_duplicates(KVs, [K|Ks], Dups); Ds -> alg_duplicates(KVs, [K|Ks], Dups++Ds) end; alg_duplicates([], _Ks, Dups) -> Dups. handle_pref_alg(Key, Vs=[{client2server,C2Ss=[_|_]},{server2client,S2Cs=[_|_]}], [{client2server,Sup_C2Ss},{server2client,Sup_S2Cs}] ) -> chk_alg_vs(Key, C2Ss, Sup_C2Ss), chk_alg_vs(Key, S2Cs, Sup_S2Cs), {Key, Vs}; handle_pref_alg(Key, Vs=[{server2client,[_|_]},{client2server,[_|_]}], Sup=[{client2server,_},{server2client,_}] ) -> handle_pref_alg(Key, lists:reverse(Vs), Sup); handle_pref_alg(Key, Vs=[V|_], Sup=[{client2server,_},{server2client,_}] ) when is_atom(V) -> handle_pref_alg(Key, [{client2server,Vs},{server2client,Vs}], Sup); handle_pref_alg(Key, Vs=[V|_], Sup=[S|_] ) when is_atom(V), is_atom(S) -> chk_alg_vs(Key, Vs, Sup), {Key, Vs}; handle_pref_alg(Key, Vs, _) -> throw({error, {{eoptions, {preferred_algorithms,[{Key,Vs}]}}, "Badly formed list"}}). chk_alg_vs(OptKey, Values, SupportedValues) -> case (Values -- SupportedValues) of [] -> Values; Bad -> throw({error, {{eoptions, {OptKey,Bad}}, "Unsupported value(s) found"}}) end. handle_ip(Inet) -> %% Default to ipv4 case lists:member(inet, Inet) of true -> Inet; false -> case lists:member(inet6, Inet) of true -> Inet; false -> [inet | Inet] end end. check_dir({_,Dir} = Opt) -> case directory_exist_readable(Dir) of ok -> Opt; {error,Error} -> throw({error, {eoptions,{Opt,Error}}}) end. directory_exist_readable(Dir) -> case file:read_file_info(Dir) of {ok, #file_info{type = directory, access = Access}} -> case Access of read -> ok; read_write -> ok; _ -> {error, eacces} end; {ok, #file_info{}}-> {error, enotdir}; {error, Error} -> {error, Error} end. collect_per_size(L) -> lists:foldr( fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc]; ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc] end, [], lists:sort(L)). read_moduli_file(D, I, Acc) -> case io:get_line(D,"") of {error,Error} -> {error,Error}; eof -> {ok, Acc}; "#" ++ _ -> read_moduli_file(D, I+1, Acc); <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc); Data -> Line = if is_binary(Data) -> binary_to_list(Data); is_list(Data) -> Data end, try [_Time,_Type,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"), M = {list_to_integer(Size), {list_to_integer(G), list_to_integer(P,16)} }, read_moduli_file(D, I+1, [M|Acc]) catch _:_ -> read_moduli_file(D, I+1, Acc) end end. handle_user_pref_pubkey_algs([], Acc) -> {true, lists:reverse(Acc)}; handle_user_pref_pubkey_algs([H|T], Acc) -> case lists:member(H, ?SUPPORTED_USER_KEYS) of true -> handle_user_pref_pubkey_algs(T, [H| Acc]); false when H==ssh_dsa -> handle_user_pref_pubkey_algs(T, ['ssh-dss'| Acc]); false when H==ssh_rsa -> handle_user_pref_pubkey_algs(T, ['ssh-rsa'| Acc]); false -> false end. fmt_host({A,B,C,D}) -> lists:concat([A,".",B,".",C,".",D]); fmt_host(T={_,_,_,_,_,_,_,_}) -> lists:flatten(string:join([io_lib:format("~.16B",[A]) || A <- tuple_to_list(T)], ":")).