diff options
Diffstat (limited to 'lib/ssh/src/ssh.erl')
-rw-r--r-- | lib/ssh/src/ssh.erl | 430 |
1 files changed, 278 insertions, 152 deletions
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index cada109df0..e5c016eb3f 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% Copyright Ericsson AB 2004-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -23,6 +23,7 @@ -include("ssh.hrl"). -include("ssh_connect.hrl"). +-include_lib("public_key/include/public_key.hrl"). -export([start/0, start/1, stop/0, connect/3, connect/4, close/1, connection_info/2, channel_info/3, @@ -30,6 +31,9 @@ stop_listener/1, stop_listener/2, stop_daemon/1, stop_daemon/2, shell/1, shell/2, shell/3]). +-deprecated({sign_data, 2, next_major_release}). +-deprecated({verify_data, 3, next_major_release}). + -export([sign_data/2, verify_data/3]). %%-------------------------------------------------------------------- @@ -68,66 +72,58 @@ stop() -> connect(Host, Port, Options) -> connect(Host, Port, Options, infinity). connect(Host, Port, Options, Timeout) -> - {SocketOpts, Opts} = handle_options(Options), - DisableIpv6 = proplists:get_value(ip_v6_disabled, Opts, false), - Inet = inetopt(DisableIpv6), + case handle_options(Options) of + {error, _Reason} = Error -> + Error; + {SocketOptions, SshOptions} -> + DisableIpv6 = proplists:get_value(ip_v6_disabled, SshOptions, false), + Inet = inetopt(DisableIpv6), + do_connect(Host, Port, [Inet | SocketOptions], + [{user_pid, self()}, {host, Host} | SshOptions], Timeout, DisableIpv6) + end. + +do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) -> try sshc_sup:start_child([[{address, Host}, {port, Port}, - {role, client}, - {channel_pid, self()}, - {socket_opts, [Inet | SocketOpts]}, - {ssh_opts, [{host, Host}| Opts]}]]) of + {role, client}, + {channel_pid, self()}, + {socket_opts, SocketOptions}, + {ssh_opts, SshOptions}]]) of {ok, ConnectionSup} -> {ok, Manager} = - ssh_connection_controler:connection_manager(ConnectionSup), - MRef = erlang:monitor(process, Manager), - receive - {Manager, is_connected} -> - do_demonitor(MRef, Manager), - {ok, Manager}; - %% When the connection fails - %% ssh_connection_sup:connection_manager - %% might return undefined as the connection manager - %% could allready have terminated, so we will not - %% match the Manager in this case - {_, not_connected, {error, Reason}} -> - do_demonitor(MRef, Manager), - {error, Reason}; - {_, not_connected, Other} -> - do_demonitor(MRef, Manager), - {error, Other}; - {'DOWN', MRef, _, Manager, Reason} when is_pid(Manager) -> - error_logger:warning_report([{ssh, connect}, - {diagnose, - "Connection was closed before properly set up."}, - {host, Host}, - {port, Port}, - {reason, Reason}]), - receive %% Clear EXIT message from queue - {'EXIT', Manager, _What} -> - {error, channel_closed} - after 0 -> - {error, channel_closed} - end - after Timeout -> - do_demonitor(MRef, Manager), - ssh_connection_manager:stop(Manager), - {error, timeout} - end + ssh_connection_sup:connection_manager(ConnectionSup), + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) catch exit:{noproc, _} -> {error, ssh_not_started} end. - -do_demonitor(MRef, Manager) -> - erlang:demonitor(MRef), +msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) -> receive - {'DOWN', MRef, _, Manager, _} -> - ok - after 0 -> - ok + {Manager, is_connected} -> + {ok, Manager}; + %% When the connection fails + %% ssh_connection_sup:connection_manager + %% might return undefined as the connection manager + %% could allready have terminated, so we will not + %% match the Manager in this case + {_, not_connected, {error, econnrefused}} when DisableIpv6 == false -> + do_connect(Host, Port, proplists:delete(inet6, SocketOptions), + SshOptions, Timeout, true); + {_, not_connected, {error, Reason}} -> + {error, Reason}; + {_, not_connected, Other} -> + {error, Other}; + {From, user_password} -> + Pass = io:get_password(), + From ! Pass, + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout); + {From, question} -> + Answer = io:get_line(""), + From ! Answer, + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) + after Timeout -> + ssh_connection_manager:stop(Manager), + {error, timeout} end. - - %%-------------------------------------------------------------------- %% Function: close(ConnectionRef) -> ok %% @@ -185,7 +181,7 @@ daemon(HostAddr, Port, Options0) -> {HostAddr, inet6, [{ip, HostAddr} | Options1]} end, - start_daemon(Host, Port, [{role, server} | Options], Inet). + start_daemon(Host, Port, Options, Inet). %%-------------------------------------------------------------------- %% Function: stop_listener(SysRef) -> ok @@ -247,48 +243,18 @@ shell(Host, Port, Options) -> Error end. - -%%-------------------------------------------------------------------- -%% Function: sign_data(Data, Algorithm) -> binary() | -%% {error, Reason} -%% -%% Data = binary() -%% Algorithm = "ssh-rsa" -%% -%% Description: Use SSH key to sign data. -%%-------------------------------------------------------------------- -sign_data(Data, Algorithm) when is_binary(Data) -> - case ssh_file:private_identity_key(Algorithm,[]) of - {ok, Key} when Algorithm == "ssh-rsa" -> - ssh_rsa:sign(Key, Data); - Error -> - Error - end. - -%%-------------------------------------------------------------------- -%% Function: verify_data(Data, Signature, Algorithm) -> ok | -%% {error, Reason} -%% -%% Data = binary() -%% Signature = binary() -%% Algorithm = "ssh-rsa" -%% -%% Description: Use SSH signature to verify data. -%%-------------------------------------------------------------------- -verify_data(Data, Signature, Algorithm) when is_binary(Data), is_binary(Signature) -> - case ssh_file:public_identity_key(Algorithm, []) of - {ok, Key} when Algorithm == "ssh-rsa" -> - ssh_rsa:verify(Key, Data, Signature); - Error -> - Error - end. - - %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- start_daemon(Host, Port, Options, Inet) -> - {SocketOpts, Opts} = handle_options(Options), + case handle_options(Options) of + {error, _Reason} = Error -> + Error; + {SocketOptions, SshOptions}-> + do_start_daemon(Host, Port,[{role, server} |SshOptions] , [Inet | SocketOptions]) + end. + +do_start_daemon(Host, Port, Options, SocketOptions) -> case ssh_system_sup:system_supervisor(Host, Port) of undefined -> %% TODO: It would proably make more sense to call the @@ -296,8 +262,8 @@ start_daemon(Host, Port, Options, Inet) -> %% monent. The name is a legacy name! try sshd_sup:start_child([{address, Host}, {port, Port}, {role, server}, - {socket_opts, [Inet | SocketOpts]}, - {ssh_opts, Opts}]) of + {socket_opts, SocketOptions}, + {ssh_opts, Options}]) of {ok, SysSup} -> {ok, SysSup}; {error, {already_started, _}} -> @@ -316,69 +282,229 @@ start_daemon(Host, Port, Options, Inet) -> end. handle_options(Opts) -> - handle_options(proplists:unfold(Opts), [], []). -handle_options([], SockOpts, Opts) -> - {SockOpts, Opts}; -%% TODO: Could do some type checks here on plain ssh-opts -handle_options([{system_dir, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{user_dir, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{user_dir_fun, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{silently_accept_hosts, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{user_interaction, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{public_key_alg, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{connect_timeout, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{user, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{password, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{user_passwords, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{pwdfun, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{user_auth, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{key_cb, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{role, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{channel, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{compression, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{allow_user_interaction, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{infofun, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{connectfun, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{disconnectfun , _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{failfun, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{ip_v6_disabled, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]); -handle_options([{ip, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, [Opt |SockOpts], Opts); -handle_options([{ifaddr, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, [Opt |SockOpts], Opts); -handle_options([{fd, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, [Opt | SockOpts], Opts); -handle_options([{nodelay, _} = Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, [Opt | SockOpts], Opts); -handle_options([Opt | Rest], SockOpts, Opts) -> - handle_options(Rest, SockOpts, [Opt | Opts]). + try handle_option(proplists:unfold(Opts), [], []) of + {_,_} = Options -> + Options + catch + throw:Error -> + Error + 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([{public_key_alg, _} = 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([{user_auth, _} = Opt | Rest],SocketOptions, SshOptions ) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{key_cb, _} = Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{role, _} = Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{compression, _} = 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([{failfun, _} = Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{ip_v6_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([{pref_public_key_algs, _} = 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([Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). +handle_ssh_option({system_dir, Value} = Opt) when is_list(Value) -> + Opt; +handle_ssh_option({user_dir, Value} = Opt) when is_list(Value) -> + Opt; +handle_ssh_option({user_dir_fun, Value} = Opt) when is_function(Value) -> + Opt; +handle_ssh_option({silently_accept_hosts, Value} = Opt) when Value == true; Value == false -> + Opt; +handle_ssh_option({user_interaction, Value} = Opt) when Value == true; Value == false -> + Opt; +handle_ssh_option({public_key_alg, Value} = Opt) when Value == ssh_rsa; Value == ssh_dsa -> + Opt; +handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 -> + case check_pref_algs(Value) of + true -> + Opt; + _ -> + throw({error, {eoptions, Opt}}) + end; +handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> + 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) -> + Opt; +handle_ssh_option({user_auth, Value} = Opt) when is_function(Value) -> + Opt; +handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) -> + 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({auth_methods, Value} = Opt) when is_list(Value) -> + 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({failfun, Value} = Opt) when is_function(Value) -> + Opt; +handle_ssh_option({ip_v6_disabled, Value} = Opt) when is_boolean(Value) -> + Opt; +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({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 Value == true; + Value == false -> + Opt; +handle_ssh_option(Opt) -> + throw({error, {eoptions, Opt}}). + +handle_inet_option({active, _} = Opt) -> + throw({error, {{eoptions, Opt}, "Ssh has built in flow control, " + "and activ is handled internaly user is not allowd" + "to specify this option"}}); +handle_inet_option({inet, _} = Opt) -> + throw({error, {{eoptions, Opt},"Is set internaly use ip_v6_disabled to" + " enforce iv4 in the server, client will fallback to ipv4 if" + " it can not use ipv6"}}); +handle_inet_option({reuseaddr, _} = Opt) -> + throw({error, {{eoptions, Opt},"Is set internaly user is not allowd" + "to specify this option"}}); +%% Option verified by inet +handle_inet_option(Opt) -> + Opt. +%% Check preferred algs +check_pref_algs([]) -> + true; +check_pref_algs([H|T]) -> + case H of + ssh_dsa -> + check_pref_algs(T); + ssh_rsa -> + check_pref_algs(T); + _ -> + false + end. %% Has IPv6 been disabled? inetopt(true) -> inet; inetopt(false) -> - inet6. + case gen_tcp:listen(0, [inet6]) of + {ok, Dummyport} -> + gen_tcp:close(Dummyport), + inet6; + _ -> + inet + end. +%%% +%% Deprecated +%%% + +%%-------------------------------------------------------------------- +%% Function: sign_data(Data, Algorithm) -> binary() | +%% {error, Reason} +%% +%% Data = binary() +%% Algorithm = "ssh-rsa" +%% +%% Description: Use SSH key to sign data. +%%-------------------------------------------------------------------- +sign_data(Data, Algorithm) when is_binary(Data) -> + case ssh_file:user_key(Algorithm,[]) of + {ok, Key} when Algorithm == "ssh-rsa" -> + public_key:sign(Data, sha, Key); + Error -> + Error + end. +%%-------------------------------------------------------------------- +%% Function: verify_data(Data, Signature, Algorithm) -> ok | +%% {error, Reason} +%% +%% Data = binary() +%% Signature = binary() +%% Algorithm = "ssh-rsa" +%% +%% Description: Use SSH signature to verify data. +%%-------------------------------------------------------------------- +verify_data(Data, Signature, Algorithm) when is_binary(Data), is_binary(Signature) -> + case ssh_file:user_key(Algorithm, []) of + {ok, #'RSAPrivateKey'{publicExponent = E, modulus = N}} when Algorithm == "ssh-rsa" -> + public_key:verify(Data, sha, Signature, #'RSAPublicKey'{publicExponent = E, modulus = N}); + Error -> + Error + end. |