summaryrefslogtreecommitdiff
path: root/lib/ssh/src/ssh_options.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh/src/ssh_options.erl')
-rw-r--r--lib/ssh/src/ssh_options.erl268
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}).
-
-%%%----------------------------------------------------------------