diff options
Diffstat (limited to 'lib/ssh/src/ssh_options.erl')
-rw-r--r-- | lib/ssh/src/ssh_options.erl | 268 |
1 files changed, 192 insertions, 76 deletions
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 33355dd504..b5b0a9d2d8 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -31,7 +31,10 @@ delete_key/5, handle_options/2, keep_user_options/2, - keep_set_options/2 + keep_set_options/2, + + initial_default_algorithms/2, + check_preferred_algorithms/1 ]). -export_type([private_options/0 @@ -158,58 +161,125 @@ delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opt handle_options(Role, PropList0) -> handle_options(Role, PropList0, #{socket_options => [], internal_options => #{}, - user_options => [] + key_cb_options => [] }). -handle_options(Role, PropList0, Opts0) when is_map(Opts0), - is_list(PropList0) -> - PropList1 = proplists:unfold(PropList0), +handle_options(Role, OptsList0, Opts0) when is_map(Opts0), + is_list(OptsList0) -> + OptsList1 = proplists:unfold( + lists:foldr(fun(T,Acc) when is_tuple(T), + size(T) =/= 2-> [{special_trpt_args,T} | Acc]; + (X,Acc) -> [X|Acc] + end, + [], OptsList0)), try OptionDefinitions = default(Role), - InitialMap = + RoleCnfs = application:get_env(ssh, cnf_key(Role), []), + {InitialMap,OptsList2} = maps:fold( - fun(K, #{default:=V}, M) -> M#{K=>V}; - (_,_,M) -> M + fun(K, #{default:=Vd}, {M,PL}) -> + %% Now set as the default value: + %% 1: from erl command list: erl -ssh opt val + %% 2: from config file: {options, [..., {opt,val}, ....]} + %% 3: from the hard-coded option values in default/1 + %% The value in the option list will be handled later in save/3 later + case config_val(K, RoleCnfs, OptsList1) of + {ok,V1} -> + %% A value set in config or options. Replace the current. + {M#{K => V1, + key_cb_options => [{K,V1} | maps:get(key_cb_options,M)]}, + [{K,V1} | PL] + }; + + {append,V1} -> + %% A value set in config or options, but should be + %% appended to the existing value + NewVal = maps:get(K,M,[]) ++ V1, + {M#{K => NewVal, + key_cb_options => [{K,NewVal} | + lists:keydelete(K,1,maps:get(key_cb_options,M))]}, + [{K,NewVal} | lists:keydelete(K,1,PL)] + }; + + undefined -> + %% Use the default value + {M#{K => Vd}, PL} + end + %% ; + %% (_,_,Acc) -> + %% Acc end, - Opts0#{user_options => - maps:get(user_options,Opts0) ++ PropList1 - }, + {Opts0#{key_cb_options => maps:get(key_cb_options,Opts0)}, + [{K,V} || {K,V} <- OptsList1, + not maps:is_key(K,Opts0) % Keep socket opts + ] + }, OptionDefinitions), + + %% Enter the user's values into the map; unknown keys are %% treated as socket options final_preferred_algorithms( lists:foldl(fun(KV, Vals) -> save(KV, OptionDefinitions, Vals) - end, InitialMap, PropList1)) + end, InitialMap, OptsList2)) catch - error:{eoptions, KV, undefined} -> - {error, {eoptions,KV}}; + error:{EO, KV, Reason} when EO == eoptions ; EO == eerl_env -> + if + Reason == undefined -> + {error, {EO,KV}}; + is_list(Reason) -> + {error, {EO,{KV,lists:flatten(Reason)}}}; + true -> + {error, {EO,{KV,Reason}}} + end + end. + +cnf_key(server) -> server_options; +cnf_key(client) -> client_options. - error:{eoptions, KV, Txt} when is_list(Txt) -> - {error, {eoptions,{KV,lists:flatten(Txt)}}}; - error:{eoptions, KV, Extra} -> - {error, {eoptions,{KV,Extra}}} +config_val(modify_algorithms=Key, RoleCnfs, Opts) -> + V = case application:get_env(ssh, Key) of + {ok,V0} -> V0; + _ -> [] + end + ++ proplists:get_value(Key, RoleCnfs, []) + ++ proplists:get_value(Key, Opts, []), + case V of + [] -> undefined; + _ -> {append,V} + end; + +config_val(Key, RoleCnfs, Opts) -> + case lists:keysearch(Key, 1, Opts) of + {value, {_,V}} -> + {ok,V}; + false -> + case lists:keysearch(Key, 1, RoleCnfs) of + {value, {_,V}} -> + {ok,V}; + false -> + application:get_env(ssh, Key) % returns {ok,V} | undefined + end end. check_fun(Key, Defs) -> - #{chk := Fun} = maps:get(Key, Defs), - Fun. + case ssh_connection_handler:prohibited_sock_option(Key) of + false -> + #{chk := Fun} = maps:get(Key, Defs), + Fun; + true -> + fun(_,_) -> forbidden end + end. %%%================================================================ %%% %%% Check and save one option %%% - -%%% First some prohibited inet options: -save({K,V}, _, _) when K == reuseaddr ; - K == active - -> - forbidden_option(K, V); - -%%% then compatibility conversions: +%%% First compatibility conversions: save({allow_user_interaction,V}, Opts, Vals) -> save({user_interaction,V}, Opts, Vals); @@ -221,6 +291,10 @@ save(Inet, Defs, OptMap) when Inet==inet ; Inet==inet6 -> save({Inet,true}, Defs, OptMap) when Inet==inet ; Inet==inet6 -> save({inet,Inet}, Defs, OptMap); save({Inet,false}, _Defs, OptMap) when Inet==inet ; Inet==inet6 -> OptMap; +%% There are inet-options that are not a tuple sized 2. They where marked earlier +save({special_trpt_args,T}, _Defs, OptMap) when is_map(OptMap) -> + OptMap#{socket_options := [T | maps:get(socket_options,OptMap)]}; + %% and finaly the 'real stuff': save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> try (check_fun(Key,Defs))(Value) @@ -230,7 +304,12 @@ save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> {true, ModifiedValue} -> OptMap#{Key := ModifiedValue}; false -> - error({eoptions, {Key,Value}, "Bad value"}) + error({eoptions, {Key,Value}, "Bad value"}); + forbidden -> + error({eoptions, {Key,Value}, + io_lib:format("The option '~s' is used internally. The " + "user is not allowed to specify this option.", + [Key])}) catch %% An unknown Key (= not in the definition map) is %% regarded as an inet option: @@ -307,7 +386,8 @@ default(server) -> #{default => ?DEFAULT_SHELL, chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A); (disabled) -> true; - (V) -> check_function1(V) orelse check_function2(V) + (V) -> check_function1(V) orelse + check_function2(V) end, class => user_option }, @@ -331,6 +411,18 @@ default(server) -> class => user_option }, + tcpip_tunnel_out => + #{default => false, + chk => fun(V) -> erlang:is_boolean(V) end, + class => user_option + }, + + tcpip_tunnel_in => + #{default => false, + chk => fun(V) -> erlang:is_boolean(V) end, + class => user_option + }, + system_dir => #{default => "/etc/ssh", chk => fun(V) -> check_string(V) andalso check_dir(V) end, @@ -345,7 +437,8 @@ default(server) -> check_string(S3) andalso is_boolean(B); (F) -> - check_function3(F) + check_function3(F) orelse + check_function4(F) end, class => user_option }, @@ -362,15 +455,21 @@ default(server) -> class => user_option }, + pk_check_user => + #{default => false, + chk => fun(V) -> erlang:is_boolean(V) end, + class => user_option + }, + password => #{default => undefined, - chk => fun check_string/1, + chk => fun(V) -> check_string(V) end, class => user_option }, dh_gex_groups => #{default => undefined, - chk => fun check_dh_gex_groups/1, + chk => fun(V) -> check_dh_gex_groups(V) end, class => user_option }, @@ -394,31 +493,37 @@ default(server) -> negotiation_timeout => #{default => 2*60*1000, + chk => fun(V) -> check_timeout(V) end, + class => user_option + }, + + hello_timeout => + #{default => 30*1000, chk => fun check_timeout/1, class => user_option }, max_sessions => #{default => infinity, - chk => fun check_pos_integer/1, + chk => fun(V) -> check_pos_integer(V) end, class => user_option }, max_channels => #{default => infinity, - chk => fun check_pos_integer/1, + chk => fun(V) -> check_pos_integer(V) end, class => user_option }, parallel_login => #{default => false, - chk => fun erlang:is_boolean/1, + chk => fun(V) -> erlang:is_boolean(V) end, class => user_option }, minimal_remote_max_packet_size => #{default => 0, - chk => fun check_pos_integer/1, + chk => fun(V) -> check_pos_integer(V) end, class => user_option }, @@ -432,7 +537,7 @@ default(server) -> connectfun => #{default => fun(_,_,_) -> void end, - chk => fun check_function3/1, + chk => fun(V) -> check_function3(V) end, class => user_option }, @@ -451,49 +556,49 @@ default(client) -> #{ dsa_pass_phrase => #{default => undefined, - chk => fun check_string/1, + chk => fun(V) -> check_string(V) end, class => user_option }, rsa_pass_phrase => #{default => undefined, - chk => fun check_string/1, + chk => fun(V) -> check_string(V) end, class => user_option }, ecdsa_pass_phrase => #{default => undefined, - chk => fun check_string/1, + chk => fun(V) -> check_string(V) end, class => user_option }, %%% Not yet implemented ed25519_pass_phrase => %%% Not yet implemented #{default => undefined, -%%% Not yet implemented chk => fun check_string/1, +%%% Not yet implemented chk => fun(V) -> check_string(V) end, %%% Not yet implemented class => user_option %%% Not yet implemented }, %%% Not yet implemented %%% Not yet implemented ed448_pass_phrase => %%% Not yet implemented #{default => undefined, -%%% Not yet implemented chk => fun check_string/1, +%%% Not yet implemented chk => fun(V) -> check_string(V) end, %%% Not yet implemented class => user_option %%% Not yet implemented }, %%% Not yet implemented silently_accept_hosts => #{default => false, - chk => fun check_silently_accept_hosts/1, + chk => fun(V) -> check_silently_accept_hosts(V) end, class => user_option }, user_interaction => #{default => true, - chk => fun erlang:is_boolean/1, + chk => fun(V) -> erlang:is_boolean(V) end, class => user_option }, save_accepted_host => #{default => true, - chk => fun erlang:is_boolean/1, + chk => fun(V) -> erlang:is_boolean(V) end, class => user_option }, @@ -509,7 +614,7 @@ default(client) -> connect_timeout => #{default => infinity, - chk => fun check_timeout/1, + chk => fun(V) -> check_timeout(V) end, class => user_option }, @@ -530,26 +635,26 @@ default(client) -> User end end, - chk => fun check_string/1, + chk => fun(V) -> check_string(V) end, class => user_option }, password => #{default => undefined, - chk => fun check_string/1, + chk => fun(V) -> check_string(V) end, class => user_option }, quiet_mode => #{default => false, - chk => fun erlang:is_boolean/1, + chk => fun(V) -> erlang:is_boolean(V) end, class => user_option }, %%%%% Undocumented keyboard_interact_fun => #{default => undefined, - chk => fun check_function3/1, + chk => fun(V) -> check_function3(V) end, class => undoc_user_option } }; @@ -562,15 +667,18 @@ default(common) -> class => user_option }, + %% NOTE: This option's default value must be undefined. + %% In the final stage that "merges" the modify_algorithms and preferred_algorithms, + %% this option's default values is set. pref_public_key_algs => - #{default => ssh_transport:default_algorithms(public_key), - chk => fun check_pref_public_key_algs/1, + #{default => undefined, + chk => fun(V) -> check_pref_public_key_algs(V) end, class => user_option }, preferred_algorithms => #{default => ssh:default_algorithms(), - chk => fun check_preferred_algorithms/1, + chk => fun(V) -> check_preferred_algorithms(V) end, class => user_option }, @@ -579,12 +687,16 @@ default(common) -> %% The preferred_algorithms is the one to use in the rest of the ssh application! modify_algorithms => #{default => undefined, % signals error if unsupported algo in preferred_algorithms :( - chk => fun check_modify_algorithms/1, + chk => fun(V) -> check_modify_algorithms(V) end, class => user_option }, id_string => - #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 + #{default => try {ok, [_|_] = VSN} = application:get_key(ssh, vsn), + "Erlang/" ++ VSN + catch + _:_ -> "" + end, chk => fun(random) -> {true, {random,2,5}}; % 2 - 5 random characters ({random,I1,I2}) -> @@ -609,31 +721,31 @@ default(common) -> profile => #{default => ?DEFAULT_PROFILE, - chk => fun erlang:is_atom/1, + chk => fun(V) -> erlang:is_atom(V) end, class => user_option }, idle_time => #{default => infinity, - chk => fun check_timeout/1, + chk => fun(V) -> check_timeout(V) end, class => user_option }, disconnectfun => #{default => fun(_) -> void end, - chk => fun check_function1/1, + chk => fun(V) -> check_function1(V) end, class => user_option }, unexpectedfun => #{default => fun(_,_) -> report end, - chk => fun check_function2/1, + chk => fun(V) -> check_function2(V) end, class => user_option }, ssh_msg_debug_fun => #{default => fun(_,_,_,_) -> void end, - chk => fun check_function4/1, + chk => fun(V) -> check_function4(V) end, class => user_option }, @@ -704,19 +816,19 @@ default(common) -> tstflg => #{default => [], - chk => fun erlang:is_list/1, + chk => fun(V) -> erlang:is_list(V) end, class => undoc_user_option }, user_dir_fun => #{default => undefined, - chk => fun check_function1/1, + chk => fun(V) -> check_function1(V) end, class => undoc_user_option }, max_random_length_padding => #{default => ?MAX_RND_PADDING_LEN, - chk => fun check_non_neg_integer/1, + chk => fun(V) -> check_non_neg_integer(V) end, class => undoc_user_option } }. @@ -902,6 +1014,11 @@ valid_hash(L, Ss) when is_list(L) -> lists:all(fun(S) -> valid_hash(S,Ss) end, L valid_hash(X, _) -> error_in_check(X, "Expect atom or list in fingerprint spec"). %%%---------------------------------------------------------------- +initial_default_algorithms(DefList, ModList) -> + {true, L0} = check_modify_algorithms(ModList), + rm_non_supported(false, eval_ops(DefList,L0)). + +%%%---------------------------------------------------------------- check_modify_algorithms(M) when is_list(M) -> [error_in_check(Op_KVs, "Bad modify_algorithms") || Op_KVs <- M, @@ -1021,19 +1138,25 @@ check_input_ok(Algs) -> orelse (size(KVs) =/= 2)]. %%%---------------------------------------------------------------- -final_preferred_algorithms(Options) -> +final_preferred_algorithms(Options0) -> Result = - case ?GET_OPT(modify_algorithms, Options) of + case ?GET_OPT(modify_algorithms, Options0) of undefined -> rm_non_supported(true, - ?GET_OPT(preferred_algorithms, Options)); + ?GET_OPT(preferred_algorithms, Options0)); ModAlgs -> rm_non_supported(false, - eval_ops(?GET_OPT(preferred_algorithms, Options), + eval_ops(?GET_OPT(preferred_algorithms, Options0), ModAlgs)) end, error_if_empty(Result), % Throws errors if any value list is empty - ?PUT_OPT({preferred_algorithms,Result}, Options). + Options1 = ?PUT_OPT({preferred_algorithms,Result}, Options0), + case ?GET_OPT(pref_public_key_algs, Options1) of + undefined -> + ?PUT_OPT({pref_public_key_algs, proplists:get_value(public_key,Result)}, Options1); + _ -> + Options1 + end. eval_ops(PrefAlgs, ModAlgs) -> lists:foldl(fun eval_op/2, PrefAlgs, ModAlgs). @@ -1087,10 +1210,3 @@ error_if_empty([]) -> ok. %%%---------------------------------------------------------------- -forbidden_option(K,V) -> - Txt = io_lib:format("The option '~s' is used internally. The " - "user is not allowed to specify this option.", - [K]), - error({eoptions, {K,V}, Txt}). - -%%%---------------------------------------------------------------- |