diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/Makefile | 6 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 220 | ||||
-rw-r--r-- | lib/ssh/src/ssh.hrl | 7 | ||||
-rw-r--r-- | lib/ssh/src/ssh_acceptor.erl | 6 | ||||
-rw-r--r-- | lib/ssh/src/ssh_acceptor_sup.erl | 24 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.erl | 194 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.hrl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_bits.erl | 8 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 473 | ||||
-rw-r--r-- | lib/ssh/src/ssh_message.erl | 34 | ||||
-rw-r--r-- | lib/ssh/src/ssh_system_sup.erl | 64 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 141 | ||||
-rw-r--r-- | lib/ssh/src/sshd_sup.erl | 27 |
13 files changed, 799 insertions, 407 deletions
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 90d71107ad..a06d8acfd4 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -75,7 +75,7 @@ MODULES= \ ssh_transport \ ssh_xfer -PUBLIC_HRL_FILES= ssh.hrl ssh_userauth.hrl ssh_xfer.hrl +HRL_FILES = ERL_FILES= \ $(MODULES:%=%.erl) \ @@ -95,7 +95,7 @@ APP_TARGET= $(EBIN)/$(APP_FILE) APPUP_SRC= $(APPUP_FILE).src APPUP_TARGET= $(EBIN)/$(APPUP_FILE) -INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl +INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl # ---------------------------------------------------- # FLAGS @@ -140,7 +140,7 @@ release_spec: opt $(INSTALL_DATA) $(BEHAVIOUR_TARGET_FILES) $(TARGET_FILES) $(APP_TARGET) \ $(APPUP_TARGET) "$(RELSYSDIR)/ebin" $(INSTALL_DIR) "$(RELSYSDIR)/include" - $(INSTALL_DATA) $(PUBLIC_HRL_FILES) "$(RELSYSDIR)/include" + release_docs_spec: diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index d4b02a024e..86c042781c 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -24,11 +24,14 @@ -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/3, connect/4, close/1, connection_info/2, channel_info/3, daemon/1, daemon/2, daemon/3, - stop_listener/1, stop_listener/2, stop_daemon/1, stop_daemon/2, + 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]). %%-------------------------------------------------------------------- @@ -157,7 +160,9 @@ daemon(HostAddr, Port, Options0) -> stop_listener(SysSup) -> ssh_system_sup:stop_listener(SysSup). stop_listener(Address, Port) -> - ssh_system_sup: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. @@ -169,8 +174,9 @@ stop_listener(Address, Port) -> stop_daemon(SysSup) -> ssh_system_sup:stop_system(SysSup). stop_daemon(Address, Port) -> - ssh_system_sup:stop_system(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(string()) -> _. -spec shell(string(), proplists:proplist()) -> _. @@ -208,6 +214,11 @@ shell(Host, Port, Options) -> end. %%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +default_algorithms() -> + ssh_transport:default_algorithms(). + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- fix_idle_time(SshOptions) -> @@ -226,7 +237,8 @@ start_daemon(Host, Port, Options, Inet) -> end. do_start_daemon(Host, Port, Options, SocketOptions) -> - case ssh_system_sup:system_supervisor(Host, Port) of + Profile = proplists:get_value(profile, Options, ?DEFAULT_PROFILE), + 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 @@ -259,7 +271,7 @@ do_start_daemon(Host, Port, Options, SocketOptions) -> end. handle_options(Opts) -> - try handle_option(proplists:unfold(Opts), [], []) of + try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of {Inet, Ssh} -> {handle_ip(Inet), Ssh} catch @@ -267,6 +279,35 @@ handle_options(Opts) -> Error end. + +algs_compatibility(Os) -> + %% Take care of old options 'public_key_alg' and 'pref_public_key_algs' + comp_pk(proplists:get_value(preferred_algorithms,Os), + proplists:get_value(pref_public_key_algs,Os), + proplists:get_value(public_key_alg, Os), + [{K,V} || {K,V} <- Os, + K =/= public_key_alg, + K =/= pref_public_key_algs] + ). + +comp_pk(undefined, undefined, undefined, Os) -> Os; +comp_pk( PrefAlgs, _, _, Os) when PrefAlgs =/= undefined -> Os; + +comp_pk(undefined, undefined, ssh_dsa, Os) -> comp_pk(undefined, undefined, 'ssh-dss', Os); +comp_pk(undefined, undefined, ssh_rsa, Os) -> comp_pk(undefined, undefined, 'ssh-rsa', Os); +comp_pk(undefined, undefined, PK, Os) -> + PKs = [PK | ssh_transport:supported_algorithms(public_key)--[PK]], + [{preferred_algorithms, [{public_key,PKs}] } | Os]; + +comp_pk(undefined, PrefPKs, _, Os) when PrefPKs =/= undefined -> + PKs = [case PK of + ssh_dsa -> 'ssh-dss'; + ssh_rsa -> 'ssh-rsa'; + _ -> PK + end || PK <- PrefPKs], + [{preferred_algorithms, [{public_key,PKs}]} | Os]. + + handle_option([], SocketOptions, SshOptions) -> {SocketOptions, SshOptions}; handle_option([{system_dir, _} = Opt | Rest], SocketOptions, SshOptions) -> @@ -279,8 +320,6 @@ handle_option([{silently_accept_hosts, _} = Opt | Rest], SocketOptions, SshOptio 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) -> @@ -297,10 +336,6 @@ handle_option([{pwdfun, _} = 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]); @@ -310,8 +345,12 @@ 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]); @@ -329,7 +368,9 @@ 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([{auth_method_kb_interactive_data, _} = 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([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); @@ -349,6 +390,8 @@ handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, S 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([Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). @@ -356,28 +399,17 @@ handle_option([Opt | Rest], SocketOptions, SshOptions) -> 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) -> - Opt; + check_dir(Opt); handle_ssh_option({user_dir, Value} = Opt) when is_list(Value) -> - Opt; + 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({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; -handle_ssh_option({public_key_alg, ssh_dsa}) -> - {public_key_alg, 'ssh-dss'}; -handle_ssh_option({public_key_alg, ssh_rsa}) -> - {public_key_alg, 'ssh-rsa'}; -handle_ssh_option({public_key_alg, Value} = Opt) when Value == 'ssh-rsa'; Value == 'ssh-dss' -> - Opt; -handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 -> - case handle_pref_algs(Value, []) of - {true, NewOpts} -> - NewOpts; - _ -> - throw({error, {eoptions, Opt}}) - end; +handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> + handle_pref_algs(Opt); 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 -> @@ -409,14 +441,25 @@ 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) -> +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}}); @@ -445,6 +488,8 @@ 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({profile, Value} = Opt) when is_atom(Value) -> + Opt; handle_ssh_option(Opt) -> throw({error, {eoptions, Opt}}). @@ -461,23 +506,83 @@ handle_inet_option({reuseaddr, _} = Opt) -> %% Option verified by inet handle_inet_option(Opt) -> Opt. + + %% Check preferred algs -handle_pref_algs([], Acc) -> - {true, lists:reverse(Acc)}; -handle_pref_algs([H|T], Acc) -> - case H of - ssh_dsa -> - handle_pref_algs(T, ['ssh-dss'| Acc]); - ssh_rsa -> - handle_pref_algs(T, ['ssh-rsa'| Acc]); - 'ssh-dss' -> - handle_pref_algs(T, ['ssh-dss'| Acc]); - 'ssh-rsa' -> - handle_pref_algs(T, ['ssh-rsa'| Acc]); - _ -> - false + +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 -> @@ -490,4 +595,31 @@ handle_ip(Inet) -> %% Default to ipv4 [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. + + + diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 0c4d34f89c..a02c87505d 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -31,6 +31,7 @@ -define(SSH_LENGHT_INDICATOR_SIZE, 4). -define(REKEY_TIMOUT, 3600000). -define(REKEY_DATA_TIMOUT, 60000). +-define(DEFAULT_PROFILE, default). -define(FALSE, 0). -define(TRUE, 1). @@ -127,8 +128,10 @@ user, service, userauth_quiet_mode, % boolean() - userauth_supported_methods , % - userauth_methods, + userauth_supported_methods, % string() eg "keyboard-interactive,password" + userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"] + kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive" + kb_data, userauth_preference, available_host_keys, authenticated = false diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 34988f17b6..6c431af270 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -21,6 +21,8 @@ -module(ssh_acceptor). +-include("ssh.hrl"). + %% Internal application API -export([start_link/5, number_of_connections/1]). @@ -82,8 +84,10 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) -> end. handle_connection(Callback, Address, Port, Options, Socket) -> - SystemSup = ssh_system_sup:system_supervisor(Address, Port), SSHopts = proplists:get_value(ssh_opts, Options, []), + Profile = proplists:get_value(profile, SSHopts, ?DEFAULT_PROFILE), + SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile), + MaxSessions = proplists:get_value(max_sessions,SSHopts,infinity), case number_of_connections(SystemSup) < MaxSessions of true -> diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 46fdef07d0..e101ce8b39 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -26,7 +26,9 @@ -module(ssh_acceptor_sup). -behaviour(supervisor). --export([start_link/1, start_child/2, stop_child/3]). +-include("ssh.hrl"). + +-export([start_link/1, start_child/2, stop_child/4]). %% Supervisor callback -export([init/1]). @@ -45,14 +47,16 @@ start_child(AccSup, ServerOpts) -> {error, already_present} -> Address = proplists:get_value(address, ServerOpts), Port = proplists:get_value(port, ServerOpts), - stop_child(AccSup, Address, Port), + Profile = proplists:get_value(profile, + proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), + stop_child(AccSup, Address, Port, Profile), supervisor:start_child(AccSup, Spec); Reply -> Reply end. -stop_child(AccSup, Address, Port) -> - Name = id(Address, Port), +stop_child(AccSup, Address, Port, Profile) -> + Name = id(Address, Port, Profile), case supervisor:terminate_child(AccSup, Name) of ok -> supervisor:delete_child(AccSup, Name); @@ -77,7 +81,8 @@ child_spec(ServerOpts) -> Address = proplists:get_value(address, ServerOpts), Port = proplists:get_value(port, ServerOpts), Timeout = proplists:get_value(timeout, ServerOpts, ?DEFAULT_TIMEOUT), - Name = id(Address, Port), + Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), + Name = id(Address, Port, Profile), SocketOpts = proplists:get_value(socket_opts, ServerOpts), StartFunc = {ssh_acceptor, start_link, [Port, Address, [{active, false}, @@ -89,6 +94,11 @@ child_spec(ServerOpts) -> Type = worker, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -id(Address, Port) -> - {ssh_acceptor_sup, Address, Port}. +id(Address, Port, Profile) -> + case is_list(Address) of + true -> + {ssh_acceptor_sup, any, Port, Profile}; + false -> + {ssh_acceptor_sup, Address, Port, Profile} + end. diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 45c4d52d7e..020fb06530 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -30,7 +30,8 @@ -export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, service_request_msg/1, init_userauth_request_msg/1, userauth_request_msg/1, handle_userauth_request/3, - handle_userauth_info_request/3, handle_userauth_info_response/2 + handle_userauth_info_request/3, handle_userauth_info_response/2, + default_public_key_algorithms/0 ]). %%-------------------------------------------------------------------- @@ -115,33 +116,16 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> service = "ssh-connection", method = "none", data = <<>>}, - case proplists:get_value(pref_public_key_algs, Opts, false) of - false -> - FirstAlg = proplists:get_value(public_key_alg, Opts, ?PREFERRED_PK_ALG), - SecondAlg = other_alg(FirstAlg), - Prefs = method_preference(FirstAlg, SecondAlg), - ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, - userauth_preference = Prefs, - userauth_methods = none, - service = "ssh-connection"}); - Algs -> - FirstAlg = lists:nth(1, Algs), - case length(Algs) =:= 2 of - true -> - SecondAlg = other_alg(FirstAlg), - Prefs = method_preference(FirstAlg, SecondAlg), - ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, - userauth_preference = Prefs, - userauth_methods = none, - service = "ssh-connection"}); - _ -> - Prefs = method_preference(FirstAlg), - ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, - userauth_preference = Prefs, - userauth_methods = none, - service = "ssh-connection"}) - end - end; + + + Algs = proplists:get_value(public_key, + proplists:get_value(preferred_algorithms, Opts, []), + default_public_key_algorithms()), + Prefs = method_preference(Algs), + ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, + userauth_preference = Prefs, + userauth_methods = none, + service = "ssh-connection"}); {error, no_user} -> ErrStr = "Could not determine the users name", throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, @@ -185,7 +169,8 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "password", data = <<?FALSE, ?UINT32(Sz), BinPwd:Sz/binary>>}, _, - #ssh{opts = Opts} = Ssh) -> + #ssh{opts = Opts, + userauth_supported_methods = Methods} = Ssh) -> Password = unicode:characters_to_list(BinPwd), case check_password(User, Password, Opts) of true -> @@ -194,7 +179,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, false -> {not_authorized, {User, {error,"Bad user or password"}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = "", + authentications = Methods, partial_success = false}, Ssh)} end; @@ -207,7 +192,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, %% ?UINT32(Sz2), NewBinPwd:Sz2/binary >> }, _, - Ssh) -> + #ssh{userauth_supported_methods = Methods} = Ssh) -> %% Password change without us having sent SSH_MSG_USERAUTH_PASSWD_CHANGEREQ (because we never do) %% RFC 4252 says: %% SSH_MSG_USERAUTH_FAILURE without partial success - The password @@ -216,7 +201,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, {not_authorized, {User, {error,"Password change not supported"}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = "", + authentications = Methods, partial_success = false}, Ssh)}; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -232,7 +217,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "publickey", data = Data}, - SessionId, #ssh{opts = Opts} = Ssh) -> + SessionId, + #ssh{opts = Opts, + userauth_supported_methods = Methods} = Ssh) -> <<?BYTE(HaveSig), ?UINT32(ALen), BAlg:ALen/binary, ?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data, Alg = binary_to_list(BAlg), @@ -247,7 +234,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, false -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications="publickey,password", + authentications = Methods, partial_success = false}, Ssh)} end; ?FALSE -> @@ -259,6 +246,65 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", + method = "keyboard-interactive", + data = _}, + _, #ssh{opts = Opts, + kb_tries_left = KbTriesLeft, + userauth_supported_methods = Methods} = Ssh) -> + case KbTriesLeft of + N when N<1 -> + {not_authorized, {User, {authmethod, "keyboard-interactive"}}, + ssh_transport:ssh_packet( + #ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh)}; + + _ -> + %% RFC4256 + %% The data field contains: + %% - language tag (deprecated). If =/=[] SHOULD use it however. We skip + %% it for simplicity. + %% - submethods. "... the user can give a hint of which actual methods + %% he wants to use. ...". It's a "MAY use" so we skip + %% it. It also needs an understanding between the client + %% and the server. + %% + %% "The server MUST reply with an SSH_MSG_USERAUTH_SUCCESS, + %% SSH_MSG_USERAUTH_FAILURE, or SSH_MSG_USERAUTH_INFO_REQUEST message." + Default = {"SSH server", + "Enter password for \""++User++"\"", + "password: ", + false}, + + {Name, Instruction, Prompt, Echo} = + case proplists:get_value(auth_method_kb_interactive_data, Opts) of + undefined -> + Default; + {_,_,_,_}=V -> + V; + F when is_function(F) -> + {_,PeerName} = Ssh#ssh.peer, + F(PeerName, User, "ssh-connection") + end, + EchoEnc = case Echo of + true -> <<?TRUE>>; + false -> <<?FALSE>> + end, + Msg = #ssh_msg_userauth_info_request{name = unicode:characters_to_list(Name), + instruction = unicode:characters_to_list(Instruction), + language_tag = "", + num_prompts = 1, + data = <<?STRING(unicode:characters_to_binary(Prompt)), + EchoEnc/binary + >> + }, + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, + kb_data = Msg + })} + end; + +handle_userauth_request(#ssh_msg_userauth_request{user = User, + service = "ssh-connection", method = Other}, _, #ssh{userauth_supported_methods = Methods} = Ssh) -> {not_authorized, {User, {authmethod, Other}}, @@ -280,6 +326,42 @@ handle_userauth_info_request( #ssh_msg_userauth_info_response{num_responses = NumPrompts, data = Responses}, Ssh)}. +handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, + data = <<?UINT32(Sz), Password:Sz/binary>>}, + #ssh{opts = Opts, + kb_tries_left = KbTriesLeft0, + kb_data = InfoMsg, + user = User, + userauth_supported_methods = Methods} = Ssh) -> + KbTriesLeft = KbTriesLeft0 - 1, + case check_password(User, unicode:characters_to_list(Password), Opts) of + true -> + {authorized, User, + ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)}; + false when KbTriesLeft > 0 -> + UserAuthInfoMsg = + InfoMsg#ssh_msg_userauth_info_request{ + name = "", + instruction = + lists:concat( + ["Bad user or password, try again. ", + integer_to_list(KbTriesLeft), + " tries left."]) + }, + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(UserAuthInfoMsg, + Ssh#ssh{kb_tries_left = KbTriesLeft})}; + + false -> + {not_authorized, {User, {error,"Bad user or password"}}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = Methods, + partial_success = false}, + Ssh#ssh{kb_data = undefined, + kb_tries_left = 0} + )} + end; + handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, @@ -287,20 +369,20 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, "keyboard-interactive", language = "en"}). + +default_public_key_algorithms() -> ?PREFERRED_PK_ALGS. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -method_preference(Alg1, Alg2) -> - [{"publickey", ?MODULE, publickey_msg, [Alg1]}, - {"publickey", ?MODULE, publickey_msg,[Alg2]}, - {"password", ?MODULE, password_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} - ]. -method_preference(Alg1) -> - [{"publickey", ?MODULE, publickey_msg, [Alg1]}, - {"password", ?MODULE, password_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} - ]. +method_preference(Algs) -> + lists:foldr(fun(A, Acc) -> + [{"publickey", ?MODULE, publickey_msg, [A]} | Acc] + end, + [{"password", ?MODULE, password_msg, []}, + {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} + ], + Algs). user_name(Opts) -> Env = case os:type() of @@ -418,27 +500,17 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> language = "en"}}) end. -other_alg('ssh-rsa') -> - 'ssh-dss'; -other_alg('ssh-dss') -> - 'ssh-rsa'. decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, - ?UINT32(Len1), BinE:Len1/binary, - ?UINT32(Len2), BinN:Len2/binary>> + ?UINT32(Len1), E:Len1/big-signed-integer-unit:8, + ?UINT32(Len2), N:Len2/big-signed-integer-unit:8>> ,"ssh-rsa") -> - E = ssh_bits:erlint(Len1, BinE), - N = ssh_bits:erlint(Len2, BinN), {ok, #'RSAPublicKey'{publicExponent = E, modulus = N}}; decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, - ?UINT32(Len1), BinP:Len1/binary, - ?UINT32(Len2), BinQ:Len2/binary, - ?UINT32(Len3), BinG:Len3/binary, - ?UINT32(Len4), BinY:Len4/binary>> + ?UINT32(Len1), P:Len1/big-signed-integer-unit:8, + ?UINT32(Len2), Q:Len2/big-signed-integer-unit:8, + ?UINT32(Len3), G:Len3/big-signed-integer-unit:8, + ?UINT32(Len4), Y:Len4/big-signed-integer-unit:8>> , "ssh-dss") -> - P = ssh_bits:erlint(Len1, BinP), - Q = ssh_bits:erlint(Len2, BinQ), - G = ssh_bits:erlint(Len3, BinG), - Y = ssh_bits:erlint(Len4, BinY), {ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}}; decode_public_key_v2(_, _) -> diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl index 6cd8e6bf14..764c9f4246 100644 --- a/lib/ssh/src/ssh_auth.hrl +++ b/lib/ssh/src/ssh_auth.hrl @@ -23,7 +23,7 @@ -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). --define(PREFERRED_PK_ALG, 'ssh-rsa'). +-define(PREFERRED_PK_ALGS, ['ssh-rsa','ssh-dss']). -define(SSH_MSG_USERAUTH_REQUEST, 50). -define(SSH_MSG_USERAUTH_FAILURE, 51). diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl index 8aaff93b9f..d5f8df6fe4 100644 --- a/lib/ssh/src/ssh_bits.erl +++ b/lib/ssh/src/ssh_bits.erl @@ -26,7 +26,7 @@ -include("ssh.hrl"). -export([encode/2]). --export([mpint/1, erlint/2, string/1, name_list/1]). +-export([mpint/1, string/1, name_list/1]). -export([random/1]). -define(name_list(X), @@ -145,11 +145,7 @@ enc(Xs, ['...'| []], _Offset) -> enc([], [],_) -> []. -erlint(Len, BinInt) -> - Sz = Len*8, - <<Int:Sz/big-signed-integer>> = BinInt, - Int. - + %% %% Create a binary with constant bytes %% diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4dea284071..e6e5749e07 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -33,7 +33,7 @@ -include("ssh_transport.hrl"). -include("ssh_auth.hrl"). -include("ssh_connect.hrl"). - +-compile(export_all). -export([start_link/3]). %% Internal application API @@ -71,6 +71,7 @@ key_exchange_init_msg, % #ssh_msg_kexinit{} renegotiate = false, % boolean() last_size_rekey = 0, + event_queue = [], connection_queue, address, port, @@ -83,6 +84,11 @@ {next_state, state_name(), term(), timeout()} | {stop, term(), term()}. +-type gen_fsm_sync_return() :: {next_state, state_name(), term()} | + {next_state, state_name(), term(), timeout()} | + {reply, term(), state_name(), term()} | + {stop, term(), term(), term()}. + %%==================================================================== %% Internal application API %%==================================================================== @@ -327,22 +333,25 @@ info(ConnectionHandler, ChannelProcess) -> hello(socket_control, #state{socket = Socket, ssh_params = Ssh} = State) -> VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)), send_msg(VsnMsg, State), - {ok, [{recbuf, Size}]} = inet:getopts(Socket, [recbuf]), - inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]), - {next_state, hello, State#state{recbuf = Size}}; + case getopt(recbuf, Socket) of + {ok, Size} -> + inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]), + {next_state, hello, State#state{recbuf = Size}}; + {error, Reason} -> + {stop, {shutdown, Reason}, State} + end; hello({info_line, _Line},#state{role = client, socket = Socket} = State) -> %% The server may send info lines before the version_exchange inet:setopts(Socket, [{active, once}]), {next_state, hello, State}; -hello({info_line, _Line},#state{role = server} = State) -> - DisconnectMsg = - #ssh_msg_disconnect{code = - ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Did not receive expected protocol version exchange", - language = "en"}, - handle_disconnect(DisconnectMsg, State); +hello({info_line, _Line},#state{role = server, + socket = Socket, + transport_cb = Transport } = State) -> + %% as openssh + Transport:send(Socket, "Protocol mismatch."), + {stop, {shutdown,"Protocol mismatch in version exchange."}, State}; hello({version_exchange, Version}, #state{ssh_params = Ssh0, socket = Socket, @@ -433,9 +442,7 @@ key_exchange(#ssh_msg_kex_dh_gex_reply{} = Msg, new_keys(#ssh_msg_newkeys{} = Msg, #state{ssh_params = Ssh0} = State0) -> {ok, Ssh} = ssh_transport:handle_new_keys(Msg, Ssh0), - {NextStateName, State} = - after_new_keys(State0#state{ssh_params = Ssh}), - {next_state, NextStateName, next_packet(State)}. + after_new_keys(next_packet(State0#state{ssh_params = Ssh})). %%-------------------------------------------------------------------- -spec userauth(#ssh_msg_service_request{} | #ssh_msg_service_accept{} | @@ -476,17 +483,22 @@ userauth(#ssh_msg_userauth_request{service = "ssh-connection", service = "ssh-connection", peer = {_, Address}} = Ssh0, opts = Opts, starter = Pid} = State) -> - case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of - {authorized, User, {Reply, Ssh}} -> - send_msg(Reply, State), - Pid ! ssh_connected, - connected_fun(User, Address, Method, Opts), - {next_state, connected, - next_packet(State#state{auth_user = User, ssh_params = Ssh})}; - {not_authorized, {User, Reason}, {Reply, Ssh}} -> - retry_fun(User, Address, Reason, Opts), - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + case lists:member(Method, Ssh0#ssh.userauth_methods) of + true -> + case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of + {authorized, User, {Reply, Ssh}} -> + send_msg(Reply, State), + Pid ! ssh_connected, + connected_fun(User, Address, Method, Opts), + {next_state, connected, + next_packet(State#state{auth_user = User, ssh_params = Ssh})}; + {not_authorized, {User, Reason}, {Reply, Ssh}} -> + retry_fun(User, Address, Reason, Opts), + send_msg(Reply, State), + {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + end; + false -> + userauth(Msg#ssh_msg_userauth_request{method="none"}, State) end; userauth(#ssh_msg_userauth_info_request{} = Msg, @@ -497,10 +509,21 @@ userauth(#ssh_msg_userauth_info_request{} = Msg, {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}; userauth(#ssh_msg_userauth_info_response{} = Msg, - #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response(Msg, Ssh0), - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}; + #state{ssh_params = #ssh{role = server, + peer = {_, Address}} = Ssh0, + opts = Opts, starter = Pid} = State) -> + case ssh_auth:handle_userauth_info_response(Msg, Ssh0) of + {authorized, User, {Reply, Ssh}} -> + send_msg(Reply, State), + Pid ! ssh_connected, + connected_fun(User, Address, "keyboard-interactive", Opts), + {next_state, connected, + next_packet(State#state{auth_user = User, ssh_params = Ssh})}; + {not_authorized, {User, Reason}, {Reply, Ssh}} -> + retry_fun(User, Address, Reason, Opts), + send_msg(Reply, State), + {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + end; userauth(#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client} = Ssh, starter = Pid} = State) -> @@ -559,11 +582,13 @@ userauth(#ssh_msg_userauth_banner{message = Msg}, -spec connected({#ssh_msg_kexinit{}, binary()}, %%| %% #ssh_msg_kexdh_init{}, #state{}) -> gen_fsm_state_return(). %%-------------------------------------------------------------------- -connected({#ssh_msg_kexinit{}, _Payload} = Event, State) -> - kexinit(Event, State#state{renegotiate = true}). -%% ; -%% connected(#ssh_msg_kexdh_init{} = Event, State) -> -%% key_exchange(Event, State#state{renegotiate = true}). +connected({#ssh_msg_kexinit{}, _Payload} = Event, #state{ssh_params = Ssh0} = State0) -> + {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), + State = State0#state{ssh_params = Ssh, + key_exchange_init_msg = KeyInitMsg, + renegotiate = true}, + send_msg(SshPacket, State), + kexinit(Event, State). %%-------------------------------------------------------------------- -spec handle_event(#ssh_msg_disconnect{} | #ssh_msg_ignore{} | #ssh_msg_debug{} | @@ -581,44 +606,17 @@ handle_event(#ssh_msg_disconnect{description = Desc} = DisconnectMsg, _StateName handle_event(#ssh_msg_ignore{}, StateName, State) -> {next_state, StateName, next_packet(State)}; -handle_event(#ssh_msg_debug{always_display = true, message = DbgMsg}, - StateName, State) -> - io:format("DEBUG: ~p\n", [DbgMsg]), - {next_state, StateName, next_packet(State)}; - -handle_event(#ssh_msg_debug{}, StateName, State) -> +handle_event(#ssh_msg_debug{always_display = Display, message = DbgMsg, language=Lang}, + StateName, #state{opts = Opts} = State) -> + F = proplists:get_value(ssh_msg_debug_fun, Opts, + fun(_ConnRef, _AlwaysDisplay, _Msg, _Language) -> ok end + ), + catch F(self(), Display, DbgMsg, Lang), {next_state, StateName, next_packet(State)}; handle_event(#ssh_msg_unimplemented{}, StateName, State) -> {next_state, StateName, next_packet(State)}; -handle_event({adjust_window, ChannelId, Bytes}, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> - State = - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{recv_window_size = WinSize, remote_id = Id} = Channel -> - ssh_channel:cache_update(Cache, Channel#channel{recv_window_size = - WinSize + Bytes}), - Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes), - send_replies([{connection_reply, Msg}], State0); - undefined -> - State0 - end, - {next_state, StateName, next_packet(State)}; - -handle_event({reply_request, success, ChannelId}, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> - State = case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = RemoteId} -> - Msg = ssh_connection:channel_success_msg(RemoteId), - send_replies([{connection_reply, Msg}], State0); - undefined -> - State0 - end, - {next_state, StateName, State}; - handle_event(renegotiate, connected, #state{ssh_params = Ssh0} = State) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), @@ -630,8 +628,7 @@ handle_event(renegotiate, connected, #state{ssh_params = Ssh0} renegotiate = true})}; handle_event(renegotiate, StateName, State) -> - timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]), - %% Allready in keyexcahange so ignore + %% Already in key-exchange so safe to ignore {next_state, StateName, State}; %% Rekey due to sent data limit reached? @@ -653,6 +650,38 @@ handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) -> {next_state, connected, next_packet(State)} end; handle_event(data_size, StateName, State) -> + %% Already in key-exchange so safe to ignore + {next_state, StateName, State}; + +handle_event(Event, StateName, State) when StateName /= connected -> + Events = [{event, Event} | State#state.event_queue], + {next_state, StateName, State#state{event_queue = Events}}; + +handle_event({adjust_window, ChannelId, Bytes}, StateName, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + State = + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{recv_window_size = WinSize, remote_id = Id} = Channel -> + ssh_channel:cache_update(Cache, Channel#channel{recv_window_size = + WinSize + Bytes}), + Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes), + send_replies([{connection_reply, Msg}], State0); + undefined -> + State0 + end, + {next_state, StateName, next_packet(State)}; + +handle_event({reply_request, success, ChannelId}, StateName, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + State = case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = RemoteId} -> + Msg = ssh_connection:channel_success_msg(RemoteId), + send_replies([{connection_reply, Msg}], State0); + undefined -> + State0 + end, {next_state, StateName, State}; handle_event({request, ChannelPid, ChannelId, Type, Data}, StateName, State0) -> @@ -683,8 +712,62 @@ handle_event({unknown, Data}, StateName, State) -> sockname]} | {channel_info, channel_id(), [recv_window | send_window]} | {close, channel_id()} | stop, term(), state_name(), #state{}) - -> gen_fsm_state_return(). + -> gen_fsm_sync_return(). %%-------------------------------------------------------------------- +handle_sync_event(get_print_info, _From, StateName, State) -> + Reply = + try + {inet:sockname(State#state.socket), + inet:peername(State#state.socket) + } + of + {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])}; + _ -> {{"-",0},"-"} + catch + _:_ -> {{"?",0},"?"} + end, + {reply, Reply, StateName, State}; + +handle_sync_event({connection_info, Options}, _From, StateName, State) -> + Info = ssh_info(Options, State, []), + {reply, Info, StateName, State}; + +handle_sync_event({channel_info, ChannelId, Options}, _From, StateName, + #state{connection_state = #connection{channel_cache = Cache}} = State) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{} = Channel -> + Info = ssh_channel_info(Options, Channel, []), + {reply, Info, StateName, State}; + undefined -> + {reply, [], StateName, State} + end; + +handle_sync_event({info, ChannelPid}, _From, StateName, + #state{connection_state = + #connection{channel_cache = Cache}} = State) -> + Result = ssh_channel:cache_foldl( + fun(Channel, Acc) when ChannelPid == all; + Channel#channel.user == ChannelPid -> + [Channel | Acc]; + (_, Acc) -> + Acc + end, [], Cache), + {reply, {ok, Result}, StateName, State}; + +handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0, + role = Role} = State0) -> + {disconnect, _Reason, {{replies, Replies}, Connection}} = + ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "User closed down connection", + language = "en"}, Connection0, Role), + State = send_replies(Replies, State0), + {stop, normal, ok, State#state{connection_state = Connection}}; + + +handle_sync_event(Event, From, StateName, State) when StateName /= connected -> + Events = [{sync, Event, From} | State#state.event_queue], + {next_state, StateName, State#state{event_queue = Events}}; + handle_sync_event({request, ChannelPid, ChannelId, Type, Data, Timeout}, From, StateName, State0) -> {{replies, Replies}, State1} = handle_request(ChannelPid, ChannelId, Type, Data, @@ -787,46 +870,6 @@ handle_sync_event({recv_window, ChannelId}, _From, StateName, end, {reply, Reply, StateName, next_packet(State)}; -handle_sync_event(get_print_info, _From, StateName, State) -> - Reply = - try - {inet:sockname(State#state.socket), - inet:peername(State#state.socket) - } - of - {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])}; - _ -> {{"-",0},"-"} - catch - _:_ -> {{"?",0},"?"} - end, - {reply, Reply, StateName, State}; - -handle_sync_event({connection_info, Options}, _From, StateName, State) -> - Info = ssh_info(Options, State, []), - {reply, Info, StateName, State}; - -handle_sync_event({channel_info, ChannelId, Options}, _From, StateName, - #state{connection_state = #connection{channel_cache = Cache}} = State) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{} = Channel -> - Info = ssh_channel_info(Options, Channel, []), - {reply, Info, StateName, State}; - undefined -> - {reply, [], StateName, State} - end; - -handle_sync_event({info, ChannelPid}, _From, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State) -> - Result = ssh_channel:cache_foldl( - fun(Channel, Acc) when ChannelPid == all; - Channel#channel.user == ChannelPid -> - [Channel | Acc]; - (_, Acc) -> - Acc - end, [], Cache), - {reply, {ok, Result}, StateName, State}; - handle_sync_event({close, ChannelId}, _, StateName, #state{connection_state = #connection{channel_cache = Cache}} = State0) -> @@ -841,19 +884,7 @@ handle_sync_event({close, ChannelId}, _, StateName, undefined -> State0 end, - {reply, ok, StateName, next_packet(State)}; - -handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0, - role = Role, - opts = Opts} = State0) -> - {disconnect, Reason, {{replies, Replies}, Connection}} = - ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "User closed down connection", - language = "en"}, Connection0, Role), - State = send_replies(Replies, State0), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), - {stop, normal, ok, State#state{connection_state = Connection}}. + {reply, ok, StateName, next_packet(State)}. %%-------------------------------------------------------------------- -spec handle_info({atom(), port(), binary()} | {atom(), port()} | @@ -958,15 +989,38 @@ handle_info({check_cache, _ , _}, #connection{channel_cache = Cache}} = State) -> {next_state, StateName, check_cache(State, Cache)}; -handle_info(UnexpectedMessage, StateName, #state{ssh_params = SshParams} = State) -> - Msg = lists:flatten(io_lib:format( - "Unexpected message '~p' received in state '~p'\n" - "Role: ~p\n" - "Peer: ~p\n" - "Local Address: ~p\n", [UnexpectedMessage, StateName, - SshParams#ssh.role, SshParams#ssh.peer, - proplists:get_value(address, SshParams#ssh.opts)])), - error_logger:info_report(Msg), +handle_info(UnexpectedMessage, StateName, #state{opts = Opts, + ssh_params = SshParams} = State) -> + case unexpected_fun(UnexpectedMessage, Opts, SshParams) of + report -> + Msg = lists:flatten( + io_lib:format( + "Unexpected message '~p' received in state '~p'\n" + "Role: ~p\n" + "Peer: ~p\n" + "Local Address: ~p\n", [UnexpectedMessage, StateName, + SshParams#ssh.role, SshParams#ssh.peer, + proplists:get_value(address, SshParams#ssh.opts)])), + error_logger:info_report(Msg); + + skip -> + ok; + + Other -> + Msg = lists:flatten( + io_lib:format("Call to fun in 'unexpectedfun' failed:~n" + "Return: ~p\n" + "Message: ~p\n" + "Role: ~p\n" + "Peer: ~p\n" + "Local Address: ~p\n", [Other, UnexpectedMessage, + SshParams#ssh.role, + element(2,SshParams#ssh.peer), + proplists:get_value(address, SshParams#ssh.opts)] + )), + + error_logger:error_report(Msg) + end, {next_state, StateName, State}. %%-------------------------------------------------------------------- @@ -1122,9 +1176,9 @@ init_ssh(client = Role, Vsn, Version, Options, Socket) -> }; init_ssh(server = Role, Vsn, Version, Options, Socket) -> - AuthMethods = proplists:get_value(auth_methods, Options, ?SUPPORTED_AUTH_METHODS), + AuthMethodsAsList = string:tokens(AuthMethods, ","), {ok, PeerAddr} = inet:peername(Socket), KeyCb = proplists:get_value(key_cb, Options, ssh_file), @@ -1135,60 +1189,46 @@ init_ssh(server = Role, Vsn, Version, Options, Socket) -> io_cb = proplists:get_value(io_cb, Options, ssh_io), opts = Options, userauth_supported_methods = AuthMethods, + userauth_methods = AuthMethodsAsList, + kb_tries_left = 3, peer = {undefined, PeerAddr}, available_host_keys = supported_host_keys(Role, KeyCb, Options) }. supported_host_keys(client, _, Options) -> try - case extract_algs(proplists:get_value(pref_public_key_algs, Options, false), []) of - false -> - ["ssh-rsa", "ssh-dss"]; - Algs -> - Algs + case proplists:get_value(public_key, + proplists:get_value(preferred_algorithms,Options,[]) + ) of + undefined -> + ssh_auth:default_public_key_algorithms(); + L -> + L -- (L--ssh_auth:default_public_key_algorithms()) end + of + [] -> + {stop, {shutdown, "No public key algs"}}; + Algs -> + [atom_to_list(A) || A<-Algs] catch exit:Reason -> {stop, {shutdown, Reason}} end; supported_host_keys(server, KeyCb, Options) -> - lists:foldl(fun(Type, Acc) -> - case available_host_key(KeyCb, Type, Options) of - {error, _} -> - Acc; - Alg -> - [Alg | Acc] - end - end, [], - %% Prefered alg last so no need to reverse - ["ssh-dss", "ssh-rsa"]). -extract_algs(false, _) -> - false; -extract_algs([],[]) -> - false; -extract_algs([], NewList) -> - lists:reverse(NewList); -extract_algs([H|T], NewList) -> - case H of - 'ssh-dss' -> - extract_algs(T, ["ssh-dss"|NewList]); - 'ssh-rsa' -> - extract_algs(T, ["ssh-rsa"|NewList]) - end. -available_host_key(KeyCb, "ssh-dss"= Alg, Opts) -> - case KeyCb:host_key('ssh-dss', Opts) of - {ok, _} -> - Alg; - Other -> - Other - end; -available_host_key(KeyCb, "ssh-rsa" = Alg, Opts) -> - case KeyCb:host_key('ssh-rsa', Opts) of - {ok, _} -> - Alg; - Other -> - Other - end. + Algs= + [atom_to_list(A) || A <- proplists:get_value(public_key, + proplists:get_value(preferred_algorithms,Options,[]), + ssh_auth:default_public_key_algorithms() + ), + available_host_key(KeyCb, A, Options) + ], + Algs. + + +%% Alg :: atom() +available_host_key(KeyCb, Alg, Opts) -> + element(1, catch KeyCb:host_key(Alg, Opts)) == ok. + send_msg(Msg, #state{socket = Socket, transport_cb = Transport}) -> Transport:send(Socket, Msg). @@ -1262,7 +1302,6 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName, #state{ role = Role, starter = User, - opts = Opts, renegotiate = Renegotiation, connection_state = Connection0} = State0, EncData) when Byte == ?SSH_MSG_GLOBAL_REQUEST; @@ -1282,8 +1321,17 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName, ConnectionMsg = ssh_message:decode(Msg), State1 = generate_event_new_state(State0, EncData), try ssh_connection:handle_msg(ConnectionMsg, Connection0, Role) of - {{replies, Replies}, Connection} -> - State = send_replies(Replies, State1#state{connection_state = Connection}), + {{replies, Replies0}, Connection} -> + if StateName == connected -> + Replies = Replies0, + State2 = State1; + true -> + {ConnReplies, Replies} = + lists:splitwith(fun not_connected_filter/1, Replies0), + Q = State1#state.event_queue ++ ConnReplies, + State2 = State1#state{ event_queue = Q } + end, + State = send_replies(Replies, State2#state{connection_state = Connection}), {next_state, StateName, next_packet(State)}; {noreply, Connection} -> {next_state, StateName, next_packet(State1#state{connection_state = Connection})}; @@ -1293,21 +1341,17 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName, User ! {self(), not_connected, Reason}, {stop, {shutdown, normal}, next_packet(State#state{connection_state = Connection})}; - {disconnect, Reason, {{replies, Replies}, Connection}} -> + {disconnect, _Reason, {{replies, Replies}, Connection}} -> State = send_replies(Replies, State1#state{connection_state = Connection}), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), {stop, {shutdown, normal}, State#state{connection_state = Connection}} catch _:Error -> - {disconnect, Reason, {{replies, Replies}, Connection}} = + {disconnect, _Reason, {{replies, Replies}, Connection}} = ssh_connection:handle_msg( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, description = "Internal error", language = "en"}, Connection0, Role), State = send_replies(Replies, State1#state{connection_state = Connection}), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), {stop, {shutdown, Error}, State#state{connection_state = Connection}} end; @@ -1456,15 +1500,43 @@ next_packet(#state{socket = Socket} = State) -> State. after_new_keys(#state{renegotiate = true} = State) -> - {connected, State#state{renegotiate = false}}; + State1 = State#state{renegotiate = false, event_queue = []}, + lists:foldr(fun after_new_keys_events/2, {next_state, connected, State1}, State#state.event_queue); after_new_keys(#state{renegotiate = false, ssh_params = #ssh{role = client} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:service_request_msg(Ssh0), send_msg(Msg, State), - {userauth, State#state{ssh_params = Ssh}}; + {next_state, userauth, State#state{ssh_params = Ssh}}; after_new_keys(#state{renegotiate = false, ssh_params = #ssh{role = server}} = State) -> - {userauth, State}. + {next_state, userauth, State}. + +after_new_keys_events({sync, _Event, From}, {stop, _Reason, _StateData}=Terminator) -> + gen_fsm:reply(From, {error, closed}), + Terminator; +after_new_keys_events(_, {stop, _Reason, _StateData}=Terminator) -> + Terminator; +after_new_keys_events({sync, Event, From}, {next_state, StateName, StateData}) -> + case handle_sync_event(Event, From, StateName, StateData) of + {reply, Reply, NextStateName, NewStateData} -> + gen_fsm:reply(From, Reply), + {next_state, NextStateName, NewStateData}; + {next_state, NextStateName, NewStateData}-> + {next_state, NextStateName, NewStateData}; + {stop, Reason, Reply, NewStateData} -> + gen_fsm:reply(From, Reply), + {stop, Reason, NewStateData} + end; +after_new_keys_events({event, Event}, {next_state, StateName, StateData}) -> + case handle_event(Event, StateName, StateData) of + {next_state, NextStateName, NewStateData}-> + {next_state, NextStateName, NewStateData}; + {stop, Reason, NewStateData} -> + {stop, Reason, NewStateData} + end; +after_new_keys_events({connection_reply, _Data} = Reply, {StateName, State}) -> + NewState = send_replies([Reply], State), + {next_state, StateName, NewState}. handle_ssh_packet_data(RemainingSshPacketLen, DecData, EncData, StateName, State) -> @@ -1526,12 +1598,14 @@ handle_disconnect(#ssh_msg_disconnect{} = DisconnectMsg, State, Error) -> handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, role = Role} = State0) -> {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role), State = send_replies(disconnect_replies(Type, Msg, Replies), State0), + disconnect_fun(Desc, State#state.opts), {stop, {shutdown, Desc}, State#state{connection_state = Connection}}. handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, role = Role} = State0, ErrorMsg) -> {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role), State = send_replies(disconnect_replies(Type, Msg, Replies), State0), + disconnect_fun(Desc, State#state.opts), {stop, {shutdown, {Desc, ErrorMsg}}, State#state{connection_state = Connection}}. disconnect_replies(own, Msg, Replies) -> @@ -1625,6 +1699,11 @@ log_error(Reason) -> error_logger:error_report(Report), "Internal error". +not_connected_filter({connection_reply, _Data}) -> + true; +not_connected_filter(_) -> + false. + send_replies([], State) -> State; send_replies([{connection_reply, Data} | Rest], #state{ssh_params = Ssh0} = State) -> @@ -1645,6 +1724,8 @@ send_reply({flow_control, Cache, Channel, From, Msg}) -> send_reply({flow_control, From, Msg}) -> gen_fsm:reply(From, Msg). +disconnect_fun({disconnect,Msg}, Opts) -> + disconnect_fun(Msg, Opts); disconnect_fun(_, undefined) -> ok; disconnect_fun(Reason, Opts) -> @@ -1655,6 +1736,15 @@ disconnect_fun(Reason, Opts) -> catch Fun(Reason) end. +unexpected_fun(UnexpectedMessage, Opts, #ssh{peer={_,Peer}}) -> + case proplists:get_value(unexpectedfun, Opts) of + undefined -> + report; + Fun -> + catch Fun(UnexpectedMessage, Peer) + end. + + check_cache(#state{opts = Opts} = State, Cache) -> %% Check the number of entries in Cache case proplists:get_value(size, ets:info(Cache)) of @@ -1722,3 +1812,12 @@ start_timeout(_,_, infinity) -> ok; start_timeout(Channel, From, Time) -> erlang:send_after(Time, self(), {timeout, {Channel, From}}). + +getopt(Opt, Socket) -> + case inet:getopts(Socket, [Opt]) of + {ok, [{Opt, Value}]} -> + {ok, Value}; + Other -> + {error, {unexpected_getopts_return, Other}} + end. + diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 66e7717095..483c6cb4aa 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -421,8 +421,8 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) -> decode(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) -> decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10); -decode(<<?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/binary>>) -> - #ssh_msg_kexdh_init{e = erlint(Len, E) +decode(<<?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/big-signed-integer-unit:8>>) -> + #ssh_msg_kexdh_init{e = E }; decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST), ?UINT32(Min), ?UINT32(N), ?UINT32(Max)>>) -> #ssh_msg_kex_dh_gex_request{ @@ -442,11 +442,11 @@ decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), g = Generator }; decode(<<?BYTE(?SSH_MSG_KEXDH_REPLY), ?UINT32(Len0), Key:Len0/binary, - ?UINT32(Len1), F:Len1/binary, + ?UINT32(Len1), F:Len1/big-signed-integer-unit:8, ?UINT32(Len2), Hashsign:Len2/binary>>) -> #ssh_msg_kexdh_reply{ public_host_key = decode_host_key(Key), - f = erlint(Len1, F), + f = F, h_sig = decode_sign(Hashsign) }; @@ -514,10 +514,7 @@ decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) -> Names = string:tokens(unicode:characters_to_list(Data), ","), decode_kex_init(Rest, [Names | Acc], N -1). -erlint(MPIntSize, MPIntValue) -> - Bits = MPIntSize * 8, - <<Integer:Bits/integer>> = MPIntValue, - Integer. + decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) -> Signature. @@ -525,18 +522,19 @@ decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) -> decode_host_key(<<?UINT32(Len), Alg:Len/binary, Rest/binary>>) -> decode_host_key(Alg, Rest). -decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/binary, - ?UINT32(Len1), N:Len1/binary>>) -> - #'RSAPublicKey'{publicExponent = erlint(Len0, E), - modulus = erlint(Len1, N)}; +decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/big-signed-integer-unit:8, + ?UINT32(Len1), N:Len1/big-signed-integer-unit:8>>) -> + #'RSAPublicKey'{publicExponent = E, + modulus = N}; decode_host_key(<<"ssh-dss">>, - <<?UINT32(Len0), P:Len0/binary, - ?UINT32(Len1), Q:Len1/binary, - ?UINT32(Len2), G:Len2/binary, - ?UINT32(Len3), Y:Len3/binary>>) -> - {erlint(Len3, Y), #'Dss-Parms'{p = erlint(Len0, P), q = erlint(Len1, Q), - g = erlint(Len2, G)}}. + <<?UINT32(Len0), P:Len0/big-signed-integer-unit:8, + ?UINT32(Len1), Q:Len1/big-signed-integer-unit:8, + ?UINT32(Len2), G:Len2/big-signed-integer-unit:8, + ?UINT32(Len3), Y:Len3/big-signed-integer-unit:8>>) -> + {Y, #'Dss-Parms'{p = P, + q = Q, + g = G}}. encode_host_key(#'RSAPublicKey'{modulus = N, publicExponent = E}) -> ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]); diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 660fe8bb65..acf94b4b73 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -28,13 +28,15 @@ -behaviour(supervisor). +-include("ssh.hrl"). + -export([start_link/1, stop_listener/1, - stop_listener/2, stop_system/1, - stop_system/2, system_supervisor/2, + stop_listener/3, stop_system/1, + stop_system/3, system_supervisor/3, subsystem_supervisor/1, channel_supervisor/1, connection_supervisor/1, - acceptor_supervisor/1, start_subsystem/2, restart_subsystem/2, - restart_acceptor/2, stop_subsystem/2]). + acceptor_supervisor/1, start_subsystem/2, restart_subsystem/3, + restart_acceptor/3, stop_subsystem/2]). %% Supervisor callback -export([init/1]). @@ -45,14 +47,15 @@ start_link(ServerOpts) -> Address = proplists:get_value(address, ServerOpts), Port = proplists:get_value(port, ServerOpts), - Name = make_name(Address, Port), + Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), + Name = make_name(Address, Port, Profile), supervisor:start_link({local, Name}, ?MODULE, [ServerOpts]). stop_listener(SysSup) -> stop_acceptor(SysSup). -stop_listener(Address, Port) -> - Name = make_name(Address, Port), +stop_listener(Address, Port, Profile) -> + Name = make_name(Address, Port, Profile), stop_acceptor(whereis(Name)). stop_system(SysSup) -> @@ -60,12 +63,12 @@ stop_system(SysSup) -> spawn(fun() -> sshd_sup:stop_child(Name) end), ok. -stop_system(Address, Port) -> - spawn(fun() -> sshd_sup:stop_child(Address, Port) end), +stop_system(Address, Port, Profile) -> + spawn(fun() -> sshd_sup:stop_child(Address, Port, Profile) end), ok. -system_supervisor(Address, Port) -> - Name = make_name(Address, Port), +system_supervisor(Address, Port, Profile) -> + Name = make_name(Address, Port, Profile), whereis(Name). subsystem_supervisor(SystemSup) -> @@ -103,9 +106,9 @@ stop_subsystem(SystemSup, SubSys) -> end. -restart_subsystem(Address, Port) -> - SysSupName = make_name(Address, Port), - SubSysName = id(ssh_subsystem_sup, Address, Port), +restart_subsystem(Address, Port, Profile) -> + SysSupName = make_name(Address, Port, Profile), + SubSysName = id(ssh_subsystem_sup, Address, Port, Profile), case supervisor:terminate_child(SysSupName, SubSysName) of ok -> supervisor:restart_child(SysSupName, SubSysName); @@ -113,9 +116,9 @@ restart_subsystem(Address, Port) -> Error end. -restart_acceptor(Address, Port) -> - SysSupName = make_name(Address, Port), - AcceptorName = id(ssh_acceptor_sup, Address, Port), +restart_acceptor(Address, Port, Profile) -> + SysSupName = make_name(Address, Port, Profile), + AcceptorName = id(ssh_acceptor_sup, Address, Port, Profile), supervisor:restart_child(SysSupName, AcceptorName). %%%========================================================================= @@ -137,7 +140,8 @@ child_specs(ServerOpts) -> ssh_acceptor_child_spec(ServerOpts) -> Address = proplists:get_value(address, ServerOpts), Port = proplists:get_value(port, ServerOpts), - Name = id(ssh_acceptor_sup, Address, Port), + Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), + Name = id(ssh_acceptor_sup, Address, Port, Profile), StartFunc = {ssh_acceptor_sup, start_link, [ServerOpts]}, Restart = transient, Shutdown = infinity, @@ -155,12 +159,23 @@ ssh_subsystem_child_spec(ServerOpts) -> {Name, StartFunc, Restart, Shutdown, Type, Modules}. -id(Sup, Address, Port) -> - {Sup, Address, Port}. - -make_name(Address, Port) -> - list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_sup", - [Address, Port]))). +id(Sup, Address, Port, Profile) -> + case is_list(Address) of + true -> + {Sup, any, Port, Profile}; + false -> + {Sup, Address, Port, Profile} + end. + +make_name(Address, Port, Profile) -> + case is_list(Address) of + true -> + list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_~p_sup", + [any, Port, Profile]))); + false -> + list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_~p_sup", + [Address, Port, Profile]))) + end. ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) -> Child; @@ -178,3 +193,4 @@ stop_acceptor(Sup) -> supervisor:which_children(Sup)], supervisor:terminate_child(AcceptorSup, Name). + diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index d6414bab6c..ea9bca2390 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -31,6 +31,8 @@ -export([versions/2, hello_version_msg/1]). -export([next_seqnum/1, decrypt_first_block/2, decrypt_blocks/3, + supported_algorithms/0, supported_algorithms/1, + default_algorithms/0, default_algorithms/1, is_valid_mac/3, handle_hello_version/1, key_exchange_init_msg/1, @@ -42,6 +44,68 @@ unpack/3, decompress/2, ssh_packet/2, pack/2, msg_data/1, sign/3, verify/4]). +%%%---------------------------------------------------------------------------- +%%% +%%% There is a difference between supported and default algorithms. The +%%% SUPPORTED algorithms can be handled (maybe untested...). The DEFAULT ones +%%% are announced in ssh_msg_kexinit and in ssh:default_algorithms/0 to the +%%% user. +%%% +%%% A supported algorithm can be requested in the option 'preferred_algorithms', +%%% but may give unexpected results because of being promoted to default. +%%% +%%% This makes it possible to add experimental algorithms (in supported_algorithms) +%%% and test them without letting the default users know about them. +%%% + +default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()]. + +algo_classes() -> [kex, public_key, cipher, mac, compression]. + +default_algorithms(compression) -> + %% Do not announce '[email protected]' because there seem to be problems + supported_algorithms(compression, same(['[email protected]'])); +default_algorithms(Alg) -> + supported_algorithms(Alg). + + +supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()]. + +supported_algorithms(kex) -> + ['diffie-hellman-group1-sha1']; +supported_algorithms(public_key) -> + ssh_auth:default_public_key_algorithms(); +supported_algorithms(cipher) -> + Supports = crypto:supports(), + CipherAlgos = [{aes_ctr, 'aes128-ctr'}, {aes_cbc128, 'aes128-cbc'}, {des3_cbc, '3des-cbc'}], + Algs = [SshAlgo || + {CryptoAlgo, SshAlgo} <- CipherAlgos, + lists:member(CryptoAlgo, proplists:get_value(ciphers, Supports, []))], + same(Algs); +supported_algorithms(mac) -> + Supports = crypto:supports(), + HashAlgos = [{sha256, 'hmac-sha2-256'}, {sha, 'hmac-sha1'}], + Algs = [SshAlgo || + {CryptoAlgo, SshAlgo} <- HashAlgos, + lists:member(CryptoAlgo, proplists:get_value(hashs, Supports, []))], + same(Algs); +supported_algorithms(compression) -> + same(['none','zlib','[email protected]']). + + +supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) -> + [{client2server,As1},{server2client,As2}] = supported_algorithms(Key), + [{client2server,As1--BL1},{server2client,As2--BL2}]; +supported_algorithms(Key, BlackList) -> + supported_algorithms(Key) -- BlackList. + + + + +same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. + + +%%%---------------------------------------------------------------------------- versions(client, Options)-> Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION), {Vsn, format_version(Vsn, software_version(Options))}; @@ -128,62 +192,45 @@ key_exchange_init_msg(Ssh0) -> kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) -> Random = ssh_bits:random(16), - Compression = case proplists:get_value(compression, Opts, none) of - openssh_zlib -> ["[email protected]", "none"]; - zlib -> ["zlib", "none"]; - none -> ["none", "zlib"] - end, - kexinit_messsage(Role, Random, Compression, HostKeyAlgs). + PrefAlgs = + case proplists:get_value(preferred_algorithms,Opts) of + undefined -> + default_algorithms(); + Algs0 -> + Algs0 + end, + kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs). key_init(client, Ssh, Value) -> Ssh#ssh{c_keyinit = Value}; key_init(server, Ssh, Value) -> Ssh#ssh{s_keyinit = Value}. -available_ssh_algos() -> - Supports = crypto:supports(), - CipherAlgos = [{aes_ctr, "aes128-ctr"}, {aes_cbc128, "aes128-cbc"}, {des3_cbc, "3des-cbc"}], - Ciphers = [SshAlgo || - {CryptoAlgo, SshAlgo} <- CipherAlgos, - lists:member(CryptoAlgo, proplists:get_value(ciphers, Supports, []))], - HashAlgos = [{sha256, "hmac-sha2-256"}, {sha, "hmac-sha1"}], - Hashs = [SshAlgo || - {CryptoAlgo, SshAlgo} <- HashAlgos, - lists:member(CryptoAlgo, proplists:get_value(hashs, Supports, []))], - {Ciphers, Hashs}. - -kexinit_messsage(client, Random, Compression, HostKeyAlgs) -> - {CipherAlgs, HashAlgs} = available_ssh_algos(), - #ssh_msg_kexinit{ - cookie = Random, - kex_algorithms = ["diffie-hellman-group1-sha1"], - server_host_key_algorithms = HostKeyAlgs, - encryption_algorithms_client_to_server = CipherAlgs, - encryption_algorithms_server_to_client = CipherAlgs, - mac_algorithms_client_to_server = HashAlgs, - mac_algorithms_server_to_client = HashAlgs, - compression_algorithms_client_to_server = Compression, - compression_algorithms_server_to_client = Compression, - languages_client_to_server = [], - languages_server_to_client = [] - }; -kexinit_messsage(server, Random, Compression, HostKeyAlgs) -> - {CipherAlgs, HashAlgs} = available_ssh_algos(), +kexinit_message(_Role, Random, Algs, HostKeyAlgs) -> #ssh_msg_kexinit{ cookie = Random, - kex_algorithms = ["diffie-hellman-group1-sha1"], + kex_algorithms = to_strings( get_algs(kex,Algs) ), server_host_key_algorithms = HostKeyAlgs, - encryption_algorithms_client_to_server = CipherAlgs, - encryption_algorithms_server_to_client = CipherAlgs, - mac_algorithms_client_to_server = HashAlgs, - mac_algorithms_server_to_client = HashAlgs, - compression_algorithms_client_to_server = Compression, - compression_algorithms_server_to_client = Compression, + encryption_algorithms_client_to_server = c2s(cipher,Algs), + encryption_algorithms_server_to_client = s2c(cipher,Algs), + mac_algorithms_client_to_server = c2s(mac,Algs), + mac_algorithms_server_to_client = s2c(mac,Algs), + compression_algorithms_client_to_server = c2s(compression,Algs), + compression_algorithms_server_to_client = s2c(compression,Algs), languages_client_to_server = [], languages_server_to_client = [] }. +c2s(Key, Algs) -> x2y(client2server, Key, Algs). +s2c(Key, Algs) -> x2y(server2client, Key, Algs). + +x2y(DirectionKey, Key, Algs) -> to_strings(proplists:get_value(DirectionKey, get_algs(Key,Algs))). + +get_algs(Key, Algs) -> proplists:get_value(Key, Algs, default_algorithms(Key)). + +to_strings(L) -> lists:map(fun erlang:atom_to_list/1, L). + new_keys_message(Ssh0) -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -448,6 +495,7 @@ select_algorithm(Role, Client, Server) -> decompress = Decompression, c_lng = C_Lng, s_lng = S_Lng}, +%%ct:pal("~p~n Client=~p~n Server=~p~n Alg=~p~n",[Role,Client,Server,Alg]), {ok, Alg}. select_encrypt_decrypt(client, Client, Server) -> @@ -537,10 +585,15 @@ alg_final(SSH0) -> {ok,SSH6} = decompress_final(SSH5), SSH6. -select_all(CL, SL) -> +select_all(CL, SL) when length(CL) + length(SL) < 50 -> A = CL -- SL, %% algortihms only used by client %% algorithms used by client and server (client pref) - lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)). + lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)); +select_all(_CL, _SL) -> + throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "Too many algorithms", + language = "en"}). + select([], []) -> none; diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 60222f5172..e879629ccb 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -26,8 +26,10 @@ -behaviour(supervisor). +-include("ssh.hrl"). + -export([start_link/1, start_child/1, stop_child/1, - stop_child/2, system_name/1]). + stop_child/3, system_name/1]). %% Supervisor callback -export([init/1]). @@ -40,13 +42,14 @@ start_link(Servers) -> start_child(ServerOpts) -> Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - case ssh_system_sup:system_supervisor(Address, Port) of + Port = proplists:get_value(port, ServerOpts), + Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), + case ssh_system_sup:system_supervisor(Address, Port, Profile) of undefined -> Spec = child_spec(Address, Port, ServerOpts), case supervisor:start_child(?MODULE, Spec) of {error, already_present} -> - Name = id(Address, Port), + Name = id(Address, Port, Profile), supervisor:delete_child(?MODULE, Name), supervisor:start_child(?MODULE, Spec); Reply -> @@ -60,8 +63,8 @@ start_child(ServerOpts) -> stop_child(Name) -> supervisor:terminate_child(?MODULE, Name). -stop_child(Address, Port) -> - Name = id(Address, Port), +stop_child(Address, Port, Profile) -> + Name = id(Address, Port, Profile), stop_child(Name). system_name(SysSup) -> @@ -87,7 +90,8 @@ init([Servers]) -> %%% Internal functions %%%========================================================================= child_spec(Address, Port, ServerOpts) -> - Name = id(Address, Port), + Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), + Name = id(Address, Port,Profile), StartFunc = {ssh_system_sup, start_link, [ServerOpts]}, Restart = temporary, Shutdown = infinity, @@ -95,8 +99,13 @@ child_spec(Address, Port, ServerOpts) -> Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -id(Address, Port) -> - {server, ssh_system_sup, Address, Port}. +id(Address, Port, Profile) -> + case is_list(Address) of + true -> + {server, ssh_system_sup, any, Port, Profile}; + false -> + {server, ssh_system_sup, Address, Port, Profile} + end. system_name([], _ ) -> undefined; |