diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/ssh.erl | 22 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 10 | ||||
-rw-r--r-- | lib/ssh/src/ssh_dbg.erl | 164 | ||||
-rw-r--r-- | lib/ssh/src/ssh_io.erl | 8 | ||||
-rw-r--r-- | lib/ssh/src/ssh_options.erl | 278 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftp.erl | 24 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 71 |
7 files changed, 448 insertions, 129 deletions
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 5ebab43c30..1a5d48baca 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -35,6 +35,7 @@ daemon/1, daemon/2, daemon/3, daemon_info/1, default_algorithms/0, + chk_algos_opts/1, stop_listener/1, stop_listener/2, stop_listener/3, stop_daemon/1, stop_daemon/2, stop_daemon/3, shell/1, shell/2, shell/3 @@ -381,6 +382,27 @@ default_algorithms() -> ssh_transport:default_algorithms(). %%-------------------------------------------------------------------- +-spec chk_algos_opts(list(any())) -> algs_list() . +%%-------------------------------------------------------------------- +chk_algos_opts(Opts) -> + case lists:foldl( + fun({preferred_algorithms,_}, Acc) -> Acc; + ({modify_algorithms,_}, Acc) -> Acc; + (KV, Acc) -> [KV|Acc] + end, [], Opts) + of + [] -> + case ssh_options:handle_options(client, Opts) of + M when is_map(M) -> + maps:get(preferred_algorithms, M); + Others -> + Others + end; + OtherOps -> + {error, {non_algo_opts_found,OtherOps}} + end. + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- %% The handle_daemon_args/2 function basically only sets the ip-option in Opts diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8d3ddb09a4..4158a52a27 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1357,6 +1357,7 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> report -> Msg = lists:flatten( io_lib:format( + "*** SSH: " "Unexpected message '~p' received in state '~p'\n" "Role: ~p\n" "Peer: ~p\n" @@ -1365,7 +1366,7 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> StateName, Ssh#ssh.role, Ssh#ssh.peer, - ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)])), + ?GET_INTERNAL_OPT(address, Ssh#ssh.opts, undefined)])), error_logger:info_report(Msg), keep_state_and_data; @@ -1374,7 +1375,8 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> Other -> Msg = lists:flatten( - io_lib:format("Call to fun in 'unexpectedfun' failed:~n" + io_lib:format("*** SSH: " + "Call to fun in 'unexpectedfun' failed:~n" "Return: ~p\n" "Message: ~p\n" "Role: ~p\n" @@ -1383,8 +1385,8 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> [Other, UnexpectedMessage, Ssh#ssh.role, - element(2,Ssh#ssh.peer), - ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)] + Ssh#ssh.peer, + ?GET_INTERNAL_OPT(address, Ssh#ssh.opts, undefined)] )), error_logger:error_report(Msg), keep_state_and_data diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 3f742ad9b6..906640b490 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -24,6 +24,7 @@ -export([messages/0, messages/1, messages/2, messages/3, auth/0, auth/1, auth/2, auth/3, + hostkey/0, hostkey/1, hostkey/2, hostkey/3, stop/0 ]). @@ -46,6 +47,11 @@ auth(F) -> start(auth,F). auth(F,X) -> start(auth,F,X). auth(F,M,I) -> start(auth,F,M,I). +hostkey() -> start(hostkey). +hostkey(F) -> start(hostkey,F). +hostkey(F,X) -> start(hostkey,F,X). +hostkey(F,M,I) -> start(hostkey,F,M,I). + stop() -> dbg:stop(). %%%---------------------------------------------------------------- @@ -71,23 +77,44 @@ fmt_fun(F) -> fun(Fmt,Args,Data) -> F(Fmt,Args), Data end. id_fun() -> fun(X) -> X end. %%%---------------------------------------------------------------- -dbg_ssh(msg) -> - dbg_ssh(auth), - dbg:tp(ssh_message,encode,1, x), - dbg:tp(ssh_message,decode,1, x), - dbg:tpl(ssh_transport,select_algorithm,4, x), - dbg:tp(ssh_transport,hello_version_msg,1, x), - dbg:tp(ssh_transport,handle_hello_version,1, x), - dbg:tpl(ssh_connection_handler,ext_info,2, x); +dbg_ssh(What) -> + case [E || E <- lists:flatten(dbg_ssh0(What)), + element(1,E) =/= ok] of + [] -> ok; + Other -> Other + end. + + +dbg_ssh0(auth) -> + [dbg:tp(ssh_transport,hello_version_msg,1, x), + dbg:tp(ssh_transport,handle_hello_version,1, x), + dbg:tp(ssh_message,encode,1, x), + dbg:tpl(ssh_transport,select_algorithm,4, x), + dbg:tpl(ssh_connection_handler,ext_info,2, x), + lists:map(fun(F) -> dbg:tp(ssh_auth, F, x) end, + [publickey_msg, password_msg, keyboard_interactive_msg]) + ]; + +dbg_ssh0(hostkey) -> + [dbg:tpl(ssh_transport, verify_host_key, 4, x), + dbg:tp(ssh_transport, verify, 4, x), + dbg:tpl(ssh_transport, known_host_key, 3, x), +%% dbg:tpl(ssh_transport, accepted_host, 4, x), + dbg:tpl(ssh_transport, add_host_key, 4, x), + dbg:tpl(ssh_transport, is_host_key, 5, x) + ]; + +dbg_ssh0(msg) -> + [dbg_ssh0(hostkey), + dbg_ssh0(auth), + dbg:tp(ssh_message,encode,1, x), + dbg:tp(ssh_message,decode,1, x), + dbg:tpl(ssh_transport,select_algorithm,4, x), + dbg:tp(ssh_transport,hello_version_msg,1, x), + dbg:tp(ssh_transport,handle_hello_version,1, x), + dbg:tpl(ssh_connection_handler,ext_info,2, x) + ]. -dbg_ssh(auth) -> - dbg:tp(ssh_transport,hello_version_msg,1, x), - dbg:tp(ssh_transport,handle_hello_version,1, x), - dbg:tp(ssh_message,encode,1, x), - dbg:tpl(ssh_transport,select_algorithm,4, x), - dbg:tpl(ssh_connection_handler,ext_info,2, x), - lists:foreach(fun(F) -> dbg:tp(ssh_auth, F, x) end, - [publickey_msg, password_msg, keyboard_interactive_msg]). %%%================================================================ cond_start(Type, WriteFun, MangleArgFun, Init) -> @@ -110,10 +137,10 @@ msg_formater(msg, {trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> msg_formater(msg, {trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> fmt("~n~s ~p RECV ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); -msg_formater(auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_failure{authentications=As},TS}, D) -> +msg_formater(_auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_failure{authentications=As},TS}, D) -> fmt("~n~s ~p Client login FAILURE. Try ~s~n", [ts(TS),Pid,As], D); -msg_formater(auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_success{},TS}, D) -> +msg_formater(_auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_success{},TS}, D) -> fmt("~n~s ~p Client login SUCCESS~n", [ts(TS),Pid], D); @@ -155,10 +182,50 @@ msg_formater(_, {trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},St D end; +msg_formater(_, {trace_ts,Pid,call, {ssh_transport,verify_host_key,[_Ssh,_PK,_Dgst,{AlgStr,_Sign}]},TS}, D) -> + fmt("~n~s ~p Client got a ~s hostkey. Will try to verify it~n", [ts(TS),Pid,AlgStr], D); +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,verify_host_key,4}, Result, TS}, D) -> + case Result of + ok -> fmt("~n~s ~p Hostkey verified.~n", [ts(TS),Pid], D); + {error,E} -> + fmt("~n~s ~p ***** Hostkey NOT verified: ~p ******!~n", [ts(TS),Pid,E], D); + _ -> fmt("~n~s ~p ***** Hostkey is NOT verified: ~p ******!~n", [ts(TS),Pid,Result], D) + end; + +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,verify,4}, Result, TS}, D) -> + case Result of + true -> D; + _ -> fmt("~n~s ~p Couldn't verify the signature!~n", [ts(TS),Pid], D) + end; + +msg_formater(_, {trace_ts,_Pid,call, {ssh_transport,is_host_key,_}, _TS}, D) -> D; +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,is_host_key,5}, {CbMod,Result}, TS}, D) -> + case Result of + true -> fmt("~n~s ~p Hostkey found by ~p.~n", [ts(TS),Pid,CbMod], D); + _ -> fmt("~n~s ~p Hostkey NOT found by ~p.~n", [ts(TS),Pid,CbMod], D) + end; + +msg_formater(_, {trace_ts,_Pid,call, {ssh_transport,add_host_key,_}, _TS}, D) -> D; +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,add_host_key,4}, {CbMod,Result}, TS}, D) -> + case Result of + ok -> fmt("~n~s ~p New hostkey added by ~p.~n", [ts(TS),Pid,CbMod], D); + _ -> D + end; + +msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,known_host_key,_},_TS}, D) -> D; +msg_formater(_, {trace_ts,Pid,return_from, {ssh_transport,known_host_key,3}, Result, TS}, D) -> + case Result of + ok -> D; + {error,E} -> fmt("~n~s ~p Hostkey addition failed: ~p~n", [ts(TS),Pid,E], D); + _ -> fmt("~n~s ~p Hostkey addition: ~p~n", [ts(TS),Pid,Result], D) + end; + msg_formater(_, {trace_ts,Pid,call,{ssh_auth,publickey_msg,[[SigAlg,#ssh{user=User}]]},TS}, D) -> fmt("~n~s ~p Client will try to login user ~p with public key algorithm ~p~n", [ts(TS),Pid,User,SigAlg], D); msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> fmt("~s ~p User ~p can't login with that kind of public key~n", [ts(TS),Pid,User], D); +msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{_,#ssh{user=User}},TS}, D) -> + fmt("~s ~p User ~p logged in~n", [ts(TS),Pid,User], D); msg_formater(_, {trace_ts,Pid,call,{ssh_auth,password_msg,[[#ssh{user=User}]]},TS}, D) -> fmt("~n~s ~p Client will try to login user ~p with password~n", [ts(TS),Pid,User], D); @@ -187,26 +254,20 @@ msg_formater(msg, {trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> fmt("~n~s ~p ERL MSG RECEIVE~n ~p~n", [ts(TS),Pid,shrink_bin(ErlangMsg)], D); -%% msg_formater(_, {trace_ts,_Pid,return_from,MFA,_Ret,_TS}=M, D) -> -%% case lists:member(MFA, [{ssh_auth,keyboard_interactive_msg,1}, -%% {ssh_auth,password_msg,1}, -%% {ssh_auth,publickey_msg,1}]) of -%% true -> -%% D; -%% false -> -%% fmt("~nDBG ~n~p~n", [shrink_bin(M)], D) -%% end; - -%% msg_formater(_, M, D) -> -%% fmt("~nDBG ~n~p~n", [shrink_bin(M)], D). - -msg_formater(_, _, D) -> - D. +msg_formater(_, _M, D) -> + fmt("~nDBG other ~n~p~n", [shrink_bin(_M)], D), + D. %%%---------------------------------------------------------------- -record(data, {writer, + initialized, acc}). +fmt(Fmt, Args, D=#data{initialized=false}) -> + fmt(Fmt, Args, + D#data{acc = (D#data.writer)("~s~n", [initial_info()], D#data.acc), + initialized = true} + ); fmt(Fmt, Args, D=#data{writer=Write, acc=Acc}) -> D#data{acc = Write(Fmt,Args,Acc)}. @@ -221,10 +282,47 @@ setup_tracer(Type, WriteFun, MangleArgFun, Init) -> msg_formater(Type, MangleArgFun(Arg), D) end, InitialData = #data{writer = WriteFun, + initialized = false, acc = Init}, {ok,_} = dbg:tracer(process, {Handler, InitialData}), ok. + +initial_info() -> + Lines = + [ts(erlang:timestamp()), + "", + "SSH:"] + ++ as_list_of_lines(case application:get_key(ssh,vsn) of + {ok,Vsn} -> Vsn; + _ -> "(ssh not started)" + end) + ++ ["", + "Cryptolib:"] + ++ as_list_of_lines(crypto:info_lib()) + ++ ["", + "Crypto app:"] + ++ as_list_of_lines(crypto:supports()), + W = max_len(Lines), + append_lines([line_of($*, W+4)] + ++ prepend_lines("* ", Lines) + ++ [line_of($-, W+4)], + io_lib:nl() + ). + + +as_list_of_lines(Term) -> + prepend_lines(" ", + string:tokens(lists:flatten(io_lib:format("~p",[Term])), + io_lib:nl() % Get line endings in current OS + ) + ). + +line_of(Char,W) -> lists:duplicate(W,Char). +max_len(L) -> lists:max([length(S) || S<-L]). +append_lines(L, X) -> [S++X || S<-L]. +prepend_lines(X, L) -> [X++S || S<-L]. + %%%---------------------------------------------------------------- shrink_bin(B) when is_binary(B), size(B)>256 -> {'*** SHRINKED BIN', size(B), diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 8ba759ad60..a7cd1daeec 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -31,8 +31,8 @@ read_line(Prompt, Opts) -> format("~s", [listify(Prompt)]), ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question}, receive - Answer when is_list(Answer) -> - Answer + Answer when is_list(Answer) or is_binary(Answer) -> + unicode:characters_to_list(Answer) end. yes_no(Prompt, Opts) -> @@ -44,7 +44,7 @@ yes_no(Prompt, Opts) -> y -> yes; n -> no; - Answer when is_list(Answer) -> + Answer when is_list(Answer) or is_binary(Answer) -> case trim(Answer) of "y" -> yes; "n" -> no; @@ -60,7 +60,7 @@ read_password(Prompt, Opts) -> format("~s", [listify(Prompt)]), ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), user_password}, receive - Answer when is_list(Answer) -> + Answer when is_list(Answer) or is_binary(Answer) -> case trim(Answer) of "" -> read_password(Prompt, Opts); diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 7eeed70739..68c99743ee 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -170,9 +170,10 @@ handle_options(Role, PropList0, Opts0) when is_map(Opts0), OptionDefinitions), %% Enter the user's values into the map; unknown keys are %% treated as socket options - lists:foldl(fun(KV, Vals) -> - save(KV, OptionDefinitions, Vals) - end, InitialMap, PropList1) + final_preferred_algorithms( + lists:foldl(fun(KV, Vals) -> + save(KV, OptionDefinitions, Vals) + end, InitialMap, PropList1)) catch error:{eoptions, KV, undefined} -> {error, {eoptions,KV}}; @@ -236,7 +237,10 @@ save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> %% by the check fun will give an error exception: error:{check,{BadValue,Extra}} -> error({eoptions, {Key,BadValue}, Extra}) - end. + end; +save(Opt, _Defs, OptMap) when is_map(OptMap) -> + OptMap#{socket_options := [Opt | maps:get(socket_options,OptMap)]}. + %%%================================================================ %%% @@ -417,6 +421,12 @@ default(client) -> class => user_options }, + {ecdsa_pass_phrase, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + {silently_accept_hosts, def} => #{default => false, chk => fun check_silently_accept_hosts/1, @@ -506,6 +516,15 @@ default(common) -> class => user_options }, + %% NOTE: This option is supposed to be used only in this very module (?MODULE). There is + %% a final stage in handle_options that "merges" the preferred_algorithms option and this one. + %% The preferred_algorithms is the one to use in the rest of the ssh application! + {modify_algorithms, def} => + #{default => undefined, % signals error if unsupported algo in preferred_algorithms :( + chk => fun check_modify_algorithms/1, + class => user_options + }, + {id_string, def} => #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 chk => fun(random) -> @@ -817,83 +836,190 @@ 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"). %%%---------------------------------------------------------------- -check_preferred_algorithms(Algs) -> - [error_in_check(K,"Bad preferred_algorithms key") - || {K,_} <- Algs, - not lists:keymember(K,1,ssh:default_algorithms())], +check_modify_algorithms(M) when is_list(M) -> + [error_in_check(Op_KVs, "Bad modify_algorithms") + || Op_KVs <- M, + not is_tuple(Op_KVs) + orelse (size(Op_KVs) =/= 2) + orelse (not lists:member(element(1,Op_KVs), [append,prepend,rm]))], + {true, [{Op,normalize_mod_algs(KVs,false)} || {Op,KVs} <- M]}; +check_modify_algorithms(_) -> + error_in_check(modify_algorithms, "Bad option value. List expected."). + + + + +normalize_mod_algs(KVs, UseDefaultAlgs) -> + normalize_mod_algs(ssh_transport:algo_classes(), KVs, [], UseDefaultAlgs). + +normalize_mod_algs([K|Ks], KVs0, Acc, UseDefaultAlgs) -> + %% Pick the expected keys in order and check if they are in the user's list + {Vs1, KVs} = + case lists:keytake(K, 1, KVs0) of + {value, {K,Vs0}, KVs1} -> + {Vs0, KVs1}; + false -> + {[], KVs0} + end, + Vs = normalize_mod_alg_list(K, Vs1, UseDefaultAlgs), + normalize_mod_algs(Ks, KVs, [{K,Vs} | Acc], UseDefaultAlgs); +normalize_mod_algs([], [], Acc, _) -> + %% No values left in the key-value list after removing the expected entries + %% (thats good) + lists:reverse(Acc); +normalize_mod_algs([], [{K,_}|_], _, _) -> + %% Some values left in the key-value list after removing the expected entries + %% (thats bad) + case ssh_transport:algo_class(K) of + true -> error_in_check(K, "Duplicate key"); + false -> error_in_check(K, "Unknown key") + end; +normalize_mod_algs([], [X|_], _, _) -> + error_in_check(X, "Bad list element"). - try alg_duplicates(Algs, [], []) - of - [] -> - {true, - [case proplists:get_value(Key, Algs) of - undefined -> - {Key,DefAlgs}; - Vals -> - handle_pref_alg(Key,Vals,SupAlgs) - end - || {{Key,DefAlgs}, {Key,SupAlgs}} <- lists:zip(ssh:default_algorithms(), - ssh_transport:supported_algorithms()) - ] - }; - - Dups -> - error_in_check(Dups, "Duplicates") - catch - _:_ -> - false - 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) + +%%% Handle the algorithms list +normalize_mod_alg_list(K, Vs, UseDefaultAlgs) -> + normalize_mod_alg_list(K, + ssh_transport:algo_two_spec_class(K), + Vs, + def_alg(K,UseDefaultAlgs)). + + +normalize_mod_alg_list(_K, _, [], Default) -> + Default; + +normalize_mod_alg_list(K, true, [{client2server,L1}], [_,{server2client,L2}]) -> + [nml1(K,{client2server,L1}), + {server2client,L2}]; + +normalize_mod_alg_list(K, true, [{server2client,L2}], [{client2server,L1},_]) -> + [{client2server,L1}, + nml1(K,{server2client,L2})]; + +normalize_mod_alg_list(K, true, [{server2client,L2},{client2server,L1}], _) -> + [nml1(K,{client2server,L1}), + nml1(K,{server2client,L2})]; + +normalize_mod_alg_list(K, true, [{client2server,L1},{server2client,L2}], _) -> + [nml1(K,{client2server,L1}), + nml1(K,{server2client,L2})]; + +normalize_mod_alg_list(K, true, L0, _) -> + L = nml(K,L0), % Throws errors + [{client2server,L}, + {server2client,L}]; + +normalize_mod_alg_list(K, false, L, _) -> + nml(K,L). + + +nml1(K, {T,V}) when T==client2server ; T==server2client -> + {T, nml({K,T}, V)}. + +nml(K, L) -> + [error_in_check(K, "Bad value for this key") % This is a throw + || V <- L, + not is_atom(V) + ], + case L -- lists:usort(L) of + [] -> ok; + Dups -> error_in_check({K,Dups}, "Duplicates") % This is a throw + end, + L. + + +def_alg(K, false) -> + case ssh_transport:algo_two_spec_class(K) of + false -> []; + true -> [{client2server,[]}, {server2client,[]}] 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, _) -> - error_in_check({Key,Vs}, "Badly formed list"). - -chk_alg_vs(OptKey, Values, SupportedValues) -> - case (Values -- SupportedValues) of - [] -> Values; - [none] -> [none]; % for testing only - Bad -> error_in_check({OptKey,Bad}, "Unsupported value(s) found") +def_alg(K, true) -> + ssh_transport:default_algorithms(K). + + + +check_preferred_algorithms(Algs) when is_list(Algs) -> + check_input_ok(Algs), + {true, normalize_mod_algs(Algs, true)}; + +check_preferred_algorithms(_) -> + error_in_check(modify_algorithms, "Bad option value. List expected."). + + +check_input_ok(Algs) -> + [error_in_check(KVs, "Bad preferred_algorithms") + || KVs <- Algs, + not is_tuple(KVs) + orelse (size(KVs) =/= 2)]. + +%%%---------------------------------------------------------------- +final_preferred_algorithms(Options) -> + Result = + case ?GET_OPT(modify_algorithms, Options) of + undefined -> + rm_non_supported(true, + ?GET_OPT(preferred_algorithms, Options)); + ModAlgs -> + rm_non_supported(false, + eval_ops(?GET_OPT(preferred_algorithms, Options), + ModAlgs)) + end, + error_if_empty(Result), % Throws errors if any value list is empty + ?PUT_OPT({preferred_algorithms,Result}, Options). + +eval_ops(PrefAlgs, ModAlgs) -> + lists:foldl(fun eval_op/2, PrefAlgs, ModAlgs). + +eval_op({Op,AlgKVs}, PrefAlgs) -> + eval_op(Op, AlgKVs, PrefAlgs, []). + +eval_op(Op, [{C,L1}|T1], [{C,L2}|T2], Acc) -> + eval_op(Op, T1, T2, [{C,eval_op(Op,L1,L2,[])} | Acc]); + +eval_op(_, [], [], Acc) -> lists:reverse(Acc); +eval_op(rm, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> Pref -- Opt; +eval_op(append, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> (Pref--Opt) ++ Opt; +eval_op(prepend, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> Opt ++ (Pref--Opt). + + +rm_non_supported(UnsupIsErrorFlg, KVs) -> + [{K,rmns(K,Vs, UnsupIsErrorFlg)} || {K,Vs} <- KVs]. + +rmns(K, Vs, UnsupIsErrorFlg) -> + case ssh_transport:algo_two_spec_class(K) of + false -> + rm_unsup(Vs, ssh_transport:supported_algorithms(K), UnsupIsErrorFlg, K); + true -> + [{C, rm_unsup(Vsx, Sup, UnsupIsErrorFlg, {K,C})} + || {{C,Vsx},{C,Sup}} <- lists:zip(Vs,ssh_transport:supported_algorithms(K)) + ] end. +rm_unsup(A, B, Flg, ErrInf) -> + case A--B of + Unsup=[_|_] when Flg==true -> error({eoptions, + {preferred_algorithms,{ErrInf,Unsup}}, + "Unsupported value(s) found" + }); + Unsup -> A -- Unsup + end. + + +error_if_empty([{K,[]}|_]) -> + error({eoptions, K, "Empty resulting algorithm list"}); +error_if_empty([{K,[{client2server,[]}, {server2client,[]}]}]) -> + error({eoptions, K, "Empty resulting algorithm list"}); +error_if_empty([{K,[{client2server,[]}|_]} | _]) -> + error({eoptions, {K,client2server}, "Empty resulting algorithm list"}); +error_if_empty([{K,[_,{server2client,[]}|_]} | _]) -> + error({eoptions, {K,server2client}, "Empty resulting algorithm list"}); +error_if_empty([_|T]) -> + error_if_empty(T); +error_if_empty([]) -> + ok. + %%%---------------------------------------------------------------- forbidden_option(K,V) -> Txt = io_lib:format("The option '~s' is used internally. The " diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index c1558a19b1..9e1229dc85 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1050,7 +1050,7 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) -> #file_info{ size = A#ssh_xfer_attr.size, type = A#ssh_xfer_attr.type, - access = read_write, %% FIXME: read/write/read_write/none + access = file_mode_to_owner_access(A#ssh_xfer_attr.permissions), atime = unix_to_datetime(A#ssh_xfer_attr.atime), mtime = unix_to_datetime(A#ssh_xfer_attr.mtime), ctime = unix_to_datetime(A#ssh_xfer_attr.createtime), @@ -1062,6 +1062,28 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) -> uid = A#ssh_xfer_attr.owner, gid = A#ssh_xfer_attr.group}. +file_mode_to_owner_access(FileMode) + when is_integer(FileMode) -> + %% The file mode contains the access permissions. + %% The read and write access permission of file owner + %% are located in 8th and 7th bit of file mode respectively. + + ReadPermission = ((FileMode bsr 8) band 1), + WritePermission = ((FileMode bsr 7) band 1), + case {ReadPermission, WritePermission} of + {1, 1} -> + read_write; + {1, 0} -> + read; + {0, 1} -> + write; + {0, 0} -> + none; + _ -> + undefined + end; +file_mode_to_owner_access(_) -> + undefined. unix_to_datetime(undefined) -> undefined; diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 412f5de9de..46154cf536 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -34,6 +34,8 @@ -export([next_seqnum/1, supported_algorithms/0, supported_algorithms/1, default_algorithms/0, default_algorithms/1, + algo_classes/0, algo_class/1, + algo_two_spec_classes/0, algo_two_spec_class/1, handle_packet_part/4, handle_hello_version/1, key_exchange_init_msg/1, @@ -81,6 +83,23 @@ default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()]. algo_classes() -> [kex, public_key, cipher, mac, compression]. +algo_class(kex) -> true; +algo_class(public_key) -> true; +algo_class(cipher) -> true; +algo_class(mac) -> true; +algo_class(compression) -> true; +algo_class(_) -> false. + + +algo_two_spec_classes() -> [cipher, mac, compression]. + +algo_two_spec_class(cipher) -> true; +algo_two_spec_class(mac) -> true; +algo_two_spec_class(compression) -> true; +algo_two_spec_class(_) -> false. + + + default_algorithms(kex) -> supported_algorithms(kex, [ 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 @@ -232,9 +251,9 @@ key_exchange_init_msg(Ssh0) -> {SshPacket, Ssh} = ssh_packet(Msg, Ssh0), {Msg, SshPacket, Ssh}. -kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) -> +kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs} = Ssh) -> Random = ssh_bits:random(16), - PrefAlgs = ?GET_OPT(preferred_algorithms, Opts), + PrefAlgs = adjust_algs_for_peer_version(Role, ?GET_OPT(preferred_algorithms, Opts), Ssh), kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs, Opts). key_init(client, Ssh, Value) -> @@ -242,7 +261,22 @@ key_init(client, Ssh, Value) -> key_init(server, Ssh, Value) -> Ssh#ssh{s_keyinit = Value}. - +adjust_algs_for_peer_version(client, PrefAlgs, #ssh{s_version=V}) -> + adjust_algs_for_peer_version(V, PrefAlgs); +adjust_algs_for_peer_version(server, PrefAlgs, #ssh{c_version=V}) -> + adjust_algs_for_peer_version(V, PrefAlgs). +%% +adjust_algs_for_peer_version("SSH-2.0-OpenSSH_6.2"++_, PrefAlgs) -> + C0 = proplists:get_value(cipher, PrefAlgs, same([])), + C = [{D,L} || D <- [client2server, server2client], + L <- [[K || K <- proplists:get_value(D, C0, []), + K =/= '[email protected]', + K =/= '[email protected]']] + ], + lists:keyreplace(cipher, 1, PrefAlgs, {cipher,C}); +adjust_algs_for_peer_version(_, PrefAlgs) -> + PrefAlgs. + kexinit_message(Role, Random, Algs, HostKeyAlgs, Opts) -> #ssh_msg_kexinit{ cookie = Random, @@ -790,6 +824,7 @@ verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature}) end. +%%% -> boolean() | {error,_} accepted_host(Ssh, PeerName, Public, Opts) -> case ?GET_OPT(silently_accept_hosts, Opts) of @@ -811,11 +846,16 @@ accepted_host(Ssh, PeerName, Public, Opts) -> %% Call-back alternatives: A user provided fun is called for the decision: F when is_function(F,2) -> - true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public))); + case catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public)) of + true -> true; + _ -> {error, fingerprint_check_failed} + end; {DigestAlg,F} when is_function(F,2) -> - true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))) - + case catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public)) of + true -> true; + _ -> {error, {fingerprint_check_failed,DigestAlg}} + end end. @@ -833,18 +873,27 @@ fmt_hostkey(X) -> X. known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}} = Ssh, Public, Alg) -> UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:is_host_key(Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of - true -> + case is_host_key(KeyCb, Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + {_,true} -> ok; - false -> + {_,false} -> case accepted_host(Ssh, PeerName, Public, Opts) of true -> - KeyCb:add_host_key(PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]); + {_,R} = add_host_key(KeyCb, PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]), + R; false -> - {error, rejected} + {error, rejected_by_user}; + {error,E} -> + {error,E} end end. +is_host_key(KeyCb, Public, PeerName, Alg, Data) -> + {KeyCb, KeyCb:is_host_key(Public, PeerName, Alg, Data)}. + +add_host_key(KeyCb, PeerName, Public, Data) -> + {KeyCb, KeyCb:add_host_key(PeerName, Public, Data)}. + %% Each of the algorithm strings MUST be a comma-separated list of %% algorithm names (see ''Algorithm Naming'' in [SSH-ARCH]). Each |