From 2e3d97a27bbfa86260dd248b793a1d358a836a1b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Jun 2016 13:03:40 +0200 Subject: ssh: Make client send a faulty pwd only once, ssh_auth part Conflicts: lib/ssh/src/ssh_connection_handler.erl --- lib/ssh/src/ssh_auth.erl | 214 ++++++++++++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 88 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index b71bed033a..86a91c6d22 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -31,12 +31,107 @@ -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/2, handle_userauth_info_response/2 ]). %%-------------------------------------------------------------------- %%% Internal application API %%-------------------------------------------------------------------- +%%%---------------------------------------------------------------- +userauth_request_msg(#ssh{userauth_methods = ServerMethods, + userauth_supported_methods = UserPrefMethods, % Note: this is not documented as supported for clients + userauth_preference = ClientMethods0 + } = Ssh0) -> + case sort_select_mthds(ClientMethods0, UserPrefMethods, ServerMethods) of + [] -> + Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + description = "Unable to connect using the available authentication methods", + language = "en"}, + {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh0)}; + + [{Pref,Module,Function,Args} | Prefs] -> + Ssh = case Pref of + "keyboard-interactive" -> Ssh0; + _ -> Ssh0#ssh{userauth_preference = Prefs} + end, + case Module:Function(Args ++ [Ssh]) of + {not_ok, Ssh1} -> + userauth_request_msg(Ssh1#ssh{userauth_preference = Prefs}); + Result -> + {Pref,Result} + end + end. + + + +sort_select_mthds(Clients, undefined, Servers) -> + %% User has not expressed an opinion via option "auth_methods", use the server's prefs + sort_select_mthds(Clients, Servers, Servers); + +sort_select_mthds(Clients, Users0, Servers0) -> + %% The User has an opinion, use the intersection of that and the Servers whishes but + %% in the Users order + Servers = unique(Servers0), + Users = unique(Users0), + [C || Key <- Users, + lists:member(Key, Servers), + C <- Clients, + element(1,C) == Key]. + +unique(L) -> + lists:reverse( + lists:foldl(fun(E,Acc) -> + case lists:member(E,Acc) of + true -> Acc; + false -> [E|Acc] + end + end, [], L)). + + +%%%---- userauth_request_msg "callbacks" +password_msg([#ssh{opts = Opts, io_cb = IoCb, + user = User, service = Service} = Ssh0]) -> + {Password,Ssh} = + case proplists:get_value(password, Opts) of + undefined when IoCb == ssh_no_io -> + {not_ok, Ssh0}; + undefined -> + {IoCb:read_password("ssh password: ",Ssh0), Ssh0}; + PW -> + %% If "password" option is given it should not be tried again + {PW, Ssh0#ssh{opts = lists:keyreplace(password,1,Opts,{password,not_ok})}} + end, + case Password of + not_ok -> + {not_ok, Ssh}; + _ -> + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "password", + data = + <>}, + Ssh) + end. + +%% See RFC 4256 for info on keyboard-interactive +keyboard_interactive_msg([#ssh{user = User, + opts = Opts, + service = Service} = Ssh]) -> + case proplists:get_value(password, Opts) of + not_ok -> + {not_ok,Ssh}; % No need to use a failed pwd once more + _ -> + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "keyboard-interactive", + data = << ?STRING(<<"">>), + ?STRING(<<>>) >> }, + Ssh) + end. + publickey_msg([Alg, #ssh{user = User, session_id = SessionId, service = Service, @@ -48,7 +143,7 @@ publickey_msg([Alg, #ssh{user = User, StrAlgo = atom_to_list(Alg), case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of not_ok -> - not_ok; + {not_ok, Ssh}; PubKeyBlob -> SigData = build_sig_data(SessionId, User, Service, PubKeyBlob, StrAlgo), @@ -65,52 +160,15 @@ publickey_msg([Alg, #ssh{user = User, Ssh) end; _Error -> - not_ok + {not_ok, Ssh} end. -password_msg([#ssh{opts = Opts, io_cb = IoCb, - user = User, service = Service} = Ssh]) -> - Password = case proplists:get_value(password, Opts) of - undefined -> - user_interaction(IoCb, Ssh); - PW -> - PW - end, - case Password of - not_ok -> - not_ok; - _ -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "password", - data = - <>}, - Ssh) - end. - -user_interaction(ssh_no_io, _) -> - not_ok; -user_interaction(IoCb, Ssh) -> - IoCb:read_password("ssh password: ", Ssh). - - -%% See RFC 4256 for info on keyboard-interactive -keyboard_interactive_msg([#ssh{user = User, - service = Service} = Ssh]) -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "keyboard-interactive", - data = << ?STRING(<<"">>), - ?STRING(<<>>) >> }, - Ssh). - +%%%---------------------------------------------------------------- service_request_msg(Ssh) -> ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"}, Ssh#ssh{service = "ssh-userauth"}). +%%%---------------------------------------------------------------- init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> case user_name(Opts) of {ok, User} -> @@ -140,34 +198,9 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> language = "en"}) end. -userauth_request_msg(#ssh{userauth_preference = []} = Ssh) -> - Msg = #ssh_msg_disconnect{code = - ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, - description = "Unable to connect using the available" - " authentication methods", - language = "en"}, - {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh)}; - -userauth_request_msg(#ssh{userauth_methods = Methods, - userauth_preference = [{Pref, Module, - Function, Args} | Prefs]} - = Ssh0) -> - Ssh = Ssh0#ssh{userauth_preference = Prefs}, - case lists:member(Pref, Methods) of - true -> - case Module:Function(Args ++ [Ssh]) of - not_ok -> - userauth_request_msg(Ssh); - Result -> - {Pref,Result} - end; - false -> - userauth_request_msg(Ssh) - end. - - -handle_userauth_request(#ssh_msg_service_request{name = - Name = "ssh-userauth"}, +%%%---------------------------------------------------------------- +%%% called by server +handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"}, _, Ssh) -> {ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name}, Ssh#ssh{service = "ssh-connection"})}; @@ -319,21 +352,28 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, partial_success = false}, Ssh)}. - -handle_userauth_info_request( - #ssh_msg_userauth_info_request{name = Name, - instruction = Instr, - num_prompts = NumPrompts, - data = Data}, IoCb, - #ssh{opts = Opts} = Ssh) -> +%%%---------------------------------------------------------------- +%%% keyboard-interactive client +handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name, + instruction = Instr, + num_prompts = NumPrompts, + data = Data}, + #ssh{opts = Opts, + io_cb = IoCb + } = Ssh) -> PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data), - Responses = keyboard_interact_get_responses(IoCb, Opts, - Name, Instr, PromptInfos), - {ok, - ssh_transport:ssh_packet( - #ssh_msg_userauth_info_response{num_responses = NumPrompts, - data = Responses}, Ssh)}. + case keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) of + not_ok -> + not_ok; + Responses -> + {ok, + ssh_transport:ssh_packet( + #ssh_msg_userauth_info_response{num_responses = NumPrompts, + data = Responses}, Ssh)} + end. +%%%---------------------------------------------------------------- +%%% keyboard-interactive server handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, data = <>}, #ssh{opts = Opts, @@ -369,11 +409,6 @@ method_preference(Algs) -> [{"publickey", ?MODULE, publickey_msg, [A]} | Acc] end, [{"password", ?MODULE, password_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} ], Algs). @@ -473,6 +508,9 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> proplists:get_value(password, Opts, undefined), IoCb, Name, Instr, PromptInfos, Opts, NumPrompts). + +keyboard_interact_get_responses(_, _, not_ok, _, _, _, _, _, _) -> + not_ok; keyboard_interact_get_responses(_, undefined, Password, _, _, _, _, _, 1) when Password =/= undefined -> [Password]; %% Password auth implemented with keyboard-interaction and passwd is known -- cgit v1.2.3 From 3ad4e41e4653a910d592ac9912ec380b1cb1b25b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 16 Jun 2016 18:26:50 +0200 Subject: ssh: Make client send a faulty pwd only once, ssh_connection_handler part --- lib/ssh/src/ssh_connection_handler.erl | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ce1931e4f4..b73f8b23d2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -612,11 +612,14 @@ userauth(#ssh_msg_userauth_banner{message = Msg}, userauth_keyboard_interactive(#ssh_msg_userauth_info_request{} = Msg, - #state{ssh_params = #ssh{role = client, - io_cb = IoCb} = Ssh0} = State) -> - {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0), - send_msg(Reply, State), - {next_state, userauth_keyboard_interactive_info_response, next_packet(State#state{ssh_params = Ssh})}; + #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> + case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of + {ok, {Reply, Ssh}} -> + send_msg(Reply, State), + {next_state, userauth_keyboard_interactive_info_response, next_packet(State#state{ssh_params = Ssh})}; + not_ok -> + userauth(Msg, State) + end; userauth_keyboard_interactive(#ssh_msg_userauth_info_response{} = Msg, #state{ssh_params = #ssh{role = server, @@ -646,7 +649,18 @@ userauth_keyboard_interactive(Msg = #ssh_msg_userauth_failure{}, userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_failure{}, - #state{ssh_params = #ssh{role = client}} = State) -> + #state{ssh_params = #ssh{role = client, + opts = Opts} = Ssh0} = State0) -> + + State = case proplists:get_value(password, Opts) of + undefined -> + State0; + _ -> + State0#state{ssh_params = + Ssh0#ssh{opts = + lists:keyreplace(password,1,Opts, + {password,not_ok})}} + end, userauth(Msg, State); userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client}} = State) -> @@ -1247,7 +1261,7 @@ init_ssh(client = Role, Vsn, Version, Options, Socket) -> end, AuthMethods = proplists:get_value(auth_methods, Options, - ?SUPPORTED_AUTH_METHODS), + undefined), {ok, PeerAddr} = inet:peername(Socket), PeerName = proplists:get_value(host, Options), -- cgit v1.2.3 From 688a278d0050ed088df17e351d14e3f9ba193501 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Jun 2016 13:34:47 +0200 Subject: ssh: Fix type error in args of ssh_auth:sort_selected_mthds --- lib/ssh/src/ssh_auth.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 86a91c6d22..07585dbacd 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -66,11 +66,15 @@ userauth_request_msg(#ssh{userauth_methods = ServerMethods, sort_select_mthds(Clients, undefined, Servers) -> %% User has not expressed an opinion via option "auth_methods", use the server's prefs - sort_select_mthds(Clients, Servers, Servers); + sort_select_mthds1(Clients, Servers, string:tokens(?SUPPORTED_AUTH_METHODS,",")); sort_select_mthds(Clients, Users0, Servers0) -> %% The User has an opinion, use the intersection of that and the Servers whishes but %% in the Users order + sort_select_mthds1(Clients, string:tokens(Users0,","), Servers0). + + +sort_select_mthds1(Clients, Users0, Servers0) -> Servers = unique(Servers0), Users = unique(Users0), [C || Key <- Users, -- cgit v1.2.3 From c66f6df0678362ce09558af1981473b0b8d82baf Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Jun 2016 14:41:22 +0200 Subject: ssh: Some code cuddling in ssh_io --- lib/ssh/src/ssh_io.erl | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index a5e627fdb3..6be5ea25bb 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -36,47 +36,42 @@ read_line(Prompt, Ssh) -> end. yes_no(Prompt, Ssh) -> - io:format("~s [y/n]?", [Prompt]), + format("~s [y/n]?", [Prompt]), proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question}, receive + %% I can't see that the atoms y and n are ever received, but it must + %% be investigated before removing + y -> yes; + n -> no; + Answer -> case trim(Answer) of "y" -> yes; "n" -> no; "Y" -> yes; "N" -> no; - y -> yes; - n -> no; _ -> - io:format("please answer y or n\n"), + format("please answer y or n\n",[]), yes_no(Prompt, Ssh) end end. -read_password(Prompt, Ssh) -> +read_password(Prompt, #ssh{opts=Opts}) -> read_password(Prompt, Opts); +read_password(Prompt, Opts) when is_list(Opts) -> format("~s", [listify(Prompt)]), - case is_list(Ssh) of - false -> - proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), user_password}; - _ -> - proplists:get_value(user_pid, Ssh) ! {self(), user_password} - end, + proplists:get_value(user_pid, Opts) ! {self(), user_password}, receive + "" -> + read_password(Prompt, Opts); Answer -> - case Answer of - "" -> - read_password(Prompt, Ssh); - Pass -> Pass - end + Answer end. -listify(A) when is_atom(A) -> - atom_to_list(A); -listify(L) when is_list(L) -> - L; -listify(B) when is_binary(B) -> - binary_to_list(B). + +listify(A) when is_atom(A) -> atom_to_list(A); +listify(L) when is_list(L) -> L; +listify(B) when is_binary(B) -> binary_to_list(B). format(Fmt, Args) -> io:format(Fmt, Args). @@ -93,6 +88,3 @@ trim1([$\r|Cs]) -> trim(Cs); trim1([$\n|Cs]) -> trim(Cs); trim1([$\t|Cs]) -> trim(Cs); trim1(Cs) -> Cs. - - - -- cgit v1.2.3 From d68b279981496c5293746524e00ff77fd8a8b84c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Jun 2016 14:49:04 +0200 Subject: ssh: Fix a hazard bug in ssh_auth --- lib/ssh/src/ssh_io.erl | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 6be5ea25bb..5e335c2063 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -31,7 +31,7 @@ read_line(Prompt, Ssh) -> format("~s", [listify(Prompt)]), proplists:get_value(user_pid, Ssh) ! {self(), question}, receive - Answer -> + Answer when is_list(Answer) -> Answer end. @@ -44,7 +44,7 @@ yes_no(Prompt, Ssh) -> y -> yes; n -> no; - Answer -> + Answer when is_list(Answer) -> case trim(Answer) of "y" -> yes; "n" -> no; @@ -62,20 +62,24 @@ read_password(Prompt, Opts) when is_list(Opts) -> format("~s", [listify(Prompt)]), proplists:get_value(user_pid, Opts) ! {self(), user_password}, receive - "" -> - read_password(Prompt, Opts); - Answer -> - Answer + Answer when is_list(Answer) -> + case trim(Answer) of + "" -> + read_password(Prompt, Opts); + Pwd -> + Pwd + end end. +format(Fmt, Args) -> + io:format(Fmt, Args). + +%%%================================================================ listify(A) when is_atom(A) -> atom_to_list(A); listify(L) when is_list(L) -> L; listify(B) when is_binary(B) -> binary_to_list(B). -format(Fmt, Args) -> - io:format(Fmt, Args). - trim(Line) when is_list(Line) -> lists:reverse(trim1(lists:reverse(trim1(Line)))); -- cgit v1.2.3 From c74cb8aaec77452f7c91ea5345c1b6120fe15224 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Jun 2016 16:01:38 +0200 Subject: ssh: polishing of password prompt's linefeed --- lib/ssh/src/ssh_auth.erl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 07585dbacd..0c378d084b 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -528,17 +528,18 @@ keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts). keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> - if Name /= "" -> IoCb:format("~s~n", [Name]); - true -> ok - end, - if Instr /= "" -> IoCb:format("~s~n", [Instr]); - true -> ok - end, + write_if_nonempty(IoCb, Name), + write_if_nonempty(IoCb, Instr), lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts); ({Prompt, false}) -> IoCb:read_password(Prompt, Opts) end, Prompts). +write_if_nonempty(_, "") -> ok; +write_if_nonempty(_, <<>>) -> ok; +write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]). + + keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, PromptInfos), -- cgit v1.2.3 From 53b5d8cf604222c970a298e98c6937e268011b54 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Jun 2016 13:03:40 +0200 Subject: ssh: Make client send a faulty pwd only once, ssh_auth part Conflicts: lib/ssh/src/ssh_connection_handler.erl --- lib/ssh/src/ssh_auth.erl | 214 ++++++++++++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 88 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 49eec8072f..1c6f656913 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -31,12 +31,107 @@ -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/2, handle_userauth_info_response/2 ]). %%-------------------------------------------------------------------- %%% Internal application API %%-------------------------------------------------------------------- +%%%---------------------------------------------------------------- +userauth_request_msg(#ssh{userauth_methods = ServerMethods, + userauth_supported_methods = UserPrefMethods, % Note: this is not documented as supported for clients + userauth_preference = ClientMethods0 + } = Ssh0) -> + case sort_select_mthds(ClientMethods0, UserPrefMethods, ServerMethods) of + [] -> + Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + description = "Unable to connect using the available authentication methods", + language = "en"}, + {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh0)}; + + [{Pref,Module,Function,Args} | Prefs] -> + Ssh = case Pref of + "keyboard-interactive" -> Ssh0; + _ -> Ssh0#ssh{userauth_preference = Prefs} + end, + case Module:Function(Args ++ [Ssh]) of + {not_ok, Ssh1} -> + userauth_request_msg(Ssh1#ssh{userauth_preference = Prefs}); + Result -> + {Pref,Result} + end + end. + + + +sort_select_mthds(Clients, undefined, Servers) -> + %% User has not expressed an opinion via option "auth_methods", use the server's prefs + sort_select_mthds(Clients, Servers, Servers); + +sort_select_mthds(Clients, Users0, Servers0) -> + %% The User has an opinion, use the intersection of that and the Servers whishes but + %% in the Users order + Servers = unique(Servers0), + Users = unique(Users0), + [C || Key <- Users, + lists:member(Key, Servers), + C <- Clients, + element(1,C) == Key]. + +unique(L) -> + lists:reverse( + lists:foldl(fun(E,Acc) -> + case lists:member(E,Acc) of + true -> Acc; + false -> [E|Acc] + end + end, [], L)). + + +%%%---- userauth_request_msg "callbacks" +password_msg([#ssh{opts = Opts, io_cb = IoCb, + user = User, service = Service} = Ssh0]) -> + {Password,Ssh} = + case proplists:get_value(password, Opts) of + undefined when IoCb == ssh_no_io -> + {not_ok, Ssh0}; + undefined -> + {IoCb:read_password("ssh password: ",Ssh0), Ssh0}; + PW -> + %% If "password" option is given it should not be tried again + {PW, Ssh0#ssh{opts = lists:keyreplace(password,1,Opts,{password,not_ok})}} + end, + case Password of + not_ok -> + {not_ok, Ssh}; + _ -> + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "password", + data = + <>}, + Ssh) + end. + +%% See RFC 4256 for info on keyboard-interactive +keyboard_interactive_msg([#ssh{user = User, + opts = Opts, + service = Service} = Ssh]) -> + case proplists:get_value(password, Opts) of + not_ok -> + {not_ok,Ssh}; % No need to use a failed pwd once more + _ -> + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "keyboard-interactive", + data = << ?STRING(<<"">>), + ?STRING(<<>>) >> }, + Ssh) + end. + publickey_msg([Alg, #ssh{user = User, session_id = SessionId, service = Service, @@ -48,7 +143,7 @@ publickey_msg([Alg, #ssh{user = User, StrAlgo = atom_to_list(Alg), case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of not_ok -> - not_ok; + {not_ok, Ssh}; PubKeyBlob -> SigData = build_sig_data(SessionId, User, Service, PubKeyBlob, StrAlgo), @@ -65,52 +160,15 @@ publickey_msg([Alg, #ssh{user = User, Ssh) end; _Error -> - not_ok + {not_ok, Ssh} end. -password_msg([#ssh{opts = Opts, io_cb = IoCb, - user = User, service = Service} = Ssh]) -> - Password = case proplists:get_value(password, Opts) of - undefined -> - user_interaction(IoCb, Ssh); - PW -> - PW - end, - case Password of - not_ok -> - not_ok; - _ -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "password", - data = - <>}, - Ssh) - end. - -user_interaction(ssh_no_io, _) -> - not_ok; -user_interaction(IoCb, Ssh) -> - IoCb:read_password("ssh password: ", Ssh). - - -%% See RFC 4256 for info on keyboard-interactive -keyboard_interactive_msg([#ssh{user = User, - service = Service} = Ssh]) -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "keyboard-interactive", - data = << ?STRING(<<"">>), - ?STRING(<<>>) >> }, - Ssh). - +%%%---------------------------------------------------------------- service_request_msg(Ssh) -> ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"}, Ssh#ssh{service = "ssh-userauth"}). +%%%---------------------------------------------------------------- init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> case user_name(Opts) of {ok, User} -> @@ -140,34 +198,9 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> description = ErrStr}) end. -userauth_request_msg(#ssh{userauth_preference = []} = Ssh) -> - Msg = #ssh_msg_disconnect{code = - ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, - description = "Unable to connect using the available" - " authentication methods", - language = "en"}, - {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh)}; - -userauth_request_msg(#ssh{userauth_methods = Methods, - userauth_preference = [{Pref, Module, - Function, Args} | Prefs]} - = Ssh0) -> - Ssh = Ssh0#ssh{userauth_preference = Prefs}, - case lists:member(Pref, Methods) of - true -> - case Module:Function(Args ++ [Ssh]) of - not_ok -> - userauth_request_msg(Ssh); - Result -> - {Pref,Result} - end; - false -> - userauth_request_msg(Ssh) - end. - - -handle_userauth_request(#ssh_msg_service_request{name = - Name = "ssh-userauth"}, +%%%---------------------------------------------------------------- +%%% called by server +handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"}, _, Ssh) -> {ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name}, Ssh#ssh{service = "ssh-connection"})}; @@ -319,21 +352,28 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, partial_success = false}, Ssh)}. - -handle_userauth_info_request( - #ssh_msg_userauth_info_request{name = Name, - instruction = Instr, - num_prompts = NumPrompts, - data = Data}, IoCb, - #ssh{opts = Opts} = Ssh) -> +%%%---------------------------------------------------------------- +%%% keyboard-interactive client +handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name, + instruction = Instr, + num_prompts = NumPrompts, + data = Data}, + #ssh{opts = Opts, + io_cb = IoCb + } = Ssh) -> PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data), - Responses = keyboard_interact_get_responses(IoCb, Opts, - Name, Instr, PromptInfos), - {ok, - ssh_transport:ssh_packet( - #ssh_msg_userauth_info_response{num_responses = NumPrompts, - data = Responses}, Ssh)}. + case keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) of + not_ok -> + not_ok; + Responses -> + {ok, + ssh_transport:ssh_packet( + #ssh_msg_userauth_info_response{num_responses = NumPrompts, + data = Responses}, Ssh)} + end. +%%%---------------------------------------------------------------- +%%% keyboard-interactive server handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, data = <>}, #ssh{opts = Opts, @@ -369,11 +409,6 @@ method_preference(Algs) -> [{"publickey", ?MODULE, publickey_msg, [A]} | Acc] end, [{"password", ?MODULE, password_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} ], Algs). @@ -473,6 +508,9 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> proplists:get_value(password, Opts, undefined), IoCb, Name, Instr, PromptInfos, Opts, NumPrompts). + +keyboard_interact_get_responses(_, _, not_ok, _, _, _, _, _, _) -> + not_ok; keyboard_interact_get_responses(_, undefined, Password, _, _, _, _, _, 1) when Password =/= undefined -> [Password]; %% Password auth implemented with keyboard-interaction and passwd is known -- cgit v1.2.3 From 4f24fd3f8943d8c7ebd56336d15f699dde9bcc61 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Jun 2016 13:03:40 +0200 Subject: ssh: Make client send a faulty pwd only once, ssh_connection_handler part --- lib/ssh/src/ssh_connection_handler.erl | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e952a333ff..27c205a932 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -428,7 +428,12 @@ init_connection(server, C = #connection{}, Opts) -> init_ssh_record(Role, Socket, Opts) -> {ok, PeerAddr} = inet:peername(Socket), KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - AuthMethods = proplists:get_value(auth_methods, Opts, ?SUPPORTED_AUTH_METHODS), + AuthMethods = proplists:get_value(auth_methods, + Opts, + case Role of + server -> ?SUPPORTED_AUTH_METHODS; + client -> undefined + end), S0 = #ssh{role = Role, key_cb = KeyCb, opts = Opts, @@ -794,9 +799,13 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) - handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client}, #data{ssh_params = Ssh0} = D) -> - {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, Ssh0#ssh.io_cb, Ssh0), - send_bytes(Reply, D), - {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}}; + case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of + {ok, {Reply, Ssh}} -> + send_bytes(Reply, D), + {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}}; + not_ok -> + {next_state, {userauth,client}, D, [{next_event, internal, Msg}]} + end; handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) -> case ssh_auth:handle_userauth_info_response(Msg, D#data.ssh_params) of @@ -819,7 +828,18 @@ handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactiv D = D0#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}, {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; -handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, D) -> +handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, + #data{ssh_params = Ssh0} = D0) -> + Opts = Ssh0#ssh.opts, + D = case proplists:get_value(password, Opts) of + undefined -> + D0; + _ -> + D0#data{ssh_params = + Ssh0#ssh{opts = + lists:keyreplace(password,1,Opts, + {password,not_ok})}} % FIXME:intermodule dependency + end, {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; handle_event(_, Msg=#ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> -- cgit v1.2.3 From 2219cdc9dfa500625527510a4e179cd9c068407a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Jun 2016 13:34:47 +0200 Subject: ssh: Fix type error in args of ssh_auth:sort_selected_mthds --- lib/ssh/src/ssh_auth.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 1c6f656913..c8f66c9d61 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -66,11 +66,15 @@ userauth_request_msg(#ssh{userauth_methods = ServerMethods, sort_select_mthds(Clients, undefined, Servers) -> %% User has not expressed an opinion via option "auth_methods", use the server's prefs - sort_select_mthds(Clients, Servers, Servers); + sort_select_mthds1(Clients, Servers, string:tokens(?SUPPORTED_AUTH_METHODS,",")); sort_select_mthds(Clients, Users0, Servers0) -> %% The User has an opinion, use the intersection of that and the Servers whishes but %% in the Users order + sort_select_mthds1(Clients, string:tokens(Users0,","), Servers0). + + +sort_select_mthds1(Clients, Users0, Servers0) -> Servers = unique(Servers0), Users = unique(Users0), [C || Key <- Users, -- cgit v1.2.3 From 51f0950b714505a036df7e1c3030b1c16828fd3c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Jun 2016 14:41:22 +0200 Subject: ssh: Some code cuddling in ssh_io --- lib/ssh/src/ssh_io.erl | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 026d0f6151..759ee71877 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -36,47 +36,42 @@ read_line(Prompt, Ssh) -> end. yes_no(Prompt, Ssh) -> - io:format("~s [y/n]?", [Prompt]), + format("~s [y/n]?", [Prompt]), proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question}, receive + %% I can't see that the atoms y and n are ever received, but it must + %% be investigated before removing + y -> yes; + n -> no; + Answer -> case trim(Answer) of "y" -> yes; "n" -> no; "Y" -> yes; "N" -> no; - y -> yes; - n -> no; _ -> - io:format("please answer y or n\n"), + format("please answer y or n\n",[]), yes_no(Prompt, Ssh) end end. -read_password(Prompt, Ssh) -> +read_password(Prompt, #ssh{opts=Opts}) -> read_password(Prompt, Opts); +read_password(Prompt, Opts) when is_list(Opts) -> format("~s", [listify(Prompt)]), - case is_list(Ssh) of - false -> - proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), user_password}; - _ -> - proplists:get_value(user_pid, Ssh) ! {self(), user_password} - end, + proplists:get_value(user_pid, Opts) ! {self(), user_password}, receive + "" -> + read_password(Prompt, Opts); Answer -> - case Answer of - "" -> - read_password(Prompt, Ssh); - Pass -> Pass - end + Answer end. -listify(A) when is_atom(A) -> - atom_to_list(A); -listify(L) when is_list(L) -> - L; -listify(B) when is_binary(B) -> - binary_to_list(B). + +listify(A) when is_atom(A) -> atom_to_list(A); +listify(L) when is_list(L) -> L; +listify(B) when is_binary(B) -> binary_to_list(B). format(Fmt, Args) -> io:format(Fmt, Args). @@ -93,6 +88,3 @@ trim1([$\r|Cs]) -> trim(Cs); trim1([$\n|Cs]) -> trim(Cs); trim1([$\t|Cs]) -> trim(Cs); trim1(Cs) -> Cs. - - - -- cgit v1.2.3 From 4586fd6fa3447a1c4191620fe5b8f4412d5af0ad Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Jun 2016 14:49:04 +0200 Subject: ssh: Fix a hazard bug in ssh_auth --- lib/ssh/src/ssh_io.erl | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 759ee71877..1d8f370884 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -31,7 +31,7 @@ read_line(Prompt, Ssh) -> format("~s", [listify(Prompt)]), proplists:get_value(user_pid, Ssh) ! {self(), question}, receive - Answer -> + Answer when is_list(Answer) -> Answer end. @@ -44,7 +44,7 @@ yes_no(Prompt, Ssh) -> y -> yes; n -> no; - Answer -> + Answer when is_list(Answer) -> case trim(Answer) of "y" -> yes; "n" -> no; @@ -62,20 +62,24 @@ read_password(Prompt, Opts) when is_list(Opts) -> format("~s", [listify(Prompt)]), proplists:get_value(user_pid, Opts) ! {self(), user_password}, receive - "" -> - read_password(Prompt, Opts); - Answer -> - Answer + Answer when is_list(Answer) -> + case trim(Answer) of + "" -> + read_password(Prompt, Opts); + Pwd -> + Pwd + end end. +format(Fmt, Args) -> + io:format(Fmt, Args). + +%%%================================================================ listify(A) when is_atom(A) -> atom_to_list(A); listify(L) when is_list(L) -> L; listify(B) when is_binary(B) -> binary_to_list(B). -format(Fmt, Args) -> - io:format(Fmt, Args). - trim(Line) when is_list(Line) -> lists:reverse(trim1(lists:reverse(trim1(Line)))); -- cgit v1.2.3 From 12cdbfec9d19378057e92200564198f359ea3c43 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Jun 2016 16:01:38 +0200 Subject: ssh: polishing of password prompt's linefeed --- lib/ssh/src/ssh_auth.erl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index c8f66c9d61..fb5e086656 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -528,17 +528,18 @@ keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts). keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> - if Name /= "" -> IoCb:format("~s~n", [Name]); - true -> ok - end, - if Instr /= "" -> IoCb:format("~s~n", [Instr]); - true -> ok - end, + write_if_nonempty(IoCb, Name), + write_if_nonempty(IoCb, Instr), lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts); ({Prompt, false}) -> IoCb:read_password(Prompt, Opts) end, Prompts). +write_if_nonempty(_, "") -> ok; +write_if_nonempty(_, <<>>) -> ok; +write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]). + + keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, PromptInfos), -- cgit v1.2.3 From 4fb1bf9584cd2a9efb5dd0b527961087a3871387 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 22 Jun 2016 12:06:37 +0200 Subject: ssh: remove 'sync sleeps' --- lib/ssh/src/ssh_connection_handler.erl | 3 --- 1 file changed, 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e952a333ff..a62af9e1ce 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1315,12 +1315,10 @@ terminate(shutdown, StateName, State0) -> State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, description = "Application shutdown"}, State0), -timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead finalize_termination(StateName, State); %% terminate({shutdown,Msg}, StateName, State0) when is_record(Msg,ssh_msg_disconnect)-> %% State = send_msg(Msg, State0), -%% timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead %% finalize_termination(StateName, Msg, State); terminate({shutdown,_R}, StateName, State) -> @@ -1635,7 +1633,6 @@ new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} = disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) -> State = send_msg(Msg, State0), disconnect_fun(Description, State), -timer:sleep(400), {stop, {shutdown,Description}, State}. %%%---------------------------------------------------------------- -- cgit v1.2.3 From 4b8ac323044db286123c3f9734681b53e824b6ac Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 22 Jun 2016 12:07:35 +0200 Subject: ssh: Extend experimental ssh_dbg.erl 1) extend ssh_dbg:message with second arg, a fun/1 that is intended to replace pids in the trace printouts with better descriptions. 2) printout improvments --- lib/ssh/src/ssh_dbg.erl | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 480795cfc7..bd6bc0335b 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, stop/0 ]). @@ -36,12 +37,16 @@ writer, acc = []}). %%%================================================================ -messages() -> messages(fun(String,_D) -> io:format(String) end). -%% messages() -> messages(fun(String,Acc) -> [String|Acc] end) +messages() -> + messages(fun(String,_D) -> io:format(String) end). messages(Write) when is_function(Write,2) -> + messages(Write, fun(X) -> X end). + +messages(Write, MangleArg) when is_function(Write,2), + is_function(MangleArg,1) -> catch dbg:start(), - setup_tracer(Write), + setup_tracer(Write, MangleArg), dbg:p(new,c), dbg_ssh_messages(). @@ -63,18 +68,30 @@ msg_formater({trace,_Pid,return_from,{ssh_message,encode,1},_Res}, D) -> msg_formater({trace,_Pid,call,{ssh_message,decode,_}}, D) -> D; msg_formater({trace,Pid,return_from,{ssh_message,decode,1},Msg}, D) -> - fmt("~nRECV ~p ~s~n", [Pid,wr_record(shrink_bin(Msg))], D); + fmt("~n~p RECV ~s~n", [Pid,wr_record(shrink_bin(Msg))], D); msg_formater({trace,_Pid,call,{ssh_transport,select_algorithm,_}}, D) -> D; msg_formater({trace,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg}}, D) -> - fmt("~nALGORITHMS ~p~n~s~n", [Pid, wr_record(Alg)], D); + fmt("~n~p ALGORITHMS~n~s~n", [Pid, wr_record(Alg)], D); + + +msg_formater({trace,Pid,send,{tcp,Sock,Bytes},Pid}, D) -> + fmt("~n~p TCP SEND on ~p~n ~p~n", [Pid,Sock, shrink_bin(Bytes)], D); + +msg_formater({trace,Pid,send,{tcp,Sock,Bytes},Dest}, D) -> + fmt("~n~p TCP SEND from ~p TO ~p~n ~p~n", [Pid,Sock,Dest, shrink_bin(Bytes)], D); msg_formater({trace,Pid,send,ErlangMsg,Dest}, D) -> - fmt("~nERL MSG ~p SEND TO ~p~n ~p~n", [Pid,Dest, shrink_bin(ErlangMsg)], D); + fmt("~n~p ERL MSG SEND TO ~p~n ~p~n", [Pid,Dest, shrink_bin(ErlangMsg)], D); + + +msg_formater({trace,Pid,'receive',{tcp,Sock,Bytes}}, D) -> + fmt("~n~p TCP RECEIVE on ~p~n ~p~n", [Pid,Sock,shrink_bin(Bytes)], D); msg_formater({trace,Pid,'receive',ErlangMsg}, D) -> - fmt("~nERL MSG ~p RECIEVE~n ~p~n", [Pid,shrink_bin(ErlangMsg)], D); + fmt("~n~p ERL MSG RECEIVE~n ~p~n", [Pid,shrink_bin(ErlangMsg)], D); + msg_formater(M, D) -> fmt("~nDBG ~n~p~n", [shrink_bin(M)], D). @@ -87,8 +104,10 @@ fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) -> D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}. %%%---------------------------------------------------------------- -setup_tracer(Write) -> - Handler = fun msg_formater/2, +setup_tracer(Write, MangleArg) -> + Handler = fun(Arg, D) -> + msg_formater(MangleArg(Arg), D) + end, InitialData = #data{writer = Write}, {ok,_} = dbg:tracer(process, {Handler, InitialData}), ok. -- cgit v1.2.3 From 698b11ec4a4f9944ea66ace7bacb17535f6cb9ee Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 23 Jun 2016 15:47:48 +0200 Subject: ssh: added dbg keys to ssh:connection_info/2 and ssh:channel_info/3 --- lib/ssh/src/ssh_connection_handler.erl | 73 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 37 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index a62af9e1ce..f825f0fae2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1006,13 +1006,13 @@ handle_event({call,From}, get_print_info, StateName, D) -> {keep_state_and_data, [{reply,From,Reply}]}; handle_event({call,From}, {connection_info, Options}, _, D) -> - Info = ssh_info(Options, D, []), + Info = fold_keys(Options, fun conn_info/2, D), {keep_state_and_data, [{reply,From,Info}]}; handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{} = Channel -> - Info = ssh_channel_info(Options, Channel, []), + Info = fold_keys(Options, fun chann_info/2, Channel), {keep_state_and_data, [{reply,From,Info}]}; undefined -> {keep_state_and_data, [{reply,From,[]}]} @@ -1641,43 +1641,43 @@ counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) -> counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) -> Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}. -ssh_info([], _State, Acc) -> - Acc; -ssh_info([client_version | Rest], #data{ssh_params = #ssh{c_vsn = IntVsn, - c_version = StringVsn}} = State, Acc) -> - ssh_info(Rest, State, [{client_version, {IntVsn, StringVsn}} | Acc]); - -ssh_info([server_version | Rest], #data{ssh_params =#ssh{s_vsn = IntVsn, - s_version = StringVsn}} = State, Acc) -> - ssh_info(Rest, State, [{server_version, {IntVsn, StringVsn}} | Acc]); -ssh_info([peer | Rest], #data{ssh_params = #ssh{peer = Peer}} = State, Acc) -> - ssh_info(Rest, State, [{peer, Peer} | Acc]); -ssh_info([sockname | Rest], #data{socket = Socket} = State, Acc) -> - {ok, SockName} = inet:sockname(Socket), - ssh_info(Rest, State, [{sockname, SockName}|Acc]); -ssh_info([user | Rest], #data{auth_user = User} = State, Acc) -> - ssh_info(Rest, State, [{user, User}|Acc]); -ssh_info([ _ | Rest], State, Acc) -> - ssh_info(Rest, State, Acc). - - -ssh_channel_info([], _, Acc) -> - Acc; +%%%---------------------------------------------------------------- +conn_info(client_version, #data{ssh_params=S}) -> {S#ssh.c_vsn, S#ssh.c_version}; +conn_info(server_version, #data{ssh_params=S}) -> {S#ssh.s_vsn, S#ssh.s_version}; +conn_info(peer, #data{ssh_params=S}) -> S#ssh.peer; +conn_info(user, D) -> D#data.auth_user; +conn_info(sockname, D) -> {ok, SockName} = inet:sockname(D#data.socket), + SockName; +%% dbg options ( = not documented): +conn_info(socket, D) -> D#data.socket; +conn_info(chan_ids, D) -> + ssh_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) -> + [Id | Acc] + end, [], cache(D)). -ssh_channel_info([recv_window | Rest], #channel{recv_window_size = WinSize, - recv_packet_size = Packsize - } = Channel, Acc) -> - ssh_channel_info(Rest, Channel, [{recv_window, {{win_size, WinSize}, - {packet_size, Packsize}}} | Acc]); -ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize, - send_packet_size = Packsize - } = Channel, Acc) -> - ssh_channel_info(Rest, Channel, [{send_window, {{win_size, WinSize}, - {packet_size, Packsize}}} | Acc]); -ssh_channel_info([ _ | Rest], Channel, Acc) -> - ssh_channel_info(Rest, Channel, Acc). +%%%---------------------------------------------------------------- +chann_info(recv_window, C) -> + {{win_size, C#channel.recv_window_size}, + {packet_size, C#channel.recv_packet_size}}; +chann_info(send_window, C) -> + {{win_size, C#channel.send_window_size}, + {packet_size, C#channel.send_packet_size}}; +%% dbg options ( = not documented): +chann_info(pid, C) -> + C#channel.user. +%%%---------------------------------------------------------------- +%% Assisting meta function for the *_info functions +fold_keys(Keys, Fun, Extra) -> + lists:foldr(fun(Key, Acc) -> + try Fun(Key, Extra) of + Value -> [{Key,Value}|Acc] + catch + _:_ -> Acc + end + end, [], Keys). +%%%---------------------------------------------------------------- log_error(Reason) -> Report = io_lib:format("Erlang ssh connection handler failed with reason:~n" " ~p~n" @@ -1686,7 +1686,6 @@ log_error(Reason) -> [Reason, erlang:get_stacktrace()]), error_logger:error_report(Report). - %%%---------------------------------------------------------------- not_connected_filter({connection_reply, _Data}) -> true; not_connected_filter(_) -> false. -- cgit v1.2.3 From 50aa639c4c3398c40fdce514e45ce687aa189189 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 28 Jun 2016 16:57:51 +0200 Subject: ssh: Remove possible hanging in TCs when server and client is on the same node --- lib/ssh/src/ssh_connection_handler.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f825f0fae2..5e05188e7f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1206,8 +1206,9 @@ handle_event(internal, prepare_next_packet, _, D) -> Sz when Sz >= Enough -> self() ! {D#data.transport_protocol, D#data.socket, <<>>}; _ -> - inet:setopts(D#data.socket, [{active, once}]) + ok end, + inet:setopts(D#data.socket, [{active, once}]), keep_state_and_data; handle_event(info, {CloseTag,Socket}, StateName, -- cgit v1.2.3 From adf04d0c3dbb20e539892a1262078eb5a6538c97 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Mon, 25 Jul 2016 15:41:19 +0200 Subject: Rewrite SSH for gen_statem M:callback_mode/0 --- lib/ssh/src/ssh_connection_handler.erl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f9f4c82351..dcb6ff9343 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -60,7 +60,8 @@ ]). %%% Behaviour callbacks --export([handle_event/4, terminate/3, format_status/2, code_change/4]). +-export([callback_mode/0, handle_event/4, terminate/3, + format_status/2, code_change/4]). %%% Exports not intended to be used :). They are used for spawning and tests -export([init_connection_handler/3, % proc_lib:spawn needs this @@ -374,14 +375,12 @@ init_connection_handler(Role, Socket, Opts) -> S -> gen_statem:enter_loop(?MODULE, [], %%[{debug,[trace,log,statistics,debug]} || Role==server], - handle_event_function, {hello,Role}, S) catch _:Error -> gen_statem:enter_loop(?MODULE, [], - handle_event_function, {init_error,Error}, S0) end. @@ -504,6 +503,9 @@ init_ssh_record(Role, Socket, Opts) -> %%% ######## Error in the initialisation #### +callback_mode() -> + handle_event_function. + handle_event(_, _Event, {init_error,Error}, _) -> case Error of {badmatch,{error,enotconn}} -> @@ -1401,12 +1403,12 @@ fmt_stat_rec(FieldNames, Rec, Exclude) -> state_name(), #data{}, term() - ) -> {gen_statem:callback_mode(), state_name(), #data{}}. + ) -> {ok, state_name(), #data{}}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . code_change(_OldVsn, StateName, State, _Extra) -> - {handle_event_function, StateName, State}. + {ok, StateName, State}. %%==================================================================== -- cgit v1.2.3 From 63b3d3a4d5afbf73f215cd343b9a84591d520d72 Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Fri, 29 Apr 2016 15:56:09 +0200 Subject: ssh: sshc_sup to use worker for ssh_con_handler --- lib/ssh/src/sshc_sup.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 8ee6aacfb5..b8275ba1eb 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -64,7 +64,7 @@ child_spec(_) -> Name = undefined, % As simple_one_for_one is used. StartFunc = {ssh_connection_handler, start_link, []}, Restart = temporary, - Shutdown = infinity, + Shutdown = 4000, Modules = [ssh_connection_handler], - Type = supervisor, + Type = worker, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -- cgit v1.2.3 From 6f15ceab49bf106bd023cad3ddec6af50235dd42 Mon Sep 17 00:00:00 2001 From: Aleksei Magusev Date: Tue, 23 Aug 2016 00:44:10 +0200 Subject: Improve ssh:start/1,2 functions Use application:ensure_all_started/2 instead of hard-coding dependencies --- lib/ssh/src/ssh.erl | 123 ++++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 62 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 0570853a9b..1d7be3547b 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -52,16 +52,15 @@ %% is temporary. see application(3) %%-------------------------------------------------------------------- start() -> - application:start(crypto), - application:start(asn1), - application:start(public_key), - application:start(ssh). + start(temporary). start(Type) -> - application:start(crypto, Type), - application:start(asn1), - application:start(public_key, Type), - application:start(ssh, Type). + case application:ensure_all_started(ssh, Type) of + {ok, _} -> + ok; + Other -> + Other + end. %%-------------------------------------------------------------------- -spec stop() -> ok | {error, term()}. @@ -90,7 +89,7 @@ connect(Socket, Options, Timeout) when is_port(Socket) -> {error, Error}; {_SocketOptions, SshOptions} -> case valid_socket_to_use(Socket, Options) of - ok -> + ok -> {ok, {Host,_Port}} = inet:sockname(Socket), Opts = [{user_pid,self()}, {host,fmt_host(Host)} | SshOptions], ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); @@ -128,23 +127,23 @@ connect(Host, Port, Options, Timeout) -> -spec close(pid()) -> ok. %% %% Description: Closes an ssh connection. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- close(ConnectionRef) -> ssh_connection_handler:stop(ConnectionRef). %%-------------------------------------------------------------------- -spec connection_info(pid(), [atom()]) -> [{atom(), term()}]. %% -%% Description: Retrieves information about a connection. -%%-------------------------------------------------------------------- +%% Description: Retrieves information about a connection. +%%-------------------------------------------------------------------- connection_info(ConnectionRef, Options) -> ssh_connection_handler:connection_info(ConnectionRef, Options). %%-------------------------------------------------------------------- -spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}]. %% -%% Description: Retrieves information about a connection. -%%-------------------------------------------------------------------- +%% Description: Retrieves information about a connection. +%%-------------------------------------------------------------------- channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). @@ -153,9 +152,9 @@ channel_info(ConnectionRef, ChannelId, Options) -> -spec daemon(integer()|port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. -spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. -%% Description: Starts a server listening for SSH connections +%% Description: Starts a server listening for SSH connections %% on the given port. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- daemon(Port) -> daemon(Port, []). @@ -188,9 +187,9 @@ daemon_info(Pid) -> -spec stop_listener(pid()) -> ok. -spec stop_listener(inet:ip_address(), integer()) -> ok. %% -%% Description: Stops the listener, but leaves +%% Description: Stops the listener, but leaves %% existing connections started by the listener up and running. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- stop_listener(SysSup) -> ssh_system_sup:stop_listener(SysSup). stop_listener(Address, Port) -> @@ -202,9 +201,9 @@ stop_listener(Address, Port, Profile) -> -spec stop_daemon(pid()) -> ok. -spec stop_daemon(inet:ip_address(), integer()) -> ok. %% -%% Description: Stops the listener and all connections started by +%% Description: Stops the listener and all connections started by %% the listener. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- stop_daemon(SysSup) -> ssh_system_sup:stop_system(SysSup). stop_daemon(Address, Port) -> @@ -243,7 +242,7 @@ start_shell({ok, ConnectionRef}) -> case ssh_connection:session_channel(ConnectionRef, infinity) of {ok,ChannelId} -> success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []), - Args = [{channel_cb, ssh_shell}, + Args = [{channel_cb, ssh_shell}, {init_args,[ConnectionRef, ChannelId]}, {cm, ConnectionRef}, {channel_id, ChannelId}], {ok, State} = ssh_channel:init([Args]), @@ -256,7 +255,7 @@ start_shell(Error) -> %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- -default_algorithms() -> +default_algorithms() -> ssh_transport:default_algorithms(). %%-------------------------------------------------------------------- @@ -296,13 +295,13 @@ daemon_shell_opt(Options) -> daemon_host_inet_opt(HostAddr, Options1) -> case HostAddr of any -> - {ok, Host0} = inet:gethostname(), + {ok, Host0} = inet:gethostname(), {Host0, proplists:get_value(inet, Options1, inet), Options1}; {_,_,_,_} -> - {HostAddr, inet, + {HostAddr, inet, [{ip, HostAddr} | Options1]}; {_,_,_,_,_,_,_,_} -> - {HostAddr, inet6, + {HostAddr, inet6, [{ip, HostAddr} | Options1]} end. @@ -313,8 +312,8 @@ start_daemon(Socket, Options) -> {error, Error}; {SocketOptions, SshOptions} -> case valid_socket_to_use(Socket, Options) of - ok -> - try + ok -> + try do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions) catch throw:bad_fd -> {error,bad_fd}; @@ -330,16 +329,16 @@ start_daemon(Host, Port, Options, Inet) -> {error, _Reason} = Error -> Error; {SocketOptions, SshOptions}-> - try + try do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions]) catch throw:bad_fd -> {error,bad_fd}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end end. - + do_start_daemon(Socket, SshOptions, SocketOptions) -> - {ok, {IP,Port}} = + {ok, {IP,Port}} = try {ok,_} = inet:sockname(Socket) catch _:_ -> throw(bad_socket) @@ -351,7 +350,7 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> {address, Host}, {port, Port}, {role, server}, - {socket_opts, SocketOptions}, + {socket_opts, SocketOptions}, {ssh_opts, SshOptions}], {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), case ssh_system_sup:system_supervisor(Host, Port, Profile) of @@ -385,7 +384,7 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> end. do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> - {Host,Port1} = + {Host,Port1} = try case proplists:get_value(fd, SocketOptions) of undefined -> @@ -402,21 +401,21 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> {Port, WaitRequestControl, Opts0} = case Port1 of 0 -> %% Allocate the socket here to get the port number... - {_, Callback, _} = + {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), {ok,LSock} = ssh_acceptor:callback_listen(Callback, 0, SocketOptions), {ok,{_,LPort}} = inet:sockname(LSock), - {LPort, - {LSock,Callback}, + {LPort, + {LSock,Callback}, [{lsocket,LSock},{lsock_owner,self()}] }; _ -> {Port1, false, []} end, - Opts = [{address, Host}, + Opts = [{address, Host}, {port, Port}, {role, server}, - {socket_opts, SocketOptions}, + {socket_opts, SocketOptions}, {ssh_opts, SshOptions} | Opts0], case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> @@ -465,7 +464,7 @@ find_hostport(Fd) -> {ok, HostPort} = inet:sockname(S), ok = inet:close(S), HostPort. - + handle_options(Opts) -> try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of @@ -480,9 +479,9 @@ handle_options(Opts) -> algs_compatibility(Os0) -> %% Take care of old options 'public_key_alg' and 'pref_public_key_algs' case proplists:get_value(public_key_alg, Os0) of - undefined -> + undefined -> Os0; - A when is_atom(A) -> + A when is_atom(A) -> %% Skip public_key_alg if pref_public_key_algs is defined: Os = lists:keydelete(public_key_alg, 1, Os0), case proplists:get_value(pref_public_key_algs,Os) of @@ -492,7 +491,7 @@ algs_compatibility(Os0) -> [{pref_public_key_algs,['ssh-dss','ssh-rsa']} | Os]; undefined -> throw({error, {eoptions, {public_key_alg,A} }}); - _ -> + _ -> Os end; V -> @@ -620,7 +619,7 @@ 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({preferred_algorithms,[_|_]} = Opt) -> +handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> handle_pref_algs(Opt); handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> @@ -629,7 +628,7 @@ handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> lists:foldl( fun({N,G,P}, Acc) when is_integer(N),N>0, is_integer(G),G>0, - is_integer(P),P>0 -> + is_integer(P),P>0 -> [{N,{G,P}} | Acc]; ({N,{G,P}}, Acc) when is_integer(N),N>0, is_integer(G),G>0, @@ -637,7 +636,7 @@ handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> [{N,{G,P}} | Acc]; ({N,GPs}, Acc) when is_list(GPs) -> lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, - is_integer(Pi),Pi>0 -> + is_integer(Pi),Pi>0 -> [{N,{Gi,Pi}} | Acci] end, Acc, GPs) end, [], L0))}; @@ -647,7 +646,7 @@ handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0, Tag == ssh_moduli_file -> {ok,GroupDefs} = case Tag of - file -> + file -> file:consult(File); ssh_moduli_file -> case file:open(File,[read]) of @@ -672,14 +671,14 @@ handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0, catch _:_ -> throw({error, {{eoptions, Opt}, "Bad format in file: "++File}}) - end; - + end; + -handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0, +handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0, is_integer(Max), Max>=Min -> %% Server Opt; -handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, +handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, is_integer(I), I>=Min, is_integer(Max), Max>=I -> %% Client @@ -724,7 +723,7 @@ handle_ssh_option({keyboard_interact_fun, Value} = Opt) when is_function(Value,3 Opt; handle_ssh_option({compression, Value} = Opt) when is_atom(Value) -> Opt; -handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module), +handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module), is_atom(Function) -> Opt; handle_ssh_option({exec, Function} = Opt) when is_function(Function) -> @@ -772,7 +771,7 @@ handle_ssh_option({quiet_mode, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> Opt; -handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> +handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> Opt; handle_ssh_option({id_string, random}) -> {id_string, {random,2,5}}; %% 2 - 5 random characters @@ -814,11 +813,11 @@ handle_pref_algs({preferred_algorithms,Algs}) -> of DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs) catch - _:_ -> throw({error, {{eoptions, {preferred_algorithms,Key}}, + _:_ -> throw({error, {{eoptions, {preferred_algorithms,Key}}, "Bad preferred_algorithms key"}}) end || {Key,Vals} <- Algs] }; - + Dups -> throw({error, {{eoptions, {preferred_algorithms,Dups}}, "Duplicates found"}}) catch @@ -857,13 +856,13 @@ handle_pref_alg(Key, ) -> handle_pref_alg(Key, lists:reverse(Vs), Sup); -handle_pref_alg(Key, +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, +handle_pref_alg(Key, Vs=[V|_], Sup=[S|_] ) when is_atom(V), is_atom(S) -> @@ -878,14 +877,14 @@ chk_alg_vs(OptKey, Values, SupportedValues) -> [] -> 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 -> Inet; false -> case lists:member(inet6, Inet) of - true -> + true -> Inet; false -> [inet | Inet] @@ -916,8 +915,8 @@ directory_exist_readable(Dir) -> {error, Error} -> {error, Error} end. - - + + collect_per_size(L) -> lists:foldr( @@ -948,7 +947,7 @@ read_moduli_file(D, I, Acc) -> read_moduli_file(D, I+1, Acc) end end. - + handle_user_pref_pubkey_algs([], Acc) -> {true, lists:reverse(Acc)}; handle_user_pref_pubkey_algs([H|T], Acc) -> @@ -963,7 +962,7 @@ handle_user_pref_pubkey_algs([H|T], Acc) -> false end. -fmt_host({A,B,C,D}) -> +fmt_host({A,B,C,D}) -> lists:concat([A,".",B,".",C,".",D]); -fmt_host(T={_,_,_,_,_,_,_,_}) -> +fmt_host(T={_,_,_,_,_,_,_,_}) -> lists:flatten(string:join([io_lib:format("~.16B",[A]) || A <- tuple_to_list(T)], ":")). -- cgit v1.2.3 From cd1ca5e2dd8dde12a33ab7d4ffb9524d7f141fa7 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Thu, 25 Aug 2016 14:18:24 +0200 Subject: Fix version numbers and dependencies --- lib/ssh/src/ssh.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 3245ba5197..76b7d8cd55 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -45,7 +45,7 @@ "erts-6.0", "kernel-3.0", "public_key-1.1", - "stdlib-3.0" + "stdlib-3.1" ]}]}. -- cgit v1.2.3 From 85fc9764cee4ba48bb6cac71efc400415508e0d0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 29 Aug 2016 13:07:57 +0200 Subject: ssh: fix Codenomicon/Defensics auth problem with incomplete pdu Trailing pdu values being 0 or empty strings are just excluded from the pdu by Codenomicon/Defensics. This is wrong but some kind of habit "out there". This commit makes Erlang SSH accept such pdu in one place because Defensics is king of security tests ... --- lib/ssh/src/ssh_auth.erl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index fb5e086656..1dcf5d0708 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -264,12 +264,23 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, SessionId, #ssh{opts = Opts, userauth_supported_methods = Methods} = Ssh) -> - <> = Data, - Alg = binary_to_list(BAlg), + + <> = Data, + + {KeyBlob, SigWLen} = + case Rest of + <> -> + {KeyBlob0, SigWLen0}; + <<>> -> + {<<>>, <<>>} + end, + case HaveSig of ?TRUE -> - case verify_sig(SessionId, User, "ssh-connection", Alg, + case verify_sig(SessionId, User, "ssh-connection", + binary_to_list(BAlg), KeyBlob, SigWLen, Opts) of true -> {authorized, User, @@ -284,7 +295,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, ?FALSE -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet( - #ssh_msg_userauth_pk_ok{algorithm_name = Alg, + #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), key_blob = KeyBlob}, Ssh)} end; -- cgit v1.2.3 From 28baf1314b556bb592c24181f6967e1f324f44a7 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 Aug 2016 13:28:00 +0200 Subject: ssh: Add non-blocking send This is to try to fix ssh_connection_SUITE:interrupted_send problem. On machines with small buffers (<65k) like some Windows and *BSDs, this test case could deadlock with both sides having filled tcp receice buffers but stuck in prim_inet:send. This commit fixes this. --- lib/ssh/src/ssh_connection_handler.erl | 92 +++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 34 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index dcb6ff9343..2eb29c9b32 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -339,6 +339,7 @@ renegotiate_data(ConnectionHandler) -> ssh_params :: #ssh{} | undefined, socket :: inet:socket(), + sender :: pid() | undefined, decrypted_data_buffer = <<>> :: binary(), encrypted_data_buffer = <<>> :: binary(), undecrypted_packet_length :: undefined | non_neg_integer(), @@ -367,9 +368,10 @@ init_connection_handler(Role, Socket, Opts) -> {Protocol, Callback, CloseTag} = proplists:get_value(transport, Opts, ?DefaultTransport), S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag + sender = spawn_link(fun() -> nonblocking_sender(Socket, Callback) end), + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag } of S -> @@ -525,7 +527,7 @@ handle_event(_, _Event, {init_error,Error}, _) -> %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)), - ok = send_bytes(VsnMsg, D), + send_bytes(VsnMsg, D), case inet:getopts(Socket=D#data.socket, [recbuf]) of {ok, [{recbuf,Size}]} -> %% Set the socket to the hello text line handling mode: @@ -550,7 +552,7 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) -> server -> %% But the client may NOT send them to the server. Openssh answers with cleartext, %% and so do we - ok = send_bytes("Protocol mismatch.", D), + send_bytes("Protocol mismatch.", D), {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}} end; @@ -565,7 +567,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D) -> {active, once}, {recbuf, D#data.inet_initial_recbuf_size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), - ok = send_bytes(SshPacket, D), + send_bytes(SshPacket, D), {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; not_supported -> @@ -583,7 +585,7 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload), Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of {ok, NextKexMsg, Ssh2} when Role==client -> - ok = send_bytes(NextKexMsg, D), + send_bytes(NextKexMsg, D), Ssh2; {ok, Ssh2} when Role==server -> Ssh2 @@ -596,43 +598,43 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, %%%---- diffie-hellman handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), - ok = send_bytes(KexdhReply, D), + send_bytes(KexdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- diffie-hellman group exchange handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - ok = send_bytes(GexGroup, D), + send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - ok = send_bytes(GexGroup, D), + send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params), - ok = send_bytes(KexGexInit, D), + send_bytes(KexGexInit, D), {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- elliptic curve diffie-hellman handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), - ok = send_bytes(KexEcdhReply, D), + send_bytes(KexEcdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; @@ -640,9 +642,9 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), - ok = send_bytes(KexGexReply, D), + send_bytes(KexGexReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; @@ -650,7 +652,7 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}}; @@ -662,7 +664,7 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> Ssh = case Role of client -> {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1), - ok = send_bytes(MsgReq, D), + send_bytes(MsgReq, D), Ssh2; server -> Ssh1 @@ -680,7 +682,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s "ssh-userauth" -> Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params, {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; _ -> @@ -692,7 +694,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), - ok = send_bytes(Msg, State), + send_bytes(Msg, State), {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; @@ -709,7 +711,7 @@ handle_event(_, %% Probably the very first userauth_request but we deny unauthorized login {not_authorized, _, {Reply,Ssh}} = ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}}; {"ssh-connection", "ssh-connection", Method} -> @@ -719,7 +721,7 @@ handle_event(_, %% Yepp! we support this method case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of {authorized, User, {Reply, Ssh}} -> - ok = send_bytes(Reply, D), + send_bytes(Reply, D), D#data.starter ! ssh_connected, connected_fun(User, Method, D), {next_state, {connected,server}, @@ -727,11 +729,11 @@ handle_event(_, ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Reason, D), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}} end; false -> @@ -1430,18 +1432,15 @@ start_the_connection_child(UserPid, Role, Socket, Options) -> %% Stopping -type finalize_termination_result() :: ok . -finalize_termination(_StateName, #data{transport_cb = Transport, - connection_state = Connection, - socket = Socket}) -> - case Connection of +finalize_termination(_StateName, D) -> + case D#data.connection_state of #connection{system_supervisor = SysSup, sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) -> ssh_system_sup:stop_subsystem(SysSup, SubSysSup); _ -> do_nothing end, - (catch Transport:close(Socket)), - ok. + close_transport(D). %%-------------------------------------------------------------------- %% "Invert" the Role @@ -1496,8 +1495,33 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> send_bytes(Bytes, State), State#data{ssh_params=Ssh}. -send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> - Transport:send(Socket, Bytes). +send_bytes(Bytes, #data{sender = Sender}) -> + Sender ! {send,Bytes}, + ok. + +close_transport(D) -> + D#data.sender ! close, + ok. + + +nonblocking_sender(Socket, Callback) -> + receive + {send, Bytes} -> + case Callback:send(Socket, Bytes) of + ok -> + nonblocking_sender(Socket, Callback); + E = {error,_} -> + exit({shutdown,E}) + end; + + close -> + case Callback:close(Socket) of + ok -> + ok; + E = {error,_} -> + exit({shutdown,E}) + end + end. handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), -- cgit v1.2.3 From 3430829486d4c2a2af32214107ba39f9028d7aa8 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 29 Aug 2016 17:20:37 +0200 Subject: ssh: reduce random padding to 15 bytes This is to get rid of some warnings in Codenomicon/Defensics. It also speeds up the communications. --- lib/ssh/src/ssh.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 868f3a9181..4cd91177f6 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -127,7 +127,7 @@ recv_sequence = 0, keyex_key, keyex_info, - random_length_padding = 255, % From RFC 4253 section 6. + random_length_padding = 15, % From RFC 4253 section 6. %% User auth user, -- cgit v1.2.3 From 7fa1f7e973369dedd56f1be17d3b075d72ec0b60 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Sep 2016 18:20:22 +0200 Subject: ssh: fix no detect of tcp close --- lib/ssh/src/ssh_connection_handler.erl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 2eb29c9b32..00bf1a3885 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1238,9 +1238,12 @@ handle_event(internal, prepare_next_packet, _, D) -> handle_event(info, {CloseTag,Socket}, StateName, D = #data{socket = Socket, transport_close_tag = CloseTag}) -> - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Connection closed"}, - StateName, D); + %% Simulate a disconnect from the peer + handle_event(info, + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "Connection closed"}, + StateName, + D); handle_event(info, {timeout, {_, From} = Request}, _, #data{connection_state = #connection{requests = Requests} = C0} = D) -> -- cgit v1.2.3 From c4e9732c040966366e0719a62550f30e45fc01a3 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Sep 2016 22:38:13 +0200 Subject: ssh: separate clauses for first and second pk auth msg SSH sends the public key and user name twice. If we do not check the validity of that pair at the first time, Codenomicon Defensics will complain. --- lib/ssh/src/ssh_auth.erl | 66 +++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 31 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 1dcf5d0708..7793d77f36 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -260,43 +260,45 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "publickey", - data = Data}, + data = <> + }, SessionId, #ssh{opts = Opts, userauth_supported_methods = Methods} = Ssh) -> - <> = Data, - - {KeyBlob, SigWLen} = - case Rest of - <> -> - {KeyBlob0, SigWLen0}; - <<>> -> - {<<>>, <<>>} - end, + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet( + #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), + key_blob = KeyBlob}, Ssh)}; - case HaveSig of - ?TRUE -> - case verify_sig(SessionId, User, "ssh-connection", - binary_to_list(BAlg), - KeyBlob, SigWLen, Opts) of - true -> - {authorized, User, - ssh_transport:ssh_packet( - #ssh_msg_userauth_success{}, Ssh)}; - false -> - {not_authorized, {User, undefined}, - ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = Methods, - partial_success = false}, Ssh)} - end; - ?FALSE -> - {not_authorized, {User, undefined}, +handle_userauth_request(#ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = "publickey", + data = <> + }, + SessionId, + #ssh{opts = Opts, + userauth_supported_methods = Methods} = Ssh) -> + + case verify_sig(SessionId, User, "ssh-connection", + binary_to_list(BAlg), + KeyBlob, SigWLen, Opts) of + true -> + {authorized, User, ssh_transport:ssh_packet( - #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), - key_blob = KeyBlob}, Ssh)} + #ssh_msg_userauth_success{}, Ssh)}; + false -> + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = Methods, + partial_success = false}, Ssh)} end; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -484,6 +486,8 @@ get_password_option(Opts, User) -> false -> proplists:get_value(password, Opts, false) end. +%%pre_verify_sig(SessionId, User, Service, Alg, KeyBlob, Opts) -> + verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> {ok, Key} = decode_public_key_v2(KeyBlob, Alg), KeyCb = proplists:get_value(key_cb, Opts, ssh_file), -- cgit v1.2.3 From 9b988fa6edd9db2396ade2141e14f0fc7b68cfd2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Sep 2016 21:31:54 +0200 Subject: ssh: make ecdsa sha dependent on curve Bug fix. --- lib/ssh/src/ssh_auth.erl | 4 ++-- lib/ssh/src/ssh_transport.erl | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 1dcf5d0708..afc6ec5a56 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -140,7 +140,7 @@ publickey_msg([Alg, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> - Hash = sha, %% Maybe option?! + Hash = ssh_transport:sha(Alg), KeyCb = proplists:get_value(key_cb, Opts, ssh_file), case KeyCb:user_key(Alg, Opts) of {ok, PrivKey} -> @@ -495,7 +495,7 @@ verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> <> = SigWLen, <> = AlgSig, - ssh_transport:verify(PlainText, sha, Sig, Key); + ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key); false -> false end. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7cb3b75ac0..15b80de30a 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -46,7 +46,7 @@ handle_kex_ecdh_reply/2, extract_public_key/1, ssh_packet/2, pack/2, - sign/3, verify/4]). + sha/1, sign/3, verify/4]). %%% For test suites -export([pack/3]). @@ -1619,6 +1619,11 @@ kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) -> crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). +sha('ssh-rsa') -> sha; +sha('ssh-dss') -> sha; +sha('ecdsa-sha2-nistp256') -> sha(secp256r1); +sha('ecdsa-sha2-nistp384') -> sha(secp384r1); +sha('ecdsa-sha2-nistp521') -> sha(secp521r1); sha(secp256r1) -> sha256; sha(secp384r1) -> sha384; sha(secp521r1) -> sha512; -- cgit v1.2.3 From c0dfb5487a5ca79c35506905090b14e4abe06e3a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Sep 2016 22:52:04 +0200 Subject: ssh: pre-verify key Conflicts: lib/ssh/src/ssh_auth.erl --- lib/ssh/src/ssh_auth.erl | 58 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 18 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 7793d77f36..0c1aaa2ede 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -266,14 +266,23 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, _/binary >> }, - SessionId, + _SessionId, #ssh{opts = Opts, userauth_supported_methods = Methods} = Ssh) -> - {not_authorized, {User, undefined}, - ssh_transport:ssh_packet( - #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), - key_blob = KeyBlob}, Ssh)}; + case pre_verify_sig(User, binary_to_list(BAlg), + KeyBlob, Opts) of + true -> + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet( + #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), + key_blob = KeyBlob}, Ssh)}; + false -> + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = Methods, + partial_success = false}, Ssh)} + end; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", @@ -486,21 +495,34 @@ get_password_option(Opts, User) -> false -> proplists:get_value(password, Opts, false) end. -%%pre_verify_sig(SessionId, User, Service, Alg, KeyBlob, Opts) -> +pre_verify_sig(User, Alg, KeyBlob, Opts) -> + try + {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + KeyCb = proplists:get_value(key_cb, Opts, ssh_file), + KeyCb:is_auth_key(Key, User, Opts) + catch + _:_ -> + false + end. verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - - case KeyCb:is_auth_key(Key, User, Opts) of - true -> - PlainText = build_sig_data(SessionId, User, - Service, KeyBlob, Alg), - <> = SigWLen, - <> = AlgSig, - ssh_transport:verify(PlainText, sha, Sig, Key); - false -> + try + {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + KeyCb = proplists:get_value(key_cb, Opts, ssh_file), + + case KeyCb:is_auth_key(Key, User, Opts) of + true -> + PlainText = build_sig_data(SessionId, User, + Service, KeyBlob, Alg), + <> = SigWLen, + <> = AlgSig, + ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key); + false -> + false + end + catch + _:_ -> false end. -- cgit v1.2.3 From df8da1d56961e999a43531b64a6f312b60da93d9 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 2 Sep 2016 14:55:58 +0200 Subject: ssh: add tstflg value one_empty to force daemon send empty ssh_msg_userauth_info_request This behavour is assumed by Codenomicon Defensics. --- lib/ssh/src/ssh_auth.erl | 131 ++++++++++++++++++++++----------- lib/ssh/src/ssh_connection_handler.erl | 14 +++- lib/ssh/src/ssh_transport.erl | 7 +- 3 files changed, 106 insertions(+), 46 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 1dcf5d0708..ac35b70209 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -140,7 +140,7 @@ publickey_msg([Alg, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> - Hash = sha, %% Maybe option?! + Hash = ssh_transport:sha(Alg), KeyCb = proplists:get_value(key_cb, Opts, ssh_file), case KeyCb:user_key(Alg, Opts) of {ok, PrivKey} -> @@ -260,43 +260,54 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "publickey", - data = Data}, - SessionId, + data = <> + }, + _SessionId, #ssh{opts = Opts, userauth_supported_methods = Methods} = Ssh) -> - <> = Data, - - {KeyBlob, SigWLen} = - case Rest of - <> -> - {KeyBlob0, SigWLen0}; - <<>> -> - {<<>>, <<>>} - end, - - case HaveSig of - ?TRUE -> - case verify_sig(SessionId, User, "ssh-connection", - binary_to_list(BAlg), - KeyBlob, SigWLen, Opts) of - true -> - {authorized, User, - ssh_transport:ssh_packet( - #ssh_msg_userauth_success{}, Ssh)}; - false -> - {not_authorized, {User, undefined}, - ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = Methods, - partial_success = false}, Ssh)} - end; - ?FALSE -> + case pre_verify_sig(User, binary_to_list(BAlg), + KeyBlob, Opts) of + true -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet( #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), - key_blob = KeyBlob}, Ssh)} + key_blob = KeyBlob}, Ssh)}; + false -> + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = Methods, + partial_success = false}, Ssh)} + end; + +handle_userauth_request(#ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = "publickey", + data = <> + }, + SessionId, + #ssh{opts = Opts, + userauth_supported_methods = Methods} = Ssh) -> + + case verify_sig(SessionId, User, "ssh-connection", + binary_to_list(BAlg), + KeyBlob, SigWLen, Opts) of + true -> + {authorized, User, + ssh_transport:ssh_packet( + #ssh_msg_userauth_success{}, Ssh)}; + false -> + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = Methods, + partial_success = false}, Ssh)} end; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -395,10 +406,22 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, kb_tries_left = KbTriesLeft, user = User, userauth_supported_methods = Methods} = Ssh) -> + SendOneEmpty = proplists:get_value(tstflg, Opts) == one_empty, case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of + {true,Ssh1} when SendOneEmpty==true -> + Msg = #ssh_msg_userauth_info_request{name = "", + instruction = "", + language_tag = "", + num_prompts = 0, + data = <> + }, + {authorized_but_one_more, User, + ssh_transport:ssh_packet(Msg, Ssh1)}; + {true,Ssh1} -> {authorized, User, ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)}; + {false,Ssh1} -> {not_authorized, {User, {error,"Bad user or password"}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ @@ -408,6 +431,11 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, )} end; +handle_userauth_info_response({extra,#ssh_msg_userauth_info_response{}}, + #ssh{user = User} = Ssh) -> + {authorized, User, + ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)}; + handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> ssh_connection_handler:disconnect( @@ -484,19 +512,34 @@ get_password_option(Opts, User) -> false -> proplists:get_value(password, Opts, false) end. -verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), +pre_verify_sig(User, Alg, KeyBlob, Opts) -> + try + {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + KeyCb = proplists:get_value(key_cb, Opts, ssh_file), + KeyCb:is_auth_key(Key, User, Opts) + catch + _:_ -> + false + end. - case KeyCb:is_auth_key(Key, User, Opts) of - true -> - PlainText = build_sig_data(SessionId, User, - Service, KeyBlob, Alg), - <> = SigWLen, - <> = AlgSig, - ssh_transport:verify(PlainText, sha, Sig, Key); - false -> +verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> + try + {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + KeyCb = proplists:get_value(key_cb, Opts, ssh_file), + + case KeyCb:is_auth_key(Key, User, Opts) of + true -> + PlainText = build_sig_data(SessionId, User, + Service, KeyBlob, Alg), + <> = SigWLen, + <> = AlgSig, + ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key); + false -> + false + end + catch + _:_ -> false end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 2eb29c9b32..8ed638cec9 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -822,9 +822,21 @@ handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_inte {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D), send_bytes(Reply, D), - {next_state, {userauth,server}, D#data{ssh_params = Ssh}} + {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; + + {authorized_but_one_more, _User, {Reply, Ssh}} -> + send_bytes(Reply, D), + {next_state, {userauth_keyboard_interactive_extra,server}, D#data{ssh_params = Ssh}} end; +handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D) -> + {authorized, User, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response({extra,Msg}, D#data.ssh_params), + send_bytes(Reply, D), + D#data.starter ! ssh_connected, + connected_fun(User, "keyboard-interactive", D), + {next_state, {connected,server}, D#data{auth_user = User, + ssh_params = Ssh#ssh{authenticated = true}}}; + handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, #data{ssh_params = Ssh0} = D0) -> Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Ssh0#ssh.userauth_preference, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7cb3b75ac0..15b80de30a 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -46,7 +46,7 @@ handle_kex_ecdh_reply/2, extract_public_key/1, ssh_packet/2, pack/2, - sign/3, verify/4]). + sha/1, sign/3, verify/4]). %%% For test suites -export([pack/3]). @@ -1619,6 +1619,11 @@ kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) -> crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). +sha('ssh-rsa') -> sha; +sha('ssh-dss') -> sha; +sha('ecdsa-sha2-nistp256') -> sha(secp256r1); +sha('ecdsa-sha2-nistp384') -> sha(secp384r1); +sha('ecdsa-sha2-nistp521') -> sha(secp521r1); sha(secp256r1) -> sha256; sha(secp384r1) -> sha384; sha(secp521r1) -> sha512; -- cgit v1.2.3 From 5e698cb679546dae32c64fabd4e5a65cb5886297 Mon Sep 17 00:00:00 2001 From: Svilen Ivanov Date: Mon, 19 Sep 2016 15:59:21 +0300 Subject: Fix SSH custom REPL exit status When user defined SSH shell REPL process exits with reason normal SSH channel callback module should report successful exit status to the SSH client. This provides simple way for SSH clients to check for successful completion of executed commands. --- lib/ssh/src/ssh_cli.erl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 74cd2e081a..8af0ecc5f9 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -208,8 +208,15 @@ handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty, write_chars(ConnectionHandler, ChannelId, Chars), {ok, State#state{buf = NewBuf}}; -handle_msg({'EXIT', Group, _Reason}, #state{group = Group, - channel = ChannelId} = State) -> +handle_msg({'EXIT', Group, Reason}, #state{group = Group, + cm = ConnectionHandler, + channel = ChannelId} = State) -> + Status = case Reason of + normal -> 0; + _ -> -1 + end, + ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), + ssh_connection:send_eof(ConnectionHandler, ChannelId), {stop, ChannelId, State}; handle_msg(_, State) -> -- cgit v1.2.3 From abf7b8c8397acaa9bee0ccf284b1af4e130c16af Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 5 Oct 2016 15:15:35 +0200 Subject: ssh: Handle gen_server:call/3 exits properly Handle all possible exit values that should be interpreted as {error, closed}. Failing to do so could lead to unexpected crashes for users of the ssh application. --- lib/ssh/src/ssh_channel.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index a8e6ebde16..426e2f5125 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -93,11 +93,16 @@ call(ChannelPid, Msg, TimeOute) -> catch exit:{noproc, _} -> {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{shutdown, _} -> + {error, closed}; + exit:{{shutdown, _}, _} -> + {error, closed}; exit:{timeout, _} -> {error, timeout} end. - cast(ChannelPid, Msg) -> gen_server:cast(ChannelPid, Msg). -- cgit v1.2.3 From eadc9b7a1a0349422a6b9ad1d52229562fc22375 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 5 Oct 2016 16:02:21 +0200 Subject: ssh: Prepare release --- lib/ssh/src/ssh.appup.src | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index e38cecf226..4cda8fee95 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -20,9 +20,13 @@ {"%VSN%", [ + {<<"4.3.2">>, [{load_module, ssh_channel, soft_purge, soft_purge, []} + ]}, {<<".*">>, [{restart_application, ssh}]} ], [ + {<<"4.3.2">>, [{load_module, ssh_channel, soft_purge, soft_purge, []} + ]}, {<<".*">>, [{restart_application, ssh}]} ] }. -- cgit v1.2.3 From 046539298604ef214c0986524f731695d0c47262 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 10 Oct 2016 12:06:46 +0200 Subject: Revert "ssh: Add non-blocking send" since it introduces Error Reports This reverts commit 28baf1314b556bb592c24181f6967e1f324f44a7. --- lib/ssh/src/ssh_connection_handler.erl | 92 +++++++++++++--------------------- 1 file changed, 34 insertions(+), 58 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index facf6b561a..abfba4baf1 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -339,7 +339,6 @@ renegotiate_data(ConnectionHandler) -> ssh_params :: #ssh{} | undefined, socket :: inet:socket(), - sender :: pid() | undefined, decrypted_data_buffer = <<>> :: binary(), encrypted_data_buffer = <<>> :: binary(), undecrypted_packet_length :: undefined | non_neg_integer(), @@ -368,10 +367,9 @@ init_connection_handler(Role, Socket, Opts) -> {Protocol, Callback, CloseTag} = proplists:get_value(transport, Opts, ?DefaultTransport), S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), - sender = spawn_link(fun() -> nonblocking_sender(Socket, Callback) end), - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag } of S -> @@ -527,7 +525,7 @@ handle_event(_, _Event, {init_error,Error}, _) -> %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)), - send_bytes(VsnMsg, D), + ok = send_bytes(VsnMsg, D), case inet:getopts(Socket=D#data.socket, [recbuf]) of {ok, [{recbuf,Size}]} -> %% Set the socket to the hello text line handling mode: @@ -552,7 +550,7 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) -> server -> %% But the client may NOT send them to the server. Openssh answers with cleartext, %% and so do we - send_bytes("Protocol mismatch.", D), + ok = send_bytes("Protocol mismatch.", D), {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}} end; @@ -567,7 +565,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D) -> {active, once}, {recbuf, D#data.inet_initial_recbuf_size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), - send_bytes(SshPacket, D), + ok = send_bytes(SshPacket, D), {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; not_supported -> @@ -585,7 +583,7 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload), Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of {ok, NextKexMsg, Ssh2} when Role==client -> - send_bytes(NextKexMsg, D), + ok = send_bytes(NextKexMsg, D), Ssh2; {ok, Ssh2} when Role==server -> Ssh2 @@ -598,43 +596,43 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, %%%---- diffie-hellman handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), - send_bytes(KexdhReply, D), + ok = send_bytes(KexdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- diffie-hellman group exchange handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - send_bytes(GexGroup, D), + ok = send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - send_bytes(GexGroup, D), + ok = send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params), - send_bytes(KexGexInit, D), + ok = send_bytes(KexGexInit, D), {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- elliptic curve diffie-hellman handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), - send_bytes(KexEcdhReply, D), + ok = send_bytes(KexEcdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; @@ -642,9 +640,9 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), - send_bytes(KexGexReply, D), + ok = send_bytes(KexGexReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; @@ -652,7 +650,7 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), - send_bytes(NewKeys, D), + ok = send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}}; @@ -664,7 +662,7 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> Ssh = case Role of client -> {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1), - send_bytes(MsgReq, D), + ok = send_bytes(MsgReq, D), Ssh2; server -> Ssh1 @@ -682,7 +680,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s "ssh-userauth" -> Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params, {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - send_bytes(Reply, D), + ok = send_bytes(Reply, D), {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; _ -> @@ -694,7 +692,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), - send_bytes(Msg, State), + ok = send_bytes(Msg, State), {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; @@ -711,7 +709,7 @@ handle_event(_, %% Probably the very first userauth_request but we deny unauthorized login {not_authorized, _, {Reply,Ssh}} = ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), - send_bytes(Reply, D), + ok = send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}}; {"ssh-connection", "ssh-connection", Method} -> @@ -721,7 +719,7 @@ handle_event(_, %% Yepp! we support this method case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of {authorized, User, {Reply, Ssh}} -> - send_bytes(Reply, D), + ok = send_bytes(Reply, D), D#data.starter ! ssh_connected, connected_fun(User, Method, D), {next_state, {connected,server}, @@ -729,11 +727,11 @@ handle_event(_, ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Reason, D), - send_bytes(Reply, D), + ok = send_bytes(Reply, D), {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D), - send_bytes(Reply, D), + ok = send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}} end; false -> @@ -1447,15 +1445,18 @@ start_the_connection_child(UserPid, Role, Socket, Options) -> %% Stopping -type finalize_termination_result() :: ok . -finalize_termination(_StateName, D) -> - case D#data.connection_state of +finalize_termination(_StateName, #data{transport_cb = Transport, + connection_state = Connection, + socket = Socket}) -> + case Connection of #connection{system_supervisor = SysSup, sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) -> ssh_system_sup:stop_subsystem(SysSup, SubSysSup); _ -> do_nothing end, - close_transport(D). + (catch Transport:close(Socket)), + ok. %%-------------------------------------------------------------------- %% "Invert" the Role @@ -1510,33 +1511,8 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> send_bytes(Bytes, State), State#data{ssh_params=Ssh}. -send_bytes(Bytes, #data{sender = Sender}) -> - Sender ! {send,Bytes}, - ok. - -close_transport(D) -> - D#data.sender ! close, - ok. - - -nonblocking_sender(Socket, Callback) -> - receive - {send, Bytes} -> - case Callback:send(Socket, Bytes) of - ok -> - nonblocking_sender(Socket, Callback); - E = {error,_} -> - exit({shutdown,E}) - end; - - close -> - case Callback:close(Socket) of - ok -> - ok; - E = {error,_} -> - exit({shutdown,E}) - end - end. +send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> + Transport:send(Socket, Bytes). handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), -- cgit v1.2.3 From f6c9b5caaa1ba6c22248ff22cc678b30d89cdd36 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 13 Oct 2016 10:33:26 +0200 Subject: ssh: Removed matching of 'ok' after send which could cause error reports --- lib/ssh/src/ssh_connection_handler.erl | 50 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 24 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index abfba4baf1..ced049f0d0 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -525,7 +525,7 @@ handle_event(_, _Event, {init_error,Error}, _) -> %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)), - ok = send_bytes(VsnMsg, D), + send_bytes(VsnMsg, D), case inet:getopts(Socket=D#data.socket, [recbuf]) of {ok, [{recbuf,Size}]} -> %% Set the socket to the hello text line handling mode: @@ -545,12 +545,13 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) -> case Role of client -> %% The server may send info lines to the client before the version_exchange + %% RFC4253/4.2 inet:setopts(D#data.socket, [{active, once}]), keep_state_and_data; server -> %% But the client may NOT send them to the server. Openssh answers with cleartext, %% and so do we - ok = send_bytes("Protocol mismatch.", D), + send_bytes("Protocol mismatch.", D), {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}} end; @@ -565,7 +566,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D) -> {active, once}, {recbuf, D#data.inet_initial_recbuf_size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), - ok = send_bytes(SshPacket, D), + send_bytes(SshPacket, D), {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; not_supported -> @@ -583,7 +584,7 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload), Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of {ok, NextKexMsg, Ssh2} when Role==client -> - ok = send_bytes(NextKexMsg, D), + send_bytes(NextKexMsg, D), Ssh2; {ok, Ssh2} when Role==server -> Ssh2 @@ -596,43 +597,43 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, %%%---- diffie-hellman handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), - ok = send_bytes(KexdhReply, D), + send_bytes(KexdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- diffie-hellman group exchange handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - ok = send_bytes(GexGroup, D), + send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - ok = send_bytes(GexGroup, D), + send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params), - ok = send_bytes(KexGexInit, D), + send_bytes(KexGexInit, D), {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- elliptic curve diffie-hellman handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), - ok = send_bytes(KexEcdhReply, D), + send_bytes(KexEcdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; @@ -640,9 +641,9 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), - ok = send_bytes(KexGexReply, D), + send_bytes(KexGexReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; @@ -650,7 +651,7 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}}; @@ -662,7 +663,7 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> Ssh = case Role of client -> {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1), - ok = send_bytes(MsgReq, D), + send_bytes(MsgReq, D), Ssh2; server -> Ssh1 @@ -680,7 +681,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s "ssh-userauth" -> Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params, {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; _ -> @@ -692,7 +693,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), - ok = send_bytes(Msg, State), + send_bytes(Msg, State), {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; @@ -709,7 +710,7 @@ handle_event(_, %% Probably the very first userauth_request but we deny unauthorized login {not_authorized, _, {Reply,Ssh}} = ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}}; {"ssh-connection", "ssh-connection", Method} -> @@ -719,7 +720,7 @@ handle_event(_, %% Yepp! we support this method case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of {authorized, User, {Reply, Ssh}} -> - ok = send_bytes(Reply, D), + send_bytes(Reply, D), D#data.starter ! ssh_connected, connected_fun(User, Method, D), {next_state, {connected,server}, @@ -727,11 +728,11 @@ handle_event(_, ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Reason, D), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}} end; false -> @@ -1512,7 +1513,8 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> State#data{ssh_params=Ssh}. send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> - Transport:send(Socket, Bytes). + _ = Transport:send(Socket, Bytes), + ok. handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), -- cgit v1.2.3 From e875ff334a6d6f8db547868e5d57e71c80ff1859 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 17 Oct 2016 12:03:21 +0200 Subject: ssh: fix renegotiation problem --- lib/ssh/src/ssh_connection_handler.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ced049f0d0..dd414894d4 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -671,8 +671,9 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> {next_state, {service_request,Role}, D#data{ssh_params=Ssh}}; %% Subsequent key exchange rounds (renegotiation): -handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, D) -> - {next_state, {connected,Role}, D}; +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) -> + {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), + {next_state, {connected,Role}, D#data{ssh_params=Ssh}}; %%% ######## {service_request, client|server} -- cgit v1.2.3 From 39cb552ba0a5aaa36cb07526b3b895677ba1f3dc Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 26 Oct 2016 12:31:27 +0200 Subject: ssh: Add a 'catch' in ssh_channel.erl to prevent double close errors --- lib/ssh/src/ssh_channel.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index 426e2f5125..85b31f3669 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -261,7 +261,7 @@ handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager, adjust_window(Msg), {noreply, State#state{channel_state = ChannelState}, Timeout}; {stop, ChannelId, ChannelState} -> - ssh_connection:close(ConnectionManager, ChannelId), + catch ssh_connection:close(ConnectionManager, ChannelId), {stop, normal, State#state{close_sent = true, channel_state = ChannelState}} end; -- cgit v1.2.3 From 22a29422236f28adc24090dace03c0fd29311c9c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 27 Oct 2016 15:14:38 +0200 Subject: ssh: more info about shrinked binaries in ssh_dbg --- lib/ssh/src/ssh_dbg.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index bd6bc0335b..ce5596e0f9 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -113,7 +113,12 @@ setup_tracer(Write, MangleArg) -> ok. %%%---------------------------------------------------------------- -shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN',size(B),element(1,split_binary(B,20)),'***'}; +shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN', + size(B), + element(1,split_binary(B,20)), + '...', + element(2,split_binary(B,size(B)-20)) + }; shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L); shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T))); shrink_bin(X) -> X. -- cgit v1.2.3 From 067d8310093620ce1139e8d8a030bce6dd22d886 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 1 Nov 2016 15:01:01 +0100 Subject: ssh: exported ssh_dbg:shrink_bin and ssh_dbg:wr_record/3 for debugging purposes --- lib/ssh/src/Makefile | 2 +- lib/ssh/src/ssh_dbg.erl | 3 +++ lib/ssh/src/ssh_dbg.hrl | 27 +++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 lib/ssh/src/ssh_dbg.hrl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 69d5a47f83..7ab6f22424 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -96,7 +96,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 ssh.hrl ssh_userauth.hrl ssh_xfer.hrl +INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl ssh_dbg.hrl # ---------------------------------------------------- # FLAGS diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index ce5596e0f9..dff2bae9f2 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -28,6 +28,9 @@ stop/0 ]). +-export([shrink_bin/1, + wr_record/3]). + -include("ssh.hrl"). -include("ssh_transport.hrl"). -include("ssh_connect.hrl"). diff --git a/lib/ssh/src/ssh_dbg.hrl b/lib/ssh/src/ssh_dbg.hrl new file mode 100644 index 0000000000..e94664737b --- /dev/null +++ b/lib/ssh/src/ssh_dbg.hrl @@ -0,0 +1,27 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(SSH_DBG_HRL). +-define(SSH_DBG_HRL, 1). + +-define(formatrec(RecName,R), + ssh_dbg:wr_record(R, record_info(fields,RecName), [])). + +-endif. % SSH_DBG_HRL defined -- cgit v1.2.3 From d1c8c59ec8bcec6758d7a00bed1d23e0907fb3a5 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 9 Nov 2016 12:51:57 +0100 Subject: ssh: Change order on next_event actions in ssh_connection_handler --- lib/ssh/src/ssh_connection_handler.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index dd414894d4..e7c52f345e 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1194,12 +1194,12 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D)) of Msg = #ssh_msg_kexinit{} -> - {keep_state, D, [{next_event, internal, {Msg,DecryptedBytes}}, - {next_event, internal, prepare_next_packet} + {keep_state, D, [{next_event, internal, prepare_next_packet}, + {next_event, internal, {Msg,DecryptedBytes}} ]}; Msg -> - {keep_state, D, [{next_event, internal, Msg}, - {next_event, internal, prepare_next_packet} + {keep_state, D, [{next_event, internal, prepare_next_packet}, + {next_event, internal, Msg} ]} catch _C:_E -> -- cgit v1.2.3 From d13af8b8c6a7b04ec541585d1b8945103fffb988 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 11 Nov 2016 12:56:59 +0100 Subject: ssh: Adjust inet buffers if too small --- lib/ssh/src/ssh_connection_handler.erl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e7c52f345e..7451c9e6d0 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -928,6 +928,7 @@ handle_event(internal, Msg=#ssh_msg_channel_request{}, StateName, D) - handle_connection_msg(Msg, StateName, D); handle_event(internal, Msg=#ssh_msg_channel_success{}, StateName, D) -> + update_inet_buffers(D#data.socket), handle_connection_msg(Msg, StateName, D); handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, D) -> @@ -1007,6 +1008,7 @@ handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = RemoteId} -> Msg = ssh_connection:channel_success_msg(RemoteId), + update_inet_buffers(D#data.socket), {keep_state, send_msg(Msg,D)}; undefined -> @@ -1738,6 +1740,11 @@ send_replies(Repls, State) -> Repls). get_repl({connection_reply,Msg}, {CallRepls,S}) -> + if is_record(Msg, ssh_msg_channel_success) -> + update_inet_buffers(S#data.socket); + true -> + ok + end, {CallRepls, send_msg(Msg,S)}; get_repl({channel_data,undefined,_Data}, Acc) -> Acc; @@ -1926,3 +1933,13 @@ handshake(Pid, Ref, Timeout) -> {error, timeout} end. +update_inet_buffers(Socket) -> + {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]), + MinVal = 655360, + case + [{Tag,MinVal} || {Tag,Val} <- BufSzs0, + Val < MinVal] + of + [] -> ok; + NewOpts -> inet:setopts(Socket, NewOpts) + end. -- cgit v1.2.3 From 8215ea28fa2f699499b64d6f2c712e068b199390 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 11 Nov 2016 16:59:08 +0100 Subject: ssh: Add fun and fingerprint to option 'silently_accept_host' --- lib/ssh/src/ssh.erl | 9 +++++++++ lib/ssh/src/ssh_transport.erl | 16 ++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 1d7be3547b..31e343e81b 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -617,6 +617,15 @@ 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({silently_accept_hosts, Value} = Opt) when is_function(Value,2) -> + Opt; +handle_ssh_option({silently_accept_hosts, {DigestAlg,Value}} = Opt) when is_function(Value,2) -> + case lists:member(DigestAlg, [md5, sha, sha224, sha256, sha384, sha512]) of + true -> + Opt; + false -> + throw({error, {eoptions, Opt}}) + end; handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 15b80de30a..21ba34506a 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -734,12 +734,16 @@ public_algo({#'ECPoint'{},{namedCurve,OID}}) -> list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). -accepted_host(Ssh, PeerName, Opts) -> +accepted_host(Ssh, PeerName, Public, Opts) -> case proplists:get_value(silently_accept_hosts, Opts, false) of + F when is_function(F,2) -> + true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public))); + {DigestAlg,F} when is_function(F,2) -> + true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))); true -> - yes; + true; false -> - yes_no(Ssh, "New host " ++ PeerName ++ " accept") + yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") end. known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh, @@ -749,10 +753,10 @@ known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh, true -> ok; false -> - case accepted_host(Ssh, PeerName, Opts) of - yes -> + case accepted_host(Ssh, PeerName, Public, Opts) of + true -> Mod:add_host_key(PeerName, Public, Opts); - no -> + false -> {error, rejected} end end. -- cgit v1.2.3 From 3a519b7b74ae07f4d66989313a0c065c96bcad8c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 21 Nov 2016 17:52:06 +0100 Subject: ssh: fix error when large client packet size and small on server --- lib/ssh/src/ssh_connection.erl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index d0f2d54c06..1153095135 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -287,6 +287,9 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, ssh_channel:cache_update(Cache, Channel#channel{ remote_id = RemoteId, + recv_packet_size = max(32768, % rfc4254/5.2 + min(PacketSz, Channel#channel.recv_packet_size) + ), send_window_size = WindowSz, send_packet_size = PacketSz}), {Reply, Connection} = reply_msg(Channel, Connection0, {open, ChannelId}), -- cgit v1.2.3 From 3eddb0f762de248d3230b38bc9d478bfbc8e7331 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Wed, 7 Dec 2016 13:15:31 +0100 Subject: Update copyright-year --- lib/ssh/src/ssh.appup.src | 2 +- lib/ssh/src/ssh_acceptor.erl | 2 +- lib/ssh/src/ssh_connection.erl | 2 +- lib/ssh/src/ssh_info.erl | 2 +- lib/ssh/src/ssh_sftpd.erl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 4cda8fee95..2540720c41 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -1,7 +1,7 @@ %% -*- erlang -*- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2015. All Rights Reserved. +%% Copyright Ericsson AB 2004-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 9f3e60bd62..13c9d9af4a 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 1153095135..c7a2c92670 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl index 0c24c09887..d464def6fa 100644 --- a/lib/ssh/src/ssh_info.erl +++ b/lib/ssh/src/ssh_info.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index dca018f20f..b739955836 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2015. All Rights Reserved. +%% Copyright Ericsson AB 2005-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From a8ea98ef814022dc02a1917105a0572007952e52 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 28 Nov 2016 14:50:08 +0100 Subject: ssh: [test] Put tstflg values in a proplist --- lib/ssh/src/ssh_auth.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index ac35b70209..9b54ecb2dd 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -406,7 +406,11 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, kb_tries_left = KbTriesLeft, user = User, userauth_supported_methods = Methods} = Ssh) -> - SendOneEmpty = proplists:get_value(tstflg, Opts) == one_empty, + SendOneEmpty = + (proplists:get_value(tstflg,Opts) == one_empty) + orelse + proplists:get_value(one_empty, proplists:get_value(tstflg,Opts,[]), false), + case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of {true,Ssh1} when SendOneEmpty==true -> Msg = #ssh_msg_userauth_info_request{name = "", -- cgit v1.2.3 From 5901661b62a006a6c55d77503a7198c7c56dabe7 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 13 Dec 2016 10:35:31 +0100 Subject: ssh: Optimize handling of #ssh.shared_secret It is not necessary to mpint-encode it every time it is used (in MAC:s), it sufficies to do it once after key exchange --- lib/ssh/src/ssh_transport.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 21ba34506a..53e9ef485b 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -367,7 +367,7 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, h_sig = H_SIG }, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}, - shared_secret = K, + shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh1, H)}}; @@ -393,7 +393,7 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, + {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H)}}; Error -> @@ -532,7 +532,7 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey, f = Public, h_sig = H_SIG}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, + {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H) }}; @@ -568,7 +568,7 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, + {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H)}}; _Error -> @@ -618,7 +618,7 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, h_sig = H_SIG}, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{MyPublic,MyPrivate},Curve}, - shared_secret = K, + shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh1, H)}} catch @@ -644,7 +644,7 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, + {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), exchanged_hash = H, session_id = sid(Ssh, H)}}; Error -> @@ -1577,7 +1577,7 @@ hash(SSH, Char, Bits) -> hash(_SSH, _Char, 0, _HASH) -> <<>>; hash(SSH, Char, N, HASH) -> - K = ssh_bits:mpint(SSH#ssh.shared_secret), +K = SSH#ssh.shared_secret, % K = ssh_bits:mpint(SSH#ssh.shared_secret), H = SSH#ssh.exchanged_hash, SessionID = SSH#ssh.session_id, K1 = HASH([K, H, Char, SessionID]), -- cgit v1.2.3 From 2b36238bc2a2444b97a3d01fa35ab1ceecfe1c4d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 13 Dec 2016 10:38:27 +0100 Subject: ssh: Optimize ssh_bits:name_list It is better (=faster) to use built-in functions and library functions. --- lib/ssh/src/ssh_bits.erl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl index 8bedaaf0c5..cc2e7e2be5 100644 --- a/lib/ssh/src/ssh_bits.erl +++ b/lib/ssh/src/ssh_bits.erl @@ -30,13 +30,7 @@ -export([random/1]). %%%---------------------------------------------------------------- -name_list([Name]) -> to_bin(Name); -name_list([Name|Ns]) -> <<(to_bin(Name))/binary, ",", (name_list(Ns))/binary>>; -name_list([]) -> <<>>. - -to_bin(A) when is_atom(A) -> list_to_binary(atom_to_list(A)); -to_bin(S) when is_list(S) -> list_to_binary(S); -to_bin(B) when is_binary(B) -> B. +name_list(NamesList) -> list_to_binary(lists:join($,, NamesList)). %%%---------------------------------------------------------------- %%% Multi Precision Integer encoding -- cgit v1.2.3 From 767234a17378db0985ca49415ae5b9d3423ed754 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 13 Dec 2016 10:40:31 +0100 Subject: ssh: Optimze ssh_bits:mpint/1 By using binary constructors we push the hard work down into the emulator --- lib/ssh/src/ssh_bits.erl | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl index cc2e7e2be5..3ce7758447 100644 --- a/lib/ssh/src/ssh_bits.erl +++ b/lib/ssh/src/ssh_bits.erl @@ -36,27 +36,25 @@ name_list(NamesList) -> list_to_binary(lists:join($,, NamesList)). %%% Multi Precision Integer encoding mpint(-1) -> <<0,0,0,1,16#ff>>; mpint(0) -> <<0,0,0,0>>; -mpint(X) when X < 0 -> mpint_neg(X,0,[]); -mpint(X) -> mpint_pos(X,0,[]). - -mpint_neg(-1,I,Ds=[MSB|_]) -> - if MSB band 16#80 =/= 16#80 -> - <>; - true -> - <> +mpint(I) when I>0 -> + <> = binary:encode_unsigned(I), + case B1 band 16#80 of + 16#80 -> + <<(size(V)+2):32/unsigned-big-integer, 0,B1,V/binary >>; + _ -> + <<(size(V)+1):32/unsigned-big-integer, B1,V/binary >> end; -mpint_neg(X,I,Ds) -> - mpint_neg(X bsr 8,I+1,[(X band 255)|Ds]). - -mpint_pos(0,I,Ds=[MSB|_]) -> - if MSB band 16#80 == 16#80 -> - <>; - true -> - <> - end; -mpint_pos(X,I,Ds) -> - mpint_pos(X bsr 8,I+1,[(X band 255)|Ds]). - +mpint(N) when N<0 -> + Sxn = 8*size(binary:encode_unsigned(-N)), + Sxn1 = Sxn+8, + <> = <<1, 0:Sxn>>, + <> = binary:encode_unsigned(W+N), + case B1 band 16#80 of + 16#80 -> + <<(size(V)+1):32/unsigned-big-integer, B1,V/binary >>; + _ -> + <<(size(V)+2):32/unsigned-big-integer, 255,B1,V/binary >> + end. %%%---------------------------------------------------------------- %% random/1 -- cgit v1.2.3 From 6d393493ded1462dd5469cb4bfc36db97134f5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Fri, 16 Dec 2016 18:48:42 +0100 Subject: ssh: Remove whitespace errors in ssh_sftp.erl --- lib/ssh/src/ssh_sftp.erl | 128 +++++++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 64 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index afc2fb88ff..a648247ef9 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -37,7 +37,7 @@ -export([open/3, open_tar/3, opendir/2, close/2, readdir/2, pread/4, read/3, open/4, open_tar/4, opendir/3, close/3, readdir/3, pread/5, read/4, apread/4, aread/3, pwrite/4, write/3, apwrite/4, awrite/3, - pwrite/5, write/4, + pwrite/5, write/4, position/3, real_path/2, read_file_info/2, get_file_info/2, position/4, real_path/3, read_file_info/3, get_file_info/3, write_file_info/3, read_link_info/2, read_link/2, make_symlink/3, @@ -52,7 +52,7 @@ %% TODO: Should be placed elsewhere ssh_sftpd should not call functions in ssh_sftp! -export([info_to_attr/1, attr_to_info/1]). --record(state, +-record(state, { xf, rep_buf = <<>>, @@ -64,7 +64,7 @@ -record(fileinf, { - handle, + handle, offset, size, mode @@ -81,7 +81,7 @@ enc_text_buf = <<>>, % Encrypted text plain_text_buf = <<>> % Decrypted text }). - + -define(FILEOP_TIMEOUT, infinity). -define(NEXT_REQID(S), @@ -98,7 +98,7 @@ start_channel(Cm) when is_pid(Cm) -> start_channel(Socket) when is_port(Socket) -> start_channel(Socket, []); start_channel(Host) when is_list(Host) -> - start_channel(Host, []). + start_channel(Host, []). start_channel(Socket, Options) when is_port(Socket) -> Timeout = @@ -110,7 +110,7 @@ start_channel(Socket, Options) when is_port(Socket) -> TO end, case ssh:connect(Socket, Options, Timeout) of - {ok,Cm} -> + {ok,Cm} -> case start_channel(Cm, Options) of {ok, Pid} -> {ok, Pid, Cm}; @@ -124,13 +124,13 @@ start_channel(Cm, Opts) when is_pid(Cm) -> Timeout = proplists:get_value(timeout, Opts, infinity), {_, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []), case ssh_xfer:attach(Cm, [], ChanOpts) of - {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, + {ok, ChannelId, Cm} -> + case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId, SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> - {ok, Pid}; + {ok, Pid}; TimeOut -> TimeOut end; @@ -150,7 +150,7 @@ start_channel(Host, Port, Opts) -> Timeout = proplists:get_value(timeout, SftpOpts, infinity), case ssh_xfer:connect(Host, Port, SshOpts, ChanOpts, Timeout) of {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, + case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId, SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of @@ -165,7 +165,7 @@ start_channel(Host, Port, Opts) -> {error, ignore} end; Error -> - Error + Error end. stop_channel(Pid) -> @@ -174,12 +174,12 @@ stop_channel(Pid) -> OldValue = process_flag(trap_exit, true), link(Pid), exit(Pid, ssh_sftp_stop_channel), - receive + receive {'EXIT', Pid, normal} -> ok after 5000 -> exit(Pid, kill), - receive + receive {'EXIT', Pid, killed} -> ok end @@ -209,9 +209,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> erl_tar:init(Pid, write, fun(write, {_,Data}) -> write_to_remote_tar(Pid, Handle, to_bin(Data), FileOpTimeout); - (position, {_,Pos}) -> + (position, {_,Pos}) -> position(Pid, Handle, Pos, FileOpTimeout); - (close, _) -> + (close, _) -> close(Pid, Handle, FileOpTimeout) end); {true,false,[{crypto,{CryptoInitFun,CryptoEncryptFun,CryptoEndFun}}]} -> @@ -245,9 +245,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> erl_tar:init(Pid, read, fun(read2, {_,Len}) -> read_repeat(Pid, Handle, Len, FileOpTimeout); - (position, {_,Pos}) -> + (position, {_,Pos}) -> position(Pid, Handle, Pos, FileOpTimeout); - (close, _) -> + (close, _) -> close(Pid, Handle, FileOpTimeout) end); {false,true,[{crypto,{CryptoInitFun,CryptoDecryptFun}}]} -> @@ -258,9 +258,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> erl_tar:init(Pid, read, fun(read2, {_,Len}) -> read_buf(Pid, SftpHandle, BufHandle, Len, FileOpTimeout); - (position, {_,Pos}) -> + (position, {_,Pos}) -> position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout); - (close, _) -> + (close, _) -> call(Pid, {erase_bufinf,BufHandle}, FileOpTimeout), close(Pid, SftpHandle, FileOpTimeout) end); @@ -292,7 +292,7 @@ pread(Pid, Handle, Offset, Len, FileOpTimeout) -> read(Pid, Handle, Len) -> read(Pid, Handle, Len, ?FILEOP_TIMEOUT). read(Pid, Handle, Len, FileOpTimeout) -> - call(Pid, {read,false,Handle, Len}, FileOpTimeout). + call(Pid, {read,false,Handle, Len}, FileOpTimeout). %% TODO this ought to be a cast! Is so in all practial meaning %% even if it is obscure! @@ -301,7 +301,7 @@ apread(Pid, Handle, Offset, Len) -> %% TODO this ought to be a cast! aread(Pid, Handle, Len) -> - call(Pid, {read,true,Handle, Len}, infinity). + call(Pid, {read,true,Handle, Len}, infinity). pwrite(Pid, Handle, Offset, Data) -> pwrite(Pid, Handle, Offset, Data, ?FILEOP_TIMEOUT). @@ -367,7 +367,7 @@ make_symlink(Pid, Name, Target) -> make_symlink(Pid, Name, Target, ?FILEOP_TIMEOUT). make_symlink(Pid, Name, Target, FileOpTimeout) -> call(Pid, {make_symlink,false, Name, Target}, FileOpTimeout). - + rename(Pid, FromFile, ToFile) -> rename(Pid, FromFile, ToFile, ?FILEOP_TIMEOUT). rename(Pid, FromFile, ToFile, FileOpTimeout) -> @@ -411,8 +411,8 @@ list_dir(Pid, Name, FileOpTimeout) -> close(Pid, Handle, FileOpTimeout), case Res of {ok, List} -> - NList = lists:foldl(fun({Nm, _Info},Acc) -> - [Nm|Acc] end, + NList = lists:foldl(fun({Nm, _Info},Acc) -> + [Nm|Acc] end, [], List), {ok,NList}; Error -> Error @@ -482,7 +482,7 @@ write_file_loop(Pid, Handle, Pos, Bin, Remain, PacketSz, FileOpTimeout) -> <<_:Pos/binary, Data:PacketSz/binary, _/binary>> = Bin, case write(Pid, Handle, Data, FileOpTimeout) of ok -> - write_file_loop(Pid, Handle, + write_file_loop(Pid, Handle, Pos+PacketSz, Bin, Remain-PacketSz, PacketSz, FileOpTimeout); Error -> @@ -510,7 +510,7 @@ init([Cm, ChannelId, Options]) -> Xf = #ssh_xfer{cm = Cm, channel = ChannelId}, {ok, #state{xf = Xf, - req_id = 0, + req_id = 0, rep_buf = <<>>, inf = new_inf(), opts = Options}}; @@ -519,7 +519,7 @@ init([Cm, ChannelId, Options]) -> Error -> {stop, {shutdown, Error}} end. - + %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages @@ -541,7 +541,7 @@ handle_call({{timeout, Timeout}, wait_for_version_negotiation}, From, handle_call({_, wait_for_version_negotiation}, _, State) -> {reply, ok, State}; - + handle_call({{timeout, infinity}, Msg}, From, State) -> do_handle_call(Msg, From, State); handle_call({{timeout, Timeout}, Msg}, From, #state{req_id = Id} = State) -> @@ -636,7 +636,7 @@ do_handle_call({pread,Async,Handle,At,Length}, From, State) -> binary -> {{ok,Data}, State2}; text -> {{ok,binary_to_list(Data)}, State2} end; - (Rep, State2) -> + (Rep, State2) -> {Rep, State2} end); Error -> @@ -777,7 +777,7 @@ do_handle_call(recv_window, _From, State) -> do_handle_call(stop, _From, State) -> {stop, shutdown, ok, State}; -do_handle_call(Call, _From, State) -> +do_handle_call(Call, _From, State) -> {reply, {error, bad_call, Call, State}, State}. %%-------------------------------------------------------------------- @@ -785,13 +785,13 @@ do_handle_call(Call, _From, State) -> %% %% Description: Handles channel messages %%-------------------------------------------------------------------- -handle_ssh_msg({ssh_cm, _ConnectionManager, - {data, _ChannelId, 0, Data}}, #state{rep_buf = Data0} = +handle_ssh_msg({ssh_cm, _ConnectionManager, + {data, _ChannelId, 0, Data}}, #state{rep_buf = Data0} = State0) -> State = handle_reply(State0, <>), {ok, State}; -handle_ssh_msg({ssh_cm, _ConnectionManager, +handle_ssh_msg({ssh_cm, _ConnectionManager, {data, _ChannelId, 1, Data}}, State) -> error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]), {ok, State}; @@ -803,7 +803,7 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) -> %% Ignore signals according to RFC 4254 section 6.9. {ok, State}; -handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State0) -> State = reply_all(State0, {error, Error}), {stop, ChannelId, State}; @@ -823,7 +823,7 @@ handle_msg({ssh_channel_up, _, _}, #state{opts = Options, xf = Xf} = State) -> {ok, State}; %% Version negotiation timed out -handle_msg({timeout, undefined, From}, +handle_msg({timeout, undefined, From}, #state{xf = #ssh_xfer{channel = ChannelId}} = State) -> ssh_channel:reply(From, {error, timeout}), {stop, ChannelId, State}; @@ -839,12 +839,12 @@ handle_msg({timeout, Id, From}, #state{req_list = ReqList0} = State) -> end; %% Connection manager goes down -handle_msg({'DOWN', _Ref, _Type, _Process, _}, +handle_msg({'DOWN', _Ref, _Type, _Process, _}, #state{xf = #ssh_xfer{channel = ChannelId}} = State) -> {stop, ChannelId, State}; - + %% Stopped by user -handle_msg({'EXIT', _, ssh_sftp_stop_channel}, +handle_msg({'EXIT', _, ssh_sftp_stop_channel}, #state{xf = #ssh_xfer{channel = ChannelId}} = State) -> {stop, ChannelId, State}; @@ -883,10 +883,10 @@ call(Pid, Msg, TimeOut) -> handle_reply(State, <>) -> do_handle_reply(State, Reply, Rest); -handle_reply(State, Data) -> +handle_reply(State, Data) -> State#state{rep_buf = Data}. -do_handle_reply(#state{xf = Xf} = State, +do_handle_reply(#state{xf = Xf} = State, <>, Rest) -> Ext = ssh_xfer:decode_ext(BinExt), case Xf#ssh_xfer.vsn of @@ -899,7 +899,7 @@ do_handle_reply(#state{xf = Xf} = State, ok end, ssh_channel:reply(From, ok) - end, + end, State#state{xf = Xf#ssh_xfer{vsn = Version, ext = Ext}, rep_buf = Rest}; do_handle_reply(State0, Data, Rest) -> @@ -919,9 +919,9 @@ handle_req_reply(State0, {_, ReqID, _} = XfReply) -> List = lists:keydelete(ReqID, 1, State0#state.req_list), State1 = State0#state { req_list = List }, case catch Fun(xreply(XfReply),State1) of - {'EXIT', _} -> + {'EXIT', _} -> State1; - State -> + State -> State end end. @@ -998,15 +998,15 @@ reply_all(State, Reply) -> make_reply(ReqID, true, From, State) -> {reply, {async, ReqID}, update_request_info(ReqID, State, - fun(Reply,State1) -> + fun(Reply,State1) -> async_reply(ReqID,Reply,From,State1) end)}; make_reply(ReqID, false, From, State) -> {noreply, update_request_info(ReqID, State, - fun(Reply,State1) -> - sync_reply(Reply, From, State1) + fun(Reply,State1) -> + sync_reply(Reply, From, State1) end)}. make_reply_post(ReqID, true, From, State, PostFun) -> @@ -1074,13 +1074,13 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) -> unix_to_datetime(undefined) -> undefined; unix_to_datetime(UTCSecs) -> - UTCDateTime = + UTCDateTime = calendar:gregorian_seconds_to_datetime(UTCSecs + 62167219200), erlang:universaltime_to_localtime(UTCDateTime). datetime_to_unix(undefined) -> undefined; -datetime_to_unix(LocalDateTime) -> +datetime_to_unix(LocalDateTime) -> UTCDateTime = erlang:localtime_to_universaltime(LocalDateTime), calendar:datetime_to_gregorian_seconds(UTCDateTime) - 62167219200. @@ -1229,7 +1229,7 @@ lseek_pos({cur, Offset}, CurOffset, _CurSize) true -> {ok, NewOffset} end; -lseek_pos({eof, Offset}, _CurOffset, CurSize) +lseek_pos({eof, Offset}, _CurOffset, CurSize) when is_integer(Offset) andalso -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset andalso Offset < ?SSH_FILEXFER_LARGEFILESIZE -> NewOffset = CurSize + Offset, @@ -1239,7 +1239,7 @@ lseek_pos({eof, Offset}, _CurOffset, CurSize) {ok, NewOffset} end; lseek_pos(_, _, _) -> - {error, einval}. + {error, einval}. %%%================================================================ %%% @@ -1277,13 +1277,13 @@ position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout) -> case Pos of {cur,0} when Mode==write -> {ok,Size+size(Buf0)}; - + {cur,0} when Mode==read -> {ok,Size}; - + _ when Mode==read, is_integer(Pos) -> Skip = Pos-Size, - if + if Skip < 0 -> {error, cannot_rewind}; Skip == 0 -> @@ -1318,7 +1318,7 @@ read_buf(Pid, SftpHandle, BufHandle, WantedLen, FileOpTimeout) -> eof end. -do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout, +do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout, B=#bufinf{plain_text_buf=PlainBuf0, size = Size}) when size(PlainBuf0) >= WantedLen -> @@ -1327,7 +1327,7 @@ do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout, {ok,ResultBin,B#bufinf{plain_text_buf=PlainBuf, size = Size + WantedLen}}; -do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, +do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B0=#bufinf{plain_text_buf = PlainBuf0, enc_text_buf = EncBuf0, chunksize = undefined @@ -1335,12 +1335,12 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, when size(EncBuf0) > 0 -> %% We have (at least) one decodable byte waiting for decodeing. {ok,DecodedBin,B} = apply_crypto(EncBuf0, B0), - do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B#bufinf{plain_text_buf = <>, enc_text_buf = <<>> }); - -do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + +do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B0=#bufinf{plain_text_buf = PlainBuf0, enc_text_buf = EncBuf0, chunksize = ChunkSize0 @@ -1349,11 +1349,11 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, %% We have (at least) one chunk of decodable bytes waiting for decodeing. <> = EncBuf0, {ok,DecodedBin,B} = apply_crypto(ToDecode, B0), - do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B#bufinf{plain_text_buf = <>, enc_text_buf = EncBuf }); - + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B=#bufinf{enc_text_buf = EncBuf0}) -> %% We must read more bytes and append to the buffer of encoded bytes. case read(Pid, SftpHandle, Packet, FileOpTimeout) of @@ -1370,7 +1370,7 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B=#bufinf{enc write_buf(Pid, SftpHandle, BufHandle, PlainBin, FileOpTimeout) -> {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout), {ok,B0=#bufinf{plain_text_buf=PTB}} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout), - case do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + case do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, B0#bufinf{plain_text_buf = <>}) of {ok, B} -> call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout), @@ -1379,7 +1379,7 @@ write_buf(Pid, SftpHandle, BufHandle, PlainBin, FileOpTimeout) -> {error,Error} end. -do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, +do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, B=#bufinf{enc_text_buf = EncBuf0, size = Size}) when size(EncBuf0) >= Packet -> @@ -1421,9 +1421,9 @@ do_the_write_buf(_Pid, _SftpHandle, _Packet, _FileOpTimeout, B) -> apply_crypto(In, B=#bufinf{crypto_state = CState0, crypto_fun = F}) -> case F(In,CState0) of - {ok,EncodedBin,CState} -> + {ok,EncodedBin,CState} -> {ok, EncodedBin, B#bufinf{crypto_state=CState}}; - {ok,EncodedBin,CState,ChunkSize} -> + {ok,EncodedBin,CState,ChunkSize} -> {ok, EncodedBin, B#bufinf{crypto_state=CState, chunksize=ChunkSize}} end. -- cgit v1.2.3 From 4161f80e0197ec5447f9a48ef3a0c9c6cfcfa5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Fri, 16 Dec 2016 18:55:40 +0100 Subject: ssh: Use maps instead of dict in ssh_sftp --- lib/ssh/src/ssh_sftp.erl | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index a648247ef9..b937f0412d 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -555,13 +555,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. do_handle_call({get_bufinf,BufHandle}, _From, S=#state{inf=I0}) -> - {reply, dict:find(BufHandle,I0), S}; + {reply, maps:find(BufHandle,I0), S}; do_handle_call({put_bufinf,BufHandle,B}, _From, S=#state{inf=I0}) -> - {reply, ok, S#state{inf=dict:store(BufHandle,B,I0)}}; + {reply, ok, S#state{inf=maps:put(BufHandle,B,I0)}}; do_handle_call({erase_bufinf,BufHandle}, _From, S=#state{inf=I0}) -> - {reply, ok, S#state{inf=dict:erase(BufHandle,I0)}}; + {reply, ok, S#state{inf=maps:remove(BufHandle,I0)}}; do_handle_call({open, Async,FileName,Mode}, From, #state{xf = XF} = State) -> {Access,Flags,Attrs} = open_mode(XF#ssh_xfer.vsn, Mode), @@ -1128,11 +1128,11 @@ open_mode3(Modes) -> end, {[], Fl, A}. -%% accessors for inf dict -new_inf() -> dict:new(). +%% accessors for inf map +new_inf() -> #{}. add_new_handle(Handle, FileMode, Inf) -> - dict:store(Handle, #fileinf{offset=0, size=0, mode=FileMode}, Inf). + maps:put(Handle, #fileinf{offset=0, size=0, mode=FileMode}, Inf). update_size(Handle, NewSize, State) -> OldSize = get_size(Handle, State), @@ -1152,27 +1152,24 @@ update_offset(Handle, NewOffset, State0) -> %% access size and offset for handle put_size(Handle, Size, State) -> Inf0 = State#state.inf, - case dict:find(Handle, Inf0) of + case maps:find(Handle, Inf0) of {ok, FI} -> - State#state{inf=dict:store(Handle, FI#fileinf{size=Size}, Inf0)}; + State#state{inf=maps:put(Handle, FI#fileinf{size=Size}, Inf0)}; _ -> - State#state{inf=dict:store(Handle, #fileinf{size=Size,offset=0}, - Inf0)} + State#state{inf=maps:put(Handle, #fileinf{size=Size,offset=0}, Inf0)} end. put_offset(Handle, Offset, State) -> Inf0 = State#state.inf, - case dict:find(Handle, Inf0) of + case maps:find(Handle, Inf0) of {ok, FI} -> - State#state{inf=dict:store(Handle, FI#fileinf{offset=Offset}, - Inf0)}; + State#state{inf=maps:put(Handle, FI#fileinf{offset=Offset}, Inf0)}; _ -> - State#state{inf=dict:store(Handle, #fileinf{size=Offset, - offset=Offset}, Inf0)} + State#state{inf=maps:put(Handle, #fileinf{size=Offset, offset=Offset}, Inf0)} end. get_size(Handle, State) -> - case dict:find(Handle, State#state.inf) of + case maps:find(Handle, State#state.inf) of {ok, FI} -> FI#fileinf.size; _ -> @@ -1180,11 +1177,11 @@ get_size(Handle, State) -> end. %% get_offset(Handle, State) -> -%% {ok, FI} = dict:find(Handle, State#state.inf), +%% {ok, FI} = maps:find(Handle, State#state.inf), %% FI#fileinf.offset. get_mode(Handle, State) -> - case dict:find(Handle, State#state.inf) of + case maps:find(Handle, State#state.inf) of {ok, FI} -> FI#fileinf.mode; _ -> @@ -1192,14 +1189,14 @@ get_mode(Handle, State) -> end. erase_handle(Handle, State) -> - FI = dict:erase(Handle, State#state.inf), + FI = maps:remove(Handle, State#state.inf), State#state{inf = FI}. %% %% Caluclate a integer offset %% lseek_position(Handle, Pos, State) -> - case dict:find(Handle, State#state.inf) of + case maps:find(Handle, State#state.inf) of {ok, #fileinf{offset=O, size=S}} -> lseek_pos(Pos, O, S); _ -> -- cgit v1.2.3 From 3c5c1d70ec006e5b0b87dad0bf97a09e62e1e7c6 Mon Sep 17 00:00:00 2001 From: Philip Cristiano Date: Wed, 11 Jan 2017 06:13:49 -0500 Subject: ssh: Correct ssh_sftpd_file_api dialzyer spec The `State` seems to have been included twice in 91acfc. --- lib/ssh/src/ssh_sftpd_file_api.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl index 78f452df67..e444e52ac0 100644 --- a/lib/ssh/src/ssh_sftpd_file_api.erl +++ b/lib/ssh/src/ssh_sftpd_file_api.erl @@ -36,7 +36,7 @@ -callback list_dir(file:name(), State::term()) -> {{ok, Filenames::term()}, State::term()} | {{error, Reason::term()}, State::term()}. -callback make_dir(Dir::term(), State::term()) -> - {{ok, State::term()},State::term()} | {{error, Reason::term()}, State::term()}. + {ok, State::term()} | {{error, Reason::term()}, State::term()}. -callback make_symlink(Path2::term(), Path::term(), State::term()) -> {ok, State::term()} | {{error, Reason::term()}, State::term()}. -callback open(Path::term(), Flags::term(), State::term()) -> -- cgit v1.2.3 From 9ff231ba932dded5d712bb34fffe1f396d975a2c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Jan 2017 16:08:01 +0100 Subject: ssh: Reduce info leakage on decrypt errors Use same message when there are packet errors like too long length, MAC, decrypt or decode errors. This is regarded as good practise to prevent some attacks --- lib/ssh/src/ssh_connection_handler.erl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 7451c9e6d0..8718e92fa2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1206,7 +1206,7 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, catch _C:_E -> disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Encountered unexpected input"}, + description = "Bad packet"}, StateName, D) end; @@ -1221,13 +1221,12 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, {bad_mac, Ssh1} -> disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad mac"}, + description = "Bad packet"}, StateName, D0#data{ssh_params=Ssh1}); - {error, {exceeds_max_size,PacketLen}} -> + {error, {exceeds_max_size,_PacketLen}} -> disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet length " - ++ integer_to_list(PacketLen)}, + description = "Bad packet"}, StateName, D0) catch _C:_E -> -- cgit v1.2.3 From 69feb8bed6118e9a955d71c8d55faa6bc5dec1b1 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 20 Jan 2017 14:57:00 +0100 Subject: ssh: ssh_dbg now reports HELLO msgs and timestamps --- lib/ssh/src/ssh_dbg.erl | 66 ++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 25 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index dff2bae9f2..0345bbdea7 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -50,50 +50,61 @@ messages(Write, MangleArg) when is_function(Write,2), is_function(MangleArg,1) -> catch dbg:start(), setup_tracer(Write, MangleArg), - dbg:p(new,c), + dbg:p(new,[c,timestamp]), dbg_ssh_messages(). dbg_ssh_messages() -> dbg:tp(ssh_message,encode,1, x), dbg:tp(ssh_message,decode,1, x), - dbg:tpl(ssh_transport,select_algorithm,3, x). - + dbg:tpl(ssh_transport,select_algorithm,3, x), + dbg:tp(ssh_transport,hello_version_msg,1, x), + dbg:tp(ssh_transport,handle_hello_version,1, x). + %%%---------------------------------------------------------------- stop() -> dbg:stop(). %%%================================================================ -msg_formater({trace,Pid,call,{ssh_message,encode,[Msg]}}, D) -> - fmt("~nSEND ~p ~s~n", [Pid,wr_record(shrink_bin(Msg))], D); -msg_formater({trace,_Pid,return_from,{ssh_message,encode,1},_Res}, D) -> +msg_formater({trace_ts,Pid,call,{ssh_message,encode,[Msg]},TS}, D) -> + fmt("~n~s SEND ~p ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); +msg_formater({trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) -> D; -msg_formater({trace,_Pid,call,{ssh_message,decode,_}}, D) -> +msg_formater({trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> D; -msg_formater({trace,Pid,return_from,{ssh_message,decode,1},Msg}, D) -> - fmt("~n~p RECV ~s~n", [Pid,wr_record(shrink_bin(Msg))], D); +msg_formater({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({trace,_Pid,call,{ssh_transport,select_algorithm,_}}, D) -> +msg_formater({trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) -> + D; +msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg},TS}, D) -> + fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D); + +msg_formater({trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> D; -msg_formater({trace,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg}}, D) -> - fmt("~n~p ALGORITHMS~n~s~n", [Pid, wr_record(Alg)], D); +msg_formater({trace_ts,Pid,return_from,{ssh_transport,hello_version_msg,1},Hello,TS}, D) -> + fmt("~n~s ~p TCP SEND HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); +msg_formater({trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS}, D) -> + fmt("~n~s ~p RECV HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); +msg_formater({trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> + D; -msg_formater({trace,Pid,send,{tcp,Sock,Bytes},Pid}, D) -> - fmt("~n~p TCP SEND on ~p~n ~p~n", [Pid,Sock, shrink_bin(Bytes)], D); +msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> + fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D); -msg_formater({trace,Pid,send,{tcp,Sock,Bytes},Dest}, D) -> - fmt("~n~p TCP SEND from ~p TO ~p~n ~p~n", [Pid,Sock,Dest, shrink_bin(Bytes)], D); +msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Dest,TS}, D) -> + fmt("~n~s ~p TCP SEND from ~p TO ~p~n ~p~n", [ts(TS),Pid,Sock,Dest, shrink_bin(Bytes)], D); -msg_formater({trace,Pid,send,ErlangMsg,Dest}, D) -> - fmt("~n~p ERL MSG SEND TO ~p~n ~p~n", [Pid,Dest, shrink_bin(ErlangMsg)], D); +msg_formater({trace_ts,Pid,send,ErlangMsg,Dest,TS}, D) -> + fmt("~n~s ~p ERL MSG SEND TO ~p~n ~p~n", [ts(TS),Pid,Dest, shrink_bin(ErlangMsg)], D); -msg_formater({trace,Pid,'receive',{tcp,Sock,Bytes}}, D) -> - fmt("~n~p TCP RECEIVE on ~p~n ~p~n", [Pid,Sock,shrink_bin(Bytes)], D); +msg_formater({trace_ts,Pid,'receive',{tcp,Sock,Bytes},TS}, D) -> + fmt("~n~s ~p TCP RECEIVE on ~p~n ~p~n", [ts(TS),Pid,Sock,shrink_bin(Bytes)], D); -msg_formater({trace,Pid,'receive',ErlangMsg}, D) -> - fmt("~n~p ERL MSG RECEIVE~n ~p~n", [Pid,shrink_bin(ErlangMsg)], D); +msg_formater({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(M, D) -> @@ -106,6 +117,11 @@ msg_formater(M, D) -> fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) -> D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}. +ts({_,_,Usec}=Now) -> + {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now), + io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); +ts(_) -> + "-". %%%---------------------------------------------------------------- setup_tracer(Write, MangleArg) -> Handler = fun(Arg, D) -> @@ -116,11 +132,11 @@ setup_tracer(Write, MangleArg) -> ok. %%%---------------------------------------------------------------- -shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN', +shrink_bin(B) when is_binary(B), size(B)>256 -> {'*** SHRINKED BIN', size(B), - element(1,split_binary(B,20)), + element(1,split_binary(B,64)), '...', - element(2,split_binary(B,size(B)-20)) + element(2,split_binary(B,size(B)-64)) }; shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L); shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T))); -- cgit v1.2.3 From e0b2554dcfae4a8a20adbb3ebf226f7ebe4f89ab Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 25 Jan 2017 16:36:38 +0100 Subject: ssh: correct host key signature calculation --- lib/ssh/src/ssh_transport.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 21ba34506a..5e8efa2af7 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -432,7 +432,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits} + keyex_info = {Min0, Max0, NBits} }}; {error,_} -> ssh_connection_handler:disconnect( -- cgit v1.2.3 From 80a162cdf59f6a3826fba0cc0d3b861451a6b102 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 26 Jan 2017 22:48:13 +0100 Subject: ssh: optimize kex dh_gex using new crypto functionality --- lib/ssh/src/ssh_connection_handler.erl | 6 +- lib/ssh/src/ssh_transport.erl | 123 ++++++++++++++++++++++++++------- 2 files changed, 102 insertions(+), 27 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8718e92fa2..4496c657c3 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -609,13 +609,15 @@ handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> %%%---- diffie-hellman group exchange handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> - {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), + {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), send_bytes(GexGroup, D), + Ssh = ssh_transport:parallell_gen_key(Ssh1), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> - {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), + {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), send_bytes(GexGroup, D), + Ssh = ssh_transport:parallell_gen_key(Ssh1), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 5e8efa2af7..a7cc4cd52c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -44,6 +44,7 @@ handle_kexdh_reply/2, handle_kex_ecdh_init/2, handle_kex_ecdh_reply/2, + parallell_gen_key/1, extract_public_key/1, ssh_packet/2, pack/2, sha/1, sign/3, verify/4]). @@ -296,9 +297,6 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, end. -%% TODO: diffie-hellman-group14-sha1 should also be supported. -%% Maybe check more things ... - verify_algorithm(#alg{kex = undefined}) -> false; verify_algorithm(#alg{hkey = undefined}) -> false; verify_algorithm(#alg{send_mac = undefined}) -> false; @@ -316,17 +314,29 @@ verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex) key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; Kex == 'diffie-hellman-group14-sha1' -> {G, P} = dh_group(Kex), - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Ssh0#ssh.algorithms), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_init{e = Public}, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}; key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ; Kex == 'diffie-hellman-group-exchange-sha256' -> - {Min,NBits,Max} = + {Min,NBits0,Max} = proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN, ?DEFAULT_DH_GROUP_NBITS, ?DEFAULT_DH_GROUP_MAX}), + DhBits = dh_bits(Ssh0#ssh.algorithms), + NBits1 = + %% NIST Special Publication 800-57 Part 1 Revision 4: Recommendation for Key Management + if + DhBits =< 112 -> 2048; + DhBits =< 128 -> 3072; + DhBits =< 192 -> 7680; + true -> 8192 + end, + NBits = min(max(max(NBits0,NBits1),Min), Max), + {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min, n = NBits, @@ -350,12 +360,13 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% diffie-hellman-group14-sha1 %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, - Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) -> + Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) -> %% server {G, P} = dh_group(Kex), if 1= - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Algs), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), K = compute_key(dh, E, Private, [P,G]), MyPrivHostKey = get_host_key(Ssh0), MyPubHostKey = extract_public_key(MyPrivHostKey), @@ -426,12 +437,11 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)) of - {ok, {_Sz, {G,P}}} -> - {Public, Private} = generate_key(dh, [P,G]), + {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, - Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, + Ssh#ssh{keyex_key = {x, {G, P}}, keyex_info = {Min0, Max0, NBits} }}; {error,_} -> @@ -461,12 +471,11 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)) of - {ok, {_Sz, {G,P}}} -> - {Public, Private} = generate_key(dh, [P,G]), + {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, - Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, + Ssh#ssh{keyex_key = {x, {G, P}}, keyex_info = {-1, -1, NBits} % flag for kex_h hash calc }}; {error,_} -> @@ -507,7 +516,8 @@ adjust_gex_min_max(Min0, Max0, Opts) -> handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) -> %% client - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Ssh0#ssh.algorithms), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0), % Pub = G^Priv mod P (def) @@ -1117,6 +1127,51 @@ verify(PlainText, Hash, Sig, Key) -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Unit: bytes + +-record(cipher_data, { + key_bytes, + iv_bytes, + block_bytes + }). + +%%% Start of a more parameterized crypto handling. +cipher('AEAD_AES_128_GCM') -> + #cipher_data{key_bytes = 16, + iv_bytes = 12, + block_bytes = 16}; + +cipher('AEAD_AES_256_GCM') -> + #cipher_data{key_bytes = 32, + iv_bytes = 12, + block_bytes = 16}; + +cipher('3des-cbc') -> + #cipher_data{key_bytes = 24, + iv_bytes = 8, + block_bytes = 8}; + +cipher('aes128-cbc') -> + #cipher_data{key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes128-ctr') -> + #cipher_data{key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes192-ctr') -> + #cipher_data{key_bytes = 24, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes256-ctr') -> + #cipher_data{key_bytes = 32, + iv_bytes = 16, + block_bytes = 16}. + + encrypt_init(#ssh{encrypt = none} = Ssh) -> {ok, Ssh}; encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> @@ -1497,11 +1552,11 @@ send_mac_init(SSH) -> common -> case SSH#ssh.role of client -> - KeySize = mac_key_size(SSH#ssh.send_mac), + KeySize = 8*mac_key_bytes(SSH#ssh.send_mac), Key = hash(SSH, "E", KeySize), {ok, SSH#ssh { send_mac_key = Key }}; server -> - KeySize = mac_key_size(SSH#ssh.send_mac), + KeySize = 8*mac_key_bytes(SSH#ssh.send_mac), Key = hash(SSH, "F", KeySize), {ok, SSH#ssh { send_mac_key = Key }} end; @@ -1520,10 +1575,10 @@ recv_mac_init(SSH) -> common -> case SSH#ssh.role of client -> - Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)), + Key = hash(SSH, "F", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }}; server -> - Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)), + Key = hash(SSH, "E", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }} end; aead -> @@ -1640,13 +1695,15 @@ sha(?'secp384r1') -> sha(secp384r1); sha(?'secp521r1') -> sha(secp521r1). -mac_key_size('hmac-sha1') -> 20*8; -mac_key_size('hmac-sha1-96') -> 20*8; -mac_key_size('hmac-md5') -> 16*8; -mac_key_size('hmac-md5-96') -> 16*8; -mac_key_size('hmac-sha2-256')-> 32*8; -mac_key_size('hmac-sha2-512')-> 512; -mac_key_size(none) -> 0. +mac_key_bytes('hmac-sha1') -> 20; +mac_key_bytes('hmac-sha1-96') -> 20; +mac_key_bytes('hmac-md5') -> 16; +mac_key_bytes('hmac-md5-96') -> 16; +mac_key_bytes('hmac-sha2-256')-> 32; +mac_key_bytes('hmac-sha2-512')-> 64; +mac_key_bytes('AEAD_AES_128_GCM') -> 0; +mac_key_bytes('AEAD_AES_256_GCM') -> 0; +mac_key_bytes(none) -> 0. mac_digest_size('hmac-sha1') -> 20; mac_digest_size('hmac-sha1-96') -> 12; @@ -1671,6 +1728,13 @@ dh_group('diffie-hellman-group1-sha1') -> ?dh_group1; dh_group('diffie-hellman-group14-sha1') -> ?dh_group14. %%%---------------------------------------------------------------- +parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}}, + algorithms = Algs}) -> + Sz = dh_bits(Algs), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), + Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}. + + generate_key(Algorithm, Args) -> {Public,Private} = crypto:generate_key(Algorithm, Args), {crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}. @@ -1681,6 +1745,15 @@ compute_key(Algorithm, OthersPublic, MyPrivate, Args) -> crypto:bytes_to_integer(Shared). +dh_bits(#alg{encrypt = Encrypt, + send_mac = SendMac}) -> + C = cipher(Encrypt), + 8 * lists:max([C#cipher_data.key_bytes, + C#cipher_data.block_bytes, + C#cipher_data.iv_bytes, + mac_key_bytes(SendMac) + ]). + ecdh_curve('ecdh-sha2-nistp256') -> secp256r1; ecdh_curve('ecdh-sha2-nistp384') -> secp384r1; ecdh_curve('ecdh-sha2-nistp521') -> secp521r1. -- cgit v1.2.3 From 61501c3d0fa0744b107576070eaf3062ae23ac82 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Jan 2017 19:49:57 +0100 Subject: ssh: reordered default algorithms list --- lib/ssh/src/ssh_transport.erl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 4012ae3914..73e5952972 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -95,19 +95,20 @@ supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()]. supported_algorithms(kex) -> select_crypto_supported( [ - {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {ec_curve,secp384r1}, {hashs,sha384}]}, - {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, + {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, + {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, + {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} ]); supported_algorithms(public_key) -> select_crypto_supported( - [{'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + [ {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, + {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} ]); @@ -115,14 +116,15 @@ supported_algorithms(public_key) -> supported_algorithms(cipher) -> same( select_crypto_supported( - [{'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, - {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, - {'aes128-ctr', [{ciphers,{aes_ctr,128}}]}, - {'aes128-cbc', [{ciphers,aes_cbc128}]}, + [ + {'aes256-gcm@openssh.com', [{ciphers,{aes_gcm,256}}]}, + {'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, + {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, {'aes128-gcm@openssh.com', [{ciphers,{aes_gcm,128}}]}, - {'aes256-gcm@openssh.com', [{ciphers,{aes_gcm,256}}]}, - {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]}, + {'aes128-ctr', [{ciphers,{aes_ctr,128}}]}, {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]}, + {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]}, + {'aes128-cbc', [{ciphers,aes_cbc128}]}, {'3des-cbc', [{ciphers,des3_cbc}]} ] )); -- cgit v1.2.3 From ef2aa76fbd0867a2901148edfedbcc8f1bf51809 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Jan 2017 20:34:27 +0100 Subject: ssh: added stronger diffie-hellman groups diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group14-sha256 --- lib/ssh/src/ssh_transport.erl | 32 +++++++++++++++++++++++++++----- lib/ssh/src/ssh_transport.hrl | 13 ++++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 73e5952972..693691f835 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -99,9 +99,12 @@ supported_algorithms(kex) -> {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, - {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, + {'diffie-hellman-group16-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 + {'diffie-hellman-group18-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 + {'diffie-hellman-group14-sha256', [{public_keys,dh}, {hashs,sha256}]}, % In OpenSSH 7.3.p1 {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} + {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, + {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} % Gone in OpenSSH 7.3.p1 ]); supported_algorithms(public_key) -> select_crypto_supported( @@ -110,7 +113,7 @@ supported_algorithms(public_key) -> {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, - {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} + {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1 ]); supported_algorithms(cipher) -> @@ -314,7 +317,11 @@ verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex) %%% Key exchange initialization %%% key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; - Kex == 'diffie-hellman-group14-sha1' -> + Kex == 'diffie-hellman-group14-sha1' ; + Kex == 'diffie-hellman-group14-sha256' ; + Kex == 'diffie-hellman-group16-sha512' ; + Kex == 'diffie-hellman-group18-sha512' + -> {G, P} = dh_group(Kex), Sz = dh_bits(Ssh0#ssh.algorithms), {Public, Private} = generate_key(dh, [P,G,2*Sz]), @@ -360,6 +367,9 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% %%% diffie-hellman-group1-sha1 %%% diffie-hellman-group14-sha1 +%%% diffie-hellman-group14-sha256 +%%% diffie-hellman-group16-sha512 +%%% diffie-hellman-group18-sha512 %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) -> @@ -1614,6 +1624,12 @@ hash(SSH, Char, Bits) -> fun(Data) -> crypto:hash(sha, Data) end; 'diffie-hellman-group14-sha1' -> fun(Data) -> crypto:hash(sha, Data) end; + 'diffie-hellman-group14-sha256' -> + fun(Data) -> crypto:hash(sha256, Data) end; + 'diffie-hellman-group16-sha512' -> + fun(Data) -> crypto:hash(sha512, Data) end; + 'diffie-hellman-group18-sha512' -> + fun(Data) -> crypto:hash(sha512, Data) end; 'diffie-hellman-group-exchange-sha1' -> fun(Data) -> crypto:hash(sha, Data) end; @@ -1690,6 +1706,9 @@ sha(secp384r1) -> sha384; sha(secp521r1) -> sha512; sha('diffie-hellman-group1-sha1') -> sha; sha('diffie-hellman-group14-sha1') -> sha; +sha('diffie-hellman-group14-sha256') -> sha256; +sha('diffie-hellman-group16-sha512') -> sha512; +sha('diffie-hellman-group18-sha512') -> sha512; sha('diffie-hellman-group-exchange-sha1') -> sha; sha('diffie-hellman-group-exchange-sha256') -> sha256; sha(?'secp256r1') -> sha(secp256r1); @@ -1727,7 +1746,10 @@ peer_name({Host, _}) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% dh_group('diffie-hellman-group1-sha1') -> ?dh_group1; -dh_group('diffie-hellman-group14-sha1') -> ?dh_group14. +dh_group('diffie-hellman-group14-sha1') -> ?dh_group14; +dh_group('diffie-hellman-group14-sha256') -> ?dh_group14; +dh_group('diffie-hellman-group16-sha512') -> ?dh_group16; +dh_group('diffie-hellman-group18-sha512') -> ?dh_group18. %%%---------------------------------------------------------------- parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}}, diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index f91cb1dd63..19b3f5c437 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -112,7 +112,7 @@ %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% diffie-hellman-group1-sha1 | diffie-hellman-group14-sha1 +%% diffie-hellman-group*-sha* -define(SSH_MSG_KEXDH_INIT, 30). -define(SSH_MSG_KEXDH_REPLY, 31). @@ -238,4 +238,15 @@ -define(dh_group14, {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF}). +%%% rfc 3526, ch5 +%%% Size 4096-bit +-define(dh_group16, + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF}). + +%%% rfc 3526, ch7 +%%% Size 8192-bit +-define(dh_group18, + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF}). + + -endif. % -ifdef(ssh_transport). -- cgit v1.2.3 From d08006aaec92873c8cca6b7aeb57dcd2786fa330 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 18 Jan 2017 20:44:31 +0100 Subject: ssh: removed 'diffie-hellman-group1-sha1' from default list Reason: very insecure --- lib/ssh/src/ssh_transport.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 693691f835..d172005a85 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -79,6 +79,10 @@ default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()]. algo_classes() -> [kex, public_key, cipher, mac, compression]. +default_algorithms(kex) -> + supported_algorithms(kex, [ + 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 + ]); default_algorithms(cipher) -> supported_algorithms(cipher, same(['AEAD_AES_128_GCM', @@ -104,7 +108,7 @@ supported_algorithms(kex) -> {'diffie-hellman-group14-sha256', [{public_keys,dh}, {hashs,sha256}]}, % In OpenSSH 7.3.p1 {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} % Gone in OpenSSH 7.3.p1 + {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} ]); supported_algorithms(public_key) -> select_crypto_supported( -- cgit v1.2.3 From 6847d9223420fb86cdf72f0e608a5f41a2673053 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 19 Jan 2017 17:19:37 +0100 Subject: ssh: removed 'ssh-dss' from default list Reason: insecure --- lib/ssh/src/ssh_transport.erl | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index d172005a85..7a01f9926c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -84,6 +84,11 @@ default_algorithms(kex) -> 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 ]); +default_algorithms(public_key) -> + supported_algorithms(public_key, [ + 'ssh-dss' % Gone in OpenSSH 7.3.p1 + ]); + default_algorithms(cipher) -> supported_algorithms(cipher, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); -- cgit v1.2.3 From d89206ccb3df4fc4fff4549f561085611febb22a Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 19 Jan 2017 10:50:01 +0100 Subject: ssh: better error msg at kex failure --- lib/ssh/src/ssh_transport.erl | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7a01f9926c..b43bcff363 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -289,11 +289,12 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, true -> key_exchange_first_msg(Algoritms#alg.kex, Ssh0#ssh{algorithms = Algoritms}); - _ -> + {false,Alg} -> %% TODO: Correct code? ssh_connection_handler:disconnect( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed" + description = "Selection of key exchange algorithm failed: " + ++ Alg }) end; @@ -303,23 +304,28 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, case verify_algorithm(Algoritms) of true -> {ok, Ssh#ssh{algorithms = Algoritms}}; - _ -> + {false,Alg} -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed" + description = "Selection of key exchange algorithm failed: " + ++ Alg }) end. -verify_algorithm(#alg{kex = undefined}) -> false; -verify_algorithm(#alg{hkey = undefined}) -> false; -verify_algorithm(#alg{send_mac = undefined}) -> false; -verify_algorithm(#alg{recv_mac = undefined}) -> false; -verify_algorithm(#alg{encrypt = undefined}) -> false; -verify_algorithm(#alg{decrypt = undefined}) -> false; -verify_algorithm(#alg{compress = undefined}) -> false; -verify_algorithm(#alg{decompress = undefined}) -> false; -verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex)). +verify_algorithm(#alg{kex = undefined}) -> {false, "kex"}; +verify_algorithm(#alg{hkey = undefined}) -> {false, "hkey"}; +verify_algorithm(#alg{send_mac = undefined}) -> {false, "send_mac"}; +verify_algorithm(#alg{recv_mac = undefined}) -> {false, "recv_mac"}; +verify_algorithm(#alg{encrypt = undefined}) -> {false, "encrypt"}; +verify_algorithm(#alg{decrypt = undefined}) -> {false, "decrypt"}; +verify_algorithm(#alg{compress = undefined}) -> {false, "compress"}; +verify_algorithm(#alg{decompress = undefined}) -> {false, "decompress"}; +verify_algorithm(#alg{kex = Kex}) -> + case lists:member(Kex, supported_algorithms(kex)) of + true -> true; + false -> {false, "kex"} + end. %%%---------------------------------------------------------------- %%% -- cgit v1.2.3 From 497fc8de10bfee9eb693d393c270d8e06dbd15be Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 30 Jan 2017 13:12:06 +0100 Subject: ssh,crypto: prepare for release --- lib/ssh/src/ssh.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 4a76fd9cd3..cb0f087cfb 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -40,6 +40,6 @@ {env, []}, {mod, {ssh_app, []}}, {runtime_dependencies, ["stdlib-2.3","public_key-0.22","kernel-3.0", - "erts-6.0","crypto-3.3"]}]}. + "erts-6.0","crypto-3.6.3.1"]}]}. -- cgit v1.2.3 From 62f9bd09023da0b318e57b6454bd4b346816a27b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 26 Jan 2017 22:48:13 +0100 Subject: ssh: optimize kex dh_gex using new crypto functionality Conflicts: lib/ssh/src/ssh_connection_handler.erl lib/ssh/src/ssh_transport.erl --- lib/ssh/src/ssh_connection_handler.erl | 6 +- lib/ssh/src/ssh_transport.erl | 125 ++++++++++++++++++++++++++------- 2 files changed, 103 insertions(+), 28 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index b73f8b23d2..8c73bb8946 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -429,14 +429,16 @@ key_exchange(#ssh_msg_kexdh_reply{} = Msg, key_exchange(#ssh_msg_kex_dh_gex_request{} = Msg, #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), + {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), send_msg(GexGroup, State), + Ssh = ssh_transport:parallell_gen_key(Ssh1), {next_state, key_exchange_dh_gex_init, next_packet(State#state{ssh_params = Ssh})}; key_exchange(#ssh_msg_kex_dh_gex_request_old{} = Msg, #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), + {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), send_msg(GexGroup, State), + Ssh = ssh_transport:parallell_gen_key(Ssh1), {next_state, key_exchange_dh_gex_init, next_packet(State#state{ssh_params = Ssh})}; key_exchange(#ssh_msg_kex_dh_gex_group{} = Msg, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 18037b8461..5391df723c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -44,6 +44,7 @@ handle_kexdh_reply/2, handle_kex_ecdh_init/2, handle_kex_ecdh_reply/2, + parallell_gen_key/1, extract_public_key/1, ssh_packet/2, pack/2, sign/3, verify/4]). @@ -287,9 +288,6 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, end. -%% TODO: diffie-hellman-group14-sha1 should also be supported. -%% Maybe check more things ... - verify_algorithm(#alg{kex = undefined}) -> false; verify_algorithm(#alg{hkey = undefined}) -> false; verify_algorithm(#alg{send_mac = undefined}) -> false; @@ -307,17 +305,29 @@ verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex) key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; Kex == 'diffie-hellman-group14-sha1' -> {G, P} = dh_group(Kex), - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Ssh0#ssh.algorithms), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_init{e = Public}, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}; key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ; Kex == 'diffie-hellman-group-exchange-sha256' -> - {Min,NBits,Max} = + {Min,NBits0,Max} = proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN, ?DEFAULT_DH_GROUP_NBITS, ?DEFAULT_DH_GROUP_MAX}), + DhBits = dh_bits(Ssh0#ssh.algorithms), + NBits1 = + %% NIST Special Publication 800-57 Part 1 Revision 4: Recommendation for Key Management + if + DhBits =< 112 -> 2048; + DhBits =< 128 -> 3072; + DhBits =< 192 -> 7680; + true -> 8192 + end, + NBits = min(max(max(NBits0,NBits1),Min), Max), + {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min, n = NBits, @@ -341,12 +351,13 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% diffie-hellman-group14-sha1 %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, - Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) -> + Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) -> %% server {G, P} = dh_group(Kex), if 1= - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Algs), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), K = compute_key(dh, E, Private, [P,G]), MyPrivHostKey = get_host_key(Ssh0), MyPubHostKey = extract_public_key(MyPrivHostKey), @@ -418,13 +429,12 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)) of - {ok, {_Sz, {G,P}}} -> - {Public, Private} = generate_key(dh, [P,G]), + {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, - Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits} + Ssh#ssh{keyex_key = {x, {G, P}}, + keyex_info = {Min0, Max0, NBits} }}; {error,_} -> throw(#ssh_msg_disconnect{ @@ -452,12 +462,11 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)) of - {ok, {_Sz, {G,P}}} -> - {Public, Private} = generate_key(dh, [P,G]), + {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, - Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}, + Ssh#ssh{keyex_key = {x, {G, P}}, keyex_info = {-1, -1, NBits} % flag for kex_h hash calc }}; {error,_} -> @@ -497,7 +506,8 @@ adjust_gex_min_max(Min0, Max0, Opts) -> handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) -> %% client - {Public, Private} = generate_key(dh, [P,G]), + Sz = dh_bits(Ssh0#ssh.algorithms), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0), % Pub = G^Priv mod P (def) @@ -1108,6 +1118,51 @@ verify(PlainText, Hash, Sig, Key) -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Unit: bytes + +-record(cipher_data, { + key_bytes, + iv_bytes, + block_bytes + }). + +%%% Start of a more parameterized crypto handling. +cipher('AEAD_AES_128_GCM') -> + #cipher_data{key_bytes = 16, + iv_bytes = 12, + block_bytes = 16}; + +cipher('AEAD_AES_256_GCM') -> + #cipher_data{key_bytes = 32, + iv_bytes = 12, + block_bytes = 16}; + +cipher('3des-cbc') -> + #cipher_data{key_bytes = 24, + iv_bytes = 8, + block_bytes = 8}; + +cipher('aes128-cbc') -> + #cipher_data{key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes128-ctr') -> + #cipher_data{key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes192-ctr') -> + #cipher_data{key_bytes = 24, + iv_bytes = 16, + block_bytes = 16}; + +cipher('aes256-ctr') -> + #cipher_data{key_bytes = 32, + iv_bytes = 16, + block_bytes = 16}. + + encrypt_init(#ssh{encrypt = none} = Ssh) -> {ok, Ssh}; encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> @@ -1488,11 +1543,11 @@ send_mac_init(SSH) -> common -> case SSH#ssh.role of client -> - KeySize = mac_key_size(SSH#ssh.send_mac), + KeySize = 8*mac_key_bytes(SSH#ssh.send_mac), Key = hash(SSH, "E", KeySize), {ok, SSH#ssh { send_mac_key = Key }}; server -> - KeySize = mac_key_size(SSH#ssh.send_mac), + KeySize = 8*mac_key_bytes(SSH#ssh.send_mac), Key = hash(SSH, "F", KeySize), {ok, SSH#ssh { send_mac_key = Key }} end; @@ -1511,10 +1566,10 @@ recv_mac_init(SSH) -> common -> case SSH#ssh.role of client -> - Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)), + Key = hash(SSH, "F", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }}; server -> - Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)), + Key = hash(SSH, "E", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }} end; aead -> @@ -1638,13 +1693,15 @@ sha(?'secp384r1') -> sha(secp384r1); sha(?'secp521r1') -> sha(secp521r1). -mac_key_size('hmac-sha1') -> 20*8; -mac_key_size('hmac-sha1-96') -> 20*8; -mac_key_size('hmac-md5') -> 16*8; -mac_key_size('hmac-md5-96') -> 16*8; -mac_key_size('hmac-sha2-256')-> 32*8; -mac_key_size('hmac-sha2-512')-> 512; -mac_key_size(none) -> 0. +mac_key_bytes('hmac-sha1') -> 20; +mac_key_bytes('hmac-sha1-96') -> 20; +mac_key_bytes('hmac-md5') -> 16; +mac_key_bytes('hmac-md5-96') -> 16; +mac_key_bytes('hmac-sha2-256')-> 32; +mac_key_bytes('hmac-sha2-512')-> 64; +mac_key_bytes('AEAD_AES_128_GCM') -> 0; +mac_key_bytes('AEAD_AES_256_GCM') -> 0; +mac_key_bytes(none) -> 0. mac_digest_size('hmac-sha1') -> 20; mac_digest_size('hmac-sha1-96') -> 12; @@ -1669,6 +1726,13 @@ dh_group('diffie-hellman-group1-sha1') -> ?dh_group1; dh_group('diffie-hellman-group14-sha1') -> ?dh_group14. %%%---------------------------------------------------------------- +parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}}, + algorithms = Algs}) -> + Sz = dh_bits(Algs), + {Public, Private} = generate_key(dh, [P,G,2*Sz]), + Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}. + + generate_key(Algorithm, Args) -> {Public,Private} = crypto:generate_key(Algorithm, Args), {crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}. @@ -1679,6 +1743,15 @@ compute_key(Algorithm, OthersPublic, MyPrivate, Args) -> crypto:bytes_to_integer(Shared). +dh_bits(#alg{encrypt = Encrypt, + send_mac = SendMac}) -> + C = cipher(Encrypt), + 8 * lists:max([C#cipher_data.key_bytes, + C#cipher_data.block_bytes, + C#cipher_data.iv_bytes, + mac_key_bytes(SendMac) + ]). + ecdh_curve('ecdh-sha2-nistp256') -> secp256r1; ecdh_curve('ecdh-sha2-nistp384') -> secp384r1; ecdh_curve('ecdh-sha2-nistp521') -> secp521r1. -- cgit v1.2.3 From c0b7998760959b02293013cc9e00599303212458 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 19 Jan 2017 20:59:19 +0100 Subject: ssh: clearer hash calculation --- lib/ssh/src/ssh_transport.erl | 56 ++++++++++++------------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index b43bcff363..85ee88ce5f 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1632,52 +1632,23 @@ mac('hmac-sha2-512', Key, SeqNum, Data) -> crypto:hmac(sha512, Key, [<>, Data]). %% return N hash bytes (HASH) -hash(SSH, Char, Bits) -> - HASH = - case SSH#ssh.kex of - 'diffie-hellman-group1-sha1' -> - fun(Data) -> crypto:hash(sha, Data) end; - 'diffie-hellman-group14-sha1' -> - fun(Data) -> crypto:hash(sha, Data) end; - 'diffie-hellman-group14-sha256' -> - fun(Data) -> crypto:hash(sha256, Data) end; - 'diffie-hellman-group16-sha512' -> - fun(Data) -> crypto:hash(sha512, Data) end; - 'diffie-hellman-group18-sha512' -> - fun(Data) -> crypto:hash(sha512, Data) end; - - 'diffie-hellman-group-exchange-sha1' -> - fun(Data) -> crypto:hash(sha, Data) end; - 'diffie-hellman-group-exchange-sha256' -> - fun(Data) -> crypto:hash(sha256, Data) end; - - 'ecdh-sha2-nistp256' -> - fun(Data) -> crypto:hash(sha256,Data) end; - 'ecdh-sha2-nistp384' -> - fun(Data) -> crypto:hash(sha384,Data) end; - 'ecdh-sha2-nistp521' -> - fun(Data) -> crypto:hash(sha512,Data) end; - _ -> - exit({bad_algorithm,SSH#ssh.kex}) - end, - hash(SSH, Char, Bits, HASH). - -hash(_SSH, _Char, 0, _HASH) -> +hash(_SSH, _Char, 0) -> <<>>; -hash(SSH, Char, N, HASH) -> -K = SSH#ssh.shared_secret, % K = ssh_bits:mpint(SSH#ssh.shared_secret), +hash(SSH, Char, N) -> + HashAlg = sha(SSH#ssh.kex), + K = SSH#ssh.shared_secret, H = SSH#ssh.exchanged_hash, - SessionID = SSH#ssh.session_id, - K1 = HASH([K, H, Char, SessionID]), + K1 = crypto:hash(HashAlg, [K, H, Char, SSH#ssh.session_id]), Sz = N div 8, - <> = hash(K, H, K1, N-128, HASH), + <> = hash(K, H, K1, N-128, HashAlg), Key. -hash(_K, _H, Ki, N, _HASH) when N =< 0 -> +hash(_K, _H, Ki, N, _HashAlg) when N =< 0 -> Ki; -hash(K, H, Ki, N, HASH) -> - Kj = HASH([K, H, Ki]), - hash(K, H, <>, N-128, HASH). +hash(K, H, Ki, N, HashAlg) -> + Kj = crypto:hash(HashAlg, [K, H, Ki]), + hash(K, H, <>, N-128, HashAlg). + kex_h(SSH, Key, E, F, K) -> KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), @@ -1728,7 +1699,10 @@ sha('diffie-hellman-group-exchange-sha1') -> sha; sha('diffie-hellman-group-exchange-sha256') -> sha256; sha(?'secp256r1') -> sha(secp256r1); sha(?'secp384r1') -> sha(secp384r1); -sha(?'secp521r1') -> sha(secp521r1). +sha(?'secp521r1') -> sha(secp521r1); +sha('ecdh-sha2-nistp256') -> sha(secp256r1); +sha('ecdh-sha2-nistp384') -> sha(secp384r1); +sha('ecdh-sha2-nistp521') -> sha(secp521r1). mac_key_bytes('hmac-sha1') -> 20; -- cgit v1.2.3 From 05473252a740ae40894fbd2e5ee4349db6db087c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 19 Jan 2017 21:51:59 +0100 Subject: ssh: minor code unfolding --- lib/ssh/src/ssh_transport.erl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 85ee88ce5f..02209d5dfd 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -783,9 +783,8 @@ accepted_host(Ssh, PeerName, Public, Opts) -> yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") end. -known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh, +known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = {PeerName,_}} = Ssh, Public, Alg) -> - PeerName = peer_name(Peer), case Mod:is_host_key(Public, PeerName, Alg, Opts) of true -> ok; @@ -1631,6 +1630,8 @@ mac('hmac-sha2-256', Key, SeqNum, Data) -> mac('hmac-sha2-512', Key, SeqNum, Data) -> crypto:hmac(sha512, Key, [<>, Data]). + +%%%---------------------------------------------------------------- %% return N hash bytes (HASH) hash(_SSH, _Char, 0) -> <<>>; @@ -1649,7 +1650,7 @@ hash(K, H, Ki, N, HashAlg) -> Kj = crypto:hash(HashAlg, [K, H, Ki]), hash(K, H, <>, N-128, HashAlg). - +%%%---------------------------------------------------------------- kex_h(SSH, Key, E, F, K) -> KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), L = < 16; mac_digest_size('AEAD_AES_256_GCM') -> 16; mac_digest_size(none) -> 0. -peer_name({Host, _}) -> - Host. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% Diffie-Hellman utils -- cgit v1.2.3 From 99a6fe8c485af3024731bbb6a5af9afac7a0045f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 20 Jan 2017 15:52:57 +0100 Subject: ssh: Enable usage of supported but not default host key algorithms --- lib/ssh/src/ssh_connection_handler.erl | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4496c657c3..dcf509ca09 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1481,31 +1481,36 @@ renegotiation(_) -> false. %%-------------------------------------------------------------------- supported_host_keys(client, _, Options) -> try - case proplists:get_value(public_key, - proplists:get_value(preferred_algorithms,Options,[]) - ) of - undefined -> - ssh_transport:default_algorithms(public_key); - L -> - L -- (L--ssh_transport:default_algorithms(public_key)) - end + find_sup_hkeys(Options) of [] -> - {stop, {shutdown, "No public key algs"}}; + error({shutdown, "No public key algs"}); Algs -> [atom_to_list(A) || A<-Algs] catch exit:Reason -> - {stop, {shutdown, Reason}} + error({shutdown, Reason}) end; supported_host_keys(server, KeyCb, Options) -> - [atom_to_list(A) || A <- proplists:get_value(public_key, - proplists:get_value(preferred_algorithms,Options,[]), - ssh_transport:default_algorithms(public_key) - ), + [atom_to_list(A) || A <- find_sup_hkeys(Options), available_host_key(KeyCb, A, Options) ]. + +find_sup_hkeys(Options) -> + case proplists:get_value(public_key, + proplists:get_value(preferred_algorithms,Options,[]) + ) + of + undefined -> + ssh_transport:default_algorithms(public_key); + L -> + NonSupported = L--ssh_transport:supported_algorithms(public_key), + L -- NonSupported + end. + + + %% Alg :: atom() available_host_key(KeyCb, Alg, Opts) -> element(1, catch KeyCb:host_key(Alg, Opts)) == ok. -- cgit v1.2.3 From da0f783dce990e6c3953a7852a8c90a1933b21b2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 6 Feb 2017 12:20:37 +0100 Subject: Revert "ssh: removed 'ssh-dss' from default list" This reverts commit 6847d9223420fb86cdf72f0e608a5f41a2673053. The removal of ssh-dss seems to give a too high risk of failing customer systems. Needs to be properly deprecated. --- lib/ssh/src/ssh_transport.erl | 5 ----- 1 file changed, 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 02209d5dfd..5d178a202d 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -84,11 +84,6 @@ default_algorithms(kex) -> 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1 ]); -default_algorithms(public_key) -> - supported_algorithms(public_key, [ - 'ssh-dss' % Gone in OpenSSH 7.3.p1 - ]); - default_algorithms(cipher) -> supported_algorithms(cipher, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); -- cgit v1.2.3 From 859ac82433da2dcd11685b8c8beb972336cf70cf Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Wed, 8 Feb 2017 15:06:43 +0200 Subject: Consider root_dir and cwd in ssh_sftpd, if both are provided The SFTPD server should use root_dir and cwd when resolving file paths, if both are provided. The root directory should be used for resolving absolute file names, and cwd should be used for resolving relative paths. --- lib/ssh/src/ssh_sftpd.erl | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index b739955836..bc30b7fb7d 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -742,6 +742,10 @@ resolve_symlinks_2([], State, _LinkCnt, AccPath) -> {{ok, AccPath}, State}. +%% The File argument is always in a user visible file system, i.e. +%% is under Root and is relative to CWD or Root, if starts with "/". +%% The result of the function is always an absolute path in a +%% "backend" file system. relate_file_name(File, State) -> relate_file_name(File, State, _Canonicalize=true). @@ -749,19 +753,20 @@ relate_file_name(File, State, Canonicalize) when is_binary(File) -> relate_file_name(unicode:characters_to_list(File), State, Canonicalize); relate_file_name(File, #state{cwd = CWD, root = ""}, Canonicalize) -> relate_filename_to_path(File, CWD, Canonicalize); -relate_file_name(File, #state{root = Root}, Canonicalize) -> - case is_within_root(Root, File) of - true -> - File; - false -> - RelFile = make_relative_filename(File), - NewFile = relate_filename_to_path(RelFile, Root, Canonicalize), - case is_within_root(Root, NewFile) of - true -> - NewFile; - false -> - Root - end +relate_file_name(File, #state{cwd = CWD, root = Root}, Canonicalize) -> + CWD1 = case is_within_root(Root, CWD) of + true -> CWD; + false -> Root + end, + AbsFile = case make_relative_filename(File) of + File -> + relate_filename_to_path(File, CWD1, Canonicalize); + RelFile -> + relate_filename_to_path(RelFile, Root, Canonicalize) + end, + case is_within_root(Root, AbsFile) of + true -> AbsFile; + false -> Root end. is_within_root(Root, File) -> -- cgit v1.2.3 From a34576111652d2d7972147160f93cfbbc9f13251 Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Tue, 7 Feb 2017 11:50:40 +0200 Subject: Fix relative path handling in sftpd Relative path handling fixed to allow opening a file by a path relative to the current working directory. --- lib/ssh/src/ssh_sftpd.erl | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index b739955836..a6f4af7879 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -664,7 +664,7 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 -> do_open(ReqId, State, Path, Flags). do_open(ReqId, State0, Path, Flags) -> - #state{file_handler = FileMod, file_state = FS0, root = Root, xf = #ssh_xfer{vsn = Vsn}} = State0, + #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0, XF = State0#state.xf, F = [binary | Flags], {IsDir, _FS1} = FileMod:is_dir(Path, FS0), @@ -676,12 +676,7 @@ do_open(ReqId, State0, Path, Flags) -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, ?SSH_FX_FAILURE, "File is a directory"); false -> - AbsPath = case Root of - "" -> - Path; - _ -> - relate_file_name(Path, State0) - end, + AbsPath = relate_file_name(Path, State0), {Res, FS1} = FileMod:open(AbsPath, F, FS0), State1 = State0#state{file_state = FS1}, case Res of -- cgit v1.2.3 From 002e507bab9209aeb5487ee3a1dbe52a73f80f84 Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Sun, 12 Feb 2017 15:00:36 +0200 Subject: Check for directory with correct path When opening file in the ssh_sftpd, directory check should be performed on the server's file tree. --- lib/ssh/src/ssh_sftpd.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index a6f4af7879..7ebe5ed4ef 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -667,7 +667,8 @@ do_open(ReqId, State0, Path, Flags) -> #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0, XF = State0#state.xf, F = [binary | Flags], - {IsDir, _FS1} = FileMod:is_dir(Path, FS0), + AbsPath = relate_file_name(Path, State0), + {IsDir, _FS1} = FileMod:is_dir(AbsPath, FS0), case IsDir of true when Vsn > 5 -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, @@ -676,7 +677,6 @@ do_open(ReqId, State0, Path, Flags) -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, ?SSH_FX_FAILURE, "File is a directory"); false -> - AbsPath = relate_file_name(Path, State0), {Res, FS1} = FileMod:open(AbsPath, F, FS0), State1 = State0#state{file_state = FS1}, case Res of -- cgit v1.2.3 From 4541b1f6c136bd2225ec6a6392454b2e5dddd6e9 Mon Sep 17 00:00:00 2001 From: Andrew Dryga Date: Tue, 14 Feb 2017 11:28:34 +0200 Subject: Fixed typos in lib/ssh --- lib/ssh/src/ssh_sftp.erl | 6 +++--- lib/ssh/src/ssh_transport.erl | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index b937f0412d..8d994cdb43 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -294,7 +294,7 @@ read(Pid, Handle, Len) -> read(Pid, Handle, Len, FileOpTimeout) -> call(Pid, {read,false,Handle, Len}, FileOpTimeout). -%% TODO this ought to be a cast! Is so in all practial meaning +%% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! apread(Pid, Handle, Offset, Len) -> call(Pid, {pread,true,Handle, Offset, Len}, infinity). @@ -313,12 +313,12 @@ write(Pid, Handle, Data) -> write(Pid, Handle, Data, FileOpTimeout) -> call(Pid, {write,false,Handle,Data}, FileOpTimeout). -%% TODO this ought to be a cast! Is so in all practial meaning +%% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! apwrite(Pid, Handle, Offset, Data) -> call(Pid, {pwrite,true,Handle,Offset,Data}, infinity). -%% TODO this ought to be a cast! Is so in all practial meaning +%% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! awrite(Pid, Handle, Data) -> call(Pid, {write,true,Handle,Data}, infinity). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 5d178a202d..a17ad560d1 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -481,7 +481,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, %% This message was in the draft-00 of rfc4419 %% (https://tools.ietf.org/html/draft-ietf-secsh-dh-group-exchange-00) %% In later drafts and the rfc is "is used for backward compatibility". - %% Unfortunatly the rfc does not specify how to treat the parameter n + %% Unfortunately the rfc does not specify how to treat the parameter n %% if there is no group of that modulus length :( %% The draft-00 however specifies that n is the "... number of bits %% the subgroup should have at least". -- cgit v1.2.3 From 9f23065062eb724e58f39a65e416e5b0e1e9d95d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 10 Feb 2017 14:37:41 +0100 Subject: ssh: allow a list of fingerprint algos in silently_accept_hosts option --- lib/ssh/src/ssh.erl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 31e343e81b..f408086c0f 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -620,11 +620,22 @@ handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) - handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_function(Value,2) -> Opt; handle_ssh_option({silently_accept_hosts, {DigestAlg,Value}} = Opt) when is_function(Value,2) -> - case lists:member(DigestAlg, [md5, sha, sha224, sha256, sha384, sha512]) of - true -> - Opt; - false -> - throw({error, {eoptions, Opt}}) + Algs = if is_atom(DigestAlg) -> [DigestAlg]; + is_list(DigestAlg) -> DigestAlg; + true -> throw({error, {eoptions, Opt}}) + end, + case [A || A <- Algs, + not lists:member(A, [md5, sha, sha224, sha256, sha384, sha512])] of + [_|_] = UnSup1 -> + throw({error, {{eoptions, Opt}, {not_fingerprint_algos,UnSup1}}}); + [] -> + CryptoHashAlgs = proplists:get_value(hashs, crypto:supports(), []), + case [A || A <- Algs, + not lists:member(A, CryptoHashAlgs)] of + [_|_] = UnSup2 -> + throw({error, {{eoptions, Opt}, {unsupported_algo,UnSup2}}}); + [] -> Opt + end end; handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; -- cgit v1.2.3 From 1ca3a090fb9027aa140fea06f57aa22f8790940a Mon Sep 17 00:00:00 2001 From: Karolis Petrauskas Date: Wed, 15 Feb 2017 08:24:32 +0200 Subject: Return correct state in the case of failure --- lib/ssh/src/ssh_sftpd.erl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 7ebe5ed4ef..13c68e4c95 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -665,23 +665,24 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 -> do_open(ReqId, State0, Path, Flags) -> #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0, - XF = State0#state.xf, - F = [binary | Flags], AbsPath = relate_file_name(Path, State0), {IsDir, _FS1} = FileMod:is_dir(AbsPath, FS0), case IsDir of true when Vsn > 5 -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, - ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"); + ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"), + State0; true -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, - ?SSH_FX_FAILURE, "File is a directory"); + ?SSH_FX_FAILURE, "File is a directory"), + State0; false -> - {Res, FS1} = FileMod:open(AbsPath, F, FS0), + OpenFlags = [binary | Flags], + {Res, FS1} = FileMod:open(AbsPath, OpenFlags, FS0), State1 = State0#state{file_state = FS1}, case Res of {ok, IoDevice} -> - add_handle(State1, XF, ReqId, file, {Path,IoDevice}); + add_handle(State1, State0#state.xf, ReqId, file, {Path,IoDevice}); {error, Error} -> ssh_xfer:xf_send_status(State1#state.xf, ReqId, ssh_xfer:encode_erlang_status(Error)), -- cgit v1.2.3 From d21031900160a70408f0ee6f1b2f8bd01f1cbde7 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 15:12:37 +0100 Subject: ssh: Add error case for bad socket --- lib/ssh/src/ssh.erl | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 31e343e81b..8d8e20730d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -317,6 +317,7 @@ start_daemon(Socket, Options) -> do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions) catch throw:bad_fd -> {error,bad_fd}; + throw:bad_socket -> {error,bad_socket}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end; {error,SockError} -> @@ -333,6 +334,7 @@ start_daemon(Host, Port, Options, Inet) -> do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions]) catch throw:bad_fd -> {error,bad_fd}; + throw:bad_socket -> {error,bad_socket}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end end. -- cgit v1.2.3 From cd88d70ffb0f325fa84c8548b3dca1f7865ee86d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 15:13:54 +0100 Subject: ssh: More exact test for is_tcp_socket --- lib/ssh/src/ssh.erl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 8d8e20730d..657cf4c62d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -280,9 +280,11 @@ valid_socket_to_use(Socket, Options) -> {error, {unsupported,L4}} end. -is_tcp_socket(Socket) -> {ok,[]} =/= inet:getopts(Socket, [delay_send]). - - +is_tcp_socket(Socket) -> + case inet:getopts(Socket, [delay_send]) of + {ok,[_]} -> true; + _ -> false + end. daemon_shell_opt(Options) -> case proplists:get_value(shell, Options) of -- cgit v1.2.3 From 8fbb5b7c55c78f5696a3c504a1f7c164d5be3dc3 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 17:00:09 +0100 Subject: ssh: handle return values and exceptions from ssh_acceptor:handle_connection --- lib/ssh/src/ssh.erl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 657cf4c62d..f572e02d5f 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -366,8 +366,7 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> - ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket), - Result; + call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, Result); Result = {error, _} -> Result catch @@ -380,8 +379,7 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> - ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket), - {ok, Sup}; + call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, {ok, Sup}); Other -> Other end @@ -451,6 +449,16 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> end end. +call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, DefaultResult) -> + try ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket) + of + {error,Error} -> {error,Error}; + _ -> DefaultResult + catch + C:R -> {error,{could_not_start_connection,{C,R}}} + end. + + sync_request_control(false) -> ok; sync_request_control({LSock,Callback}) -> -- cgit v1.2.3 From de437e3639912a0570541fa10c473ac0f0372806 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Feb 2017 14:08:15 +0100 Subject: ssh: replace byte-only function with element-size agnostic An error report on ssh_cli pointed to a usage of erlang:iolist_size/1. It is replaced by a specialized function. --- lib/ssh/src/ssh_cli.erl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 8af0ecc5f9..6f8c050486 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -453,14 +453,20 @@ move_cursor(From, To, #ssh_pty{width=Width, term=Type}) -> %% %%% make sure that there is data to send %% %%% before calling ssh_connection:send write_chars(ConnectionHandler, ChannelId, Chars) -> - case erlang:iolist_size(Chars) of - 0 -> - ok; - _ -> - ssh_connection:send(ConnectionHandler, ChannelId, - ?SSH_EXTENDED_DATA_DEFAULT, Chars) + case has_chars(Chars) of + false -> ok; + true -> ssh_connection:send(ConnectionHandler, + ChannelId, + ?SSH_EXTENDED_DATA_DEFAULT, + Chars) end. +has_chars([C|_]) when is_integer(C) -> true; +has_chars([H|T]) when is_list(H) ; is_binary(H) -> has_chars(H) orelse has_chars(T); +has_chars(<<_:8,_/binary>>) -> true; +has_chars(_) -> false. + + %%% tail, works with empty lists tl1([_|A]) -> A; tl1(_) -> []. -- cgit v1.2.3 From 89a829f32d855610b0bc0c3ea53e7c05454b7a24 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 16 Feb 2017 14:48:04 +0100 Subject: ssh: Initial commit of option handling changes --- lib/ssh/src/Makefile | 1 + lib/ssh/src/ssh.app.src | 1 + lib/ssh/src/ssh.erl | 817 ++++++------------------------ lib/ssh/src/ssh.hrl | 27 +- lib/ssh/src/ssh_acceptor.erl | 117 +++-- lib/ssh/src/ssh_acceptor_sup.erl | 28 +- lib/ssh/src/ssh_auth.erl | 79 ++- lib/ssh/src/ssh_cli.erl | 12 +- lib/ssh/src/ssh_connection.erl | 38 +- lib/ssh/src/ssh_connection_handler.erl | 124 +++-- lib/ssh/src/ssh_file.erl | 4 +- lib/ssh/src/ssh_io.erl | 16 +- lib/ssh/src/ssh_options.erl | 897 +++++++++++++++++++++++++++++++++ lib/ssh/src/ssh_sftp.erl | 43 +- lib/ssh/src/ssh_subsystem_sup.erl | 36 +- lib/ssh/src/ssh_system_sup.erl | 34 +- lib/ssh/src/ssh_transport.erl | 72 +-- lib/ssh/src/sshd_sup.erl | 22 +- 18 files changed, 1369 insertions(+), 999 deletions(-) create mode 100644 lib/ssh/src/ssh_options.erl (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 7ab6f22424..f826fdfd9b 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -51,6 +51,7 @@ MODULES= \ ssh_sup \ sshc_sup \ sshd_sup \ + ssh_options \ ssh_connection_sup \ ssh_connection \ ssh_connection_handler \ diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 2bb7491b0c..95d2686094 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -7,6 +7,7 @@ ssh_app, ssh_acceptor, ssh_acceptor_sup, + ssh_options, ssh_auth, ssh_message, ssh_bits, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 68d98d3875..0186ac7922 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -41,7 +41,8 @@ %%% Type exports -export_type([connection_ref/0, - channel_id/0 + channel_id/0, + role/0 ]). %%-------------------------------------------------------------------- @@ -71,55 +72,63 @@ stop() -> application:stop(ssh). %%-------------------------------------------------------------------- --spec connect(port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec connect(inet:socket(), proplists:proplist()) -> ok_error(connection_ref()). --spec connect(port(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()} - ; (string(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec connect(inet:socket(), proplists:proplist(), timeout()) -> ok_error(connection_ref()) + ; (string(), inet:port_number(), proplists:proplist()) -> ok_error(connection_ref()). + +-spec connect(string(), inet:port_number(), proplists:proplist(), timeout()) -> ok_error(connection_ref()). --spec connect(string(), integer(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()}. %% %% Description: Starts an ssh connection. %%-------------------------------------------------------------------- -connect(Socket, Options) -> - connect(Socket, Options, infinity). +connect(Socket, UserOptions) when is_port(Socket), + is_list(UserOptions) -> + connect(Socket, UserOptions, infinity). -connect(Socket, Options, Timeout) when is_port(Socket) -> - case handle_options(Options) of +connect(Socket, UserOptions, Timeout) when is_port(Socket), + is_list(UserOptions) -> + case ssh_options:handle_options(client, UserOptions) of {error, Error} -> {error, Error}; - {_SocketOptions, SshOptions} -> - case valid_socket_to_use(Socket, Options) of + Options -> + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> {ok, {Host,_Port}} = inet:sockname(Socket), - Opts = [{user_pid,self()}, {host,fmt_host(Host)} | SshOptions], + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error,SockError} -> {error,SockError} end end; -connect(Host, Port, Options) when is_integer(Port), Port>0 -> - connect(Host, Port, Options, infinity). +connect(Host, Port, UserOptions) when is_integer(Port), + Port>0, + is_list(UserOptions) -> + connect(Host, Port, UserOptions, infinity). -connect(Host, Port, Options, Timeout) -> - case handle_options(Options) of +connect(Host, Port, UserOptions, Timeout) when is_integer(Port), + Port>0, + is_list(UserOptions) -> + case ssh_options:handle_options(client, UserOptions) of {error, _Reason} = Error -> Error; - {SocketOptions, SshOptions} -> - {_, Transport, _} = TransportOpts = - proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), - ConnectionTimeout = proplists:get_value(connect_timeout, Options, infinity), - try Transport:connect(Host, Port, [ {active, false} | SocketOptions], ConnectionTimeout) of + Options -> + {_, Transport, _} = TransportOpts = ?GET_OPT(transport, Options), + ConnectionTimeout = ?GET_OPT(connect_timeout, Options), + SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)], + try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> - Opts = [{user_pid,self()}, {host,Host} | SshOptions], + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error, Reason} -> {error, Reason} catch - exit:{function_clause, _} -> + exit:{function_clause, _F} -> + io:format('function_clause ~p~n',[_F]), {error, {options, {transport, TransportOpts}}}; exit:badarg -> - {error, {options, {socket_options, SocketOptions}}} + {error, {options, {socket_options, SocketOpts}}} end end. @@ -148,9 +157,11 @@ channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- --spec daemon(integer()) -> {ok, pid()} | {error, term()}. --spec daemon(integer()|port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. --spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec daemon(inet:port_number()) -> ok_error(pid()). +-spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(pid()). +-spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(pid()) + ;(socket, inet:socket(), proplists:proplist()) -> ok_error(pid()) + . %% Description: Starts a server listening for SSH connections %% on the given port. @@ -158,19 +169,21 @@ channel_info(ConnectionRef, ChannelId, Options) -> daemon(Port) -> daemon(Port, []). -daemon(Port, Options) when is_integer(Port) -> - daemon(any, Port, Options); -daemon(Socket, Options0) when is_port(Socket) -> - Options = daemon_shell_opt(Options0), - start_daemon(Socket, Options). +daemon(Port, UserOptions) when is_integer(Port), Port >= 0 -> + daemon(any, Port, UserOptions); + +daemon(Socket, UserOptions) when is_port(Socket) -> + daemon(socket, Socket, UserOptions). + -daemon(HostAddr, Port, Options0) -> - Options1 = daemon_shell_opt(Options0), - {Host, Inet, Options} = daemon_host_inet_opt(HostAddr, Options1), - start_daemon(Host, Port, Options, Inet). +daemon(Host0, Port, UserOptions0) -> + {Host, UserOptions} = handle_daemon_args(Host0, UserOptions0), + start_daemon(Host, Port, ssh_options:handle_options(server, UserOptions)). %%-------------------------------------------------------------------- +-spec daemon_info(pid()) -> ok_error( [{atom(), term()}] ). + daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> @@ -185,7 +198,7 @@ daemon_info(Pid) -> %%-------------------------------------------------------------------- -spec stop_listener(pid()) -> ok. --spec stop_listener(inet:ip_address(), integer()) -> ok. +-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. %% %% Description: Stops the listener, but leaves %% existing connections started by the listener up and running. @@ -199,7 +212,8 @@ stop_listener(Address, Port, Profile) -> %%-------------------------------------------------------------------- -spec stop_daemon(pid()) -> ok. --spec stop_daemon(inet:ip_address(), integer()) -> ok. +-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok. +-spec stop_daemon(inet:ip_address(), inet:port_number(), atom()) -> ok. %% %% Description: Stops the listener and all connections started by %% the listener. @@ -210,10 +224,11 @@ stop_daemon(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(port() | string()) -> _. --spec shell(port() | string(), proplists:proplist()) -> _. --spec shell(string(), integer(), proplists:proplist()) -> _. +-spec shell(inet:socket() | string()) -> _. +-spec shell(inet:socket() | string(), proplists:proplist()) -> _. +-spec shell(string(), inet:port_number(), proplists:proplist()) -> _. %% Host = string() %% Port = integer() @@ -261,112 +276,96 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -valid_socket_to_use(Socket, Options) -> - case proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}) of - {tcp,_,_} -> - %% Is this tcp-socket a valid socket? - case {is_tcp_socket(Socket), - {ok,[{active,false}]} == inet:getopts(Socket, [active]) - } - of - {true, true} -> - ok; - {true, false} -> - {error, not_passive_mode}; - _ -> - {error, not_tcp_socket} - end; - {L4,_,_} -> - {error, {unsupported,L4}} +handle_daemon_args(Host, UserOptions0) -> + case Host of + socket -> + {Host, UserOptions0}; + any -> + {ok, Host0} = inet:gethostname(), + Inet = proplists:get_value(inet, UserOptions0, inet), + {Host0, [Inet | UserOptions0]}; + {_,_,_,_} -> + {Host, [inet, {ip,Host} | UserOptions0]}; + {_,_,_,_,_,_,_,_} -> + {Host, [inet6, {ip,Host} | UserOptions0]}; + _ -> + error(badarg) end. +%%%---------------------------------------------------------------- +valid_socket_to_use(Socket, {tcp,_,_}) -> + %% Is this tcp-socket a valid socket? + case {is_tcp_socket(Socket), + {ok,[{active,false}]} == inet:getopts(Socket, [active]) + } + of + {true, true} -> + ok; + {true, false} -> + {error, not_passive_mode}; + _ -> + {error, not_tcp_socket} + end; + +valid_socket_to_use(_, {L4,_,_}) -> + {error, {unsupported,L4}}. + + is_tcp_socket(Socket) -> case inet:getopts(Socket, [delay_send]) of {ok,[_]} -> true; _ -> false end. -daemon_shell_opt(Options) -> - case proplists:get_value(shell, Options) of - undefined -> - [{shell, {shell, start, []}} | Options]; - _ -> - Options - end. - -daemon_host_inet_opt(HostAddr, Options1) -> - case HostAddr of - any -> - {ok, Host0} = inet:gethostname(), - {Host0, proplists:get_value(inet, Options1, inet), Options1}; - {_,_,_,_} -> - {HostAddr, inet, - [{ip, HostAddr} | Options1]}; - {_,_,_,_,_,_,_,_} -> - {HostAddr, inet6, - [{ip, HostAddr} | Options1]} - end. - +%%%---------------------------------------------------------------- +start_daemon(_, _, {error,Error}) -> + {error,Error}; + +start_daemon(socket, Socket, Options) -> + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of + ok -> + try + do_start_daemon(Socket, Options) + catch + throw:bad_fd -> {error,bad_fd}; + throw:bad_socket -> {error,bad_socket}; + _C:_E -> {error,{cannot_start_daemon,_C,_E}} + end; + {error,SockError} -> + {error,SockError} + end; -start_daemon(Socket, Options) -> - case handle_options(Options) of - {error, Error} -> - {error, Error}; - {SocketOptions, SshOptions} -> - case valid_socket_to_use(Socket, Options) of - ok -> - try - do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions) - catch - throw:bad_fd -> {error,bad_fd}; - throw:bad_socket -> {error,bad_socket}; - _C:_E -> {error,{cannot_start_daemon,_C,_E}} - end; - {error,SockError} -> - {error,SockError} - end +start_daemon(Host, Port, Options) -> + try + do_start_daemon(Host, Port, Options) + catch + throw:bad_fd -> {error,bad_fd}; + throw:bad_socket -> {error,bad_socket}; + _C:_E -> {error,{cannot_start_daemon,_C,_E}} end. -start_daemon(Host, Port, Options, Inet) -> - case handle_options(Options) of - {error, _Reason} = Error -> - Error; - {SocketOptions, SshOptions}-> - try - do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions]) - catch - throw:bad_fd -> {error,bad_fd}; - throw:bad_socket -> {error,bad_socket}; - _C:_E -> {error,{cannot_start_daemon,_C,_E}} - end - end. -do_start_daemon(Socket, SshOptions, SocketOptions) -> +do_start_daemon(Socket, Options) -> {ok, {IP,Port}} = try {ok,_} = inet:sockname(Socket) catch _:_ -> throw(bad_socket) end, Host = fmt_host(IP), - Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE), - Opts = [{asocket, Socket}, - {asock_owner,self()}, - {address, Host}, - {port, Port}, - {role, server}, - {socket_opts, SocketOptions}, - {ssh_opts, SshOptions}], - {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), + Opts = ?PUT_INTERNAL_OPT([{asocket, Socket}, + {asock_owner,self()}, + {address, Host}, + {port, Port}, + {role, server}], Options), + + Profile = ?GET_OPT(profile, Options), 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 - %% monent. The name is a legacy name! try sshd_sup:start_child(Opts) of {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> - call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, Result); + call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, Result); Result = {error, _} -> Result catch @@ -379,56 +378,47 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> - call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, {ok, Sup}); + call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, {ok,Sup}); Other -> Other end end. -do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> +do_start_daemon(Host0, Port0, Options0) -> {Host,Port1} = try - case proplists:get_value(fd, SocketOptions) of + case ?GET_SOCKET_OPT(fd, Options0) of undefined -> {Host0,Port0}; Fd when Port0==0 -> - find_hostport(Fd); - _ -> - {Host0,Port0} + find_hostport(Fd) end catch _:_ -> throw(bad_fd) end, - Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE), - {Port, WaitRequestControl, Opts0} = + {Port, WaitRequestControl, Options1} = case Port1 of 0 -> %% Allocate the socket here to get the port number... - {_, Callback, _} = - proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), - {ok,LSock} = ssh_acceptor:callback_listen(Callback, 0, SocketOptions), + {ok,LSock} = ssh_acceptor:callback_listen(0, Options0), {ok,{_,LPort}} = inet:sockname(LSock), {LPort, - {LSock,Callback}, - [{lsocket,LSock},{lsock_owner,self()}] + LSock, + ?PUT_INTERNAL_OPT({lsocket,{LSock,self()}}, Options0) }; _ -> - {Port1, false, []} + {Port1, false, Options0} end, - Opts = [{address, Host}, - {port, Port}, - {role, server}, - {socket_opts, SocketOptions}, - {ssh_opts, SshOptions} | Opts0], + Options = ?PUT_INTERNAL_OPT([{address, Host}, + {port, Port}, + {role, server}], Options1), + Profile = ?GET_OPT(profile, Options0), 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 - %% monent. The name is a legacy name! - try sshd_sup:start_child(Opts) of + try sshd_sup:start_child(Options) of {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> - sync_request_control(WaitRequestControl), + sync_request_control(WaitRequestControl, Options), Result; Result = {error, _} -> Result @@ -436,21 +426,22 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> exit:{noproc, _} -> {error, ssh_not_started} end; - Sup -> + Sup -> AccPid = ssh_system_sup:acceptor_supervisor(Sup), - case ssh_acceptor_sup:start_child(AccPid, Opts) of + case ssh_acceptor_sup:start_child(AccPid, Options) of {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> - sync_request_control(WaitRequestControl), + sync_request_control(WaitRequestControl, Options), {ok, Sup}; Other -> Other end end. -call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, DefaultResult) -> - try ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket) +call_ssh_acceptor_handle_connection(Host, Port, Options, Socket, DefaultResult) -> + {_, Callback, _} = ?GET_OPT(transport, Options), + try ssh_acceptor:handle_connection(Callback, Host, Port, Options, Socket) of {error,Error} -> {error,Error}; _ -> DefaultResult @@ -459,9 +450,10 @@ call_ssh_acceptor_handle_connection(Callback, Host, Port, Opts, Socket, DefaultR end. -sync_request_control(false) -> +sync_request_control(false, _Options) -> ok; -sync_request_control({LSock,Callback}) -> +sync_request_control(LSock, Options) -> + {_, Callback, _} = ?GET_OPT(transport, Options), receive {request_control,LSock,ReqPid} -> ok = Callback:controlling_process(LSock, ReqPid), @@ -477,523 +469,6 @@ find_hostport(Fd) -> ok = inet:close(S), HostPort. - -handle_options(Opts) -> - try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of - {Inet, Ssh} -> - {handle_ip(Inet), Ssh} - catch - throw:Error -> - Error - end. - - -algs_compatibility(Os0) -> - %% Take care of old options 'public_key_alg' and 'pref_public_key_algs' - case proplists:get_value(public_key_alg, Os0) of - undefined -> - Os0; - A when is_atom(A) -> - %% Skip public_key_alg if pref_public_key_algs is defined: - Os = lists:keydelete(public_key_alg, 1, Os0), - case proplists:get_value(pref_public_key_algs,Os) of - undefined when A == 'ssh-rsa' ; A==ssh_rsa -> - [{pref_public_key_algs,['ssh-rsa','ssh-dss']} | Os]; - undefined when A == 'ssh-dss' ; A==ssh_dsa -> - [{pref_public_key_algs,['ssh-dss','ssh-rsa']} | Os]; - undefined -> - throw({error, {eoptions, {public_key_alg,A} }}); - _ -> - Os - end; - V -> - throw({error, {eoptions, {public_key_alg,V} }}) - end. - - -handle_option([], SocketOptions, SshOptions) -> - {SocketOptions, SshOptions}; -handle_option([{system_dir, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_dir, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_dir_fun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{silently_accept_hosts, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_interaction, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{connect_timeout, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{dsa_pass_phrase, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{rsa_pass_phrase, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{password, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_passwords, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{pwdfun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{key_cb, {Module, Options}} | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option({key_cb, Module}), - handle_ssh_priv_option({key_cb_private, Options}) | - SshOptions]); -handle_option([{key_cb, Module} | Rest], SocketOptions, SshOptions) -> - handle_option([{key_cb, {Module, []}} | Rest], SocketOptions, SshOptions); -handle_option([{keyboard_interact_fun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -%%Backwards compatibility -handle_option([{allow_user_interaction, Value} | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option({user_interaction, Value}) | SshOptions]); -handle_option([{infofun, _} = Opt | Rest],SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{connectfun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{disconnectfun, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{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]); -handle_option([{ipv6_disabled, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{transport, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{subsystems, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{ssh_cli, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{shell, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{exec, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{auth_methods, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{auth_method_kb_interactive_data, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{pref_public_key_algs, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{preferred_algorithms,_} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{dh_gex_groups,_} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{dh_gex_limits,_} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{rekey_limit, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{max_sessions, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{max_channels, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{negotiation_timeout, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{parallel_login, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -%% (Is handled by proplists:unfold above:) -%% handle_option([parallel_login|Rest], SocketOptions, SshOptions) -> -%% handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | SshOptions]); -handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, SshOptions) -> - 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([{max_random_length_padding, _Bool} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{tstflg, _} = Opt|Rest], SocketOptions, SshOptions) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([Opt | Rest], SocketOptions, SshOptions) -> - handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). - - -handle_ssh_option({tstflg,_F} = Opt) -> Opt; -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) -> - check_dir(Opt); -handle_ssh_option({user_dir, Value} = Opt) when is_list(Value) -> - 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({silently_accept_hosts, Value} = Opt) when is_function(Value,2) -> - Opt; -handle_ssh_option({silently_accept_hosts, {DigestAlg,Value}} = Opt) when is_function(Value,2) -> - Algs = if is_atom(DigestAlg) -> [DigestAlg]; - is_list(DigestAlg) -> DigestAlg; - true -> throw({error, {eoptions, Opt}}) - end, - case [A || A <- Algs, - not lists:member(A, [md5, sha, sha224, sha256, sha384, sha512])] of - [_|_] = UnSup1 -> - throw({error, {{eoptions, Opt}, {not_fingerprint_algos,UnSup1}}}); - [] -> - CryptoHashAlgs = proplists:get_value(hashs, crypto:supports(), []), - case [A || A <- Algs, - not lists:member(A, CryptoHashAlgs)] of - [_|_] = UnSup2 -> - throw({error, {{eoptions, Opt}, {unsupported_algo,UnSup2}}}); - [] -> Opt - end - end; -handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> - Opt; -handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> - handle_pref_algs(Opt); - -handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> - {dh_gex_groups, - collect_per_size( - lists:foldl( - fun({N,G,P}, Acc) when is_integer(N),N>0, - is_integer(G),G>0, - is_integer(P),P>0 -> - [{N,{G,P}} | Acc]; - ({N,{G,P}}, Acc) when is_integer(N),N>0, - is_integer(G),G>0, - is_integer(P),P>0 -> - [{N,{G,P}} | Acc]; - ({N,GPs}, Acc) when is_list(GPs) -> - lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, - is_integer(Pi),Pi>0 -> - [{N,{Gi,Pi}} | Acci] - end, Acc, GPs) - end, [], L0))}; - -handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0, - Tag == file ; - Tag == ssh_moduli_file -> - {ok,GroupDefs} = - case Tag of - file -> - file:consult(File); - ssh_moduli_file -> - case file:open(File,[read]) of - {ok,D} -> - try - {ok,Moduli} = read_moduli_file(D, 1, []), - file:close(D), - {ok, Moduli} - catch - _:_ -> - throw({error, {{eoptions, Opt}, "Bad format in file "++File}}) - end; - {error,enoent} -> - throw({error, {{eoptions, Opt}, "File not found:"++File}}); - {error,Error} -> - throw({error, {{eoptions, Opt}, io_lib:format("Error reading file ~s: ~p",[File,Error])}}) - end - end, - - try - handle_ssh_option({dh_gex_groups,GroupDefs}) - catch - _:_ -> - throw({error, {{eoptions, Opt}, "Bad format in file: "++File}}) - end; - - -handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0, - is_integer(Max), Max>=Min -> - %% Server - Opt; -handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, - is_integer(I), I>=Min, - is_integer(Max), Max>=I -> - %% Client - Opt; -handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 -> - case handle_user_pref_pubkey_algs(Value, []) of - {true, NewOpts} -> - {pref_public_key_algs, NewOpts}; - _ -> - throw({error, {eoptions, Opt}}) - end; -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 -> - Opt; -handle_ssh_option({max_channels, Value} = Opt) when is_integer(Value), Value>0 -> - Opt; -handle_ssh_option({negotiation_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> - Opt; -handle_ssh_option({parallel_login, Value} = Opt) when Value==true ; Value==false -> - Opt; -handle_ssh_option({user, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({dsa_pass_phrase, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({rsa_pass_phrase, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({password, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({user_passwords, Value} = Opt) when is_list(Value)-> - Opt; -handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,2) -> - Opt; -handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,4) -> - Opt; -handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) -> - Opt; -handle_ssh_option({key_cb, {CallbackMod, CallbackOptions}} = Opt) when is_atom(CallbackMod), - is_list(CallbackOptions) -> - Opt; -handle_ssh_option({keyboard_interact_fun, Value} = Opt) when is_function(Value,3) -> - Opt; -handle_ssh_option({compression, Value} = Opt) when is_atom(Value) -> - Opt; -handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module), - is_atom(Function) -> - Opt; -handle_ssh_option({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) -> - 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}}); -handle_ssh_option({transport, {Protocol, Cb, ClosTag}} = Opt) when is_atom(Protocol), - is_atom(Cb), - is_atom(ClosTag) -> - Opt; -handle_ssh_option({subsystems, Value} = Opt) when is_list(Value) -> - Opt; -handle_ssh_option({ssh_cli, {Cb, _}}= Opt) when is_atom(Cb) -> - Opt; -handle_ssh_option({ssh_cli, no_cli} = Opt) -> - Opt; -handle_ssh_option({shell, {Module, Function, _}} = Opt) when is_atom(Module), - is_atom(Function) -> - Opt; -handle_ssh_option({shell, Value} = Opt) when is_function(Value) -> - Opt; -handle_ssh_option({quiet_mode, Value} = Opt) when is_boolean(Value) -> - Opt; -handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> - Opt; -handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> - Opt; -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({max_random_length_padding, Value} = Opt) when is_integer(Value), - Value =< 255 -> - Opt; -handle_ssh_option({profile, Value} = Opt) when is_atom(Value) -> - Opt; -handle_ssh_option(Opt) -> - throw({error, {eoptions, Opt}}). - -handle_ssh_priv_option({key_cb_private, Value} = Opt) when is_list(Value) -> - Opt. - -handle_inet_option({active, _} = Opt) -> - throw({error, {{eoptions, Opt}, "SSH has built in flow control, " - "and active is handled internally, user is not allowed" - "to specify this option"}}); - -handle_inet_option({inet, Value}) when (Value == inet) or (Value == inet6) -> - Value; -handle_inet_option({reuseaddr, _} = Opt) -> - throw({error, {{eoptions, Opt},"Is set internally, user is not allowed" - "to specify this option"}}); -%% Option verified by inet -handle_inet_option(Opt) -> - Opt. - - -%% Check preferred algs - -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 -> - Inet; - false -> - case lists:member(inet6, Inet) of - true -> - Inet; - false -> - [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. - - - -collect_per_size(L) -> - lists:foldr( - fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc]; - ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc] - end, [], lists:sort(L)). - -read_moduli_file(D, I, Acc) -> - case io:get_line(D,"") of - {error,Error} -> - {error,Error}; - eof -> - {ok, Acc}; - "#" ++ _ -> read_moduli_file(D, I+1, Acc); - <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc); - Data -> - Line = if is_binary(Data) -> binary_to_list(Data); - is_list(Data) -> Data - end, - try - [_Time,_Type,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"), - M = {list_to_integer(Size), - {list_to_integer(G), list_to_integer(P,16)} - }, - read_moduli_file(D, I+1, [M|Acc]) - catch - _:_ -> - read_moduli_file(D, I+1, Acc) - end - end. - -handle_user_pref_pubkey_algs([], Acc) -> - {true, lists:reverse(Acc)}; -handle_user_pref_pubkey_algs([H|T], Acc) -> - case lists:member(H, ?SUPPORTED_USER_KEYS) of - true -> - handle_user_pref_pubkey_algs(T, [H| Acc]); - - false when H==ssh_dsa -> handle_user_pref_pubkey_algs(T, ['ssh-dss'| Acc]); - false when H==ssh_rsa -> handle_user_pref_pubkey_algs(T, ['ssh-rsa'| Acc]); - - false -> - false - end. - fmt_host({A,B,C,D}) -> lists:concat([A,".",B,".",C,".",D]); fmt_host(T={_,_,_,_,_,_,_,_}) -> diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 4cd91177f6..475534f572 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -33,6 +33,10 @@ -define(REKEY_DATA_TIMOUT, 60000). -define(DEFAULT_PROFILE, default). +-define(DEFAULT_TRANSPORT, {tcp, gen_tcp, tcp_closed} ). + +-define(MAX_RND_PADDING_LEN, 15). + -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). -define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']). @@ -64,10 +68,31 @@ -define(string_utf8(X), << ?STRING(unicode:characters_to_binary(X)) >> ). -define(binary(X), << ?STRING(X) >>). +%% Cipher details -define(SSH_CIPHER_NONE, 0). -define(SSH_CIPHER_3DES, 3). -define(SSH_CIPHER_AUTHFILE, ?SSH_CIPHER_3DES). +%% Option access macros +-define(do_get_opt(C,K,O), ssh_options:get_value(C,K,O, ?MODULE,?LINE)). +-define(do_get_opt(C,K,O,D), ssh_options:get_value(C,K,O,D,?MODULE,?LINE)). + +-define(GET_OPT(Key,Opts), ?do_get_opt(user_options, Key,Opts ) ). +-define(GET_INTERNAL_OPT(Key,Opts), ?do_get_opt(internal_options,Key,Opts ) ). +-define(GET_INTERNAL_OPT(Key,Opts,Def), ?do_get_opt(internal_options,Key,Opts,Def) ). +-define(GET_SOCKET_OPT(Key,Opts), ?do_get_opt(socket_options, Key,Opts ) ). +-define(GET_SOCKET_OPT(Key,Opts,Def), ?do_get_opt(socket_options, Key,Opts,Def) ). + +-define(do_put_opt(C,KV,O), ssh_options:put_value(C,KV,O, ?MODULE,?LINE)). + +-define(PUT_OPT(KeyVal,Opts), ?do_put_opt(user_options, KeyVal,Opts) ). +-define(PUT_INTERNAL_OPT(KeyVal,Opts), ?do_put_opt(internal_options,KeyVal,Opts) ). +-define(PUT_SOCKET_OPT(KeyVal,Opts), ?do_put_opt(socket_options, KeyVal,Opts) ). + +%% Types +-type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} . + +%% Records -record(ssh, { role, %% client | server @@ -127,7 +152,7 @@ recv_sequence = 0, keyex_key, keyex_info, - random_length_padding = 15, % From RFC 4253 section 6. + random_length_padding = ?MAX_RND_PADDING_LEN, % From RFC 4253 section 6. %% User auth user, diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 13c9d9af4a..42be18f2ad 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -25,56 +25,63 @@ -include("ssh.hrl"). %% Internal application API --export([start_link/5, +-export([start_link/4, number_of_connections/1, - callback_listen/3, + callback_listen/2, handle_connection/5]). %% spawn export --export([acceptor_init/6, acceptor_loop/6]). +-export([acceptor_init/5, acceptor_loop/6]). -define(SLEEP_TIME, 200). %%==================================================================== %% Internal application API %%==================================================================== -start_link(Port, Address, SockOpts, Opts, AcceptTimeout) -> - Args = [self(), Port, Address, SockOpts, Opts, AcceptTimeout], +start_link(Port, Address, Options, AcceptTimeout) -> + Args = [self(), Port, Address, Options, AcceptTimeout], proc_lib:start_link(?MODULE, acceptor_init, Args). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -acceptor_init(Parent, Port, Address, SockOpts, Opts, AcceptTimeout) -> - {_, Callback, _} = - proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}), - - SockOwner = proplists:get_value(lsock_owner, Opts), - LSock = proplists:get_value(lsocket, Opts), - UseExistingSocket = - case catch inet:sockname(LSock) of - {ok,{_,Port}} -> is_pid(SockOwner); - _ -> false - end, - - case UseExistingSocket of - true -> - proc_lib:init_ack(Parent, {ok, self()}), +acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + try + {LSock0,SockOwner0} = ?GET_INTERNAL_OPT(lsocket, Opts), + true = is_pid(SockOwner0), + {ok,{_,Port}} = inet:sockname(LSock0), + {LSock0, SockOwner0} + of + {LSock, SockOwner} -> + %% Use existing socket + proc_lib:init_ack(Parent, {ok, self()}), request_ownership(LSock, SockOwner), - acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout); - - false -> - case (catch do_socket_listen(Callback, Port, SockOpts)) of - {ok, ListenSocket} -> - proc_lib:init_ack(Parent, {ok, self()}), - acceptor_loop(Callback, - Port, Address, Opts, ListenSocket, AcceptTimeout); - Error -> - proc_lib:init_ack(Parent, Error), - error - end + acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout) + catch + error:{badkey,lsocket} -> + %% Open new socket + try + socket_listen(Port, Opts) + of + {ok, ListenSocket} -> + proc_lib:init_ack(Parent, {ok, self()}), + {_, Callback, _} = ?GET_OPT(transport, Opts), + acceptor_loop(Callback, + Port, Address, Opts, ListenSocket, AcceptTimeout); + {error,Error} -> + proc_lib:init_ack(Parent, Error), + {error,Error} + catch + _:_ -> + {error,listen_socket_failed} + end; + + _:_ -> + {error,use_existing_socket_failed} end. + request_ownership(LSock, SockOwner) -> SockOwner ! {request_control,LSock,self()}, receive @@ -82,23 +89,25 @@ request_ownership(LSock, SockOwner) -> end. -do_socket_listen(Callback, Port0, Opts) -> - Port = - case proplists:get_value(fd, Opts) of - undefined -> Port0; - _ -> 0 - end, - callback_listen(Callback, Port, Opts). - -callback_listen(Callback, Port, Opts0) -> - Opts = [{active, false}, {reuseaddr,true} | Opts0], - case Callback:listen(Port, Opts) of +socket_listen(Port0, Opts) -> + Port = case ?GET_SOCKET_OPT(fd, Opts) of + undefined -> Port0; + _ -> 0 + end, + callback_listen(Port, Opts). + + +callback_listen(Port, Opts0) -> + {_, Callback, _} = ?GET_OPT(transport, Opts0), + Opts = ?PUT_SOCKET_OPT([{active, false}, {reuseaddr,true}], Opts0), + SockOpts = ?GET_OPT(socket_options, Opts), + case Callback:listen(Port, SockOpts) of {error, nxdomain} -> - Callback:listen(Port, lists:delete(inet6, Opts)); + Callback:listen(Port, lists:delete(inet6, SockOpts)); {error, enetunreach} -> - Callback:listen(Port, lists:delete(inet6, Opts)); + Callback:listen(Port, lists:delete(inet6, SockOpts)); {error, eafnosupport} -> - Callback:listen(Port, lists:delete(inet6, Opts)); + Callback:listen(Port, lists:delete(inet6, SockOpts)); Other -> Other end. @@ -120,21 +129,21 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) -> end. handle_connection(Callback, Address, Port, Options, Socket) -> - SSHopts = proplists:get_value(ssh_opts, Options, []), - Profile = proplists:get_value(profile, SSHopts, ?DEFAULT_PROFILE), + Profile = ?GET_OPT(profile, Options), SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile), - MaxSessions = proplists:get_value(max_sessions,SSHopts,infinity), + MaxSessions = ?GET_OPT(max_sessions, Options), case number_of_connections(SystemSup) < MaxSessions of true -> {ok, SubSysSup} = ssh_system_sup:start_subsystem(SystemSup, Options), ConnectionSup = ssh_subsystem_sup:connection_supervisor(SubSysSup), - Timeout = proplists:get_value(negotiation_timeout, SSHopts, 2*60*1000), + NegTimeout = ?GET_OPT(negotiation_timeout, Options), ssh_connection_handler:start_connection(server, Socket, - [{supervisors, [{system_sup, SystemSup}, - {subsystem_sup, SubSysSup}, - {connection_sup, ConnectionSup}]} - | Options], Timeout); + ?PUT_INTERNAL_OPT( + {supervisors, [{system_sup, SystemSup}, + {subsystem_sup, SubSysSup}, + {connection_sup, ConnectionSup}]}, + Options), NegTimeout); false -> Callback:close(Socket), IPstr = if is_tuple(Address) -> inet:ntoa(Address); diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 129f85a3e0..77f7826918 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -44,14 +44,13 @@ start_link(Servers) -> supervisor:start_link(?MODULE, [Servers]). -start_child(AccSup, ServerOpts) -> - Spec = child_spec(ServerOpts), +start_child(AccSup, Options) -> + Spec = child_spec(Options), case supervisor:start_child(AccSup, Spec) of {error, already_present} -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Profile = proplists:get_value(profile, - proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Profile = ?GET_OPT(profile, Options), stop_child(AccSup, Address, Port, Profile), supervisor:start_child(AccSup, Spec); Reply -> @@ -70,24 +69,23 @@ stop_child(AccSup, Address, Port, Profile) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= -init([ServerOpts]) -> +init([Options]) -> RestartStrategy = one_for_one, MaxR = 10, MaxT = 3600, - Children = [child_spec(ServerOpts)], + Children = [child_spec(Options)], {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Timeout = proplists:get_value(timeout, ServerOpts, ?DEFAULT_TIMEOUT), - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +child_spec(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT), + Profile = ?GET_OPT(profile, Options), Name = id(Address, Port, Profile), - SocketOpts = proplists:get_value(socket_opts, ServerOpts), - StartFunc = {ssh_acceptor, start_link, [Port, Address, SocketOpts, ServerOpts, Timeout]}, + StartFunc = {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, Restart = transient, Shutdown = brutal_kill, Modules = [ssh_acceptor], diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 9b54ecb2dd..88c8144063 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -96,14 +96,14 @@ unique(L) -> password_msg([#ssh{opts = Opts, io_cb = IoCb, user = User, service = Service} = Ssh0]) -> {Password,Ssh} = - case proplists:get_value(password, Opts) of + case ?GET_OPT(password, Opts) of undefined when IoCb == ssh_no_io -> {not_ok, Ssh0}; undefined -> - {IoCb:read_password("ssh password: ",Ssh0), Ssh0}; + {IoCb:read_password("ssh password: ",Opts), Ssh0}; PW -> %% If "password" option is given it should not be tried again - {PW, Ssh0#ssh{opts = lists:keyreplace(password,1,Opts,{password,not_ok})}} + {PW, Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} end, case Password of not_ok -> @@ -123,7 +123,7 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb, keyboard_interactive_msg([#ssh{user = User, opts = Opts, service = Service} = Ssh]) -> - case proplists:get_value(password, Opts) of + case ?GET_OPT(password, Opts) of not_ok -> {not_ok,Ssh}; % No need to use a failed pwd once more _ -> @@ -141,8 +141,9 @@ publickey_msg([Alg, #ssh{user = User, service = Service, opts = Opts} = Ssh]) -> Hash = ssh_transport:sha(Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - case KeyCb:user_key(Alg, Opts) of + {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), + UserOpts = ?GET_OPT(user_options, Opts), + case KeyCb:user_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of {ok, PrivKey} -> StrAlgo = atom_to_list(Alg), case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of @@ -174,13 +175,19 @@ service_request_msg(Ssh) -> %%%---------------------------------------------------------------- init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> - case user_name(Opts) of - {ok, User} -> + case ?GET_OPT(user, Opts) of + undefined -> + ErrStr = "Could not determine the users name", + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, + description = ErrStr}); + + User -> Msg = #ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "none", data = <<>>}, - Algs0 = proplists:get_value(pref_public_key_algs, Opts, ?SUPPORTED_USER_KEYS), + Algs0 = ?GET_OPT(pref_public_key_algs, Opts), %% The following line is not strictly correct. The call returns the %% supported HOST key types while we are interested in USER keys. However, %% they "happens" to be the same (for now). This could change.... @@ -194,12 +201,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> 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", - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, - description = ErrStr}) + service = "ssh-connection"}) end. %%%---------------------------------------------------------------- @@ -342,7 +344,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, false}, {Name, Instruction, Prompt, Echo} = - case proplists:get_value(auth_method_kb_interactive_data, Opts) of + case ?GET_OPT(auth_method_kb_interactive_data, Opts) of undefined -> Default; {_,_,_,_}=V -> @@ -407,9 +409,9 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, user = User, userauth_supported_methods = Methods} = Ssh) -> SendOneEmpty = - (proplists:get_value(tstflg,Opts) == one_empty) + (?GET_OPT(tstflg,Opts) == one_empty) orelse - proplists:get_value(one_empty, proplists:get_value(tstflg,Opts,[]), false), + proplists:get_value(one_empty, ?GET_OPT(tstflg,Opts), false), case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of {true,Ssh1} when SendOneEmpty==true -> @@ -460,27 +462,8 @@ method_preference(Algs) -> ], Algs). -user_name(Opts) -> - Env = case os:type() of - {win32, _} -> - "USERNAME"; - {unix, _} -> - "LOGNAME" - end, - case proplists:get_value(user, Opts, os:getenv(Env)) of - false -> - case os:getenv("USER") of - false -> - {error, no_user}; - User -> - {ok, User} - end; - User -> - {ok, User} - end. - check_password(User, Password, Opts, Ssh) -> - case proplists:get_value(pwdfun, Opts) of + case ?GET_OPT(pwdfun, Opts) of undefined -> Static = get_password_option(Opts, User), {Password == Static, Ssh}; @@ -510,17 +493,18 @@ check_password(User, Password, Opts, Ssh) -> end. get_password_option(Opts, User) -> - Passwords = proplists:get_value(user_passwords, Opts, []), + Passwords = ?GET_OPT(user_passwords, Opts), case lists:keysearch(User, 1, Passwords) of {value, {User, Pw}} -> Pw; - false -> proplists:get_value(password, Opts, false) + false -> ?GET_OPT(password, Opts) end. pre_verify_sig(User, Alg, KeyBlob, Opts) -> try {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - KeyCb:is_auth_key(Key, User, Opts) + {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), + UserOpts = ?GET_OPT(user_options, Opts), + KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) catch _:_ -> false @@ -529,9 +513,10 @@ pre_verify_sig(User, Alg, KeyBlob, Opts) -> verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> try {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - case KeyCb:is_auth_key(Key, User, Opts) of + {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), + UserOpts = ?GET_OPT(user_options, Opts), + case KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) of true -> PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg), @@ -565,9 +550,9 @@ decode_keyboard_interactive_prompts(_NumPrompts, Data) -> keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> NumPrompts = length(PromptInfos), - keyboard_interact_get_responses(proplists:get_value(user_interaction, Opts, true), - proplists:get_value(keyboard_interact_fun, Opts), - proplists:get_value(password, Opts, undefined), IoCb, Name, + keyboard_interact_get_responses(?GET_OPT(user_interaction, Opts), + ?GET_OPT(keyboard_interact_fun, Opts), + ?GET_OPT(password, Opts), IoCb, Name, Instr, PromptInfos, Opts, NumPrompts). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 6f8c050486..4c4f61e036 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -499,14 +499,12 @@ start_shell(ConnectionHandler, State) -> [peer, user]), ShellFun = case is_function(Shell) of true -> - User = - proplists:get_value(user, ConnectionInfo), + User = proplists:get_value(user, ConnectionInfo), case erlang:fun_info(Shell, arity) of {arity, 1} -> fun() -> Shell(User) end; {arity, 2} -> - {_, PeerAddr} = - proplists:get_value(peer, ConnectionInfo), + {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), fun() -> Shell(User, PeerAddr) end; _ -> Shell @@ -525,8 +523,7 @@ start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]), - User = - proplists:get_value(user, ConnectionInfo), + User = proplists:get_value(user, ConnectionInfo), ShellFun = case erlang:fun_info(Shell, arity) of {arity, 1} -> @@ -534,8 +531,7 @@ start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function {arity, 2} -> fun() -> Shell(Cmd, User) end; {arity, 3} -> - {_, PeerAddr} = - proplists:get_value(peer, ConnectionInfo), + {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo), fun() -> Shell(Cmd, User, PeerAddr) end; _ -> Shell diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index c7a2c92670..6a48ed581c 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -197,16 +197,16 @@ reply_request(_,false, _, _) -> ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> - Options = backwards_compatible(Options0, []), - {Width, PixWidth} = pty_default_dimensions(width, Options), - {Height, PixHeight} = pty_default_dimensions(height, Options), + TermData = backwards_compatible(Options0, []), % FIXME + {Width, PixWidth} = pty_default_dimensions(width, TermData), + {Height, PixHeight} = pty_default_dimensions(height, TermData), pty_req(ConnectionHandler, Channel, - proplists:get_value(term, Options, os:getenv("TERM", ?DEFAULT_TERMINAL)), - proplists:get_value(width, Options, Width), - proplists:get_value(height, Options, Height), - proplists:get_value(pixel_widh, Options, PixWidth), - proplists:get_value(pixel_height, Options, PixHeight), - proplists:get_value(pty_opts, Options, []), TimeOut + proplists:get_value(term, TermData, os:getenv("TERM", ?DEFAULT_TERMINAL)), + proplists:get_value(width, TermData, Width), + proplists:get_value(height, TermData, Height), + proplists:get_value(pixel_widh, TermData, PixWidth), + proplists:get_value(pixel_height, TermData, PixHeight), + proplists:get_value(pty_opts, TermData, []), TimeOut ). %%-------------------------------------------------------------------- %% Not yet officialy supported! The following functions are part of the @@ -417,7 +417,8 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, maximum_packet_size = PacketSz}, #connection{options = SSHopts} = Connection0, server) -> - MinAcceptedPackSz = proplists:get_value(minimal_remote_max_packet_size, SSHopts, 0), + MinAcceptedPackSz = + ?GET_OPT(minimal_remote_max_packet_size, SSHopts), if MinAcceptedPackSz =< PacketSz -> @@ -574,7 +575,6 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, PixWidth, PixHeight, decode_pty_opts(Modes)}, Channel = ssh_channel:cache_lookup(Cache, ChannelId), - handle_cli_msg(Connection, Channel, {pty, ChannelId, WantReply, PtyRequest}); @@ -691,7 +691,6 @@ handle_cli_msg(#connection{channel_cache = Cache} = Connection, #channel{user = undefined, remote_id = RemoteId, local_id = ChannelId} = Channel0, Reply0) -> - case (catch start_cli(Connection, ChannelId)) of {ok, Pid} -> erlang:monitor(process, Pid), @@ -819,7 +818,7 @@ start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> ssh_channel_sup:start_child(ChannelSup, ChildSpec). assert_limit_num_channels_not_exceeded(ChannelSup, Opts) -> - MaxNumChannels = proplists:get_value(max_channels, Opts, infinity), + MaxNumChannels = ?GET_OPT(max_channels, Opts), NumChannels = length([x || {_,_,worker,[ssh_channel]} <- supervisor:which_children(ChannelSup)]), if @@ -858,8 +857,8 @@ setup_session(#connection{channel_cache = Cache check_subsystem("sftp"= SsName, Options) -> - case proplists:get_value(subsystems, Options, no_subsys) of - no_subsys -> + case ?GET_OPT(subsystems, Options) of + no_subsys -> % FIXME: Can 'no_subsys' ever be matched? {SsName, {Cb, Opts}} = ssh_sftpd:subsystem_spec([]), {Cb, Opts}; SubSystems -> @@ -867,7 +866,7 @@ check_subsystem("sftp"= SsName, Options) -> end; check_subsystem(SsName, Options) -> - Subsystems = proplists:get_value(subsystems, Options, []), + Subsystems = ?GET_OPT(subsystems, Options), case proplists:get_value(SsName, Subsystems, {none, []}) of Fun when is_function(Fun) -> {Fun, []}; @@ -1022,12 +1021,13 @@ pty_req(ConnectionHandler, Channel, Term, Width, Height, ?uint32(PixWidth),?uint32(PixHeight), encode_pty_opts(PtyOpts)], TimeOut). -pty_default_dimensions(Dimension, Options) -> - case proplists:get_value(Dimension, Options, 0) of +pty_default_dimensions(Dimension, TermData) -> + case proplists:get_value(Dimension, TermData, 0) of N when is_integer(N), N > 0 -> {N, 0}; _ -> - case proplists:get_value(list_to_atom("pixel_" ++ atom_to_list(Dimension)), Options, 0) of + PixelDim = list_to_atom("pixel_" ++ atom_to_list(Dimension)), + case proplists:get_value(PixelDim, TermData, 0) of N when is_integer(N), N > 0 -> {0, N}; _ -> diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index dcf509ca09..706b68d78b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -76,7 +76,7 @@ %%-------------------------------------------------------------------- -spec start_link(role(), inet:socket(), - proplists:proplist() + ssh_options:options() ) -> {ok, pid()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . start_link(Role, Socket, Options) -> @@ -99,12 +99,10 @@ stop(ConnectionHandler)-> %% Internal application API %%==================================================================== --define(DefaultTransport, {tcp, gen_tcp, tcp_closed} ). - %%-------------------------------------------------------------------- -spec start_connection(role(), inet:socket(), - proplists:proplist(), + ssh_options:options(), timeout() ) -> {ok, connection_ref()} | {error, term()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -121,9 +119,8 @@ start_connection(client = Role, Socket, Options, Timeout) -> end; start_connection(server = Role, Socket, Options, Timeout) -> - SSH_Opts = proplists:get_value(ssh_opts, Options, []), try - case proplists:get_value(parallel_login, SSH_Opts, false) of + case ?GET_OPT(parallel_login, Options) of true -> HandshakerPid = spawn_link(fun() -> @@ -346,7 +343,7 @@ renegotiate_data(ConnectionHandler) -> | undefined, last_size_rekey = 0 :: non_neg_integer(), event_queue = [] :: list(), - opts :: proplists:proplist(), + opts :: ssh_options:options(), inet_initial_recbuf_size :: pos_integer() | undefined }). @@ -357,15 +354,14 @@ renegotiate_data(ConnectionHandler) -> %%-------------------------------------------------------------------- -spec init_connection_handler(role(), inet:socket(), - proplists:proplist() + ssh_options:options() ) -> no_return(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . init_connection_handler(Role, Socket, Opts) -> process_flag(trap_exit, true), S0 = init_process_state(Role, Socket, Opts), try - {Protocol, Callback, CloseTag} = - proplists:get_value(transport, Opts, ?DefaultTransport), + {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), transport_protocol = Protocol, transport_cb = Callback, @@ -393,7 +389,7 @@ init_process_state(Role, Socket, Opts) -> port_bindings = [], requests = [], options = Opts}, - starter = proplists:get_value(user_pid, Opts), + starter = ?GET_INTERNAL_OPT(user_pid, Opts), socket = Socket, opts = Opts }, @@ -409,13 +405,18 @@ init_process_state(Role, Socket, Opts) -> init_connection(server, C = #connection{}, Opts) -> - Sups = proplists:get_value(supervisors, Opts), - SystemSup = proplists:get_value(system_sup, Sups), - SubSystemSup = proplists:get_value(subsystem_sup, Sups), + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + + SystemSup = proplists:get_value(system_sup, Sups), + SubSystemSup = proplists:get_value(subsystem_sup, Sups), ConnectionSup = proplists:get_value(connection_sup, Sups), - Shell = proplists:get_value(shell, Opts), - Exec = proplists:get_value(exec, Opts), - CliSpec = proplists:get_value(ssh_cli, Opts, {ssh_cli, [Shell]}), + + Shell = ?GET_OPT(shell, Opts), + Exec = ?GET_OPT(exec, Opts), + CliSpec = case ?GET_OPT(ssh_cli, Opts) of + undefined -> {ssh_cli, [Shell]}; + Spec -> Spec + end, C#connection{cli_spec = CliSpec, exec = Exec, system_supervisor = SystemSup, @@ -426,41 +427,38 @@ init_connection(server, C = #connection{}, Opts) -> init_ssh_record(Role, Socket, Opts) -> {ok, PeerAddr} = inet:peername(Socket), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - AuthMethods = proplists:get_value(auth_methods, - Opts, - case Role of - server -> ?SUPPORTED_AUTH_METHODS; - client -> undefined - end), + KeyCb = ?GET_OPT(key_cb, Opts), + AuthMethods = + case Role of + server -> ?GET_OPT(auth_methods, Opts); + client -> undefined + end, S0 = #ssh{role = Role, key_cb = KeyCb, opts = Opts, userauth_supported_methods = AuthMethods, available_host_keys = supported_host_keys(Role, KeyCb, Opts), - random_length_padding = proplists:get_value(max_random_length_padding, - Opts, - (#ssh{})#ssh.random_length_padding) + random_length_padding = ?GET_OPT(max_random_length_padding, Opts) }, {Vsn, Version} = ssh_transport:versions(Role, Opts), case Role of client -> - PeerName = proplists:get_value(host, Opts), + PeerName = ?GET_INTERNAL_OPT(host, Opts), S0#ssh{c_vsn = Vsn, c_version = Version, - io_cb = case proplists:get_value(user_interaction, Opts, true) of + io_cb = case ?GET_OPT(user_interaction, Opts) of true -> ssh_io; false -> ssh_no_io end, - userauth_quiet_mode = proplists:get_value(quiet_mode, Opts, false), + userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), peer = {PeerName, PeerAddr} }; server -> S0#ssh{s_vsn = Vsn, s_version = Version, - io_cb = proplists:get_value(io_cb, Opts, ssh_io), + io_cb = ?GET_INTERNAL_OPT(io_cb, Opts, ssh_io), userauth_methods = string:tokens(AuthMethods, ","), kb_tries_left = 3, peer = {undefined, PeerAddr} @@ -849,14 +847,12 @@ handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactiv handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, #data{ssh_params = Ssh0} = D0) -> Opts = Ssh0#ssh.opts, - D = case proplists:get_value(password, Opts) of + D = case ?GET_OPT(password, Opts) of undefined -> D0; _ -> D0#data{ssh_params = - Ssh0#ssh{opts = - lists:keyreplace(password,1,Opts, - {password,not_ok})}} % FIXME:intermodule dependency + Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} % FIXME:intermodule dependency end, {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; @@ -954,7 +950,7 @@ handle_event(cast, renegotiate, _, _) -> handle_event(cast, data_size, {connected,Role}, D) -> {ok, [{send_oct,Sent0}]} = inet:getstat(D#data.socket, [send_oct]), Sent = Sent0 - D#data.last_size_rekey, - MaxSent = proplists:get_value(rekey_limit, D#data.opts, 1024000000), + MaxSent = ?GET_OPT(rekey_limit, D#data.opts), timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), case Sent >= MaxSent of true -> @@ -1294,11 +1290,12 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> "Unexpected message '~p' received in state '~p'\n" "Role: ~p\n" "Peer: ~p\n" - "Local Address: ~p\n", [UnexpectedMessage, - StateName, - Ssh#ssh.role, - Ssh#ssh.peer, - proplists:get_value(address, Ssh#ssh.opts)])), + "Local Address: ~p\n", + [UnexpectedMessage, + StateName, + Ssh#ssh.role, + Ssh#ssh.peer, + ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)])), error_logger:info_report(Msg), keep_state_and_data; @@ -1312,11 +1309,12 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> "Message: ~p\n" "Role: ~p\n" "Peer: ~p\n" - "Local Address: ~p\n", [Other, - UnexpectedMessage, - Ssh#ssh.role, - element(2,Ssh#ssh.peer), - proplists:get_value(address, Ssh#ssh.opts)] + "Local Address: ~p\n", + [Other, + UnexpectedMessage, + Ssh#ssh.role, + element(2,Ssh#ssh.peer), + ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)] )), error_logger:error_report(Msg), keep_state_and_data @@ -1438,11 +1436,11 @@ code_change(_OldVsn, StateName, State, _Extra) -> %%-------------------------------------------------------------------- %% Starting -start_the_connection_child(UserPid, Role, Socket, Options) -> - Sups = proplists:get_value(supervisors, Options), +start_the_connection_child(UserPid, Role, Socket, Options0) -> + Sups = ?GET_INTERNAL_OPT(supervisors, Options0), ConnectionSup = proplists:get_value(connection_sup, Sups), - Opts = [{supervisors, Sups}, {user_pid, UserPid} | proplists:get_value(ssh_opts, Options, [])], - {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Opts]), + Options = ?PUT_INTERNAL_OPT({user_pid,UserPid}, Options0), + {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Options]), ok = socket_control(Socket, Pid, Options), Pid. @@ -1499,7 +1497,7 @@ supported_host_keys(server, KeyCb, Options) -> find_sup_hkeys(Options) -> case proplists:get_value(public_key, - proplists:get_value(preferred_algorithms,Options,[]) + ?GET_OPT(preferred_algorithms,Options) ) of undefined -> @@ -1512,9 +1510,10 @@ find_sup_hkeys(Options) -> %% Alg :: atom() -available_host_key(KeyCb, Alg, Opts) -> - element(1, catch KeyCb:host_key(Alg, Opts)) == ok. - +available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) -> + UserOpts = ?GET_OPT(user_options, Opts), + element(1, + catch KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts])) == ok. send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), @@ -1773,7 +1772,7 @@ get_repl(X, Acc) -> disconnect_fun({disconnect,Msg}, D) -> disconnect_fun(Msg, D); disconnect_fun(Reason, #data{opts=Opts}) -> - case proplists:get_value(disconnectfun, Opts) of + case ?GET_OPT(disconnectfun, Opts) of undefined -> ok; Fun -> @@ -1783,7 +1782,7 @@ disconnect_fun(Reason, #data{opts=Opts}) -> unexpected_fun(UnexpectedMessage, #data{opts = Opts, ssh_params = #ssh{peer = {_,Peer} } } ) -> - case proplists:get_value(unexpectedfun, Opts) of + case ?GET_OPT(unexpectedfun, Opts) of undefined -> report; Fun -> @@ -1795,7 +1794,7 @@ debug_fun(#ssh_msg_debug{always_display = Display, message = DbgMsg, language = Lang}, #data{opts = Opts}) -> - case proplists:get_value(ssh_msg_debug_fun, Opts) of + case ?GET_OPT(ssh_msg_debug_fun, Opts) of undefined -> ok; Fun -> @@ -1805,7 +1804,7 @@ debug_fun(#ssh_msg_debug{always_display = Display, connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}, opts = Opts}) -> - case proplists:get_value(connectfun, Opts) of + case ?GET_OPT(connectfun, Opts) of undefined -> ok; Fun -> @@ -1824,7 +1823,7 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts, _ -> {infofun, Reason} end, - Fun = proplists:get_value(Tag, Opts, fun(_,_)-> ok end), + Fun = ?GET_OPT(Tag, Opts), try erlang:fun_info(Fun, arity) of {arity, 2} -> %% Backwards compatible @@ -1843,7 +1842,7 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts, %%% channels open for a while. cache_init_idle_timer(D) -> - case proplists:get_value(idle_time, D#data.opts, infinity) of + case ?GET_OPT(idle_time, D#data.opts) of infinity -> D#data{idle_timer_value = infinity, idle_timer_ref = infinity % A flag used later... @@ -1906,9 +1905,8 @@ start_channel_request_timer(Channel, From, Time) -> %%% Connection start and initalization helpers socket_control(Socket, Pid, Options) -> - {_, TransportCallback, _} = % For example {_,gen_tcp,_} - proplists:get_value(transport, Options, ?DefaultTransport), - case TransportCallback:controlling_process(Socket, Pid) of + {_, Callback, _} = ?GET_OPT(transport, Options), + case Callback:controlling_process(Socket, Pid) of ok -> gen_statem:cast(Pid, socket_control); {error, Reason} -> diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 216f65f33a..898b4cc5c4 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -192,8 +192,8 @@ lookup_user_key(Key, User, Opts) -> ssh_dir({remoteuser, User}, Opts) -> case proplists:get_value(user_dir_fun, Opts) of undefined -> - case proplists:get_value(user_dir, Opts) of - undefined -> + case proplists:get_value(user_dir, Opts, false) of + false -> default_user_dir(); Dir -> Dir diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 1d8f370884..6828fd4760 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -27,17 +27,17 @@ -export([yes_no/2, read_password/2, read_line/2, format/2]). -include("ssh.hrl"). -read_line(Prompt, Ssh) -> +read_line(Prompt, Opts) -> format("~s", [listify(Prompt)]), - proplists:get_value(user_pid, Ssh) ! {self(), question}, + ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question}, receive Answer when is_list(Answer) -> Answer end. -yes_no(Prompt, Ssh) -> +yes_no(Prompt, Opts) -> format("~s [y/n]?", [Prompt]), - proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question}, + ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question}, receive %% I can't see that the atoms y and n are ever received, but it must %% be investigated before removing @@ -52,15 +52,13 @@ yes_no(Prompt, Ssh) -> "N" -> no; _ -> format("please answer y or n\n",[]), - yes_no(Prompt, Ssh) + yes_no(Prompt, Opts) end end. - -read_password(Prompt, #ssh{opts=Opts}) -> read_password(Prompt, Opts); -read_password(Prompt, Opts) when is_list(Opts) -> +read_password(Prompt, Opts) -> format("~s", [listify(Prompt)]), - proplists:get_value(user_pid, Opts) ! {self(), user_password}, + ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), user_password}, receive Answer when is_list(Answer) -> case trim(Answer) of diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl new file mode 100644 index 0000000000..52dea5d183 --- /dev/null +++ b/lib/ssh/src/ssh_options.erl @@ -0,0 +1,897 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssh_options). + +-include("ssh.hrl"). +-include_lib("kernel/include/file.hrl"). + +-export([default/1, + get_value/5, get_value/6, + put_value/5, + handle_options/2 + ]). + +-export_type([options/0 + ]). + +%%%================================================================ +%%% Types + +-type options() :: #{socket_options := socket_options(), + internal_options := internal_options(), + option_key() => any() + }. + +-type socket_options() :: proplists:proplist(). +-type internal_options() :: #{option_key() => any()}. + +-type option_key() :: atom(). + +-type option_in() :: proplists:property() | proplists:proplist() . + +-type option_class() :: internal_options | socket_options | user_options . + +-type option_declaration() :: #{class := user_options, + chk := fun((any) -> boolean() | {true,any()}), + default => any() + }. + +-type option_declarations() :: #{ {option_key(),def} := option_declaration() }. + +-type role() :: client | server . + +-type error() :: {error,{eoptions,any()}} . + +%%%================================================================ +%%% +%%% Get an option +%%% + +-spec get_value(option_class(), option_key(), options(), + atom(), non_neg_integer()) -> any() | no_return(). + +get_value(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + case Class of + internal_options -> maps:get(Key, maps:get(internal_options,Opts)); + socket_options -> proplists:get_value(Key, maps:get(socket_options,Opts)); + user_options -> maps:get(Key, Opts) + end; +get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> + io:format("*** Bad Opts GET OPT ~p ~p:~p Key=~p,~n Opts=~p~n",[Class,_CallerMod,_CallerLine,Key,Opts]), + error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). + + +-spec get_value(option_class(), option_key(), options(), any(), + atom(), non_neg_integer()) -> any() | no_return(). + +get_value(socket_options, Key, Opts, Def, _CallerMod, _CallerLine) when is_map(Opts) -> + proplists:get_value(Key, maps:get(socket_options,Opts), Def); +get_value(Class, Key, Opts, Def, CallerMod, CallerLine) when is_map(Opts) -> + try get_value(Class, Key, Opts, CallerMod, CallerLine) + catch + error:{badkey,Key} -> Def + end; +get_value(Class, Key, Opts, _Def, _CallerMod, _CallerLine) -> + io:format("*** Bad Opts GET OPT ~p ~p:~p Key=~p,~n Opts=~p~n",[Class,_CallerMod,_CallerLine,Key,Opts]), + error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). + + +%%%================================================================ +%%% +%%% Put an option +%%% + +-spec put_value(option_class(), option_in(), options(), + atom(), non_neg_integer()) -> options(). + +put_value(user_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + put_user_value(KeyVal, Opts); + +put_value(internal_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + InternalOpts = maps:get(internal_options,Opts), + Opts#{internal_options := put_internal_value(KeyVal, InternalOpts)}; + +put_value(socket_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + SocketOpts = maps:get(socket_options,Opts), + Opts#{socket_options := put_socket_value(KeyVal, SocketOpts)}. + + +%%%---------------- +put_user_value(L, Opts) when is_list(L) -> + lists:foldl(fun put_user_value/2, Opts, L); +put_user_value({Key,Value}, Opts) -> + Opts#{Key := Value}. + +%%%---------------- +put_internal_value(L, IntOpts) when is_list(L) -> + lists:foldl(fun put_internal_value/2, IntOpts, L); +put_internal_value({Key,Value}, IntOpts) -> + IntOpts#{Key => Value}. + +%%%---------------- +put_socket_value(L, SockOpts) when is_list(L) -> + L ++ SockOpts; +put_socket_value({Key,Value}, SockOpts) -> + [{Key,Value} | SockOpts]; +put_socket_value(A, SockOpts) when is_atom(A) -> + [A | SockOpts]. + +%%%================================================================ +%%% +%%% Initialize the options +%%% + +-spec handle_options(role(), proplists:proplist()) -> options() | error() . + +-spec handle_options(role(), proplists:proplist(), options()) -> options() | error() . + +handle_options(Role, PropList0) -> + handle_options(Role, PropList0, #{socket_options => [], + internal_options => #{}, + user_options => [] + }). + +handle_options(Role, PropList0, Opts0) when is_map(Opts0), + is_list(PropList0) -> + PropList1 = proplists:unfold(PropList0), + try + OptionDefinitions = default(Role), + InitialMap = + maps:fold( + fun({K,def}, #{default:=V}, M) -> M#{K=>V}; + (_,_,M) -> M + end, + Opts0#{user_options => + maps:get(user_options,Opts0) ++ PropList1 + }, + 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) + catch + error:{eoptions, KV, undefined} -> + {error, {eoptions,KV}}; + + error:{eoptions, KV, Txt} when is_list(Txt) -> + {error, {eoptions,{KV,lists:flatten(Txt)}}}; + + error:{eoptions, KV, Extra} -> + {error, {eoptions,{KV,Extra}}} + end. + + +check_fun(Key, Defs) -> + #{chk := Fun} = maps:get({Key,def}, Defs), + Fun. + +%%%================================================================ +%%% +%%% 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: +save({allow_user_interaction,V}, Opts, Vals) -> + save({user_interaction,V}, Opts, Vals); + +save({public_key_alg,V}, Defs, Vals) -> % To remove in OTP-20 + New = case V of + 'ssh-rsa' -> ['ssh-rsa', 'ssh-dss']; + ssh_rsa -> ['ssh-rsa', 'ssh-dss']; + 'ssh-dss' -> ['ssh-dss', 'ssh-rsa']; + ssh_dsa -> ['ssh-dss', 'ssh-rsa']; + _ -> error({eoptions, {public_key_alg,V}, + "Unknown algorithm, try pref_public_key_algs instead"}) + end, + save({pref_public_key_algs,New}, Defs, Vals); + +%% Special case for socket options 'inet' and 'inet6' +save(Inet, Defs, OptMap) when Inet==inet ; Inet==inet6 -> + save({inet,Inet}, Defs, OptMap); + +%% Two clauses to prepare for a proplists:unfold +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; + +%% and finaly the 'real stuff': +save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> + try (check_fun(Key,Defs))(Value) + of + true -> + OptMap#{Key := Value}; + {true, ModifiedValue} -> + OptMap#{Key := ModifiedValue}; + false -> + error({eoptions, {Key,Value}, "Bad value"}) + catch + %% An unknown Key (= not in the definition map) is + %% regarded as an inet option: + error:{badkey,{inet,def}} -> + %% atomic (= non-tuple) options 'inet' and 'inet6': + OptMap#{socket_options := [Value | maps:get(socket_options,OptMap)]}; + error:{badkey,{Key,def}} -> + OptMap#{socket_options := [{Key,Value} | maps:get(socket_options,OptMap)]}; + + %% But a Key that is known but the value does not validate + %% by the check fun will give an error exception: + error:{check,{BadValue,Extra}} -> + error({eoptions, {Key,BadValue}, Extra}) + end. + +%%%================================================================ +%%% +%%% Default options +%%% + +-spec default(role() | common) -> option_declarations() . + +default(server) -> + (default(common)) + #{ + {subsystems, def} => + #{default => [ssh_sftpd:subsystem_spec([])], + chk => fun(L) -> + is_list(L) andalso + lists:all(fun({Name,{CB,Args}}) -> + check_string(Name) andalso + is_atom(CB) andalso + is_list(Args); + (_) -> + false + end, L) + end, + class => user_options + }, + + {shell, def} => + #{default => {shell, start, []}, + chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A); + (V) -> check_function1(V) orelse check_function2(V) + end, + class => user_options + }, + + {exec, def} => % FIXME: need some archeology.... + #{default => undefined, + chk => fun({M,F,_}) -> is_atom(M) andalso is_atom(F); + (V) -> is_function(V) + end, + class => user_options + }, + + {ssh_cli, def} => + #{default => undefined, + chk => fun({Cb, As}) -> is_atom(Cb) andalso is_list(As); + (V) -> V == no_cli + end, + class => user_options + }, + + {system_dir, def} => + #{default => "/etc/ssh", + chk => fun(V) -> check_string(V) andalso check_dir(V) end, + class => user_options + }, + + {auth_methods, def} => + #{default => ?SUPPORTED_AUTH_METHODS, + chk => fun check_string/1, + class => user_options + }, + + {auth_method_kb_interactive_data, def} => + #{default => undefined, % Default value can be constructed when User is known + chk => fun({S1,S2,S3,B}) -> + check_string(S1) andalso + check_string(S2) andalso + check_string(S3) andalso + is_boolean(B); + (F) -> + check_function3(F) + end, + class => user_options + }, + + {user_passwords, def} => + #{default => [], + chk => fun(V) -> + is_list(V) andalso + lists:all(fun({S1,S2}) -> + check_string(S1) andalso + check_string(S2) + end, V) + end, + class => user_options + }, + + {password, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + + {dh_gex_groups, def} => + #{default => undefined, + chk => fun check_dh_gex_groups/1, + class => user_options + }, + + {dh_gex_limits, def} => + #{default => {0, infinity}, + chk => fun({I1,I2}) -> + check_pos_integer(I1) andalso + check_pos_integer(I2) andalso + I1 < I2; + (_) -> + false + end, + class => user_options + }, + + {pwdfun, def} => + #{default => undefined, + chk => fun(V) -> check_function4(V) orelse check_function2(V) end, + class => user_options + }, + + {negotiation_timeout, def} => + #{default => 2*60*1000, + chk => fun check_timeout/1, + class => user_options + }, + + {max_sessions, def} => + #{default => infinity, + chk => fun check_pos_integer/1, + class => user_options + }, + + {max_channels, def} => + #{default => infinity, + chk => fun check_pos_integer/1, + class => user_options + }, + + {parallel_login, def} => + #{default => false, + chk => fun erlang:is_boolean/1, + class => user_options + }, + + {minimal_remote_max_packet_size, def} => + #{default => 0, + chk => fun check_pos_integer/1, + class => user_options + }, + + {failfun, def} => + #{default => fun(_,_,_) -> void end, + chk => fun(V) -> check_function3(V) orelse + check_function2(V) % Backwards compatibility + end, + class => user_options + }, + + {connectfun, def} => + #{default => fun(_,_,_) -> void end, + chk => fun check_function3/1, + class => user_options + }, + +%%%%% Undocumented + {infofun, def} => + #{default => fun(_,_,_) -> void end, + chk => fun(V) -> check_function3(V) orelse + check_function2(V) % Backwards compatibility + end, + class => user_options + } + }; + +default(client) -> + (default(common)) + #{ + {dsa_pass_phrase, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + + {rsa_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, + class => user_options + }, + + {user_interaction, def} => + #{default => true, + chk => fun erlang:is_boolean/1, + class => user_options + }, + + {pref_public_key_algs, def} => + #{default => + %% Get dynamically supported keys in the order of the ?SUPPORTED_USER_KEYS + [A || A <- ?SUPPORTED_USER_KEYS, + lists:member(A, ssh_transport:supported_algorithms(public_key))], + chk => + fun check_pref_public_key_algs/1, + class => + ssh + }, + + {dh_gex_limits, def} => + #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? + chk => fun({Min,I,Max}) -> + lists:all(fun check_pos_integer/1, + [Min,I,Max]); + (_) -> false + end, + class => user_options + }, + + {connect_timeout, def} => + #{default => infinity, + chk => fun check_timeout/1, + class => user_options + }, + + {user, def} => + #{default => + begin + Env = case os:type() of + {win32, _} -> "USERNAME"; + {unix, _} -> "LOGNAME" + end, + case os:getenv(Env) of + false -> + case os:getenv("USER") of + false -> undefined; + User -> User + end; + User -> + User + end + end, + chk => fun check_string/1, + class => user_options + }, + + {password, def} => + #{default => undefined, + chk => fun check_string/1, + class => user_options + }, + + {quiet_mode, def} => + #{default => false, + chk => fun erlang:is_boolean/1, + class => user_options + }, + + {idle_time, def} => + #{default => infinity, + chk => fun check_timeout/1, + class => user_options + }, + +%%%%% Undocumented + {keyboard_interact_fun, def} => + #{default => undefined, + chk => fun check_function3/1, + class => user_options + } + }; + +default(common) -> + #{ + {user_dir, def} => + #{default => false, % FIXME: TBD ~/.ssh at time of call when user is known + chk => fun(V) -> check_string(V) andalso check_dir(V) end, + class => user_options + }, + + {preferred_algorithms, def} => + #{default => ssh:default_algorithms(), + chk => fun check_preferred_algorithms/1, + class => user_options + }, + + {id_string, def} => + #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 + chk => fun(random) -> + {true, {random,2,5}}; % 2 - 5 random characters + ({random,I1,I2}) -> + %% Undocumented + check_pos_integer(I1) andalso + check_pos_integer(I2) andalso + I1= + check_string(V) + end, + class => user_options + }, + + {key_cb, def} => + #{default => {ssh_file, []}, + chk => fun({Mod,Opts}) -> is_atom(Mod) andalso is_list(Opts); + (Mod) when is_atom(Mod) -> {true, {Mod,[]}}; + (_) -> false + end, + class => user_options + }, + + {profile, def} => + #{default => ?DEFAULT_PROFILE, + chk => fun erlang:is_atom/1, + class => user_options + }, + + %% This is a "SocketOption"... + %% {fd, def} => + %% #{default => undefined, + %% chk => fun erlang:is_integer/1, + %% class => user_options + %% }, + + {disconnectfun, def} => + #{default => fun(_) -> void end, + chk => fun check_function1/1, + class => user_options + }, + + {unexpectedfun, def} => + #{default => fun(_,_) -> report end, + chk => fun check_function2/1, + class => user_options + }, + + {ssh_msg_debug_fun, def} => + #{default => fun(_,_,_,_) -> void end, + chk => fun check_function4/1, + class => user_options + }, + + {rekey_limit, def} => % FIXME: Why not common? + #{default => 1024000000, + chk => fun check_non_neg_integer/1, + class => user_options + }, + +%%%%% Undocumented + {transport, def} => + #{default => ?DEFAULT_TRANSPORT, + chk => fun({A,B,C}) -> + is_atom(A) andalso is_atom(B) andalso is_atom(C) + end, + class => user_options + }, + + {vsn, def} => + #{default => {2,0}, + chk => fun({Maj,Min}) -> check_non_neg_integer(Maj) andalso check_non_neg_integer(Min); + (_) -> false + end, + class => user_options + }, + + {tstflg, def} => + #{default => [], + chk => fun erlang:is_list/1, + class => user_options + }, + + {user_dir_fun, def} => + #{default => undefined, + chk => fun check_function1/1, + class => user_options + }, + + {max_random_length_padding, def} => + #{default => ?MAX_RND_PADDING_LEN, + chk => fun check_non_neg_integer/1, + class => user_options + } + }. + + +%%%================================================================ +%%%================================================================ +%%%================================================================ + +%%% +%%% check_*/1 -> true | false | error({check,Spec}) +%%% See error_in_check/2,3 +%%% + +%%% error_in_check(BadValue) -> error_in_check(BadValue, undefined). + +error_in_check(BadValue, Extra) -> error({check,{BadValue,Extra}}). + + +%%%---------------------------------------------------------------- +check_timeout(infinity) -> true; +check_timeout(I) -> check_pos_integer(I). + +%%%---------------------------------------------------------------- +check_pos_integer(I) -> is_integer(I) andalso I>0. + +%%%---------------------------------------------------------------- +check_non_neg_integer(I) -> is_integer(I) andalso I>=0. + +%%%---------------------------------------------------------------- +check_function1(F) -> is_function(F,1). +check_function2(F) -> is_function(F,2). +check_function3(F) -> is_function(F,3). +check_function4(F) -> is_function(F,4). + +%%%---------------------------------------------------------------- +check_pref_public_key_algs(V) -> + %% Get the dynamically supported keys, that is, thoose + %% that are stored + PKs = ssh_transport:supported_algorithms(public_key), + CHK = fun(A, Ack) -> + case lists:member(A, PKs) of + true -> + [A|Ack]; + false -> + %% Check with the documented options, that is, + %% the one we can handle + case lists:member(A,?SUPPORTED_USER_KEYS) of + false -> + %% An algorithm ssh never can handle + error_in_check(A, "Not supported public key"); + true -> + %% An algorithm ssh can handle, but not in + %% this very call + Ack + end + end + end, + case lists:foldr( + fun(ssh_dsa, Ack) -> CHK('ssh-dss', Ack); % compatibility + (ssh_rsa, Ack) -> CHK('ssh-rsa', Ack); % compatibility + (X, Ack) -> CHK(X, Ack) + end, [], V) + of + V -> true; + [] -> false; + V1 -> {true,V1} + end. + + +%%%---------------------------------------------------------------- +%% Check that it is a directory and is readable +check_dir(Dir) -> + case file:read_file_info(Dir) of + {ok, #file_info{type = directory, + access = Access}} -> + case Access of + read -> true; + read_write -> true; + _ -> error_in_check(Dir, eacces) + end; + + {ok, #file_info{}}-> + error_in_check(Dir, enotdir); + + {error, Error} -> + error_in_check(Dir, Error) + end. + +%%%---------------------------------------------------------------- +check_string(S) -> is_list(S). % FIXME: stub + +%%%---------------------------------------------------------------- +check_dh_gex_groups({file,File}) when is_list(File) -> + case file:consult(File) of + {ok, GroupDefs} -> + check_dh_gex_groups(GroupDefs); + {error, Error} -> + error_in_check({file,File},Error) + end; + +check_dh_gex_groups({ssh_moduli_file,File}) when is_list(File) -> + case file:open(File,[read]) of + {ok,D} -> + try + read_moduli_file(D, 1, []) + of + {ok,Moduli} -> + check_dh_gex_groups(Moduli); + {error,Error} -> + error_in_check({ssh_moduli_file,File}, Error) + catch + _:_ -> + error_in_check({ssh_moduli_file,File}, "Bad format in file "++File) + after + file:close(D) + end; + + {error, Error} -> + error_in_check({ssh_moduli_file,File}, Error) + end; + +check_dh_gex_groups(L0) when is_list(L0), is_tuple(hd(L0)) -> + {true, + collect_per_size( + lists:foldl( + fun({N,G,P}, Acc) when is_integer(N),N>0, + is_integer(G),G>0, + is_integer(P),P>0 -> + [{N,{G,P}} | Acc]; + ({N,{G,P}}, Acc) when is_integer(N),N>0, + is_integer(G),G>0, + is_integer(P),P>0 -> + [{N,{G,P}} | Acc]; + ({N,GPs}, Acc) when is_list(GPs) -> + lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, + is_integer(Pi),Pi>0 -> + [{N,{Gi,Pi}} | Acci] + end, Acc, GPs) + end, [], L0))}; + +check_dh_gex_groups(_) -> + false. + + + +collect_per_size(L) -> + lists:foldr( + fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc]; + ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc] + end, [], lists:sort(L)). + +read_moduli_file(D, I, Acc) -> + case io:get_line(D,"") of + {error,Error} -> + {error,Error}; + eof -> + {ok, Acc}; + "#" ++ _ -> read_moduli_file(D, I+1, Acc); + <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc); + Data -> + Line = if is_binary(Data) -> binary_to_list(Data); + is_list(Data) -> Data + end, + try + [_Time,_Class,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"), + M = {list_to_integer(Size), + {list_to_integer(G), list_to_integer(P,16)} + }, + read_moduli_file(D, I+1, [M|Acc]) + catch + _:_ -> + read_moduli_file(D, I+1, Acc) + end + end. + +%%%---------------------------------------------------------------- +-define(SHAs, [md5, sha, sha224, sha256, sha384, sha512]). + +check_silently_accept_hosts(B) when is_boolean(B) -> true; +check_silently_accept_hosts(F) when is_function(F,2) -> true; +check_silently_accept_hosts({S,F}) when is_atom(S), + is_function(F,2) -> + lists:member(S, ?SHAs) andalso + lists:member(S, proplists:get_value(hashs,crypto:supports())); +check_silently_accept_hosts({L,F}) when is_list(L), + is_function(F,2) -> + lists:all(fun(S) -> + lists:member(S, ?SHAs) andalso + lists:member(S, proplists:get_value(hashs,crypto:supports())) + end, L); +check_silently_accept_hosts(_) -> false. + +%%%---------------------------------------------------------------- +check_preferred_algorithms(Algs) -> + try alg_duplicates(Algs, [], []) + of + [] -> + {true, + [try ssh_transport:supported_algorithms(Key) + of + DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs) + catch + _:_ -> error_in_check(Key,"Bad preferred_algorithms key") + end || {Key,Vals} <- Algs] + }; + + 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) + 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; + Bad -> error_in_check({OptKey,Bad}, "Unsupported value(s) found") + end. + +%%%---------------------------------------------------------------- +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}). + +%%%---------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 8d994cdb43..140856c8e3 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -100,18 +100,14 @@ start_channel(Socket) when is_port(Socket) -> start_channel(Host) when is_list(Host) -> start_channel(Host, []). -start_channel(Socket, Options) when is_port(Socket) -> - Timeout = - %% A mixture of ssh:connect and ssh_sftp:start_channel: - case proplists:get_value(connect_timeout, Options, undefined) of - undefined -> - proplists:get_value(timeout, Options, infinity); - TO -> - TO - end, - case ssh:connect(Socket, Options, Timeout) of +start_channel(Socket, UserOptions) when is_port(Socket) -> + {SshOpts, _ChanOpts, SftpOpts} = handle_options(UserOptions), + Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: + proplists:get_value(connect_timeout, SshOpts, + proplists:get_value(timeout, SftpOpts, infinity)), + case ssh:connect(Socket, SshOpts, Timeout) of {ok,Cm} -> - case start_channel(Cm, Options) of + case start_channel(Cm, UserOptions) of {ok, Pid} -> {ok, Pid, Cm}; Error -> @@ -120,9 +116,9 @@ start_channel(Socket, Options) when is_port(Socket) -> Error -> Error end; -start_channel(Cm, Opts) when is_pid(Cm) -> - Timeout = proplists:get_value(timeout, Opts, infinity), - {_, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []), +start_channel(Cm, UserOptions) when is_pid(Cm) -> + Timeout = proplists:get_value(timeout, UserOptions, infinity), + {_SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), case ssh_xfer:attach(Cm, [], ChanOpts) of {ok, ChannelId, Cm} -> case ssh_channel:start(Cm, ChannelId, @@ -143,15 +139,17 @@ start_channel(Cm, Opts) when is_pid(Cm) -> Error end; -start_channel(Host, Opts) -> - start_channel(Host, 22, Opts). -start_channel(Host, Port, Opts) -> - {SshOpts, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []), - Timeout = proplists:get_value(timeout, SftpOpts, infinity), +start_channel(Host, UserOptions) -> + start_channel(Host, 22, UserOptions). + +start_channel(Host, Port, UserOptions) -> + {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), + Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: + proplists:get_value(connect_timeout, SshOpts, + proplists:get_value(timeout, SftpOpts, infinity)), case ssh_xfer:connect(Host, Port, SshOpts, ChanOpts, Timeout) of {ok, ChannelId, Cm} -> - case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, - ChannelId, SftpOpts]) of + case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,ChannelId,SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> @@ -865,6 +863,9 @@ terminate(_Reason, State) -> %%==================================================================== %% Internal functions %%==================================================================== +handle_options(UserOptions) -> + handle_options(UserOptions, [], [], []). + handle_options([], Sftp, Chan, Ssh) -> {Ssh, Chan, Sftp}; handle_options([{timeout, _} = Opt | Rest], Sftp, Chan, Ssh) -> diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index 637f5f398f..cf82db458f 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -26,6 +26,8 @@ -behaviour(supervisor). +-include("ssh.hrl"). + -export([start_link/1, connection_supervisor/1, channel_supervisor/1 @@ -37,8 +39,8 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Opts) -> - supervisor:start_link(?MODULE, [Opts]). +start_link(Options) -> + supervisor:start_link(?MODULE, [Options]). connection_supervisor(SupPid) -> Children = supervisor:which_children(SupPid), @@ -53,42 +55,42 @@ channel_supervisor(SupPid) -> %%%========================================================================= -spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . -init([Opts]) -> +init([Options]) -> RestartStrategy = one_for_all, MaxR = 0, MaxT = 3600, - Children = child_specs(Opts), + Children = child_specs(Options), {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(Opts) -> - case proplists:get_value(role, Opts) of +child_specs(Options) -> + case ?GET_INTERNAL_OPT(role, Options) of client -> []; server -> - [ssh_channel_child_spec(Opts), ssh_connectinon_child_spec(Opts)] + [ssh_channel_child_spec(Options), ssh_connectinon_child_spec(Options)] end. -ssh_connectinon_child_spec(Opts) -> - Address = proplists:get_value(address, Opts), - Port = proplists:get_value(port, Opts), - Role = proplists:get_value(role, Opts), +ssh_connectinon_child_spec(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Role = ?GET_INTERNAL_OPT(role, Options), Name = id(Role, ssh_connection_sup, Address, Port), - StartFunc = {ssh_connection_sup, start_link, [Opts]}, + StartFunc = {ssh_connection_sup, start_link, [Options]}, Restart = temporary, Shutdown = 5000, Modules = [ssh_connection_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_channel_child_spec(Opts) -> - Address = proplists:get_value(address, Opts), - Port = proplists:get_value(port, Opts), - Role = proplists:get_value(role, Opts), +ssh_channel_child_spec(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Role = ?GET_INTERNAL_OPT(role, Options), Name = id(Role, ssh_channel_sup, Address, Port), - StartFunc = {ssh_channel_sup, start_link, [Opts]}, + StartFunc = {ssh_channel_sup, start_link, [Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_channel_sup], diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index e97ac7b01a..b0bbd3aae5 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -45,12 +45,12 @@ %%%========================================================================= %%% Internal API %%%========================================================================= -start_link(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +start_link(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Profile = ?GET_OPT(profile, Options), Name = make_name(Address, Port, Profile), - supervisor:start_link({local, Name}, ?MODULE, [ServerOpts]). + supervisor:start_link({local, Name}, ?MODULE, [Options]). stop_listener(SysSup) -> stop_acceptor(SysSup). @@ -127,12 +127,12 @@ restart_acceptor(Address, Port, Profile) -> %%%========================================================================= -spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . -init([ServerOpts]) -> +init([Options]) -> RestartStrategy = one_for_one, MaxR = 0, MaxT = 3600, - Children = case proplists:get_value(asocket,ServerOpts) of - undefined -> child_specs(ServerOpts); + Children = case ?GET_INTERNAL_OPT(asocket,Options,undefined) of + undefined -> child_specs(Options); _ -> [] end, {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. @@ -140,24 +140,24 @@ init([ServerOpts]) -> %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(ServerOpts) -> - [ssh_acceptor_child_spec(ServerOpts)]. +child_specs(Options) -> + [ssh_acceptor_child_spec(Options)]. -ssh_acceptor_child_spec(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +ssh_acceptor_child_spec(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Profile = ?GET_OPT(profile, Options), Name = id(ssh_acceptor_sup, Address, Port, Profile), - StartFunc = {ssh_acceptor_sup, start_link, [ServerOpts]}, + StartFunc = {ssh_acceptor_sup, start_link, [Options]}, Restart = transient, Shutdown = infinity, Modules = [ssh_acceptor_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_subsystem_child_spec(ServerOpts) -> +ssh_subsystem_child_spec(Options) -> Name = make_ref(), - StartFunc = {ssh_subsystem_sup, start_link, [ServerOpts]}, + StartFunc = {ssh_subsystem_sup, start_link, [Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_subsystem_sup], diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index a17ad560d1..02c995399a 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -153,14 +153,14 @@ supported_algorithms(compression) -> %%%---------------------------------------------------------------------------- versions(client, Options)-> - Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION), + Vsn = ?GET_INTERNAL_OPT(vsn, Options, ?DEFAULT_CLIENT_VERSION), {Vsn, format_version(Vsn, software_version(Options))}; versions(server, Options) -> - Vsn = proplists:get_value(vsn, Options, ?DEFAULT_SERVER_VERSION), + Vsn = ?GET_INTERNAL_OPT(vsn, Options, ?DEFAULT_SERVER_VERSION), {Vsn, format_version(Vsn, software_version(Options))}. software_version(Options) -> - case proplists:get_value(id_string, Options) of + case ?GET_OPT(id_string, Options) of undefined -> "Erlang"++ssh_vsn(); {random,Nlo,Nup} -> @@ -171,7 +171,7 @@ software_version(Options) -> ssh_vsn() -> try {ok,L} = application:get_all_key(ssh), - proplists:get_value(vsn,L,"") + proplists:get_value(vsn, L, "") of "" -> ""; VSN when is_list(VSN) -> "/" ++ VSN; @@ -232,13 +232,7 @@ key_exchange_init_msg(Ssh0) -> kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) -> Random = ssh_bits:random(16), - PrefAlgs = - case proplists:get_value(preferred_algorithms,Opts) of - undefined -> - default_algorithms(); - Algs0 -> - Algs0 - end, + PrefAlgs = ?GET_OPT(preferred_algorithms, Opts), kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs). key_init(client, Ssh, Value) -> @@ -341,10 +335,7 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ; Kex == 'diffie-hellman-group-exchange-sha256' -> - {Min,NBits0,Max} = - proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN, - ?DEFAULT_DH_GROUP_NBITS, - ?DEFAULT_DH_GROUP_MAX}), + {Min,NBits0,Max} = ?GET_OPT(dh_gex_limits, Opts), DhBits = dh_bits(Ssh0#ssh.algorithms), NBits1 = %% NIST Special Publication 800-57 Part 1 Revision 4: Recommendation for Key Management @@ -458,7 +449,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, %% server {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, - proplists:get_value(dh_gex_groups,Opts)) of + ?GET_OPT(dh_gex_groups,Opts)) of {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), @@ -492,7 +483,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, Max0 = 8192, {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts), case public_key:dh_gex_group(Min, NBits, Max, - proplists:get_value(dh_gex_groups,Opts)) of + ?GET_OPT(dh_gex_groups,Opts)) of {ok, {_, {G,P}}} -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), @@ -517,22 +508,18 @@ handle_kex_dh_gex_request(_, _) -> adjust_gex_min_max(Min0, Max0, Opts) -> - case proplists:get_value(dh_gex_limits, Opts) of - undefined -> - {Min0, Max0}; - {Min1, Max1} -> - Min2 = max(Min0, Min1), - Max2 = min(Max0, Max1), - if - Min2 =< Max2 -> - {Min2, Max2}; - Max2 < Min2 -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "No possible diffie-hellman-group-exchange group possible" - }) - end + {Min1, Max1} = ?GET_OPT(dh_gex_limits, Opts), + Min2 = max(Min0, Min1), + Max2 = min(Max0, Max1), + if + Min2 =< Max2 -> + {Min2, Max2}; + Max2 < Min2 -> + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "No possible diffie-hellman-group-exchange group possible" + }) end. @@ -719,9 +706,9 @@ sid(#ssh{session_id = Id}, _) -> %% The host key should be read from storage %% get_host_key(SSH) -> - #ssh{key_cb = Mod, opts = Opts, algorithms = ALG} = SSH, - - case Mod:host_key(ALG#alg.hkey, Opts) of + #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts, algorithms = ALG} = SSH, + UserOpts = ?GET_OPT(user_options, Opts), + case KeyCb:host_key(ALG#alg.hkey, [{key_cb_private,KeyCbOpts}|UserOpts]) of {ok, #'RSAPrivateKey'{} = Key} -> Key; {ok, #'DSAPrivateKey'{} = Key} -> Key; {ok, #'ECPrivateKey'{} = Key} -> Key; @@ -767,7 +754,7 @@ public_algo({#'ECPoint'{},{namedCurve,OID}}) -> accepted_host(Ssh, PeerName, Public, Opts) -> - case proplists:get_value(silently_accept_hosts, Opts, false) of + case ?GET_OPT(silently_accept_hosts, Opts) of F when is_function(F,2) -> true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public))); {DigestAlg,F} when is_function(F,2) -> @@ -778,15 +765,16 @@ accepted_host(Ssh, PeerName, Public, Opts) -> yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") end. -known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = {PeerName,_}} = Ssh, +known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}} = Ssh, Public, Alg) -> - case Mod:is_host_key(Public, PeerName, Alg, Opts) of + UserOpts = ?GET_OPT(user_options, Opts), + case KeyCb:is_host_key(Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of true -> ok; false -> case accepted_host(Ssh, PeerName, Public, Opts) of true -> - Mod:add_host_key(PeerName, Public, Opts); + KeyCb:add_host_key(PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]); false -> {error, rejected} end @@ -1822,10 +1810,6 @@ len_supported(Name, Len) -> same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. - -%% default_algorithms(kex) -> % Example of how to disable an algorithm -%% supported_algorithms(kex, ['ecdh-sha2-nistp521']); - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% Other utils diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 04d2df30f7..14f1937abd 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -41,13 +41,13 @@ start_link(Servers) -> supervisor:start_link({local, ?MODULE}, ?MODULE, [Servers]). -start_child(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +start_child(Options) -> + Address = ?GET_INTERNAL_OPT(address, Options), + Port = ?GET_INTERNAL_OPT(port, Options), + Profile = ?GET_OPT(profile, Options), case ssh_system_sup:system_supervisor(Address, Port, Profile) of undefined -> - Spec = child_spec(Address, Port, ServerOpts), + Spec = child_spec(Address, Port, Options), case supervisor:start_child(?MODULE, Spec) of {error, already_present} -> Name = id(Address, Port, Profile), @@ -58,7 +58,7 @@ start_child(ServerOpts) -> end; Pid -> AccPid = ssh_system_sup:acceptor_supervisor(Pid), - ssh_acceptor_sup:start_child(AccPid, ServerOpts) + ssh_acceptor_sup:start_child(AccPid, Options) end. stop_child(Name) -> @@ -82,8 +82,8 @@ init([Servers]) -> MaxR = 10, MaxT = 3600, Fun = fun(ServerOpts) -> - Address = proplists:get_value(address, ServerOpts), - Port = proplists:get_value(port, ServerOpts), + Address = ?GET_INTERNAL_OPT(address, ServerOpts), + Port = ?GET_INTERNAL_OPT(port, ServerOpts), child_spec(Address, Port, ServerOpts) end, Children = lists:map(Fun, Servers), @@ -92,10 +92,10 @@ init([Servers]) -> %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(Address, Port, ServerOpts) -> - Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE), +child_spec(Address, Port, Options) -> + Profile = ?GET_OPT(profile, Options), Name = id(Address, Port,Profile), - StartFunc = {ssh_system_sup, start_link, [ServerOpts]}, + StartFunc = {ssh_system_sup, start_link, [Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_system_sup], -- cgit v1.2.3 From 7e2ceb5d44dc5004ea4d8271ee1e961bfa4987fd Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 23 Feb 2017 15:57:12 +0100 Subject: ssh: Types and spec fixes to conform to the ref manual --- lib/ssh/src/ssh.erl | 34 ++++++++++++++++++++++++---------- lib/ssh/src/ssh.hrl | 18 ++++++++++++++++++ lib/ssh/src/ssh_connect.hrl | 4 ++-- lib/ssh/src/ssh_connection.erl | 30 +++++++++++++++--------------- lib/ssh/src/ssh_options.erl | 2 -- 5 files changed, 59 insertions(+), 29 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 0186ac7922..53aba14458 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -40,11 +40,24 @@ ]). %%% Type exports --export_type([connection_ref/0, - channel_id/0, - role/0 +-export_type([ssh_daemon_ref/0, + ssh_connection_ref/0, + ssh_channel_id/0, + role/0, + subsystem_spec/0, + subsystem_name/0, + channel_callback/0, + channel_init_args/0, + algs_list/0, + alg_entry/0, + simple_algs/0, + double_algs/0 ]). +-opaque ssh_daemon_ref() :: daemon_ref() . +-opaque ssh_connection_ref() :: connection_ref() . +-opaque ssh_channel_id() :: channel_id(). + %%-------------------------------------------------------------------- -spec start() -> ok | {error, term()}. -spec start(permanent | transient | temporary) -> ok | {error, term()}. @@ -157,10 +170,10 @@ channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- --spec daemon(inet:port_number()) -> ok_error(pid()). --spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(pid()). --spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(pid()) - ;(socket, inet:socket(), proplists:proplist()) -> ok_error(pid()) +-spec daemon(inet:port_number()) -> ok_error(daemon_ref()). +-spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(daemon_ref()). +-spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(daemon_ref()) + ;(socket, inet:socket(), proplists:proplist()) -> ok_error(daemon_ref()) . %% Description: Starts a server listening for SSH connections @@ -182,7 +195,7 @@ daemon(Host0, Port, UserOptions0) -> start_daemon(Host, Port, ssh_options:handle_options(server, UserOptions)). %%-------------------------------------------------------------------- --spec daemon_info(pid()) -> ok_error( [{atom(), term()}] ). +-spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ). daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of @@ -197,7 +210,7 @@ daemon_info(Pid) -> end. %%-------------------------------------------------------------------- --spec stop_listener(pid()) -> ok. +-spec stop_listener(daemon_ref()) -> ok. -spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. %% %% Description: Stops the listener, but leaves @@ -211,7 +224,7 @@ stop_listener(Address, Port, Profile) -> ssh_system_sup:stop_listener(Address, Port, Profile). %%-------------------------------------------------------------------- --spec stop_daemon(pid()) -> ok. +-spec stop_daemon(daemon_ref()) -> ok. -spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok. -spec stop_daemon(inet:ip_address(), inet:port_number(), atom()) -> ok. %% @@ -269,6 +282,7 @@ start_shell(Error) -> Error. %%-------------------------------------------------------------------- +-spec default_algorithms() -> algs_list() . %%-------------------------------------------------------------------- default_algorithms() -> ssh_transport:default_algorithms(). diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 475534f572..c1ba58ed40 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -90,7 +90,25 @@ -define(PUT_SOCKET_OPT(KeyVal,Opts), ?do_put_opt(socket_options, KeyVal,Opts) ). %% Types +-type role() :: client | server . -type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} . +-type daemon_ref() :: pid() . + +-type subsystem_spec() :: {subsystem_name(), {channel_callback(), channel_init_args()}} . +-type subsystem_name() :: string() . +-type channel_callback() :: atom() . +-type channel_init_args() :: list() . + +-type algs_list() :: list( alg_entry() ). +-type alg_entry() :: {kex, simple_algs()} + | {public_key, simple_algs()} + | {cipher, double_algs()} + | {mac, double_algs()} + | {compression, double_algs()} . +-type simple_algs() :: list( atom() ) . +-type double_algs() :: list( {client2serverlist,simple_algs()} | {server2client,simple_algs()} ) + | simple_algs() . + %% Records -record(ssh, diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 4fb6bc39f3..c91c56435e 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -22,9 +22,9 @@ %%% Description : SSH connection protocol --type role() :: client | server . --type connection_ref() :: pid(). -type channel_id() :: pos_integer(). +-type connection_ref() :: pid(). + -define(DEFAULT_PACKET_SIZE, 65536). -define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE). diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 6a48ed581c..930ccecb4c 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -56,8 +56,8 @@ %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec session_channel(pid(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. --spec session_channel(pid(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. +-spec session_channel(connection_ref(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. +-spec session_channel(connection_ref(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}. %% Description: Opens a channel for a ssh session. A session is a %% remote execution of a program. The program may be a shell, an @@ -81,7 +81,7 @@ session_channel(ConnectionHandler, InitialWindowSize, end. %%-------------------------------------------------------------------- --spec exec(pid(), channel_id(), string(), timeout()) -> +-spec exec(connection_ref(), channel_id(), string(), timeout()) -> success | failure | {error, timeout | closed}. %% Description: Will request that the server start the @@ -92,7 +92,7 @@ exec(ConnectionHandler, ChannelId, Command, TimeOut) -> true, [?string(Command)], TimeOut). %%-------------------------------------------------------------------- --spec shell(pid(), channel_id()) -> _. +-spec shell(connection_ref(), channel_id()) -> _. %% Description: Will request that the user's default shell (typically %% defined in /etc/passwd in UNIX systems) be started at the other @@ -102,7 +102,7 @@ shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "shell", false, <<>>, 0). %%-------------------------------------------------------------------- --spec subsystem(pid(), channel_id(), string(), timeout()) -> +-spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> success | failure | {error, timeout | closed}. %% %% Description: Executes a predefined subsystem. @@ -112,11 +112,11 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ChannelId, "subsystem", true, [?string(SubSystem)], TimeOut). %%-------------------------------------------------------------------- --spec send(pid(), channel_id(), iodata()) -> +-spec send(connection_ref(), channel_id(), iodata()) -> ok | {error, closed}. --spec send(pid(), channel_id(), integer()| iodata(), timeout() | iodata()) -> +-spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> ok | {error, timeout} | {error, closed}. --spec send(pid(), channel_id(), integer(), iodata(), timeout()) -> +-spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> ok | {error, timeout} | {error, closed}. %% %% @@ -134,7 +134,7 @@ send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). %%-------------------------------------------------------------------- --spec send_eof(pid(), channel_id()) -> ok | {error, closed}. +-spec send_eof(connection_ref(), channel_id()) -> ok | {error, closed}. %% %% %% Description: Sends eof on the channel . @@ -143,7 +143,7 @@ send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- --spec adjust_window(pid(), channel_id(), integer()) -> ok | {error, closed}. +-spec adjust_window(connection_ref(), channel_id(), integer()) -> ok | {error, closed}. %% %% %% Description: Adjusts the ssh flowcontrol window. @@ -152,7 +152,7 @@ adjust_window(ConnectionHandler, Channel, Bytes) -> ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes). %%-------------------------------------------------------------------- --spec setenv(pid(), channel_id(), string(), string(), timeout()) -> +-spec setenv(connection_ref(), channel_id(), string(), string(), timeout()) -> success | failure | {error, timeout | closed}. %% %% @@ -165,7 +165,7 @@ setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) -> %%-------------------------------------------------------------------- --spec close(pid(), channel_id()) -> ok. +-spec close(connection_ref(), channel_id()) -> ok. %% %% %% Description: Sends a close message on the channel . @@ -174,7 +174,7 @@ close(ConnectionHandler, ChannelId) -> ssh_connection_handler:close(ConnectionHandler, ChannelId). %%-------------------------------------------------------------------- --spec reply_request(pid(), boolean(), success | failure, channel_id()) -> ok. +-spec reply_request(connection_ref(), boolean(), success | failure, channel_id()) -> ok. %% %% %% Description: Send status replies to requests that want such replies. @@ -185,9 +185,9 @@ reply_request(_,false, _, _) -> ok. %%-------------------------------------------------------------------- --spec ptty_alloc(pid(), channel_id(), proplists:proplist()) -> +-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> success | failiure | {error, closed}. --spec ptty_alloc(pid(), channel_id(), proplists:proplist(), timeout()) -> +-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> success | failiure | {error, timeout} | {error, closed}. %% diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 52dea5d183..395be6b220 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -58,8 +58,6 @@ -type option_declarations() :: #{ {option_key(),def} := option_declaration() }. --type role() :: client | server . - -type error() :: {error,{eoptions,any()}} . %%%================================================================ -- cgit v1.2.3 From 931df53bc431c47140620864b04d4622f9e41421 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 23 Feb 2017 17:53:46 +0100 Subject: ssh: Simplify calling of user's callback funs Since the Options now are initialized with a correct fun (that does nothing), we can just call it without tests --- lib/ssh/src/ssh_connection_handler.erl | 45 +++++++++------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 706b68d78b..b9c643c77e 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1769,47 +1769,24 @@ get_repl(X, Acc) -> exit({get_repl,X,Acc}). %%%---------------------------------------------------------------- -disconnect_fun({disconnect,Msg}, D) -> - disconnect_fun(Msg, D); -disconnect_fun(Reason, #data{opts=Opts}) -> - case ?GET_OPT(disconnectfun, Opts) of - undefined -> - ok; - Fun -> - catch Fun(Reason) - end. - -unexpected_fun(UnexpectedMessage, #data{opts = Opts, - ssh_params = #ssh{peer = {_,Peer} } - } ) -> - case ?GET_OPT(unexpectedfun, Opts) of - undefined -> - report; - Fun -> - catch Fun(UnexpectedMessage, Peer) - end. +-define(CALL_FUN(Key,D), catch (?GET_OPT(Key, D#data.opts)) ). + +disconnect_fun({disconnect,Msg}, D) -> ?CALL_FUN(disconnectfun,D)(Msg); +disconnect_fun(Reason, D) -> ?CALL_FUN(disconnectfun,D)(Reason). +unexpected_fun(UnexpectedMessage, #data{ssh_params = #ssh{peer = {_,Peer} }} = D) -> + ?CALL_FUN(unexpectedfun,D)(UnexpectedMessage, Peer). debug_fun(#ssh_msg_debug{always_display = Display, message = DbgMsg, language = Lang}, - #data{opts = Opts}) -> - case ?GET_OPT(ssh_msg_debug_fun, Opts) of - undefined -> - ok; - Fun -> - catch Fun(self(), Display, DbgMsg, Lang) - end. + D) -> + ?CALL_FUN(ssh_msg_debug_fun,D)(self(), Display, DbgMsg, Lang). -connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}, - opts = Opts}) -> - case ?GET_OPT(connectfun, Opts) of - undefined -> - ok; - Fun -> - catch Fun(User, Peer, Method) - end. +connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}} = D) -> + ?CALL_FUN(connectfun,D)(User, Peer, Method). + retry_fun(_, undefined, _) -> ok; -- cgit v1.2.3 From 6fb1523e86bc3561f3fd397b3b9cbad5e3ca90a0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 8 Mar 2017 16:45:08 +0100 Subject: ssh: Update .app file --- lib/ssh/src/ssh.app.src | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 2bb7491b0c..7859ab4064 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -41,10 +41,10 @@ {env, []}, {mod, {ssh_app, []}}, {runtime_dependencies, [ - "crypto-3.3", + "crypto-3.7.3", "erts-6.0", "kernel-3.0", - "public_key-1.1", - "stdlib-3.1" + "public_key-1.4", + "stdlib-3.3" ]}]}. -- cgit v1.2.3 From 1af346afd2d8cabc48bf9673dc62672e36b7b8a2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 6 Mar 2017 16:24:03 +0100 Subject: ssh: Host and Profile info returned in ssh:daemon_info --- lib/ssh/src/ssh.erl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 53aba14458..e2a289d737 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -200,11 +200,13 @@ daemon(Host0, Port, UserOptions0) -> daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> - [Port] = - [Prt || {{ssh_acceptor_sup,any,Prt,default}, + [{ListenAddr,Port,Profile}] = + [{LA,Prt,Prf} || {{ssh_acceptor_sup,LA,Prt,Prf}, _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)], - {ok, [{port,Port}]}; - + {ok, [{port,Port}, + {listen_address,ListenAddr}, + {profile,Profile} + ]}; _ -> {error,bad_daemon_ref} end. -- cgit v1.2.3 From 26c3cd82529836cb5b6eefbf7f92f318fd91f847 Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Fri, 10 Mar 2017 15:00:46 +0100 Subject: Update copyright year --- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh_cli.erl | 2 +- lib/ssh/src/ssh_connection_handler.erl | 2 +- lib/ssh/src/ssh_dbg.erl | 2 +- lib/ssh/src/ssh_sftpd.erl | 2 +- lib/ssh/src/ssh_sftpd_file_api.erl | 2 +- lib/ssh/src/ssh_transport.erl | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 68d98d3875..1f3f77a4e4 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -1,7 +1,7 @@ % %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 6f8c050486..7c7b9e7922 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4496c657c3..fc75945a5b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 0345bbdea7..251741da7e 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 9352046795..fb680fe11c 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl index e444e52ac0..81f181f1fc 100644 --- a/lib/ssh/src/ssh_sftpd_file_api.erl +++ b/lib/ssh/src/ssh_sftpd_file_api.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index a7cc4cd52c..9bebaf2d9b 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From a4edbd619c624d2ca2f343da19a88fba74470e93 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 17 Mar 2017 13:23:42 +0100 Subject: ssh: remove from code --- lib/ssh/src/ssh_options.erl | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 395be6b220..a882a01eaf 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -200,17 +200,6 @@ save({K,V}, _, _) when K == reuseaddr ; save({allow_user_interaction,V}, Opts, Vals) -> save({user_interaction,V}, Opts, Vals); -save({public_key_alg,V}, Defs, Vals) -> % To remove in OTP-20 - New = case V of - 'ssh-rsa' -> ['ssh-rsa', 'ssh-dss']; - ssh_rsa -> ['ssh-rsa', 'ssh-dss']; - 'ssh-dss' -> ['ssh-dss', 'ssh-rsa']; - ssh_dsa -> ['ssh-dss', 'ssh-rsa']; - _ -> error({eoptions, {public_key_alg,V}, - "Unknown algorithm, try pref_public_key_algs instead"}) - end, - save({pref_public_key_algs,New}, Defs, Vals); - %% Special case for socket options 'inet' and 'inet6' save(Inet, Defs, OptMap) when Inet==inet ; Inet==inet6 -> save({inet,Inet}, Defs, OptMap); -- cgit v1.2.3 From 7f5d5119d59e1741aac6b622880dbc2f08b394de Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 24 Mar 2017 16:02:15 +0100 Subject: ssh: fixed crash in ssh:daemon_info --- lib/ssh/src/ssh.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 1f3f77a4e4..290525cec0 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -175,7 +175,7 @@ daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> [Port] = - [Prt || {{ssh_acceptor_sup,any,Prt,default}, + [Prt || {{ssh_acceptor_sup,_,Prt,_}, _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)], {ok, [{port,Port}]}; -- cgit v1.2.3 From 1cc5affef1dee69e5cb4b7d4aca0465e1072d4d9 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 30 Mar 2017 17:17:34 +0200 Subject: ssh: idle_timer on daemon - implementation --- lib/ssh/src/ssh_connection_handler.erl | 8 +++++++- lib/ssh/src/ssh_options.erl | 12 ++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index b9c643c77e..15a05a1b85 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -400,7 +400,9 @@ init_process_state(Role, Socket, Opts) -> timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), cache_init_idle_timer(D); server -> - D#data{connection_state = init_connection(Role, C, Opts)} + cache_init_idle_timer( + D#data{connection_state = init_connection(Role, C, Opts)} + ) end. @@ -919,6 +921,9 @@ handle_event(internal, Msg=#ssh_msg_channel_extended_data{}, StateName, D) - handle_event(internal, Msg=#ssh_msg_channel_eof{}, StateName, D) -> handle_connection_msg(Msg, StateName, D); +handle_event(internal, Msg=#ssh_msg_channel_close{}, {connected,server} = StateName, D) -> + handle_connection_msg(Msg, StateName, cache_request_idle_timer_check(D)); + handle_event(internal, Msg=#ssh_msg_channel_close{}, StateName, D) -> handle_connection_msg(Msg, StateName, D); @@ -1280,6 +1285,7 @@ handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> {stop, {shutdown, Reason}}; handle_event(info, check_cache, _, D) -> +ct:pal("check_cache",[]), {keep_state, cache_check_set_idle_timer(D)}; handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index a882a01eaf..55f9c6bdc8 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -490,12 +490,6 @@ default(client) -> class => user_options }, - {idle_time, def} => - #{default => infinity, - chk => fun check_timeout/1, - class => user_options - }, - %%%%% Undocumented {keyboard_interact_fun, def} => #{default => undefined, @@ -548,6 +542,12 @@ default(common) -> class => user_options }, + {idle_time, def} => + #{default => infinity, + chk => fun check_timeout/1, + class => user_options + }, + %% This is a "SocketOption"... %% {fd, def} => %% #{default => undefined, -- cgit v1.2.3 From 9d174dd62112ec5129b66ffafc1d99650f037d54 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 6 Apr 2017 15:50:38 +0200 Subject: ssh: remove log printout --- lib/ssh/src/ssh_connection_handler.erl | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 15a05a1b85..5a13209ae3 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1285,7 +1285,6 @@ handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> {stop, {shutdown, Reason}}; handle_event(info, check_cache, _, D) -> -ct:pal("check_cache",[]), {keep_state, cache_check_set_idle_timer(D)}; handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> -- cgit v1.2.3 From 7b21b7b5fa0b6da173080bc8322e99eead905191 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 10 Mar 2017 12:56:33 +0100 Subject: ssh: Bug fix when calling ssh_io:yes_no This was introduced by the new option handling in commit 89a829f32d855610b0bc0c3ea53e7c05454b7a24 --- lib/ssh/src/ssh_transport.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 02c995399a..5d896e02a2 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -201,7 +201,7 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm, Mac == mac(Algorithm, Key, SeqNum, Data). yes_no(Ssh, Prompt) -> - (Ssh#ssh.io_cb):yes_no(Prompt, Ssh). + (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts). format_version({Major,Minor}, SoftwareVersion) -> "SSH-" ++ integer_to_list(Major) ++ "." ++ -- cgit v1.2.3 From 7925b59450dd6f34b756da7b10dd10af95304d94 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 28 Feb 2017 12:19:37 +0100 Subject: ssh: Option pruning --- lib/ssh/src/ssh.erl | 3 +-- lib/ssh/src/ssh_acceptor.erl | 1 - lib/ssh/src/ssh_system_sup.erl | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 369a00ac40..c1be9f732d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -368,8 +368,7 @@ do_start_daemon(Socket, Options) -> _:_ -> throw(bad_socket) end, Host = fmt_host(IP), - Opts = ?PUT_INTERNAL_OPT([{asocket, Socket}, - {asock_owner,self()}, + Opts = ?PUT_INTERNAL_OPT([{connected_socket, Socket}, {address, Host}, {port, Port}, {role, server}], Options), diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 42be18f2ad..4943f062b4 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -66,7 +66,6 @@ acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) -> of {ok, ListenSocket} -> proc_lib:init_ack(Parent, {ok, self()}), - {_, Callback, _} = ?GET_OPT(transport, Opts), acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout); {error,Error} -> diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index b0bbd3aae5..5a58ef1c44 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -131,7 +131,7 @@ init([Options]) -> RestartStrategy = one_for_one, MaxR = 0, MaxT = 3600, - Children = case ?GET_INTERNAL_OPT(asocket,Options,undefined) of + Children = case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of undefined -> child_specs(Options); _ -> [] end, -- cgit v1.2.3 From ccef9f6e379a2cee828a9b914a49a4ebc831f936 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 2 Mar 2017 20:02:50 +0100 Subject: ssh: Make an internal option delete function --- lib/ssh/src/ssh.hrl | 4 ++++ lib/ssh/src/ssh_options.erl | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index c1ba58ed40..63eeb0bd0a 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -89,6 +89,10 @@ -define(PUT_INTERNAL_OPT(KeyVal,Opts), ?do_put_opt(internal_options,KeyVal,Opts) ). -define(PUT_SOCKET_OPT(KeyVal,Opts), ?do_put_opt(socket_options, KeyVal,Opts) ). +-define(do_del_opt(C,K,O), ssh_options:delete_key(C,K,O, ?MODULE,?LINE)). +-define(DELETE_INTERNAL_OPT(Key,Opts), ?do_del_opt(internal_options,Key,Opts) ). + + %% Types -type role() :: client | server . -type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} . diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 55f9c6bdc8..512aefa76d 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -28,6 +28,7 @@ -export([default/1, get_value/5, get_value/6, put_value/5, + delete_key/5, handle_options/2 ]). @@ -75,7 +76,6 @@ get_value(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> user_options -> maps:get(Key, Opts) end; get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> - io:format("*** Bad Opts GET OPT ~p ~p:~p Key=~p,~n Opts=~p~n",[Class,_CallerMod,_CallerLine,Key,Opts]), error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). @@ -90,7 +90,6 @@ get_value(Class, Key, Opts, Def, CallerMod, CallerLine) when is_map(Opts) -> error:{badkey,Key} -> Def end; get_value(Class, Key, Opts, _Def, _CallerMod, _CallerLine) -> - io:format("*** Bad Opts GET OPT ~p ~p:~p Key=~p,~n Opts=~p~n",[Class,_CallerMod,_CallerLine,Key,Opts]), error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). @@ -134,6 +133,19 @@ put_socket_value({Key,Value}, SockOpts) -> put_socket_value(A, SockOpts) when is_atom(A) -> [A | SockOpts]. +%%%================================================================ +%%% +%%% Delete an option +%%% + +-spec delete_key(option_class(), option_key(), options(), + atom(), non_neg_integer()) -> options(). + +delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> + InternalOpts = maps:get(internal_options,Opts), + Opts#{internal_options := maps:remove(Key, InternalOpts)}. + + %%%================================================================ %%% %%% Initialize the options -- cgit v1.2.3 From 2f212c1a3e8bc3070b51dfc5607f30e501ba24ea Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 10 Mar 2017 13:08:08 +0100 Subject: ssh: option 'silently_accept_hosts' reworked New (yet) undocumented option value {false,Alg} where Alg :: md5 | sha | sha224 | sha256 | sha384 | sha512 This option includes the fingerprint value in the accept question to the user. The fingerprint is calculated with the Alg provided --- lib/ssh/src/ssh_options.erl | 21 ++++++++++----------- lib/ssh/src/ssh_transport.erl | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 20 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 512aefa76d..6a2e7ce696 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -431,7 +431,7 @@ default(client) -> }, {silently_accept_hosts, def} => - #{default => false, + #{default => {false,none}, chk => fun check_silently_accept_hosts/1, class => user_options }, @@ -804,18 +804,17 @@ read_moduli_file(D, I, Acc) -> check_silently_accept_hosts(B) when is_boolean(B) -> true; check_silently_accept_hosts(F) when is_function(F,2) -> true; -check_silently_accept_hosts({S,F}) when is_atom(S), - is_function(F,2) -> - lists:member(S, ?SHAs) andalso - lists:member(S, proplists:get_value(hashs,crypto:supports())); -check_silently_accept_hosts({L,F}) when is_list(L), - is_function(F,2) -> - lists:all(fun(S) -> - lists:member(S, ?SHAs) andalso - lists:member(S, proplists:get_value(hashs,crypto:supports())) - end, L); +check_silently_accept_hosts({false,S}) when is_atom(S) -> valid_hash(S); +check_silently_accept_hosts({S,F}) when is_function(F,2) -> valid_hash(S); check_silently_accept_hosts(_) -> false. + +valid_hash(S) -> valid_hash(S, proplists:get_value(hashs,crypto:supports())). + +valid_hash(S, Ss) when is_atom(S) -> lists:member(S, ?SHAs) andalso lists:member(S, Ss); +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) -> try alg_duplicates(Algs, [], []) diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 5d896e02a2..54ea80c727 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -200,9 +200,6 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm, recv_mac_key = Key, recv_sequence = SeqNum}) -> Mac == mac(Algorithm, Key, SeqNum, Data). -yes_no(Ssh, Prompt) -> - (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts). - format_version({Major,Minor}, SoftwareVersion) -> "SSH-" ++ integer_to_list(Major) ++ "." ++ integer_to_list(Minor) ++ "-" ++ SoftwareVersion. @@ -755,16 +752,44 @@ public_algo({#'ECPoint'{},{namedCurve,OID}}) -> accepted_host(Ssh, PeerName, Public, Opts) -> case ?GET_OPT(silently_accept_hosts, Opts) of - F when is_function(F,2) -> + + %% Original option values; User question and no host key fingerprints known. + %% Keep the original question unchanged: + false -> yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept"); + true -> true; + + %% Variant: User question but with host key fingerprint in the question: + {false,Alg} -> + HostKeyAlg = (Ssh#ssh.algorithms)#alg.hkey, + Prompt = io_lib:format("The authenticity of the host can't be established.~n" + "~s host key fingerprint is ~s.~n" + "New host ~p accept", + [fmt_hostkey(HostKeyAlg), + public_key:ssh_hostkey_fingerprint(Alg,Public), + PeerName]), + yes == yes_no(Ssh, Prompt); + + %% 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))); + {DigestAlg,F} when is_function(F,2) -> - true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))); - true -> - true; - false -> - yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") + true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))) + end. + +yes_no(Ssh, Prompt) -> + (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts). + + +fmt_hostkey('ssh-rsa') -> "RSA"; +fmt_hostkey('ssh-dss') -> "DSA"; +fmt_hostkey(A) when is_atom(A) -> fmt_hostkey(atom_to_list(A)); +fmt_hostkey("ecdsa"++_) -> "ECDSA"; +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), -- cgit v1.2.3 From 6ff33b1548a24d9f195c27a1ee5bcfcdb1b892d8 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 16 Mar 2017 14:42:30 +0100 Subject: ssh: enable 'none' as a secret accepted value in negotiation This is for testing only to disable e.g. encryption/decryption is measurements. The value must be explicitly enabled like {preferred_algorithms,[{cipher,[none]}]} --- lib/ssh/src/ssh_options.erl | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6a2e7ce696..cb3f63103c 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -882,6 +882,7 @@ handle_pref_alg(Key, Vs, _) -> 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") end. -- cgit v1.2.3 From da7902412f1e77b8241c0bacbeac2d6013e8f345 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 2 Mar 2017 16:37:54 +0100 Subject: ssh: Unified way of starting listening sockets --- lib/ssh/src/ssh.erl | 215 ++++++++++++++++++------------------------- lib/ssh/src/ssh_acceptor.erl | 106 ++++++++++----------- 2 files changed, 136 insertions(+), 185 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index c1be9f732d..c139556791 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -132,13 +132,12 @@ connect(Host, Port, UserOptions, Timeout) when is_integer(Port), SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)], try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> - Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error, Reason} -> {error, Reason} catch exit:{function_clause, _F} -> - io:format('function_clause ~p~n',[_F]), {error, {options, {transport, TransportOpts}}}; exit:badarg -> {error, {options, {socket_options, SocketOpts}}} @@ -311,16 +310,15 @@ handle_daemon_args(Host, UserOptions0) -> %%%---------------------------------------------------------------- valid_socket_to_use(Socket, {tcp,_,_}) -> %% Is this tcp-socket a valid socket? - case {is_tcp_socket(Socket), - {ok,[{active,false}]} == inet:getopts(Socket, [active]) - } + try {is_tcp_socket(Socket), + {ok,[{active,false}]} == inet:getopts(Socket, [active]) + } of - {true, true} -> - ok; - {true, false} -> - {error, not_passive_mode}; - _ -> - {error, not_tcp_socket} + {true, true} -> ok; + {true, false} -> {error, not_passive_mode}; + _ -> {error, not_tcp_socket} + catch + _:_ -> {error, bad_socket} end; valid_socket_to_use(_, {L4,_,_}) -> @@ -340,13 +338,7 @@ start_daemon(_, _, {error,Error}) -> start_daemon(socket, Socket, Options) -> case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> - try - do_start_daemon(Socket, Options) - catch - throw:bad_fd -> {error,bad_fd}; - throw:bad_socket -> {error,bad_socket}; - _C:_E -> {error,{cannot_start_daemon,_C,_E}} - end; + start_daemon(inet:sockname(Socket), Socket, Options); {error,SockError} -> {error,SockError} end; @@ -355,136 +347,107 @@ start_daemon(Host, Port, Options) -> try do_start_daemon(Host, Port, Options) catch - throw:bad_fd -> {error,bad_fd}; - throw:bad_socket -> {error,bad_socket}; - _C:_E -> {error,{cannot_start_daemon,_C,_E}} + throw:bad_fd -> + {error,bad_fd}; + throw:bad_socket -> + {error,bad_socket}; + error:{badmatch,{error,Error}} -> + {error,Error}; + _C:_E -> + {error,{cannot_start_daemon,_C,_E}} end. +%%%---------------------------------------------------------------- +do_start_daemon({error,Error}, _, _) -> + {error,Error}; -do_start_daemon(Socket, Options) -> - {ok, {IP,Port}} = - try {ok,_} = inet:sockname(Socket) - catch - _:_ -> throw(bad_socket) - end, - Host = fmt_host(IP), - Opts = ?PUT_INTERNAL_OPT([{connected_socket, Socket}, - {address, Host}, - {port, Port}, - {role, server}], Options), - - Profile = ?GET_OPT(profile, Options), - case ssh_system_sup:system_supervisor(Host, Port, Profile) of - undefined -> - try sshd_sup:start_child(Opts) of - {error, {already_started, _}} -> - {error, eaddrinuse}; - Result = {ok,_} -> - call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, Result); - Result = {error, _} -> - Result - catch - exit:{noproc, _} -> - {error, ssh_not_started} - end; - Sup -> - AccPid = ssh_system_sup:acceptor_supervisor(Sup), - case ssh_acceptor_sup:start_child(AccPid, Opts) of - {error, {already_started, _}} -> - {error, eaddrinuse}; - {ok, _} -> - call_ssh_acceptor_handle_connection(Host, Port, Opts, Socket, {ok,Sup}); - Other -> - Other - end - end. +do_start_daemon({ok, {IP,Port}}, Socket, Options0) -> + finalize_start(fmt_host(IP), + Port, + ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options0), + fun(Opts, DefaultResult) -> + try ssh_acceptor:handle_established_connection( + ?GET_INTERNAL_OPT(address, Opts), + ?GET_INTERNAL_OPT(port, Opts), + Opts, + Socket) + of + {error,Error} -> + {error,Error}; + _ -> + DefaultResult + catch + C:R -> + {error,{could_not_start_connection,{C,R}}} + end + end); do_start_daemon(Host0, Port0, Options0) -> - {Host,Port1} = - try - case ?GET_SOCKET_OPT(fd, Options0) of - undefined -> - {Host0,Port0}; - Fd when Port0==0 -> - find_hostport(Fd) - end - catch - _:_ -> throw(bad_fd) - end, - {Port, WaitRequestControl, Options1} = - case Port1 of - 0 -> %% Allocate the socket here to get the port number... - {ok,LSock} = ssh_acceptor:callback_listen(0, Options0), - {ok,{_,LPort}} = inet:sockname(LSock), - {LPort, - LSock, - ?PUT_INTERNAL_OPT({lsocket,{LSock,self()}}, Options0) - }; - _ -> - {Port1, false, Options0} - end, + {{Host,Port}, ListenSocket} = + open_listen_socket(Host0, Port0, Options0), + + %% Now Host,Port is what to use for the supervisor to register its name, + %% and ListenSocket is for listening on connections. But it is still owned + %% by self()... + + finalize_start(Host, Port, + ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), + fun(Opts, Result) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + receive + {request_control, ListenSocket, ReqPid} -> + ok = Callback:controlling_process(ListenSocket, ReqPid), + ReqPid ! {its_yours,ListenSocket}, + Result + end + end). + + +open_listen_socket(Host0, Port0, Options0) -> + case ?GET_SOCKET_OPT(fd, Options0) of + undefined -> + {ok,LSock} = ssh_acceptor:listen(Port0, Options0), + {ok,{_,LPort}} = inet:sockname(LSock), + {{fmt_host(Host0),LPort}, LSock}; + + Fd when is_integer(Fd) -> + %% Do gen_tcp:listen with the option {fd,Fd}: + {ok,LSock} = ssh_acceptor:listen(0, Options0), + {ok,{LHost,LPort}} = inet:sockname(LSock), + {{fmt_host(LHost),LPort}, LSock} + end. + +%%%---------------------------------------------------------------- +finalize_start(Host, Port, Options0, F) -> Options = ?PUT_INTERNAL_OPT([{address, Host}, {port, Port}, - {role, server}], Options1), - Profile = ?GET_OPT(profile, Options0), + {role, server}], Options0), + Profile = ?GET_OPT(profile, Options), case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> try sshd_sup:start_child(Options) of {error, {already_started, _}} -> {error, eaddrinuse}; + {error, Error} -> + {error, Error}; Result = {ok,_} -> - sync_request_control(WaitRequestControl, Options), - Result; - Result = {error, _} -> - Result + F(Options, Result) catch exit:{noproc, _} -> {error, ssh_not_started} end; - Sup -> + Sup -> AccPid = ssh_system_sup:acceptor_supervisor(Sup), case ssh_acceptor_sup:start_child(AccPid, Options) of {error, {already_started, _}} -> {error, eaddrinuse}; + {error, Error} -> + {error, Error}; {ok, _} -> - sync_request_control(WaitRequestControl, Options), - {ok, Sup}; - Other -> - Other + F(Options, {ok,Sup}) end end. -call_ssh_acceptor_handle_connection(Host, Port, Options, Socket, DefaultResult) -> - {_, Callback, _} = ?GET_OPT(transport, Options), - try ssh_acceptor:handle_connection(Callback, Host, Port, Options, Socket) - of - {error,Error} -> {error,Error}; - _ -> DefaultResult - catch - C:R -> {error,{could_not_start_connection,{C,R}}} - end. - - -sync_request_control(false, _Options) -> - ok; -sync_request_control(LSock, Options) -> - {_, Callback, _} = ?GET_OPT(transport, Options), - receive - {request_control,LSock,ReqPid} -> - ok = Callback:controlling_process(LSock, ReqPid), - ReqPid ! {its_yours,LSock}, - ok - end. - -find_hostport(Fd) -> - %% Using internal functions inet:open/8 and inet:close/0. - %% Don't try this at home unless you know what you are doing! - {ok,S} = inet:open(Fd, {0,0,0,0}, 0, [], tcp, inet, stream, inet_tcp), - {ok, HostPort} = inet:sockname(S), - ok = inet:close(S), - HostPort. - -fmt_host({A,B,C,D}) -> - lists:concat([A,".",B,".",C,".",D]); -fmt_host(T={_,_,_,_,_,_,_,_}) -> - lists:flatten(string:join([io_lib:format("~.16B",[A]) || A <- tuple_to_list(T)], ":")). +%%%---------------------------------------------------------------- +fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); +fmt_host(Str) when is_list(Str) -> Str. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 4943f062b4..f9e2280212 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -27,8 +27,8 @@ %% Internal application API -export([start_link/4, number_of_connections/1, - callback_listen/2, - handle_connection/5]). + listen/2, + handle_established_connection/4]). %% spawn export -export([acceptor_init/5, acceptor_loop/6]). @@ -42,40 +42,57 @@ start_link(Port, Address, Options, AcceptTimeout) -> Args = [self(), Port, Address, Options, AcceptTimeout], proc_lib:start_link(?MODULE, acceptor_init, Args). +%%%---------------------------------------------------------------- +number_of_connections(SystemSup) -> + length([X || + {R,X,supervisor,[ssh_subsystem_sup]} <- supervisor:which_children(SystemSup), + is_pid(X), + is_reference(R) + ]). + +%%%---------------------------------------------------------------- +listen(Port, Options) -> + {_, Callback, _} = ?GET_OPT(transport, Options), + SockOpts = [{active, false}, {reuseaddr,true} | ?GET_OPT(socket_options, Options)], + case Callback:listen(Port, SockOpts) of + {error, nxdomain} -> + Callback:listen(Port, lists:delete(inet6, SockOpts)); + {error, enetunreach} -> + Callback:listen(Port, lists:delete(inet6, SockOpts)); + {error, eafnosupport} -> + Callback:listen(Port, lists:delete(inet6, SockOpts)); + Other -> + Other + end. + +%%%---------------------------------------------------------------- +handle_established_connection(Address, Port, Options, Socket) -> + {_, Callback, _} = ?GET_OPT(transport, Options), + handle_connection(Callback, Address, Port, Options, Socket). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) -> - {_, Callback, _} = ?GET_OPT(transport, Opts), try - {LSock0,SockOwner0} = ?GET_INTERNAL_OPT(lsocket, Opts), - true = is_pid(SockOwner0), - {ok,{_,Port}} = inet:sockname(LSock0), - {LSock0, SockOwner0} + ?GET_INTERNAL_OPT(lsocket, Opts) of {LSock, SockOwner} -> - %% Use existing socket - proc_lib:init_ack(Parent, {ok, self()}), - request_ownership(LSock, SockOwner), - acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout) - catch - error:{badkey,lsocket} -> - %% Open new socket - try - socket_listen(Port, Opts) - of - {ok, ListenSocket} -> + case inet:sockname(LSock) of + {ok,{_,Port}} -> % A usable, open LSock proc_lib:init_ack(Parent, {ok, self()}), - acceptor_loop(Callback, - Port, Address, Opts, ListenSocket, AcceptTimeout); - {error,Error} -> - proc_lib:init_ack(Parent, Error), - {error,Error} - catch - _:_ -> - {error,listen_socket_failed} - end; + request_ownership(LSock, SockOwner), + {_, Callback, _} = ?GET_OPT(transport, Opts), + acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout); + {error,_} -> % Not open, a restart + {ok,NewLSock} = listen(Port, Opts), + proc_lib:init_ack(Parent, {ok, self()}), + Opts1 = ?DELETE_INTERNAL_OPT(lsocket, Opts), + {_, Callback, _} = ?GET_OPT(transport, Opts1), + acceptor_loop(Callback, Port, Address, Opts1, NewLSock, AcceptTimeout) + end + catch _:_ -> {error,use_existing_socket_failed} end. @@ -87,30 +104,7 @@ request_ownership(LSock, SockOwner) -> {its_yours,LSock} -> ok end. - -socket_listen(Port0, Opts) -> - Port = case ?GET_SOCKET_OPT(fd, Opts) of - undefined -> Port0; - _ -> 0 - end, - callback_listen(Port, Opts). - - -callback_listen(Port, Opts0) -> - {_, Callback, _} = ?GET_OPT(transport, Opts0), - Opts = ?PUT_SOCKET_OPT([{active, false}, {reuseaddr,true}], Opts0), - SockOpts = ?GET_OPT(socket_options, Opts), - case Callback:listen(Port, SockOpts) of - {error, nxdomain} -> - Callback:listen(Port, lists:delete(inet6, SockOpts)); - {error, enetunreach} -> - Callback:listen(Port, lists:delete(inet6, SockOpts)); - {error, eafnosupport} -> - Callback:listen(Port, lists:delete(inet6, SockOpts)); - Other -> - Other - end. - +%%%---------------------------------------------------------------- acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) -> case (catch Callback:accept(ListenSocket, AcceptTimeout)) of {ok, Socket} -> @@ -127,6 +121,7 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) -> ListenSocket, AcceptTimeout) end. +%%%---------------------------------------------------------------- handle_connection(Callback, Address, Port, Options, Socket) -> Profile = ?GET_OPT(profile, Options), SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile), @@ -158,7 +153,7 @@ handle_connection(Callback, Address, Port, Options, Socket) -> {error,max_sessions} end. - +%%%---------------------------------------------------------------- handle_error(timeout) -> ok; @@ -185,10 +180,3 @@ handle_error(Reason) -> error_logger:error_report(String), exit({accept_failed, String}). - -number_of_connections(SystemSup) -> - length([X || - {R,X,supervisor,[ssh_subsystem_sup]} <- supervisor:which_children(SystemSup), - is_pid(X), - is_reference(R) - ]). -- cgit v1.2.3 From e20ce5b9174e5ac0e1279a1af5be80f9c1b35caa Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 15:21:46 +0100 Subject: ssh: handle HostAddr arg and ip-option for daemons --- lib/ssh/src/ssh.erl | 221 +++++++++++++++++++++++++++++----------------------- 1 file changed, 124 insertions(+), 97 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index c139556791..ff424b738c 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -182,16 +182,86 @@ daemon(Port) -> daemon(Port, []). -daemon(Port, UserOptions) when is_integer(Port), Port >= 0 -> - daemon(any, Port, UserOptions); - daemon(Socket, UserOptions) when is_port(Socket) -> - daemon(socket, Socket, UserOptions). + try + #{} = Options = ssh_options:handle_options(server, UserOptions), + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of + ok -> + {ok, {IP,Port}} = inet:sockname(Socket), + finalize_start(fmt_host(IP), Port, ?GET_OPT(profile, Options), + ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options), + fun(Opts, DefaultResult) -> + try ssh_acceptor:handle_established_connection( + ?GET_INTERNAL_OPT(address, Opts), + ?GET_INTERNAL_OPT(port, Opts), + Opts, + Socket) + of + {error,Error} -> + {error,Error}; + _ -> + DefaultResult + catch + C:R -> + {error,{could_not_start_connection,{C,R}}} + end + end); + {error,SockError} -> + {error,SockError} + end + catch + throw:bad_fd -> + {error,bad_fd}; + throw:bad_socket -> + {error,bad_socket}; + error:{badmatch,{error,Error}} -> + {error,Error}; + error:Error -> + {error,Error}; + _C:_E -> + {error,{cannot_start_daemon,_C,_E}} + end; + +daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 -> + daemon(any, Port, UserOptions). + +daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> + try + {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), + #{} = Options0 = ssh_options:handle_options(server, UserOptions), + + {{Host,Port}, ListenSocket} = + open_listen_socket(Host1, Port0, Options0), + + %% Now Host,Port is what to use for the supervisor to register its name, + %% and ListenSocket is for listening on connections. But it is still owned + %% by self()... + + finalize_start(fmt_host(Host), Port, ?GET_OPT(profile, Options0), + ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), + fun(Opts, Result) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + receive + {request_control, ListenSocket, ReqPid} -> + ok = Callback:controlling_process(ListenSocket, ReqPid), + ReqPid ! {its_yours,ListenSocket}, + Result + end + end) + catch + throw:bad_fd -> + {error,bad_fd}; + throw:bad_socket -> + {error,bad_socket}; + error:{badmatch,{error,Error}} -> + {error,Error}; + error:Error -> + {error,Error}; + _C:_E -> + {error,{cannot_start_daemon,_C,_E}} + end. -daemon(Host0, Port, UserOptions0) -> - {Host, UserOptions} = handle_daemon_args(Host0, UserOptions0), - start_daemon(Host, Port, ssh_options:handle_options(server, UserOptions)). %%-------------------------------------------------------------------- -spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ). @@ -291,21 +361,49 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -handle_daemon_args(Host, UserOptions0) -> - case Host of - socket -> - {Host, UserOptions0}; - any -> - {ok, Host0} = inet:gethostname(), - Inet = proplists:get_value(inet, UserOptions0, inet), - {Host0, [Inet | UserOptions0]}; - {_,_,_,_} -> - {Host, [inet, {ip,Host} | UserOptions0]}; - {_,_,_,_,_,_,_,_} -> - {Host, [inet6, {ip,Host} | UserOptions0]}; - _ -> - error(badarg) - end. +handle_daemon_args(HostAddr, Opts) -> + IP = proplists:get_value(ip, Opts), + IPh = case inet:parse_strict_address(HostAddr) of + {ok, IPtuple} -> IPtuple; + {error, einval} when is_tuple(HostAddr), + size(HostAddr)==4 ; size(HostAddr)==6 -> HostAddr; + _ -> undefined + end, + handle_daemon_args(HostAddr, IPh, IP, Opts). + + +%% HostAddr is 'any' +handle_daemon_args(any, undefined, undefined, Opts) -> {any, Opts}; +handle_daemon_args(any, undefined, IP, Opts) -> {IP, Opts}; + +%% HostAddr is 'loopback' or "localhost" +handle_daemon_args(loopback, undefined, {127,_,_,_}=IP, Opts) -> {IP, Opts}; +handle_daemon_args(loopback, undefined, {0,0,0,0,0,0,0,1}=IP, Opts) -> {IP, Opts}; +handle_daemon_args(loopback, undefined, undefined, Opts) -> + IP = case proplists:get_value(inet,Opts) of + true -> {127,0,0,1}; + inet -> {127,0,0,1}; + inet6 -> {0,0,0,0,0,0,0,1}; + _ -> case proplists:get_value(inet6,Opts) of + true -> {0,0,0,0,0,0,0,1}; + _ -> {127,0,0,1} % default if no 'inet' nor 'inet6' + end + end, + {IP, [{ip,IP}|Opts]}; +handle_daemon_args("localhost", IPh, IP, Opts) -> + handle_daemon_args(loopback, IPh, IP, Opts); + +%% HostAddr is ip and no ip-option +handle_daemon_args(_, IP, undefined, Opts) when is_tuple(IP) -> {IP, [{ip,IP}|Opts]}; + +%% HostAddr and ip-option are equal +handle_daemon_args(_, IP, IP, Opts) when is_tuple(IP) -> {IP, Opts}; + +%% HostAddr is ip, but ip-option is different! +handle_daemon_args(_, IPh, IPo, _) when is_tuple(IPh), is_tuple(IPo) -> error({eoption,{ip,IPo}}); + +%% Something else. Whatever it is, it is wrong. +handle_daemon_args(_, _, _, _) -> error(badarg). %%%---------------------------------------------------------------- valid_socket_to_use(Socket, {tcp,_,_}) -> @@ -332,97 +430,25 @@ is_tcp_socket(Socket) -> end. %%%---------------------------------------------------------------- -start_daemon(_, _, {error,Error}) -> - {error,Error}; - -start_daemon(socket, Socket, Options) -> - case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of - ok -> - start_daemon(inet:sockname(Socket), Socket, Options); - {error,SockError} -> - {error,SockError} - end; - -start_daemon(Host, Port, Options) -> - try - do_start_daemon(Host, Port, Options) - catch - throw:bad_fd -> - {error,bad_fd}; - throw:bad_socket -> - {error,bad_socket}; - error:{badmatch,{error,Error}} -> - {error,Error}; - _C:_E -> - {error,{cannot_start_daemon,_C,_E}} - end. - -%%%---------------------------------------------------------------- -do_start_daemon({error,Error}, _, _) -> - {error,Error}; - -do_start_daemon({ok, {IP,Port}}, Socket, Options0) -> - finalize_start(fmt_host(IP), - Port, - ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options0), - fun(Opts, DefaultResult) -> - try ssh_acceptor:handle_established_connection( - ?GET_INTERNAL_OPT(address, Opts), - ?GET_INTERNAL_OPT(port, Opts), - Opts, - Socket) - of - {error,Error} -> - {error,Error}; - _ -> - DefaultResult - catch - C:R -> - {error,{could_not_start_connection,{C,R}}} - end - end); - -do_start_daemon(Host0, Port0, Options0) -> - {{Host,Port}, ListenSocket} = - open_listen_socket(Host0, Port0, Options0), - - %% Now Host,Port is what to use for the supervisor to register its name, - %% and ListenSocket is for listening on connections. But it is still owned - %% by self()... - - finalize_start(Host, Port, - ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), - fun(Opts, Result) -> - {_, Callback, _} = ?GET_OPT(transport, Opts), - receive - {request_control, ListenSocket, ReqPid} -> - ok = Callback:controlling_process(ListenSocket, ReqPid), - ReqPid ! {its_yours,ListenSocket}, - Result - end - end). - - open_listen_socket(Host0, Port0, Options0) -> case ?GET_SOCKET_OPT(fd, Options0) of undefined -> {ok,LSock} = ssh_acceptor:listen(Port0, Options0), {ok,{_,LPort}} = inet:sockname(LSock), - {{fmt_host(Host0),LPort}, LSock}; + {{Host0,LPort}, LSock}; Fd when is_integer(Fd) -> %% Do gen_tcp:listen with the option {fd,Fd}: {ok,LSock} = ssh_acceptor:listen(0, Options0), {ok,{LHost,LPort}} = inet:sockname(LSock), - {{fmt_host(LHost),LPort}, LSock} + {{LHost,LPort}, LSock} end. %%%---------------------------------------------------------------- -finalize_start(Host, Port, Options0, F) -> +finalize_start(Host, Port, Profile, Options0, F) -> Options = ?PUT_INTERNAL_OPT([{address, Host}, {port, Port}, {role, server}], Options0), - Profile = ?GET_OPT(profile, Options), case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> try sshd_sup:start_child(Options) of @@ -449,5 +475,6 @@ finalize_start(Host, Port, Options0, F) -> end. %%%---------------------------------------------------------------- +fmt_host(any) -> any; fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); fmt_host(Str) when is_list(Str) -> Str. -- cgit v1.2.3 From ee8a5fa4da90016d6f17db2aa9f43bd98ca04985 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 13 Mar 2017 14:54:40 +0100 Subject: ssh: Removed outdated comment in ssh_sftpd.erl --- lib/ssh/src/ssh_sftp.erl | 9 --------- 1 file changed, 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 140856c8e3..f1f7b57e8d 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1063,15 +1063,6 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) -> gid = A#ssh_xfer_attr.group}. -%% Added workaround for sftp timestam problem. (Timestamps should be -%% in UTC but they where not) . The workaround uses a deprecated -%% function i calandar. This will work as expected most of the time -%% but has problems for the same reason as -%% calendar:local_time_to_universal_time/1. We consider it better that -%% the timestamps work as expected most of the time instead of none of -%% the time. Hopfully the file-api will be updated so that we can -%% solve this problem in a better way in the future. - unix_to_datetime(undefined) -> undefined; unix_to_datetime(UTCSecs) -> -- cgit v1.2.3 From f64dc7858c06d3096b47532270d9f6b732aa7ece Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 20 Mar 2017 13:15:34 +0100 Subject: ssh: remove deprecated ssh_sftpd:listen and ssh_sftpd:stop --- lib/ssh/src/ssh_sftpd.erl | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 9352046795..b879116393 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -34,8 +34,7 @@ %%-------------------------------------------------------------------- %% External exports --export([subsystem_spec/1, - listen/1, listen/2, listen/3, stop/1]). +-export([subsystem_spec/1]). -export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). @@ -76,29 +75,6 @@ subsystem_spec(Options) -> {"sftp", {?MODULE, Options}}. -%%% DEPRECATED START %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%-------------------------------------------------------------------- -%% Function: listen() -> Pid | {error,Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- -listen(Port) -> - listen(any, Port, []). -listen(Port, Options) -> - listen(any, Port, Options). -listen(Addr, Port, Options) -> - SubSystems = [subsystem_spec(Options)], - ssh:daemon(Addr, Port, [{subsystems, SubSystems} |Options]). - -%%-------------------------------------------------------------------- -%% Function: stop(Pid) -> ok -%% Description: Stops the listener -%%-------------------------------------------------------------------- -stop(Pid) -> - ssh:stop_listener(Pid). - - -%%% DEPRECATED END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%==================================================================== %% subsystem callbacks -- cgit v1.2.3 From 3bbb2c9d5f92205f91cc68b9cebe263b84afe3e2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 15:20:58 +0100 Subject: ssh: -type and -spec adjustments --- lib/ssh/src/ssh.hrl | 17 +++++++++++++++-- lib/ssh/src/ssh_options.erl | 12 +----------- 2 files changed, 16 insertions(+), 13 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 63eeb0bd0a..e03c15454c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -113,12 +113,25 @@ -type double_algs() :: list( {client2serverlist,simple_algs()} | {server2client,simple_algs()} ) | simple_algs() . +-type options() :: #{socket_options := socket_options(), + internal_options := internal_options(), + option_key() => any() + }. + +-type socket_options() :: proplists:proplist(). +-type internal_options() :: #{option_key() => any()}. + +-type option_key() :: atom(). + + %% Records -record(ssh, { - role, %% client | server - peer, %% string version of peer address + role :: client | role(), + peer :: undefined | + {inet:hostname(), + {inet:ip_adress(),inet:port_number()}}, %% string version of peer address c_vsn, %% client version {Major,Minor} s_vsn, %% server version {Major,Minor} diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index cb3f63103c..febd3f6eef 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -38,16 +38,6 @@ %%%================================================================ %%% Types --type options() :: #{socket_options := socket_options(), - internal_options := internal_options(), - option_key() => any() - }. - --type socket_options() :: proplists:proplist(). --type internal_options() :: #{option_key() => any()}. - --type option_key() :: atom(). - -type option_in() :: proplists:property() | proplists:proplist() . -type option_class() :: internal_options | socket_options | user_options . @@ -431,7 +421,7 @@ default(client) -> }, {silently_accept_hosts, def} => - #{default => {false,none}, + #{default => false, chk => fun check_silently_accept_hosts/1, class => user_options }, -- cgit v1.2.3 From 4d6393bc4df58defbc22c5d97e28bbfdd8794fc6 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 6 Apr 2017 19:31:13 +0200 Subject: ssh: Lazy default in get options macro --- lib/ssh/src/ssh.hrl | 5 ++++- lib/ssh/src/ssh_connection_handler.erl | 10 ++-------- lib/ssh/src/ssh_options.erl | 15 +++++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index e03c15454c..315310f700 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -75,9 +75,12 @@ %% Option access macros -define(do_get_opt(C,K,O), ssh_options:get_value(C,K,O, ?MODULE,?LINE)). --define(do_get_opt(C,K,O,D), ssh_options:get_value(C,K,O,D,?MODULE,?LINE)). +-define(do_get_opt(C,K,O,D), ssh_options:get_value(C,K,O,?LAZY(D),?MODULE,?LINE)). + +-define(LAZY(D), fun()-> D end). -define(GET_OPT(Key,Opts), ?do_get_opt(user_options, Key,Opts ) ). +-define(GET_OPT(Key,Opts,Def), ?do_get_opt(user_options, Key,Opts,Def) ). -define(GET_INTERNAL_OPT(Key,Opts), ?do_get_opt(internal_options,Key,Opts ) ). -define(GET_INTERNAL_OPT(Key,Opts,Def), ?do_get_opt(internal_options,Key,Opts,Def) ). -define(GET_SOCKET_OPT(Key,Opts), ?do_get_opt(socket_options, Key,Opts ) ). diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 5a13209ae3..50a29bbb53 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -413,14 +413,8 @@ init_connection(server, C = #connection{}, Opts) -> SubSystemSup = proplists:get_value(subsystem_sup, Sups), ConnectionSup = proplists:get_value(connection_sup, Sups), - Shell = ?GET_OPT(shell, Opts), - Exec = ?GET_OPT(exec, Opts), - CliSpec = case ?GET_OPT(ssh_cli, Opts) of - undefined -> {ssh_cli, [Shell]}; - Spec -> Spec - end, - C#connection{cli_spec = CliSpec, - exec = Exec, + C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), + exec = ?GET_OPT(exec, Opts), system_supervisor = SystemSup, sub_system_supervisor = SubSystemSup, connection_supervisor = ConnectionSup diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index febd3f6eef..ee3cdbb8a0 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -69,17 +69,20 @@ get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). --spec get_value(option_class(), option_key(), options(), any(), +-spec get_value(option_class(), option_key(), options(), fun(() -> any()), atom(), non_neg_integer()) -> any() | no_return(). -get_value(socket_options, Key, Opts, Def, _CallerMod, _CallerLine) when is_map(Opts) -> - proplists:get_value(Key, maps:get(socket_options,Opts), Def); -get_value(Class, Key, Opts, Def, CallerMod, CallerLine) when is_map(Opts) -> +get_value(socket_options, Key, Opts, DefFun, _CallerMod, _CallerLine) when is_map(Opts) -> + proplists:get_value(Key, maps:get(socket_options,Opts), DefFun); +get_value(Class, Key, Opts, DefFun, CallerMod, CallerLine) when is_map(Opts) -> try get_value(Class, Key, Opts, CallerMod, CallerLine) + of + undefined -> DefFun(); + Value -> Value catch - error:{badkey,Key} -> Def + error:{badkey,Key} -> DefFun() end; -get_value(Class, Key, Opts, _Def, _CallerMod, _CallerLine) -> +get_value(Class, Key, Opts, _DefFun, _CallerMod, _CallerLine) -> error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). -- cgit v1.2.3 From 57d994270d63e7a9ce80eece3c1c3aeca79d3ea4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 15:44:44 +0100 Subject: ssh: fix ssh_system_sup naming of Host-Port-Profile --- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh_acceptor_sup.erl | 7 +------ lib/ssh/src/ssh_system_sup.erl | 16 ++-------------- lib/ssh/src/sshd_sup.erl | 7 +------ 4 files changed, 5 insertions(+), 27 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index ff424b738c..9047b7e0f0 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -475,6 +475,6 @@ finalize_start(Host, Port, Profile, Options0, F) -> end. %%%---------------------------------------------------------------- -fmt_host(any) -> any; +fmt_host(any) -> "any"; fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); fmt_host(Str) when is_list(Str) -> Str. diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 77f7826918..613d8fbc75 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -93,10 +93,5 @@ child_spec(Options) -> {Name, StartFunc, Restart, Shutdown, Type, Modules}. id(Address, Port, Profile) -> - case is_list(Address) of - true -> - {ssh_acceptor_sup, any, Port, Profile}; - false -> - {ssh_acceptor_sup, Address, Port, Profile} - end. + {ssh_acceptor_sup, Address, Port, Profile}. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 5a58ef1c44..4083f666c3 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -166,22 +166,10 @@ ssh_subsystem_child_spec(Options) -> id(Sup, Address, Port, Profile) -> - case is_list(Address) of - true -> - {Sup, any, Port, Profile}; - false -> - {Sup, Address, Port, Profile} - end. + {Sup, Address, Port, Profile}. 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. + list_to_atom(lists:flatten(io_lib:format("ssh_system_~s_~p_~p_sup", [Address, Port, Profile]))). ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) -> Child; diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 14f1937abd..791456839d 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -103,12 +103,7 @@ child_spec(Address, Port, Options) -> {Name, StartFunc, Restart, Shutdown, Type, Modules}. 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. + {server, ssh_system_sup, Address, Port, Profile}. system_name([], _ ) -> undefined; -- cgit v1.2.3 From 6158cb432092c47e178b4dc1177b46cb8c310ab4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 19:50:49 +0100 Subject: ssh: Fix supervisors, start daemon and connect code Remove many internal options and made them as explicit arguments. --- lib/ssh/src/ssh.erl | 240 +++++++++++++++++++++------------ lib/ssh/src/ssh_acceptor.erl | 3 +- lib/ssh/src/ssh_acceptor_sup.erl | 22 ++- lib/ssh/src/ssh_connection_handler.erl | 7 +- lib/ssh/src/ssh_file.erl | 2 + lib/ssh/src/ssh_subsystem_sup.erl | 34 ++--- lib/ssh/src/ssh_sup.erl | 53 +------- lib/ssh/src/ssh_system_sup.erl | 43 +++--- lib/ssh/src/sshc_sup.erl | 14 +- lib/ssh/src/sshd_sup.erl | 55 +++----- 10 files changed, 237 insertions(+), 236 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 9047b7e0f0..680047dffd 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -108,7 +108,7 @@ connect(Socket, UserOptions, Timeout) when is_port(Socket), case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> {ok, {Host,_Port}} = inet:sockname(Socket), - Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options), + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error,SockError} -> {error,SockError} @@ -132,7 +132,7 @@ connect(Host, Port, UserOptions, Timeout) when is_integer(Port), SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)], try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> - Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,fmt_host(Host)}], Options), + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error, Reason} -> {error, Reason} @@ -188,14 +188,11 @@ daemon(Socket, UserOptions) when is_port(Socket) -> case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> {ok, {IP,Port}} = inet:sockname(Socket), - finalize_start(fmt_host(IP), Port, ?GET_OPT(profile, Options), + finalize_start(IP, Port, ?GET_OPT(profile, Options), ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options), fun(Opts, DefaultResult) -> try ssh_acceptor:handle_established_connection( - ?GET_INTERNAL_OPT(address, Opts), - ?GET_INTERNAL_OPT(port, Opts), - Opts, - Socket) + IP, Port, Opts, Socket) of {error,Error} -> {error,Error}; @@ -238,7 +235,7 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> %% and ListenSocket is for listening on connections. But it is still owned %% by self()... - finalize_start(fmt_host(Host), Port, ?GET_OPT(profile, Options0), + finalize_start(Host, Port, ?GET_OPT(profile, Options0), ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), fun(Opts, Result) -> {_, Callback, _} = ?GET_OPT(transport, Opts), @@ -269,17 +266,27 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> - [{ListenAddr,Port,Profile}] = - [{LA,Prt,Prf} || {{ssh_acceptor_sup,LA,Prt,Prf}, - _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)], + [{Name,Port,Profile}] = + [{Nam,Prt,Prf} + || {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]} + <- supervisor:which_children(AsupPid), + Nam <- [case inet:parse_strict_address(Hst) of + {ok,IP} -> IP; + _ when Hst=="any" -> any; + _ when Hst=="loopback" -> loopback; + _ -> Hst + end] + ], {ok, [{port,Port}, - {listen_address,ListenAddr}, + {name,Name}, {profile,Profile} ]}; _ -> {error,bad_daemon_ref} end. + + %%-------------------------------------------------------------------- -spec stop_listener(daemon_ref()) -> ok. -spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. @@ -361,49 +368,128 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -handle_daemon_args(HostAddr, Opts) -> + +%% - if Address is 'any' and no ip-option is present, the name is +%% 'any' and the socket will listen to all addresses +%% +%% - if Address is 'any' and an ip-option is present, the name is +%% set to the value of the ip-option and the socket will listen +%% to that address +%% +%% - if Address is 'loopback' and no ip-option is present, the name +%% is 'loopback' and an loopback address will be choosen by the +%% underlying layers +%% +%% - if Address is 'loopback' and an ip-option is present, the name +%% is set to the value of the ip-option kept and the socket will +%% listen to that address +%% +%% - if Address is an ip-address, that ip-address is the name and +%% the listening address. An ip-option will be discarded. +%% +%% - if Address is a HostName, and that resolves to an ip-address, +%% that ip-address is the name and the listening address. An +%% ip-option will be discarded. +%% +%% - if Address is a string or an atom other than thoose defined +%% above, that Address will be the name and the listening address +%% will be choosen by the lower layers taking an ip-option in +%% consideration +%% + +handle_daemon_args(any, Opts) -> + case proplists:get_value(ip, Opts) of + undefined -> {any, Opts}; + IP -> {IP, Opts} + end; + +handle_daemon_args(loopback, Opts) -> + case proplists:get_value(ip, Opts) of + undefined -> {loopback, [{ip,loopback}|Opts]}; + IP -> {IP, Opts} + end; + +handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) -> + case proplists:get_value(ip, Opts) of + undefined -> {IPaddr, [{ip,IPaddr}|Opts]}; + IPaddr -> {IPaddr, Opts}; + IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility + end; + +handle_daemon_args(Address, Opts) when is_list(Address) ; is_atom(Address) -> IP = proplists:get_value(ip, Opts), - IPh = case inet:parse_strict_address(HostAddr) of - {ok, IPtuple} -> IPtuple; - {error, einval} when is_tuple(HostAddr), - size(HostAddr)==4 ; size(HostAddr)==6 -> HostAddr; - _ -> undefined - end, - handle_daemon_args(HostAddr, IPh, IP, Opts). - - -%% HostAddr is 'any' -handle_daemon_args(any, undefined, undefined, Opts) -> {any, Opts}; -handle_daemon_args(any, undefined, IP, Opts) -> {IP, Opts}; - -%% HostAddr is 'loopback' or "localhost" -handle_daemon_args(loopback, undefined, {127,_,_,_}=IP, Opts) -> {IP, Opts}; -handle_daemon_args(loopback, undefined, {0,0,0,0,0,0,0,1}=IP, Opts) -> {IP, Opts}; -handle_daemon_args(loopback, undefined, undefined, Opts) -> - IP = case proplists:get_value(inet,Opts) of - true -> {127,0,0,1}; - inet -> {127,0,0,1}; - inet6 -> {0,0,0,0,0,0,0,1}; - _ -> case proplists:get_value(inet6,Opts) of - true -> {0,0,0,0,0,0,0,1}; - _ -> {127,0,0,1} % default if no 'inet' nor 'inet6' - end - end, - {IP, [{ip,IP}|Opts]}; -handle_daemon_args("localhost", IPh, IP, Opts) -> - handle_daemon_args(loopback, IPh, IP, Opts); - -%% HostAddr is ip and no ip-option -handle_daemon_args(_, IP, undefined, Opts) when is_tuple(IP) -> {IP, [{ip,IP}|Opts]}; - -%% HostAddr and ip-option are equal -handle_daemon_args(_, IP, IP, Opts) when is_tuple(IP) -> {IP, Opts}; - -%% HostAddr is ip, but ip-option is different! -handle_daemon_args(_, IPh, IPo, _) when is_tuple(IPh), is_tuple(IPo) -> error({eoption,{ip,IPo}}); - -%% Something else. Whatever it is, it is wrong. -handle_daemon_args(_, _, _, _) -> error(badarg). + case inet:parse_strict_address(Address) of + {ok, IP} -> {IP, Opts}; + {ok, OtherIP} -> {OtherIP, [{ip,OtherIP}|Opts--[{ip,IP}]]}; + _ -> + case inet:getaddr(Address, family(Opts)) of + {ok, IP} -> {Address, Opts}; + {ok, OtherIP} -> {Address, [{ip,OtherIP}|Opts--[{ip,IP}]]}; + _ -> {Address, Opts} + end + end. + + +-ifdef(hulahopp). +%% Check the Address parameter and set an ip-option in some cases. The +%% Address parameter is left unchanged because ssh:stop_listener and +%% ssh:stop_daemon needs to find the system supervisor by name + +handle_daemon_args(any, Opts) -> + %% Listen to 0.0.0.0. The caller may have set an ip-option. Trust + %% that one in such a case. + {any, Opts}; + +handle_daemon_args(loopback, Opts) -> + %% Listen to a loopback address. Let the underlying layers decide + %% in case the caller hasn't set the ip-option. + {loopback, ensure_ip_option(loopback,Opts)}; + +handle_daemon_args(IP, Opts) when is_tuple(IP) -> + %% An IP address in Erlang tuple format: + {IP, ensure_ip_option(IP,Opts)}; + +handle_daemon_args(Address, Opts) when is_list(Address) ; is_atom(Address) -> + %% This might be a host name, an FQDN, an IP address in string format ("127.1.1.1") + %% etc. It might be a string or an atom since inet:hostname() is defined in that way + case inet:parse_strict_address(Address) of + {ok, IP} -> + {Address, ensure_ip_option(IP,Opts)}; + _ -> + %% Try to lookup as a hostname: + case inet:getaddr(Address, family(Opts)) of + {ok, IP} -> + {Address, ensure_ip_option(IP,Opts)}; + _ -> + %% Give up and let the underlying system handle this + {Address, Opts} + end + end. + + +%% Add an ip-option if not already present. +ensure_ip_option(Address, Opts) -> + case proplists:get_value(ip, Opts) of + undefined -> [{ip,Address}|Opts]; + _ -> Opts + end. +-endif. + + +%% Has the caller indicated the address family? +family(Opts) -> + family(Opts, inet). + +family(Opts, Default) -> + case proplists:get_value(inet,Opts) of + true -> inet; + inet -> inet; + inet6 -> inet6; + _ -> case proplists:get_value(inet6,Opts) of + true -> inet6; + _ -> Default + end + end. %%%---------------------------------------------------------------- valid_socket_to_use(Socket, {tcp,_,_}) -> @@ -434,8 +520,9 @@ open_listen_socket(Host0, Port0, Options0) -> case ?GET_SOCKET_OPT(fd, Options0) of undefined -> {ok,LSock} = ssh_acceptor:listen(Port0, Options0), - {ok,{_,LPort}} = inet:sockname(LSock), - {{Host0,LPort}, LSock}; + {ok,{_LHost,LPort}} = inet:sockname(LSock), + {{_LHost,LPort}, LSock}; +%% {{Host0,LPort}, LSock}; Fd when is_integer(Fd) -> %% Do gen_tcp:listen with the option {fd,Fd}: @@ -446,35 +533,18 @@ open_listen_socket(Host0, Port0, Options0) -> %%%---------------------------------------------------------------- finalize_start(Host, Port, Profile, Options0, F) -> - Options = ?PUT_INTERNAL_OPT([{address, Host}, - {port, Port}, - {role, server}], Options0), - case ssh_system_sup:system_supervisor(Host, Port, Profile) of - undefined -> - try sshd_sup:start_child(Options) of - {error, {already_started, _}} -> - {error, eaddrinuse}; - {error, Error} -> - {error, Error}; - Result = {ok,_} -> - F(Options, Result) - catch - exit:{noproc, _} -> - {error, ssh_not_started} - end; - Sup -> - AccPid = ssh_system_sup:acceptor_supervisor(Sup), - case ssh_acceptor_sup:start_child(AccPid, Options) of - {error, {already_started, _}} -> - {error, eaddrinuse}; - {error, Error} -> - {error, Error}; - {ok, _} -> - F(Options, {ok,Sup}) - end + try + sshd_sup:start_child(Host, Port, Profile, Options0) + of + {error, {already_started, _}} -> + {error, eaddrinuse}; + {error, Error} -> + {error, Error}; + Result = {ok,_} -> + F(Options0, Result) + catch + exit:{noproc, _} -> + {error, ssh_not_started} end. %%%---------------------------------------------------------------- -fmt_host(any) -> "any"; -fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); -fmt_host(Str) when is_list(Str) -> Str. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index f9e2280212..f7fbd7ccad 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -129,7 +129,8 @@ handle_connection(Callback, Address, Port, Options, Socket) -> MaxSessions = ?GET_OPT(max_sessions, Options), case number_of_connections(SystemSup) < MaxSessions of true -> - {ok, SubSysSup} = ssh_system_sup:start_subsystem(SystemSup, Options), + {ok, SubSysSup} = + ssh_system_sup:start_subsystem(SystemSup, server, Address, Port, Profile, Options), ConnectionSup = ssh_subsystem_sup:connection_supervisor(SubSysSup), NegTimeout = ?GET_OPT(negotiation_timeout, Options), ssh_connection_handler:start_connection(server, Socket, diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 613d8fbc75..4606107f56 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -29,7 +29,7 @@ -include("ssh.hrl"). --export([start_link/1, start_child/2, stop_child/4]). +-export([start_link/4, start_child/5, stop_child/4]). %% Supervisor callback -export([init/1]). @@ -41,16 +41,13 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Servers) -> - supervisor:start_link(?MODULE, [Servers]). +start_link(Address, Port, Profile, Options) -> + supervisor:start_link(?MODULE, [Address, Port, Profile, Options]). -start_child(AccSup, Options) -> - Spec = child_spec(Options), +start_child(AccSup, Address, Port, Profile, Options) -> + Spec = child_spec(Address, Port, Profile, Options), case supervisor:start_child(AccSup, Spec) of {error, already_present} -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Profile = ?GET_OPT(profile, Options), stop_child(AccSup, Address, Port, Profile), supervisor:start_child(AccSup, Spec); Reply -> @@ -69,21 +66,18 @@ stop_child(AccSup, Address, Port, Profile) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= -init([Options]) -> +init([Address, Port, Profile, Options]) -> RestartStrategy = one_for_one, MaxR = 10, MaxT = 3600, - Children = [child_spec(Options)], + Children = [child_spec(Address, Port, Profile, Options)], {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), +child_spec(Address, Port, Profile, Options) -> Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT), - Profile = ?GET_OPT(profile, Options), Name = id(Address, Port, Profile), StartFunc = {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, Restart = transient, diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 50a29bbb53..ff94e5dfb6 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -440,7 +440,12 @@ init_ssh_record(Role, Socket, Opts) -> {Vsn, Version} = ssh_transport:versions(Role, Opts), case Role of client -> - PeerName = ?GET_INTERNAL_OPT(host, Opts), + PeerName = case ?GET_INTERNAL_OPT(host, Opts) of + PeerIP when is_tuple(PeerIP) -> + inet_parse:ntoa(PeerIP); + PeerName0 -> + PeerName0 + end, S0#ssh{c_vsn = Vsn, c_version = Version, io_cb = case ?GET_OPT(user_interaction, Opts) of diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 898b4cc5c4..88f4d10792 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -221,6 +221,8 @@ file_name(Type, Name, Opts) -> %% in: "host" out: "host,1.2.3.4. +add_ip(IP) when is_tuple(IP) -> + ssh_connection:encode_ip(IP); add_ip(Host) -> case inet:getaddr(Host, inet) of {ok, Addr} -> diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index cf82db458f..c5ab422265 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -28,7 +28,7 @@ -include("ssh.hrl"). --export([start_link/1, +-export([start_link/5, connection_supervisor/1, channel_supervisor/1 ]). @@ -39,8 +39,8 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Options) -> - supervisor:start_link(?MODULE, [Options]). +start_link(Role, Address, Port, Profile, Options) -> + supervisor:start_link(?MODULE, [Role, Address, Port, Profile, Options]). connection_supervisor(SupPid) -> Children = supervisor:which_children(SupPid), @@ -53,30 +53,23 @@ channel_supervisor(SupPid) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init([Options]) -> +init([Role, Address, Port, Profile, Options]) -> RestartStrategy = one_for_all, MaxR = 0, MaxT = 3600, - Children = child_specs(Options), + Children = child_specs(Role, Address, Port, Profile, Options), {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(Options) -> - case ?GET_INTERNAL_OPT(role, Options) of - client -> - []; - server -> - [ssh_channel_child_spec(Options), ssh_connectinon_child_spec(Options)] - end. +child_specs(client, _Address, _Port, _Profile, _Options) -> + []; +child_specs(server, Address, Port, Profile, Options) -> + [ssh_channel_child_spec(server, Address, Port, Profile, Options), + ssh_connection_child_spec(server, Address, Port, Profile, Options)]. -ssh_connectinon_child_spec(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Role = ?GET_INTERNAL_OPT(role, Options), +ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> Name = id(Role, ssh_connection_sup, Address, Port), StartFunc = {ssh_connection_sup, start_link, [Options]}, Restart = temporary, @@ -85,10 +78,7 @@ ssh_connectinon_child_spec(Options) -> Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_channel_child_spec(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Role = ?GET_INTERNAL_OPT(role, Options), +ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> Name = id(Role, ssh_channel_sup, Address, Port), StartFunc = {ssh_channel_sup, start_link, [Options]}, Restart = temporary, diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 8b57387589..5463401dcd 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -31,63 +31,20 @@ %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init([]) -> +init(_) -> SupFlags = {one_for_one, 10, 3600}, - Children = children(), + Children = [child_spec(sshd_sup), child_spec(sshc_sup)], %%children(), {ok, {SupFlags, Children}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -get_services() -> - case (catch application:get_env(ssh, services)) of - {ok, Services} -> - Services; - _ -> - [] - end. - -children() -> - Services = get_services(), - Clients = [Service || Service <- Services, is_client(Service)], - Servers = [Service || Service <- Services, is_server(Service)], - - [server_child_spec(Servers), client_child_spec(Clients)]. - -server_child_spec(Servers) -> - Name = sshd_sup, - StartFunc = {sshd_sup, start_link, [Servers]}, +child_spec(Name) -> + StartFunc = {Name, start_link, []}, Restart = permanent, Shutdown = infinity, - Modules = [sshd_sup], + Modules = [Name], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -client_child_spec(Clients) -> - Name = sshc_sup, - StartFunc = {sshc_sup, start_link, [Clients]}, - Restart = permanent, - Shutdown = infinity, - Modules = [sshc_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -is_server({sftpd, _}) -> - true; -is_server({shelld, _}) -> - true; -is_server(_) -> - false. - -is_client({sftpc, _}) -> - true; -is_client({shellc, _}) -> - true; -is_client(_) -> - false. - - - diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 4083f666c3..a923b5ef71 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -31,12 +31,12 @@ -include("ssh.hrl"). --export([start_link/1, stop_listener/1, +-export([start_link/4, stop_listener/1, 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/3, + acceptor_supervisor/1, start_subsystem/6, restart_subsystem/3, restart_acceptor/3, stop_subsystem/2]). %% Supervisor callback @@ -45,12 +45,9 @@ %%%========================================================================= %%% Internal API %%%========================================================================= -start_link(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Profile = ?GET_OPT(profile, Options), +start_link(Address, Port, Profile, Options) -> Name = make_name(Address, Port, Profile), - supervisor:start_link({local, Name}, ?MODULE, [Options]). + supervisor:start_link({local, Name}, ?MODULE, [Address, Port, Profile, Options]). stop_listener(SysSup) -> stop_acceptor(SysSup). @@ -86,8 +83,8 @@ connection_supervisor(SystemSup) -> acceptor_supervisor(SystemSup) -> ssh_acceptor_sup(supervisor:which_children(SystemSup)). -start_subsystem(SystemSup, Options) -> - Spec = ssh_subsystem_child_spec(Options), +start_subsystem(SystemSup, Role, Address, Port, Profile, Options) -> + Spec = ssh_subsystem_child_spec(Role, Address, Port, Profile, Options), supervisor:start_child(SystemSup, Spec). stop_subsystem(SystemSup, SubSys) -> @@ -125,14 +122,12 @@ restart_acceptor(Address, Port, Profile) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init([Options]) -> +init([Address, Port, Profile, Options]) -> RestartStrategy = one_for_one, MaxR = 0, MaxT = 3600, Children = case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of - undefined -> child_specs(Options); + undefined -> child_specs(Address, Port, Profile, Options); _ -> [] end, {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. @@ -140,24 +135,21 @@ init([Options]) -> %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(Options) -> - [ssh_acceptor_child_spec(Options)]. +child_specs(Address, Port, Profile, Options) -> + [ssh_acceptor_child_spec(Address, Port, Profile, Options)]. -ssh_acceptor_child_spec(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Profile = ?GET_OPT(profile, Options), +ssh_acceptor_child_spec(Address, Port, Profile, Options) -> Name = id(ssh_acceptor_sup, Address, Port, Profile), - StartFunc = {ssh_acceptor_sup, start_link, [Options]}, + StartFunc = {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}, Restart = transient, Shutdown = infinity, Modules = [ssh_acceptor_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_subsystem_child_spec(Options) -> +ssh_subsystem_child_spec(Role, Address, Port, Profile, Options) -> Name = make_ref(), - StartFunc = {ssh_subsystem_sup, start_link, [Options]}, + StartFunc = {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_subsystem_sup], @@ -169,7 +161,12 @@ id(Sup, Address, Port, Profile) -> {Sup, Address, Port, Profile}. make_name(Address, Port, Profile) -> - list_to_atom(lists:flatten(io_lib:format("ssh_system_~s_~p_~p_sup", [Address, Port, Profile]))). + list_to_atom(lists:flatten(io_lib:format("ssh_system_~s_~p_~p_sup", [fmt_host(Address), Port, Profile]))). + +fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); +fmt_host(A) when is_atom(A) -> A; +fmt_host(S) when is_list(S) -> S. + ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) -> Child; diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 15858f36e1..9aab9d57e9 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -27,7 +27,7 @@ -behaviour(supervisor). --export([start_link/1, start_child/1, stop_child/1]). +-export([start_link/0, start_child/1, stop_child/1]). %% Supervisor callback -export([init/1]). @@ -35,8 +35,8 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Args) -> - supervisor:start_link({local, ?MODULE}, ?MODULE, [Args]). +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). start_child(Args) -> supervisor:start_child(?MODULE, Args). @@ -51,18 +51,16 @@ stop_child(Client) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init(Args) -> +init(_) -> RestartStrategy = simple_one_for_one, MaxR = 0, MaxT = 3600, - {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec(Args)]}}. + {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec()]}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(_) -> +child_spec() -> Name = undefined, % As simple_one_for_one is used. StartFunc = {ssh_connection_handler, start_link, []}, Restart = temporary, diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 791456839d..d4805e9465 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -29,8 +29,11 @@ -include("ssh.hrl"). --export([start_link/1, start_child/1, stop_child/1, - stop_child/3, system_name/1]). +-export([start_link/0, + start_child/4, + stop_child/1, + stop_child/3, + system_name/1]). %% Supervisor callback -export([init/1]). @@ -38,27 +41,23 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Servers) -> - supervisor:start_link({local, ?MODULE}, ?MODULE, [Servers]). +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). -start_child(Options) -> - Address = ?GET_INTERNAL_OPT(address, Options), - Port = ?GET_INTERNAL_OPT(port, Options), - Profile = ?GET_OPT(profile, Options), +start_child(Address, Port, Profile, Options) -> +io:format("~p:~p ~p:~p~n",[?MODULE,?LINE,Address, Port]), case ssh_system_sup:system_supervisor(Address, Port, Profile) of undefined -> - Spec = child_spec(Address, Port, Options), - case supervisor:start_child(?MODULE, Spec) of - {error, already_present} -> - Name = id(Address, Port, Profile), - supervisor:delete_child(?MODULE, Name), - supervisor:start_child(?MODULE, Spec); - Reply -> - Reply - end; +io:format("~p:~p undefined~n",[?MODULE,?LINE]), + Spec = child_spec(Address, Port, Profile, Options), + Reply = supervisor:start_child(?MODULE, Spec), +io:format("~p:~p Reply=~p~n",[?MODULE,?LINE,Reply]), + Reply; Pid -> +io:format("~p:~p Pid=~p~n",[?MODULE,?LINE,Pid]), AccPid = ssh_system_sup:acceptor_supervisor(Pid), - ssh_acceptor_sup:start_child(AccPid, Options) + ssh_acceptor_sup:start_child(AccPid, Address, Port, Profile, Options), + {ok,Pid} end. stop_child(Name) -> @@ -75,27 +74,15 @@ system_name(SysSup) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - -init([Servers]) -> - RestartStrategy = one_for_one, - MaxR = 10, - MaxT = 3600, - Fun = fun(ServerOpts) -> - Address = ?GET_INTERNAL_OPT(address, ServerOpts), - Port = ?GET_INTERNAL_OPT(port, ServerOpts), - child_spec(Address, Port, ServerOpts) - end, - Children = lists:map(Fun, Servers), - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. +init(_) -> + {ok, {{one_for_one, 10, 3600}, []}}. %%%========================================================================= %%% Internal functions %%%========================================================================= -child_spec(Address, Port, Options) -> - Profile = ?GET_OPT(profile, Options), +child_spec(Address, Port, Profile, Options) -> Name = id(Address, Port,Profile), - StartFunc = {ssh_system_sup, start_link, [Options]}, + StartFunc = {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, Restart = temporary, Shutdown = infinity, Modules = [ssh_system_sup], -- cgit v1.2.3 From 8f4bb9b0bd3aed663521371726ea3ec460e231a0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 23 Mar 2017 16:53:53 +0100 Subject: ssh: Mappify supervisors --- lib/ssh/src/ssh_acceptor_sup.erl | 30 ++++--- lib/ssh/src/ssh_connection_sup.erl | 28 +++---- lib/ssh/src/ssh_subsystem_sup.erl | 39 ++++----- lib/ssh/src/ssh_sup.erl | 30 +++---- lib/ssh/src/ssh_system_sup.erl | 159 ++++++++++++++++--------------------- lib/ssh/src/sshc_sup.erl | 35 ++++---- lib/ssh/src/sshd_sup.erl | 73 +++++++++-------- 7 files changed, 190 insertions(+), 204 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 4606107f56..3ad842f98c 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -48,9 +48,12 @@ start_child(AccSup, Address, Port, Profile, Options) -> Spec = child_spec(Address, Port, Profile, Options), case supervisor:start_child(AccSup, Spec) of {error, already_present} -> + %% Is this ever called? stop_child(AccSup, Address, Port, Profile), supervisor:start_child(AccSup, Spec); Reply -> + %% Reply = {ok,SystemSupPid} when the user calls ssh:daemon + %% after having called ssh:stop_listening Reply end. @@ -67,24 +70,27 @@ stop_child(AccSup, Address, Port, Profile) -> %%% Supervisor callback %%%========================================================================= init([Address, Port, Profile, Options]) -> - RestartStrategy = one_for_one, - MaxR = 10, - MaxT = 3600, - Children = [child_spec(Address, Port, Profile, Options)], - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. + %% Initial start of ssh_acceptor_sup for this port or new start after + %% ssh:stop_daemon + SupFlags = #{strategy => one_for_one, + intensity => 10, + period => 3600 + }, + ChildSpecs = [child_spec(Address, Port, Profile, Options)], + {ok, {SupFlags,ChildSpecs}}. %%%========================================================================= %%% Internal functions %%%========================================================================= child_spec(Address, Port, Profile, Options) -> Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT), - Name = id(Address, Port, Profile), - StartFunc = {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, - Restart = transient, - Shutdown = brutal_kill, - Modules = [ssh_acceptor], - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + #{id => id(Address, Port, Profile), + start => {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, + restart => transient, + shutdown => brutal_kill, + type => worker, + modules => [ssh_acceptor] + }. id(Address, Port, Profile) -> {ssh_acceptor_sup, Address, Port, Profile}. diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl index 0f54053f52..fad796f196 100644 --- a/lib/ssh/src/ssh_connection_sup.erl +++ b/lib/ssh/src/ssh_connection_sup.erl @@ -45,19 +45,17 @@ start_child(Sup, Args) -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= --spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore . - init(_) -> - RestartStrategy = simple_one_for_one, - MaxR = 0, - MaxT = 3600, - - Name = undefined, % As simple_one_for_one is used. - StartFunc = {ssh_connection_handler, start_link, []}, - Restart = temporary, % E.g. should not be restarted - Shutdown = 4000, - Modules = [ssh_connection_handler], - Type = worker, - - ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, - {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. + SupFlags = #{strategy => simple_one_for_one, + intensity => 0, + period => 3600 + }, + ChildSpecs = [#{id => undefined, % As simple_one_for_one is used. + start => {ssh_connection_handler, start_link, []}, + restart => temporary, + shutdown => 4000, + type => worker, + modules => [ssh_connection_handler] + } + ], + {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index c5ab422265..cf409ade6b 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -54,11 +54,12 @@ channel_supervisor(SupPid) -> %%% Supervisor callback %%%========================================================================= init([Role, Address, Port, Profile, Options]) -> - RestartStrategy = one_for_all, - MaxR = 0, - MaxT = 3600, - Children = child_specs(Role, Address, Port, Profile, Options), - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. + SupFlags = #{strategy => one_for_all, + intensity => 0, + period => 3600 + }, + ChildSpecs = child_specs(Role, Address, Port, Profile, Options), + {ok, {SupFlags,ChildSpecs}}. %%%========================================================================= %%% Internal functions @@ -70,22 +71,22 @@ child_specs(server, Address, Port, Profile, Options) -> ssh_connection_child_spec(server, Address, Port, Profile, Options)]. ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> - Name = id(Role, ssh_connection_sup, Address, Port), - StartFunc = {ssh_connection_sup, start_link, [Options]}, - Restart = temporary, - Shutdown = 5000, - Modules = [ssh_connection_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + #{id => id(Role, ssh_connection_sup, Address, Port), + start => {ssh_connection_sup, start_link, [Options]}, + restart => temporary, + shutdown => 5000, + type => supervisor, + modules => [ssh_connection_sup] + }. ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> - Name = id(Role, ssh_channel_sup, Address, Port), - StartFunc = {ssh_channel_sup, start_link, [Options]}, - Restart = temporary, - Shutdown = infinity, - Modules = [ssh_channel_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + #{id => id(Role, ssh_channel_sup, Address, Port), + start => {ssh_channel_sup, start_link, [Options]}, + restart => temporary, + shutdown => infinity, + type => supervisor, + modules => [ssh_channel_sup] + }. id(Role, Sup, Address, Port) -> {Role, Sup, Address, Port}. diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 5463401dcd..6be809b1bd 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -32,19 +32,19 @@ %%% Supervisor callback %%%========================================================================= init(_) -> - SupFlags = {one_for_one, 10, 3600}, - Children = [child_spec(sshd_sup), child_spec(sshc_sup)], %%children(), - {ok, {SupFlags, Children}}. - -%%%========================================================================= -%%% Internal functions -%%%========================================================================= -child_spec(Name) -> - StartFunc = {Name, start_link, []}, - Restart = permanent, - Shutdown = infinity, - Modules = [Name], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - + SupFlags = #{strategy => one_for_one, + intensity => 10, + period => 3600 + }, + ChildSpecs = [#{id => Module, + start => {Module, start_link, []}, + restart => permanent, + shutdown => brutal_kill, + type => supervisor, + modules => [Module] + } + || Module <- [sshd_sup, + sshc_sup] + ], + {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index a923b5ef71..84b4cd3241 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -21,7 +21,7 @@ %% %%---------------------------------------------------------------------- %% Purpose: The ssh server instance supervisor, an instans of this supervisor -%% exists for every ip-address and port combination, hangs under +%% exists for every ip-address and port combination, hangs under %% sshd_sup. %%---------------------------------------------------------------------- @@ -34,58 +34,100 @@ -export([start_link/4, stop_listener/1, 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/6, restart_subsystem/3, - restart_acceptor/3, stop_subsystem/2]). + subsystem_supervisor/1, channel_supervisor/1, + connection_supervisor/1, + acceptor_supervisor/1, start_subsystem/6, + stop_subsystem/2]). %% Supervisor callback -export([init/1]). %%%========================================================================= -%%% Internal API +%%% API %%%========================================================================= start_link(Address, Port, Profile, Options) -> Name = make_name(Address, Port, Profile), supervisor:start_link({local, Name}, ?MODULE, [Address, Port, Profile, Options]). -stop_listener(SysSup) -> - stop_acceptor(SysSup). +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init([Address, Port, Profile, Options]) -> + SupFlags = #{strategy => one_for_one, + intensity => 0, + period => 3600 + }, + ChildSpecs = + case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of + undefined -> + [#{id => id(ssh_acceptor_sup, Address, Port, Profile), + start => {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}, + restart => transient, + shutdown => infinity, + type => supervisor, + modules => [ssh_acceptor_sup] + }]; + _ -> + [] + end, + {ok, {SupFlags,ChildSpecs}}. + +%%%========================================================================= +%%% Service API +%%%========================================================================= +stop_listener(SystemSup) -> + {Name, AcceptorSup, _, _} = lookup(ssh_acceptor_sup, SystemSup), + case supervisor:terminate_child(AcceptorSup, Name) of + ok -> + supervisor:delete_child(AcceptorSup, Name); + Error -> + Error + end. stop_listener(Address, Port, Profile) -> - Name = make_name(Address, Port, Profile), - stop_acceptor(whereis(Name)). - + stop_listener( + system_supervisor(Address, Port, Profile)). + + stop_system(SysSup) -> - Name = sshd_sup:system_name(SysSup), - spawn(fun() -> sshd_sup:stop_child(Name) end), + spawn(fun() -> sshd_sup:stop_child(SysSup) end), ok. -stop_system(Address, Port, Profile) -> +stop_system(Address, Port, Profile) -> spawn(fun() -> sshd_sup:stop_child(Address, Port, Profile) end), ok. + system_supervisor(Address, Port, Profile) -> Name = make_name(Address, Port, Profile), whereis(Name). subsystem_supervisor(SystemSup) -> - ssh_subsystem_sup(supervisor:which_children(SystemSup)). + {_, Child, _, _} = lookup(ssh_subsystem_sup, SystemSup), + Child. channel_supervisor(SystemSup) -> - SubSysSup = ssh_subsystem_sup(supervisor:which_children(SystemSup)), - ssh_subsystem_sup:channel_supervisor(SubSysSup). + ssh_subsystem_sup:channel_supervisor( + subsystem_supervisor(SystemSup)). connection_supervisor(SystemSup) -> - SubSysSup = ssh_subsystem_sup(supervisor:which_children(SystemSup)), - ssh_subsystem_sup:connection_supervisor(SubSysSup). + ssh_subsystem_sup:connection_supervisor( + subsystem_supervisor(SystemSup)). acceptor_supervisor(SystemSup) -> - ssh_acceptor_sup(supervisor:which_children(SystemSup)). + {_, Child, _, _} = lookup(ssh_acceptor_sup, SystemSup), + Child. + start_subsystem(SystemSup, Role, Address, Port, Profile, Options) -> - Spec = ssh_subsystem_child_spec(Role, Address, Port, Profile, Options), - supervisor:start_child(SystemSup, Spec). + SubsystemSpec = + #{id => make_ref(), + start => {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]}, + restart => temporary, + shutdown => infinity, + type => supervisor, + modules => [ssh_subsystem_sup]}, + supervisor:start_child(SystemSup, SubsystemSpec). stop_subsystem(SystemSup, SubSys) -> case catch lists:keyfind(SubSys, 2, supervisor:which_children(SystemSup)) of @@ -103,60 +145,9 @@ stop_subsystem(SystemSup, SubSys) -> ok end. - -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); - Error -> - Error - end. - -restart_acceptor(Address, Port, Profile) -> - SysSupName = make_name(Address, Port, Profile), - AcceptorName = id(ssh_acceptor_sup, Address, Port, Profile), - supervisor:restart_child(SysSupName, AcceptorName). - -%%%========================================================================= -%%% Supervisor callback -%%%========================================================================= -init([Address, Port, Profile, Options]) -> - RestartStrategy = one_for_one, - MaxR = 0, - MaxT = 3600, - Children = case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of - undefined -> child_specs(Address, Port, Profile, Options); - _ -> [] - end, - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. - %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(Address, Port, Profile, Options) -> - [ssh_acceptor_child_spec(Address, Port, Profile, Options)]. - -ssh_acceptor_child_spec(Address, Port, Profile, Options) -> - Name = id(ssh_acceptor_sup, Address, Port, Profile), - StartFunc = {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}, - Restart = transient, - Shutdown = infinity, - Modules = [ssh_acceptor_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -ssh_subsystem_child_spec(Role, Address, Port, Profile, Options) -> - Name = make_ref(), - StartFunc = {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]}, - Restart = temporary, - Shutdown = infinity, - Modules = [ssh_subsystem_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - - id(Sup, Address, Port, Profile) -> {Sup, Address, Port, Profile}. @@ -168,23 +159,7 @@ fmt_host(A) when is_atom(A) -> A; fmt_host(S) when is_list(S) -> S. -ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) -> - Child; -ssh_subsystem_sup([_ | Rest]) -> - ssh_subsystem_sup(Rest). - -ssh_acceptor_sup([{_, Child, _, [ssh_acceptor_sup]} | _]) -> - Child; -ssh_acceptor_sup([_ | Rest]) -> - ssh_acceptor_sup(Rest). +lookup(SupModule, SystemSup) -> + lists:keyfind([SupModule], 4, + supervisor:which_children(SystemSup)). -stop_acceptor(Sup) -> - [{Name, AcceptorSup}] = - [{SupName, ASup} || {SupName, ASup, _, [ssh_acceptor_sup]} <- - supervisor:which_children(Sup)], - case supervisor:terminate_child(AcceptorSup, Name) of - ok -> - supervisor:delete_child(AcceptorSup, Name); - Error -> - Error - end. diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 9aab9d57e9..c71b81dc6d 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -32,18 +32,20 @@ %% Supervisor callback -export([init/1]). +-define(SSHC_SUP, ?MODULE). + %%%========================================================================= %%% API %%%========================================================================= start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). + supervisor:start_link({local,?SSHC_SUP}, ?MODULE, []). start_child(Args) -> supervisor:start_child(?MODULE, Args). stop_child(Client) -> spawn(fun() -> - ClientSup = whereis(?MODULE), + ClientSup = whereis(?SSHC_SUP), supervisor:terminate_child(ClientSup, Client) end), ok. @@ -52,19 +54,16 @@ stop_child(Client) -> %%% Supervisor callback %%%========================================================================= init(_) -> - RestartStrategy = simple_one_for_one, - MaxR = 0, - MaxT = 3600, - {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec()]}}. - -%%%========================================================================= -%%% Internal functions -%%%========================================================================= -child_spec() -> - Name = undefined, % As simple_one_for_one is used. - StartFunc = {ssh_connection_handler, start_link, []}, - Restart = temporary, - Shutdown = 4000, - Modules = [ssh_connection_handler], - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + SupFlags = #{strategy => simple_one_for_one, + intensity => 0, + period => 3600 + }, + ChildSpecs = [#{id => undefined, % As simple_one_for_one is used. + start => {ssh_connection_handler, start_link, []}, + restart => temporary, + shutdown => 4000, + type => worker, + modules => [ssh_connection_handler] + } + ], + {ok, {SupFlags,ChildSpecs}}. diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index d4805e9465..449ba20d02 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -19,7 +19,7 @@ %% %% %%---------------------------------------------------------------------- -%% Purpose: The top supervisor for ssh servers hangs under +%% Purpose: The top supervisor for ssh servers hangs under %% ssh_sup. %%---------------------------------------------------------------------- @@ -29,72 +29,79 @@ -include("ssh.hrl"). --export([start_link/0, +-export([start_link/0, start_child/4, stop_child/1, - stop_child/3, - system_name/1]). + stop_child/3 +]). %% Supervisor callback -export([init/1]). +-define(SSHD_SUP, ?MODULE). + %%%========================================================================= %%% API %%%========================================================================= start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). + %% No children are start now. We wait until the user calls ssh:daemon + %% and uses start_child/4 to create the children + supervisor:start_link({local,?SSHD_SUP}, ?MODULE, []). start_child(Address, Port, Profile, Options) -> -io:format("~p:~p ~p:~p~n",[?MODULE,?LINE,Address, Port]), case ssh_system_sup:system_supervisor(Address, Port, Profile) of undefined -> -io:format("~p:~p undefined~n",[?MODULE,?LINE]), + %% Here we start listening on a new Host/Port/Profile Spec = child_spec(Address, Port, Profile, Options), - Reply = supervisor:start_child(?MODULE, Spec), -io:format("~p:~p Reply=~p~n",[?MODULE,?LINE,Reply]), - Reply; + supervisor:start_child(?SSHD_SUP, Spec); Pid -> -io:format("~p:~p Pid=~p~n",[?MODULE,?LINE,Pid]), + %% Here we resume listening on a new Host/Port/Profile after + %% haveing stopped listening to he same with ssh:stop_listen(Pid) AccPid = ssh_system_sup:acceptor_supervisor(Pid), ssh_acceptor_sup:start_child(AccPid, Address, Port, Profile, Options), {ok,Pid} end. -stop_child(Name) -> - supervisor:terminate_child(?MODULE, Name). +stop_child(ChildId) when is_tuple(ChildId) -> + supervisor:terminate_child(?SSHD_SUP, ChildId); +stop_child(ChildPid) when is_pid(ChildPid)-> + stop_child(system_name(ChildPid)). -stop_child(Address, Port, Profile) -> - Name = id(Address, Port, Profile), - stop_child(Name). -system_name(SysSup) -> - Children = supervisor:which_children(sshd_sup), - system_name(SysSup, Children). +stop_child(Address, Port, Profile) -> + Id = id(Address, Port, Profile), + stop_child(Id). %%%========================================================================= %%% Supervisor callback %%%========================================================================= init(_) -> - {ok, {{one_for_one, 10, 3600}, []}}. + SupFlags = #{strategy => one_for_one, + intensity => 10, + period => 3600 + }, + ChildSpecs = [ + ], + {ok, {SupFlags,ChildSpecs}}. %%%========================================================================= %%% Internal functions %%%========================================================================= child_spec(Address, Port, Profile, Options) -> - Name = id(Address, Port,Profile), - StartFunc = {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, - Restart = temporary, - Shutdown = infinity, - Modules = [ssh_system_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. + #{id => id(Address, Port, Profile), + start => {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, + restart => temporary, + shutdown => infinity, + type => supervisor, + modules => [ssh_system_sup] + }. id(Address, Port, Profile) -> {server, ssh_system_sup, Address, Port, Profile}. -system_name([], _ ) -> - undefined; -system_name(SysSup, [{Name, SysSup, _, _} | _]) -> - Name; -system_name(SysSup, [_ | Rest]) -> - system_name(SysSup, Rest). +system_name(SysSup) -> + case lists:keyfind(SysSup, 2, supervisor:which_children(?SSHD_SUP)) of + {Name, SysSup, _, _} -> Name; + false -> undefind + end. + -- cgit v1.2.3 From 92bdf870ec6509eb958535780b8655206478f7db Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 28 Mar 2017 17:20:46 +0200 Subject: ssh: change 'brutal_kill' to timeout' --- lib/ssh/src/ssh_acceptor_sup.erl | 2 +- lib/ssh/src/ssh_sup.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 3ad842f98c..26defcfdbd 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -87,7 +87,7 @@ child_spec(Address, Port, Profile, Options) -> #{id => id(Address, Port, Profile), start => {ssh_acceptor, start_link, [Port, Address, Options, Timeout]}, restart => transient, - shutdown => brutal_kill, + shutdown => 5500, %brutal_kill, type => worker, modules => [ssh_acceptor] }. diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 6be809b1bd..26574763e4 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -39,7 +39,7 @@ init(_) -> ChildSpecs = [#{id => Module, start => {Module, start_link, []}, restart => permanent, - shutdown => brutal_kill, + shutdown => 4000, %brutal_kill, type => supervisor, modules => [Module] } -- cgit v1.2.3 From 3bed79615b9702f8335dbe75295c6610b097175e Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 29 Mar 2017 13:13:43 +0200 Subject: ssh: remove dead code and add comments --- lib/ssh/src/ssh.erl | 55 ++++++++--------------------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 680047dffd..aff143dc26 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -416,66 +416,27 @@ handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) -> IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility end; -handle_daemon_args(Address, Opts) when is_list(Address) ; is_atom(Address) -> +handle_daemon_args(Address, Opts) when is_list(Address) ; % IP address in string or a domain + is_atom(Address) % domains could be atoms in inet + -> IP = proplists:get_value(ip, Opts), case inet:parse_strict_address(Address) of + %% check if Address is an IP-address {ok, IP} -> {IP, Opts}; {ok, OtherIP} -> {OtherIP, [{ip,OtherIP}|Opts--[{ip,IP}]]}; _ -> + %% Not an IP-address. Check if it is a host name: case inet:getaddr(Address, family(Opts)) of {ok, IP} -> {Address, Opts}; {ok, OtherIP} -> {Address, [{ip,OtherIP}|Opts--[{ip,IP}]]}; - _ -> {Address, Opts} - end - end. - - --ifdef(hulahopp). -%% Check the Address parameter and set an ip-option in some cases. The -%% Address parameter is left unchanged because ssh:stop_listener and -%% ssh:stop_daemon needs to find the system supervisor by name - -handle_daemon_args(any, Opts) -> - %% Listen to 0.0.0.0. The caller may have set an ip-option. Trust - %% that one in such a case. - {any, Opts}; - -handle_daemon_args(loopback, Opts) -> - %% Listen to a loopback address. Let the underlying layers decide - %% in case the caller hasn't set the ip-option. - {loopback, ensure_ip_option(loopback,Opts)}; - -handle_daemon_args(IP, Opts) when is_tuple(IP) -> - %% An IP address in Erlang tuple format: - {IP, ensure_ip_option(IP,Opts)}; - -handle_daemon_args(Address, Opts) when is_list(Address) ; is_atom(Address) -> - %% This might be a host name, an FQDN, an IP address in string format ("127.1.1.1") - %% etc. It might be a string or an atom since inet:hostname() is defined in that way - case inet:parse_strict_address(Address) of - {ok, IP} -> - {Address, ensure_ip_option(IP,Opts)}; - _ -> - %% Try to lookup as a hostname: - case inet:getaddr(Address, family(Opts)) of - {ok, IP} -> - {Address, ensure_ip_option(IP,Opts)}; _ -> - %% Give up and let the underlying system handle this + %% Not a Host name and not an IP address, let + %% inet and the OS later figure out what it + %% could be {Address, Opts} end end. - -%% Add an ip-option if not already present. -ensure_ip_option(Address, Opts) -> - case proplists:get_value(ip, Opts) of - undefined -> [{ip,Address}|Opts]; - _ -> Opts - end. --endif. - - %% Has the caller indicated the address family? family(Opts) -> family(Opts, inet). -- cgit v1.2.3 From a9fc169d5d0ca63a3062800429e3a16169901ab3 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 29 Mar 2017 15:08:48 +0200 Subject: ssh: Change handling of IP addresses, 'any' and names in sup structure --- lib/ssh/src/ssh.erl | 100 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 30 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index aff143dc26..8c802d46eb 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -26,6 +26,7 @@ -include("ssh_connect.hrl"). -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). +-include_lib("kernel/include/inet.hrl"). -export([start/0, start/1, stop/0, connect/2, connect/3, connect/4, @@ -120,7 +121,7 @@ connect(Host, Port, UserOptions) when is_integer(Port), is_list(UserOptions) -> connect(Host, Port, UserOptions, infinity). -connect(Host, Port, UserOptions, Timeout) when is_integer(Port), +connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), Port>0, is_list(UserOptions) -> case ssh_options:handle_options(client, UserOptions) of @@ -130,6 +131,7 @@ connect(Host, Port, UserOptions, Timeout) when is_integer(Port), {_, Transport, _} = TransportOpts = ?GET_OPT(transport, Options), ConnectionTimeout = ?GET_OPT(connect_timeout, Options), SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)], + Host = mangle_connect_address(Host0, SocketOpts), try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), @@ -227,7 +229,7 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> try {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), #{} = Options0 = ssh_options:handle_options(server, UserOptions), - + {{Host,Port}, ListenSocket} = open_listen_socket(Host1, Port0, Options0), @@ -266,27 +268,23 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> daemon_info(Pid) -> case catch ssh_system_sup:acceptor_supervisor(Pid) of AsupPid when is_pid(AsupPid) -> - [{Name,Port,Profile}] = - [{Nam,Prt,Prf} + [{IP,Port,Profile}] = + [{IP,Prt,Prf} || {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid), - Nam <- [case inet:parse_strict_address(Hst) of - {ok,IP} -> IP; - _ when Hst=="any" -> any; - _ when Hst=="loopback" -> loopback; - _ -> Hst - end] + IP <- [case inet:parse_strict_address(Hst) of + {ok,IP} -> IP; + _ -> Hst + end] ], {ok, [{port,Port}, - {name,Name}, + {ip,IP}, {profile,Profile} ]}; _ -> {error,bad_daemon_ref} end. - - %%-------------------------------------------------------------------- -spec stop_listener(daemon_ref()) -> ok. -spec stop_listener(inet:ip_address(), inet:port_number()) -> ok. @@ -298,8 +296,14 @@ stop_listener(SysSup) -> ssh_system_sup:stop_listener(SysSup). stop_listener(Address, Port) -> stop_listener(Address, Port, ?DEFAULT_PROFILE). +stop_listener(any, Port, Profile) -> + map_ip(fun(IP) -> + ssh_system_sup:stop_listener(IP, Port, Profile) + end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]); stop_listener(Address, Port, Profile) -> - ssh_system_sup:stop_listener(Address, Port, Profile). + map_ip(fun(IP) -> + ssh_system_sup:stop_listener(IP, Port, Profile) + end, {address,Address}). %%-------------------------------------------------------------------- -spec stop_daemon(daemon_ref()) -> ok. @@ -312,9 +316,15 @@ stop_listener(Address, Port, Profile) -> stop_daemon(SysSup) -> ssh_system_sup:stop_system(SysSup). stop_daemon(Address, Port) -> - ssh_system_sup:stop_system(Address, Port, ?DEFAULT_PROFILE). + stop_daemon(Address, Port, ?DEFAULT_PROFILE). +stop_daemon(any, Port, Profile) -> + map_ip(fun(IP) -> + ssh_system_sup:stop_system(IP, Port, Profile) + end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]); stop_daemon(Address, Port, Profile) -> - ssh_system_sup:stop_system(Address, Port, Profile). + map_ip(fun(IP) -> + ssh_system_sup:stop_system(IP, Port, Profile) + end, {address,Address}). %%-------------------------------------------------------------------- -spec shell(inet:socket() | string()) -> _. @@ -397,6 +407,9 @@ default_algorithms() -> %% consideration %% +%% The handle_daemon_args/2 function basically only sets the ip-option in Opts +%% so that it is correctly set when opening the listening socket. + handle_daemon_args(any, Opts) -> case proplists:get_value(ip, Opts) of undefined -> {any, Opts}; @@ -477,20 +490,17 @@ is_tcp_socket(Socket) -> end. %%%---------------------------------------------------------------- -open_listen_socket(Host0, Port0, Options0) -> - case ?GET_SOCKET_OPT(fd, Options0) of - undefined -> - {ok,LSock} = ssh_acceptor:listen(Port0, Options0), - {ok,{_LHost,LPort}} = inet:sockname(LSock), - {{_LHost,LPort}, LSock}; -%% {{Host0,LPort}, LSock}; - - Fd when is_integer(Fd) -> - %% Do gen_tcp:listen with the option {fd,Fd}: - {ok,LSock} = ssh_acceptor:listen(0, Options0), - {ok,{LHost,LPort}} = inet:sockname(LSock), - {{LHost,LPort}, LSock} - end. +open_listen_socket(_Host0, Port0, Options0) -> + {ok,LSock} = + case ?GET_SOCKET_OPT(fd, Options0) of + undefined -> + ssh_acceptor:listen(Port0, Options0); + Fd when is_integer(Fd) -> + %% Do gen_tcp:listen with the option {fd,Fd}: + ssh_acceptor:listen(0, Options0) + end, + {ok,{LHost,LPort}} = inet:sockname(LSock), + {{LHost,LPort}, LSock}. %%%---------------------------------------------------------------- finalize_start(Host, Port, Profile, Options0, F) -> @@ -509,3 +519,33 @@ finalize_start(Host, Port, Profile, Options0, F) -> end. %%%---------------------------------------------------------------- +map_ip(Fun, {address,IP}) when is_tuple(IP) -> + Fun(IP); +map_ip(Fun, {address,Address}) -> + IPs = try {ok,#hostent{h_addr_list=IP0s}} = inet:gethostbyname(Address), + IP0s + catch + _:_ -> [] + end, + map_ip(Fun, IPs); +map_ip(Fun, IPs) -> + lists:map(Fun, IPs). + +%%%---------------------------------------------------------------- +mangle_connect_address(A, SockOpts) -> + mangle_connect_address1(A, proplists:get_value(inet6,SockOpts,false)). + +loopback(true) -> {0,0,0,0,0,0,0,1}; +loopback(false) -> {127,0,0,1}. + +mangle_connect_address1( loopback, V6flg) -> loopback(V6flg); +mangle_connect_address1( any, V6flg) -> loopback(V6flg); +mangle_connect_address1({0,0,0,0}, _) -> loopback(false); +mangle_connect_address1({0,0,0,0,0,0,0,0}, _) -> loopback(true); +mangle_connect_address1( IP, _) when is_tuple(IP) -> IP; +mangle_connect_address1(A, _) -> + case catch inet:parse_address(A) of + {ok, {0,0,0,0}} -> loopback(false); + {ok, {0,0,0,0,0,0,0,0}} -> loopback(true); + _ -> A + end. -- cgit v1.2.3 From 7ad21ca66f5a46be231fffe884ac2c3b5d97c7ae Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 4 Apr 2017 19:53:05 +0200 Subject: ssh: document what happens when ssh:daemon sets both HostAddr and ip option The idea is that the HostAddress argument takes precedence over an ip-option. However, an ip-option overrides the 'any' HostAddr. This fixes the case of dameon(Port, [{ip,IP}..] in a non-surprising way. --- lib/ssh/src/ssh.erl | 82 ++++++----------------------------------------------- 1 file changed, 8 insertions(+), 74 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 8c802d46eb..3e80a04b70 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -225,7 +225,8 @@ daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 -> daemon(any, Port, UserOptions). -daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> +daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535, + Host0 == any ; Host0 == loopback ; is_tuple(Host0) -> try {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), #{} = Options0 = ssh_options:handle_options(server, UserOptions), @@ -259,7 +260,11 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> {error,Error}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} - end. + end; + +daemon(_, _, _) -> + {error, badarg}. + %%-------------------------------------------------------------------- @@ -378,35 +383,6 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- - -%% - if Address is 'any' and no ip-option is present, the name is -%% 'any' and the socket will listen to all addresses -%% -%% - if Address is 'any' and an ip-option is present, the name is -%% set to the value of the ip-option and the socket will listen -%% to that address -%% -%% - if Address is 'loopback' and no ip-option is present, the name -%% is 'loopback' and an loopback address will be choosen by the -%% underlying layers -%% -%% - if Address is 'loopback' and an ip-option is present, the name -%% is set to the value of the ip-option kept and the socket will -%% listen to that address -%% -%% - if Address is an ip-address, that ip-address is the name and -%% the listening address. An ip-option will be discarded. -%% -%% - if Address is a HostName, and that resolves to an ip-address, -%% that ip-address is the name and the listening address. An -%% ip-option will be discarded. -%% -%% - if Address is a string or an atom other than thoose defined -%% above, that Address will be the name and the listening address -%% will be choosen by the lower layers taking an ip-option in -%% consideration -%% - %% The handle_daemon_args/2 function basically only sets the ip-option in Opts %% so that it is correctly set when opening the listening socket. @@ -416,53 +392,11 @@ handle_daemon_args(any, Opts) -> IP -> {IP, Opts} end; -handle_daemon_args(loopback, Opts) -> - case proplists:get_value(ip, Opts) of - undefined -> {loopback, [{ip,loopback}|Opts]}; - IP -> {IP, Opts} - end; - -handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) -> +handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) ; IPaddr == loopback -> case proplists:get_value(ip, Opts) of undefined -> {IPaddr, [{ip,IPaddr}|Opts]}; IPaddr -> {IPaddr, Opts}; IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility - end; - -handle_daemon_args(Address, Opts) when is_list(Address) ; % IP address in string or a domain - is_atom(Address) % domains could be atoms in inet - -> - IP = proplists:get_value(ip, Opts), - case inet:parse_strict_address(Address) of - %% check if Address is an IP-address - {ok, IP} -> {IP, Opts}; - {ok, OtherIP} -> {OtherIP, [{ip,OtherIP}|Opts--[{ip,IP}]]}; - _ -> - %% Not an IP-address. Check if it is a host name: - case inet:getaddr(Address, family(Opts)) of - {ok, IP} -> {Address, Opts}; - {ok, OtherIP} -> {Address, [{ip,OtherIP}|Opts--[{ip,IP}]]}; - _ -> - %% Not a Host name and not an IP address, let - %% inet and the OS later figure out what it - %% could be - {Address, Opts} - end - end. - -%% Has the caller indicated the address family? -family(Opts) -> - family(Opts, inet). - -family(Opts, Default) -> - case proplists:get_value(inet,Opts) of - true -> inet; - inet -> inet; - inet6 -> inet6; - _ -> case proplists:get_value(inet6,Opts) of - true -> inet6; - _ -> Default - end end. %%%---------------------------------------------------------------- -- cgit v1.2.3 From 43dfbf7533ff9d176051231e52d308613a8d4bd1 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 31 Jan 2017 13:59:19 +0100 Subject: ssh: added message_queue_data,off_heap to spawn Seems to solve some test case problems when heavily loaded --- lib/ssh/src/ssh_connection_handler.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ff94e5dfb6..84adf952e6 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -80,7 +80,11 @@ ) -> {ok, pid()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . start_link(Role, Socket, Options) -> - {ok, proc_lib:spawn_link(?MODULE, init_connection_handler, [Role, Socket, Options])}. + {ok, proc_lib:spawn_opt(?MODULE, + init_connection_handler, + [Role, Socket, Options], + [link, {message_queue_data,off_heap}] + )}. %%-------------------------------------------------------------------- -- cgit v1.2.3 From a0ab002c9f23865b96595a4a95750d85801f93d1 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 12 Apr 2017 19:07:27 +0200 Subject: ssh: change next_event to postpone --- lib/ssh/src/ssh_connection_handler.erl | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 84adf952e6..ca9790ba0d 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -813,7 +813,7 @@ handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_inter send_bytes(Reply, D), {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}}; not_ok -> - {next_state, {userauth,client}, D, [{next_event, internal, Msg}]} + {next_state, {userauth,client}, D, [postpone]} end; handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) -> @@ -842,14 +842,14 @@ handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_inte {next_state, {connected,server}, D#data{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}}}; -handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, +handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, #data{ssh_params = Ssh0} = D0) -> Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Ssh0#ssh.userauth_preference, Method =/= "keyboard-interactive"], D = D0#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}, - {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; + {next_state, {userauth,client}, D, [postpone]}; -handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, +handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, #data{ssh_params = Ssh0} = D0) -> Opts = Ssh0#ssh.opts, D = case ?GET_OPT(password, Opts) of @@ -859,23 +859,23 @@ handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_ D0#data{ssh_params = Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} % FIXME:intermodule dependency end, - {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; + {next_state, {userauth,client}, D, [postpone]}; -handle_event(_, Msg=#ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> - {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; +handle_event(_, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> + {next_state, {userauth,client}, D, [postpone]}; -handle_event(_, Msg=#ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) -> - {next_state, {userauth_keyboard_interactive,client}, D, [{next_event, internal, Msg}]}; +handle_event(_, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) -> + {next_state, {userauth_keyboard_interactive,client}, D, [postpone]}; %%% ######## {connected, client|server} #### -handle_event(_, {#ssh_msg_kexinit{},_} = Event, {connected,Role}, D0) -> +handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params), D = D0#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}, send_bytes(SshPacket, D), - {next_state, {kexinit,Role,renegotiate}, D, [{next_event, internal, Event}]}; + {next_state, {kexinit,Role,renegotiate}, D, [postpone]}; handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) -> {disconnect, _, {{replies,Replies}, _}} = -- cgit v1.2.3 From 24cce98e38f1c8d36abb67bc7aca0668cf64c1ad Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 12 Apr 2017 19:56:12 +0200 Subject: ssh: replace deprecated crypto:rand_uniform --- lib/ssh/src/ssh_transport.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 54ea80c727..6b47868d5c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -181,7 +181,7 @@ ssh_vsn() -> end. random_id(Nlo, Nup) -> - [crypto:rand_uniform($a,$z+1) || _<- lists:duplicate(crypto:rand_uniform(Nlo,Nup+1),x) ]. + [$a + rand:uniform($z-$a+1) - 1 || _<- lists:duplicate(Nlo + rand:uniform(Nup-Nlo+1) - 1, x)]. hello_version_msg(Data) -> [Data,"\r\n"]. @@ -1041,7 +1041,7 @@ padding_length(Size, #ssh{encrypt_block_size = BlockSize, end, PadBlockSize = max(BlockSize,4), MaxExtraBlocks = (max(RandomLengthPadding,MinPaddingLen) - MinPaddingLen) div PadBlockSize, - ExtraPaddingLen = try crypto:rand_uniform(0,MaxExtraBlocks)*PadBlockSize + ExtraPaddingLen = try (rand:uniform(MaxExtraBlocks+1) - 1) * PadBlockSize catch _:_ -> 0 end, MinPaddingLen + ExtraPaddingLen. -- cgit v1.2.3 From 5e2f2fb80636e858877fa4d4ff2d9834bc1cd616 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 13 Apr 2017 14:38:50 +0200 Subject: ssh: re-write to use callback init/1 --- lib/ssh/src/ssh_connection_handler.erl | 144 +++++++++++++++------------------ 1 file changed, 67 insertions(+), 77 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 84adf952e6..11d182849c 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -60,7 +60,7 @@ ]). %%% Behaviour callbacks --export([callback_mode/0, handle_event/4, terminate/3, +-export([init/1, callback_mode/0, handle_event/4, terminate/3, format_status/2, code_change/4]). %%% Exports not intended to be used :). They are used for spawning and tests @@ -362,71 +362,79 @@ renegotiate_data(ConnectionHandler) -> ) -> no_return(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . init_connection_handler(Role, Socket, Opts) -> - process_flag(trap_exit, true), - S0 = init_process_state(Role, Socket, Opts), - try - {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), - S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag - } - of - S -> - gen_statem:enter_loop(?MODULE, - [], %%[{debug,[trace,log,statistics,debug]} || Role==server], - {hello,Role}, - S) - catch - _:Error -> - gen_statem:enter_loop(?MODULE, - [], - {init_error,Error}, - S0) - end. - - -init_process_state(Role, Socket, Opts) -> - D = #data{connection_state = - C = #connection{channel_cache = ssh_channel:cache_create(), - channel_id_seed = 0, - port_bindings = [], - requests = [], - options = Opts}, - starter = ?GET_INTERNAL_OPT(user_pid, Opts), - socket = Socket, - opts = Opts - }, - case Role of - client -> - %% Start the renegotiation timers - timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), - timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), - cache_init_idle_timer(D); - server -> - cache_init_idle_timer( - D#data{connection_state = init_connection(Role, C, Opts)} - ) + case init([Role, Socket, Opts]) of + {ok, StartState, D} -> + process_flag(trap_exit, true), + gen_statem:enter_loop(?MODULE, + [], %%[{debug,[trace,log,statistics,debug]} || Role==server], + StartState, + D); + + {stop, {error,enotconn}} -> + %% Handles the abnormal sequence: + %% SYN-> + %% <-SYNACK + %% ACK-> + %% RST-> + exit({shutdown, "TCP connection to server was prematurely closed by the client"}); + + {stop, OtherError} -> + exit({shutdown, {init,OtherError}}) end. -init_connection(server, C = #connection{}, Opts) -> - Sups = ?GET_INTERNAL_OPT(supervisors, Opts), - SystemSup = proplists:get_value(system_sup, Sups), - SubSystemSup = proplists:get_value(subsystem_sup, Sups), - ConnectionSup = proplists:get_value(connection_sup, Sups), +init([Role,Socket,Opts]) -> + case inet:peername(Socket) of + {ok, PeerAddr} -> + {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), + C = #connection{channel_cache = ssh_channel:cache_create(), + channel_id_seed = 0, + port_bindings = [], + requests = [], + options = Opts}, + D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), + socket = Socket, + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag, + ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts), + opts = Opts + }, + D = case Role of + client -> + %% Start the renegotiation timers + timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), + timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), + cache_init_idle_timer( + D0#data{connection_state = C} + ); + server -> + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + cache_init_idle_timer( + D0#data{connection_state = + C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), + exec = ?GET_OPT(exec, Opts), + system_supervisor = proplists:get_value(system_sup, Sups), + sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), + connection_supervisor = proplists:get_value(connection_sup, Sups) + }}) + end, + {ok, {hello,Role}, D}; + + {error,Error} -> + {stop, Error} + end. - C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), - exec = ?GET_OPT(exec, Opts), - system_supervisor = SystemSup, - sub_system_supervisor = SubSystemSup, - connection_supervisor = ConnectionSup - }. init_ssh_record(Role, Socket, Opts) -> - {ok, PeerAddr} = inet:peername(Socket), + %% Export of this internal function is + %% intended for low-level protocol test suites + {ok,PeerAddr} = inet:peername(Socket), + init_ssh_record(Role, Socket, PeerAddr, Opts). + +init_ssh_record(Role, _Socket, PeerAddr, Opts) -> KeyCb = ?GET_OPT(key_cb, Opts), AuthMethods = case Role of @@ -481,8 +489,7 @@ init_ssh_record(Role, Socket, Opts) -> -type renegotiate_flag() :: init | renegotiate. -type state_name() :: - {init_error,any()} - | {hello, role()} + {hello, role()} | {kexinit, role(), renegotiate_flag()} | {key_exchange, role(), renegotiate_flag()} | {key_exchange_dh_gex_init, server, renegotiate_flag()} @@ -504,26 +511,9 @@ init_ssh_record(Role, Socket, Opts) -> %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -%%% ######## Error in the initialisation #### - callback_mode() -> handle_event_function. -handle_event(_, _Event, {init_error,Error}, _) -> - case Error of - {badmatch,{error,enotconn}} -> - %% Handles the abnormal sequence: - %% SYN-> - %% <-SYNACK - %% ACK-> - %% RST-> - {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}}; - - OtherError -> - {stop, {shutdown,{init,OtherError}}} - end; - - %%% ######## {hello, client|server} #### %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> -- cgit v1.2.3 From 0d91185b9093de3a254f0a869e7dadfcfa79295d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 18 Apr 2017 12:15:58 +0200 Subject: ssh: fix dialyzer errors --- lib/ssh/src/ssh_connection_handler.erl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 1a8e022da8..84bb7dc23f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -337,8 +337,7 @@ renegotiate_data(ConnectionHandler) -> transport_protocol :: atom(), % ex: tcp transport_cb :: atom(), % ex: gen_tcp transport_close_tag :: atom(), % ex: tcp_closed - ssh_params :: #ssh{} - | undefined, + ssh_params :: #ssh{}, socket :: inet:socket(), decrypted_data_buffer = <<>> :: binary(), encrypted_data_buffer = <<>> :: binary(), @@ -370,7 +369,7 @@ init_connection_handler(Role, Socket, Opts) -> StartState, D); - {stop, {error,enotconn}} -> + {stop, enotconn} -> %% Handles the abnormal sequence: %% SYN-> %% <-SYNACK @@ -394,21 +393,20 @@ init([Role,Socket,Opts]) -> requests = [], options = Opts}, D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), + connection_state = C, socket = Socket, transport_protocol = Protocol, transport_cb = Callback, transport_close_tag = CloseTag, ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts), - opts = Opts + opts = Opts }, D = case Role of client -> %% Start the renegotiation timers timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), - cache_init_idle_timer( - D0#data{connection_state = C} - ); + cache_init_idle_timer(D0); server -> Sups = ?GET_INTERNAL_OPT(supervisors, Opts), cache_init_idle_timer( -- cgit v1.2.3 From 192379acc9e112f393ad18e20f4951d1e318a7a0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 26 Apr 2017 11:49:43 +0200 Subject: ssh: Correction of misspelled type --- lib/ssh/src/ssh.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 315310f700..c7ed11895c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -134,7 +134,7 @@ role :: client | role(), peer :: undefined | {inet:hostname(), - {inet:ip_adress(),inet:port_number()}}, %% string version of peer address + {inet:ip_address(),inet:port_number()}}, %% string version of peer address c_vsn, %% client version {Major,Minor} s_vsn, %% server version {Major,Minor} -- cgit v1.2.3 From c0d2e134f90ddd3fd2f5b0f9a94a5b0d55c93416 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 10 Apr 2017 13:19:37 +0200 Subject: ssh: clearify public key option handling Change the handling of option pref_public_key_algs so that the same checks are not performed twice. --- lib/ssh/src/ssh_auth.erl | 47 ++++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 88c8144063..51df54341f 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -175,6 +175,7 @@ service_request_msg(Ssh) -> %%%---------------------------------------------------------------- init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> + %% Client side case ?GET_OPT(user, Opts) of undefined -> ErrStr = "Could not determine the users name", @@ -183,25 +184,17 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> description = ErrStr}); User -> - Msg = #ssh_msg_userauth_request{user = User, - service = "ssh-connection", - method = "none", - data = <<>>}, - Algs0 = ?GET_OPT(pref_public_key_algs, Opts), - %% The following line is not strictly correct. The call returns the - %% supported HOST key types while we are interested in USER keys. However, - %% they "happens" to be the same (for now). This could change.... - %% There is no danger as long as the set of user keys is a subset of the set - %% of host keys. - CryptoSupported = ssh_transport:supported_algorithms(public_key), - Algs = [A || A <- Algs0, - lists:member(A, CryptoSupported)], - - Prefs = method_preference(Algs), - ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, - userauth_preference = Prefs, - userauth_methods = none, - service = "ssh-connection"}) + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = "none", + data = <<>>}, + Ssh#ssh{user = User, + userauth_preference = + method_preference(?GET_OPT(pref_public_key_algs, Opts)), + userauth_methods = none, + service = "ssh-connection"} + ) end. %%%---------------------------------------------------------------- @@ -453,14 +446,14 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -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). +method_preference(PubKeyAlgs) -> + %% PubKeyAlgs: List of user (client) public key algorithms to try to use. + %% All of the acceptable algorithms is the default values. + PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- PubKeyAlgs], + NonPKmethods = [{"password", ?MODULE, password_msg, []}, + {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} + ], + PubKeyDefs ++ NonPKmethods. check_password(User, Password, Opts, Ssh) -> case ?GET_OPT(pwdfun, Opts) of -- cgit v1.2.3 From 29d7533c715f972ee996382c2c45cc0c055e10d2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 10 Apr 2017 16:25:06 +0200 Subject: ssh: Implement ext-info extension. draft-ietf-curdle-ssh-ext-info This is only a draft extension, but it is quite stable and already supported by some implementations. OpenSSH has had it for some year now. --- lib/ssh/src/ssh.hrl | 7 +- lib/ssh/src/ssh_connection_handler.erl | 142 ++++++++++++++++----- lib/ssh/src/ssh_dbg.erl | 5 +- lib/ssh/src/ssh_message.erl | 33 +++++ lib/ssh/src/ssh_options.erl | 22 +++- lib/ssh/src/ssh_transport.erl | 221 ++++++++++++++++++++++----------- lib/ssh/src/ssh_transport.hrl | 15 +++ 7 files changed, 334 insertions(+), 111 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index c7ed11895c..1a95bb27e7 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -145,6 +145,9 @@ c_keyinit, %% binary payload of kexinit packet s_keyinit, %% binary payload of kexinit packet + send_ext_info, %% May send ext-info to peer + recv_ext_info, %% Expect ext-info from peer + algorithms, %% #alg{} kex, %% key exchange algorithm @@ -216,7 +219,9 @@ compress, decompress, c_lng, - s_lng + s_lng, + send_ext_info, + recv_ext_info }). -record(ssh_key, diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 84bb7dc23f..0ff7c9b3a1 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -453,7 +453,9 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> PeerName = case ?GET_INTERNAL_OPT(host, Opts) of PeerIP when is_tuple(PeerIP) -> inet_parse:ntoa(PeerIP); - PeerName0 -> + PeerName0 when is_atom(PeerName0) -> + atom_to_list(PeerName0); + PeerName0 when is_list(PeerName0) -> PeerName0 end, S0#ssh{c_vsn = Vsn, @@ -493,6 +495,7 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> | {key_exchange_dh_gex_init, server, renegotiate_flag()} | {key_exchange_dh_gex_reply, client, renegotiate_flag()} | {new_keys, role()} + | {ext_info, role(), renegotiate_flag()} | {service_request, role()} | {userauth, role()} | {userauth_keyboard_interactive, role()} @@ -589,13 +592,17 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), send_bytes(KexdhReply, D), - {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), + {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), + send_bytes(ExtInfo, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> - {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), + {ok, NewKeys, Ssh1} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + send_bytes(ExtInfo, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- diffie-hellman group exchange @@ -620,13 +627,17 @@ handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), send_bytes(KexEcdhReply, D), - {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), + {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), + send_bytes(ExtInfo, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> - {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), + {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + send_bytes(ExtInfo, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; @@ -635,8 +646,10 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), send_bytes(KexGexReply, D), - {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), + {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, D), + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), + send_bytes(ExtInfo, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; @@ -645,30 +658,60 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), send_bytes(NewKeys, D), - {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}}; + {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + send_bytes(ExtInfo, D), + {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%% ######## {new_keys, client|server} #### %% First key exchange round: -handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D) -> {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), - Ssh = case Role of - client -> - {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1), - send_bytes(MsgReq, D), - Ssh2; - server -> - Ssh1 - end, - {next_state, {service_request,Role}, D#data{ssh_params=Ssh}}; + %% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1), + %% send_bytes(ExtInfo, D), + {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1), + send_bytes(MsgReq, D), + {next_state, {ext_info,client,init}, D#data{ssh_params=Ssh}}; + +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, D) -> + {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), + %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + %% send_bytes(ExtInfo, D), + {next_state, {ext_info,server,init}, D#data{ssh_params=Ssh}}; %% Subsequent key exchange rounds (renegotiation): handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) -> {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), - {next_state, {connected,Role}, D#data{ssh_params=Ssh}}; + %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), + %% send_bytes(ExtInfo, D), + {next_state, {ext_info,Role,renegotiate}, D#data{ssh_params=Ssh}}; + + +%%% ######## {ext_info, client|server, init|renegotiate} #### + +handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,init}, D0) -> + D = handle_ssh_msg_ext_info(Msg, D0), + {next_state, {service_request,Role}, D}; + +handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,renegotiate}, D0) -> + D = handle_ssh_msg_ext_info(Msg, D0), + {next_state, {connected,Role}, D}; + +handle_event(_, #ssh_msg_newkeys{}=Msg, {ext_info,_Role,renegotiate}, D) -> + {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), + {keep_state, D#data{ssh_params = Ssh}}; + + +handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) -> + %% If something else arrives, goto next state and handle the event in that one + {next_state, {service_request,Role}, D, [postpone]}; -%%% ######## {service_request, client|server} +handle_event(internal, Msg, {ext_info,Role,renegotiate}, D) when is_tuple(Msg) -> + %% If something else arrives, goto next state and handle the event in that one + {next_state, {connected,Role}, D, [postpone]}; + +%%% ######## {service_request, client|server} #### handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D) -> case ServiceName of @@ -747,6 +790,11 @@ handle_event(_, end; %%---- userauth success to client +handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) -> + %% FIXME: need new state to receive this msg! + D = handle_ssh_msg_ext_info(Msg, D0), + {keep_state, D}; + handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) -> D#data.starter ! ssh_connected, {next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}}; @@ -849,6 +897,11 @@ handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info end, {next_state, {userauth,client}, D, [postpone]}; +handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth_keyboard_interactive_info_response, client}, D0) -> + %% FIXME: need new state to receive this msg! + D = handle_ssh_msg_ext_info(Msg, D0), + {keep_state, D}; + handle_event(_, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> {next_state, {userauth,client}, D, [postpone]}; @@ -1080,26 +1133,34 @@ handle_event({call,_}, _, StateName, _) when StateName /= {connected,server}, StateName /= {connected,client} -> {keep_state_and_data, [postpone]}; -handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, {connected,_}, D0) -> +handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0), %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; -handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, {connected,_}, D0) -> +handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> D = handle_request(ChannelId, Type, Data, true, From, D0), %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; -handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, {connected,_}, D0) -> +handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> {{replies, Replies}, Connection} = ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), {Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}), start_channel_request_timer(ChannelId, From, Timeout), % FIXME: No message exchange so why? {keep_state, D, Repls}; -handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) -> +handle_event({call,From}, {eof, ChannelId}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id, sent_close = false} -> D = send_msg(ssh_connection:channel_eof_msg(Id), D0), @@ -1110,8 +1171,9 @@ handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) -> handle_event({call,From}, {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, - {connected,_}, - D0) -> + StateName, + D0) when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> erlang:monitor(process, ChannelPid), {ChannelId, D1} = new_channel_id(D0), D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId, @@ -1131,7 +1193,9 @@ handle_event({call,From}, start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_cancel_idle_timer(D)}; -handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) -> +handle_event({call,From}, {send_window, ChannelId}, StateName, D) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{send_window_size = WinSize, send_packet_size = Packsize} -> @@ -1141,7 +1205,9 @@ handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) -> end, {keep_state_and_data, [{reply,From,Reply}]}; -handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) -> +handle_event({call,From}, {recv_window, ChannelId}, StateName, D) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_packet_size = Packsize} -> @@ -1151,7 +1217,9 @@ handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) -> end, {keep_state_and_data, [{reply,From,Reply}]}; -handle_event({call,From}, {close, ChannelId}, {connected,_}, D0) -> +handle_event({call,From}, {close, ChannelId}, StateName, D0) + when element(1,StateName) == connected ; + element(1,StateName) == ext_info -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id} = Channel -> D1 = send_msg(ssh_connection:channel_close_msg(Id), D0), @@ -1323,7 +1391,8 @@ handle_event(Type, Ev, StateName, D) -> Descr = case catch atom_to_list(element(1,Ev)) of "ssh_msg_" ++_ when Type==internal -> - "Message in wrong state"; +%% "Message in wrong state"; +lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); _ -> "Internal error" end, @@ -1516,6 +1585,8 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> send_bytes(Bytes, State), State#data{ssh_params=Ssh}. +send_bytes("", _D) -> + ok; send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> _ = Transport:send(Socket, Bytes), ok. @@ -1621,6 +1692,19 @@ kex(_) -> undefined. cache(#data{connection_state=C}) -> C#connection.channel_cache. +%%%---------------------------------------------------------------- +handle_ssh_msg_ext_info(#ssh_msg_ext_info{}, D=#data{ssh_params = #ssh{recv_ext_info=false}} ) -> + % The peer sent this although we didn't allow it! + D; +handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> + lists:foldl(fun ext_info/2, D0, Data). + +%% ext_info({ExtName,ExtValue}, D0) -> +%% D0; +ext_info(_, D0) -> + %% Not implemented + D0. + %%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 0345bbdea7..9431bf1817 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -56,7 +56,7 @@ messages(Write, MangleArg) when is_function(Write,2), dbg_ssh_messages() -> dbg:tp(ssh_message,encode,1, x), dbg:tp(ssh_message,decode,1, x), - dbg:tpl(ssh_transport,select_algorithm,3, 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). @@ -77,7 +77,7 @@ msg_formater({trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) -> msg_formater({trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) -> D; -msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg},TS}, D) -> +msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) -> fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D); msg_formater({trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> @@ -160,6 +160,7 @@ shrink_bin(X) -> X. ?wr_record(ssh_msg_kexdh_init); ?wr_record(ssh_msg_kexdh_reply); ?wr_record(ssh_msg_newkeys); +?wr_record(ssh_msg_ext_info); ?wr_record(ssh_msg_kex_dh_gex_request); ?wr_record(ssh_msg_kex_dh_gex_request_old); ?wr_record(ssh_msg_kex_dh_gex_group); diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 562f040477..56f678876c 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -215,6 +215,16 @@ encode(#ssh_msg_service_accept{ }) -> <>; +encode(#ssh_msg_ext_info{ + nr_extensions = N, + data = Data + }) -> + lists:foldl(fun({ExtName,ExtVal}, Acc) -> + <> + end, + <>, + Data); + encode(#ssh_msg_newkeys{}) -> <>; @@ -435,6 +445,18 @@ decode(<>) -> num_responses = Num, data = Data}; +decode(<>) -> + Data = bin_foldr( + fun(Bin,Acc) when length(Acc) == N -> + {Bin,Acc}; + (<>, Acc) -> + {Rest,[{binary_to_list(V0),binary_to_list(V1)}|Acc]} + end, [], BinData), + #ssh_msg_ext_info{ + nr_extensions = N, + data = Data + }; + %%% Keyexchange messages decode(<>) -> decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10); @@ -537,17 +559,28 @@ decode(< + lists:reverse(bin_foldl(Fun, Acc, Bin)). + +bin_foldl(_, Acc, <<>>) -> Acc; +bin_foldl(Fun, Acc0, Bin0) -> + {Bin,Acc} = Fun(Bin0,Acc0), + bin_foldl(Fun, Acc, Bin). + +%%%---------------------------------------------------------------- decode_keyboard_interactive_prompts(<<>>, Acc) -> lists:reverse(Acc); decode_keyboard_interactive_prompts(<>, Acc) -> decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]). +%%%---------------------------------------------------------------- erl_boolean(0) -> false; erl_boolean(1) -> true. +%%%---------------------------------------------------------------- decode_kex_init(<>, Acc, 0) -> list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc])); decode_kex_init(<>, Acc, 0) -> diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index ee3cdbb8a0..6e898b4fde 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -614,11 +614,23 @@ default(common) -> }, {max_random_length_padding, def} => - #{default => ?MAX_RND_PADDING_LEN, - chk => fun check_non_neg_integer/1, - class => user_options - } - }. + #{default => ?MAX_RND_PADDING_LEN, + chk => fun check_non_neg_integer/1, + class => user_options + }, + + {send_ext_info, def} => + #{default => true, + chk => fun erlang:is_boolean/1, + class => user_options + }, + + {recv_ext_info, def} => + #{default => true, + chk => fun erlang:is_boolean/1, + class => user_options + } + }. %%%================================================================ diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 6b47868d5c..d623d24529 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -38,6 +38,7 @@ handle_hello_version/1, key_exchange_init_msg/1, key_init/3, new_keys_message/1, + ext_info_message/1, handle_kexinit_msg/3, handle_kexdh_init/2, handle_kex_dh_gex_group/2, handle_kex_dh_gex_init/2, handle_kex_dh_gex_reply/2, handle_new_keys/2, handle_kex_dh_gex_request/2, @@ -230,7 +231,7 @@ key_exchange_init_msg(Ssh0) -> kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) -> Random = ssh_bits:random(16), PrefAlgs = ?GET_OPT(preferred_algorithms, Opts), - kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs). + kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs, Opts). key_init(client, Ssh, Value) -> Ssh#ssh{c_keyinit = Value}; @@ -238,10 +239,11 @@ key_init(server, Ssh, Value) -> Ssh#ssh{s_keyinit = Value}. -kexinit_message(_Role, Random, Algs, HostKeyAlgs) -> +kexinit_message(Role, Random, Algs, HostKeyAlgs, Opts) -> #ssh_msg_kexinit{ cookie = Random, - kex_algorithms = to_strings( get_algs(kex,Algs) ), + kex_algorithms = to_strings( get_algs(kex,Algs) ) + ++ kex_ext_info(Role,Opts), server_host_key_algorithms = HostKeyAlgs, encryption_algorithms_client_to_server = c2s(cipher,Algs), encryption_algorithms_server_to_client = s2c(cipher,Algs), @@ -263,39 +265,42 @@ 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), + {SshPacket, Ssh1} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), + Ssh = install_alg(snd, Ssh1), {ok, SshPacket, Ssh}. handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, - #ssh{role = client} = Ssh0) -> - {ok, Algoritms} = select_algorithm(client, Own, CounterPart), - case verify_algorithm(Algoritms) of - true -> - key_exchange_first_msg(Algoritms#alg.kex, - Ssh0#ssh{algorithms = Algoritms}); - {false,Alg} -> - %% TODO: Correct code? - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed: " - ++ Alg - }) + #ssh{role = client} = Ssh) -> + try + {ok, Algorithms} = select_algorithm(client, Own, CounterPart, Ssh#ssh.opts), + true = verify_algorithm(Algorithms), + Algorithms + of + Algos -> + key_exchange_first_msg(Algos#alg.kex, + Ssh#ssh{algorithms = Algos}) + catch + _:_ -> + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Selection of key exchange algorithm failed"}) end; handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, - #ssh{role = server} = Ssh) -> - {ok, Algoritms} = select_algorithm(server, CounterPart, Own), - case verify_algorithm(Algoritms) of - true -> - {ok, Ssh#ssh{algorithms = Algoritms}}; - {false,Alg} -> - ssh_connection_handler:disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange algorithm failed: " - ++ Alg - }) + #ssh{role = server} = Ssh) -> + try + {ok, Algorithms} = select_algorithm(server, CounterPart, Own, Ssh#ssh.opts), + true = verify_algorithm(Algorithms), + Algorithms + of + Algos -> + {ok, Ssh#ssh{algorithms = Algos}} + catch + _:_ -> + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Selection of key exchange algorithm failed"}) end. @@ -308,6 +313,8 @@ verify_algorithm(#alg{decrypt = undefined}) -> {false, "decrypt"}; verify_algorithm(#alg{compress = undefined}) -> {false, "compress"}; verify_algorithm(#alg{decompress = undefined}) -> {false, "decompress"}; verify_algorithm(#alg{kex = Kex}) -> + %% This also catches the error if 'ext-info-s' or 'ext-info-c' is selected. + %% (draft-ietf-curdle-ssh-ext-info-04 2.2) case lists:member(Kex, supported_algorithms(kex)) of true -> true; false -> {false, "kex"} @@ -414,9 +421,9 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), - exchanged_hash = H, - session_id = sid(Ssh, H)}}; + {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K), + exchanged_hash = H, + session_id = sid(Ssh, H)})}; Error -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{ @@ -584,9 +591,9 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), - exchanged_hash = H, - session_id = sid(Ssh, H)}}; + {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K), + exchanged_hash = H, + session_id = sid(Ssh, H)})}; _Error -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{ @@ -660,9 +667,9 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), - exchanged_hash = H, - session_id = sid(Ssh, H)}}; + {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K), + exchanged_hash = H, + session_id = sid(Ssh, H)})}; Error -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{ @@ -682,7 +689,7 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, %%%---------------------------------------------------------------- handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> - try install_alg(Ssh0) of + try install_alg(rcv, Ssh0) of #ssh{} = Ssh -> {ok, Ssh} catch @@ -693,6 +700,34 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> }) end. + +%%%---------------------------------------------------------------- +kex_ext_info(Role, Opts) -> + case ?GET_OPT(recv_ext_info,Opts) of + true when Role==client -> ["ext-info-c"]; + true when Role==server -> ["ext-info-s"]; + false -> [] + end. + +ext_info_message(#ssh{role=client, + algorithms=#alg{send_ext_info=true}} = Ssh0) -> + %% FIXME: no extensions implemented for clients + {ok, "", Ssh0}; + +ext_info_message(#ssh{role=server, + algorithms=#alg{send_ext_info=true}} = Ssh0) -> + AlgsList = lists:map(fun erlang:atom_to_list/1, + ssh_transport:default_algorithms(public_key)), + Msg = #ssh_msg_ext_info{nr_extensions = 1, + data = [{"server-sig-algs", string:join(AlgsList,",")}] + }, + {SshPacket, Ssh} = ssh_packet(Msg, Ssh0), + {ok, SshPacket, Ssh}; + +ext_info_message(Ssh0) -> + {ok, "", Ssh0}. % "" means: 'do not send' + +%%%---------------------------------------------------------------- %% select session id sid(#ssh{session_id = undefined}, H) -> H; @@ -812,7 +847,7 @@ known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_} %% %% The first algorithm in each list MUST be the preferred (guessed) %% algorithm. Each string MUST contain at least one algorithm name. -select_algorithm(Role, Client, Server) -> +select_algorithm(Role, Client, Server, Opts) -> {Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server), {SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server), @@ -837,17 +872,34 @@ select_algorithm(Role, Client, Server) -> Kex = select(Client#ssh_msg_kexinit.kex_algorithms, Server#ssh_msg_kexinit.kex_algorithms), - Alg = #alg{kex = Kex, - hkey = HK, - encrypt = Encrypt, - decrypt = Decrypt, - send_mac = SendMac, - recv_mac = RecvMac, - compress = Compression, - decompress = Decompression, - c_lng = C_Lng, - s_lng = S_Lng}, - {ok, Alg}. + SendExtInfo = + %% To send we must have that option enabled and ... + ?GET_OPT(send_ext_info,Opts) andalso + %% ... the peer must have told us to send: + case Role of + server -> lists:member("ext-info-c", Client#ssh_msg_kexinit.kex_algorithms); + client -> lists:member("ext-info-s", Server#ssh_msg_kexinit.kex_algorithms) + end, + + RecvExtInfo = + %% The peer should not send unless told so by us (which is + %% guided by an option). + %% (However a malicious peer could send anyway, so we must be prepared) + ?GET_OPT(recv_ext_info,Opts), + + {ok, #alg{kex = Kex, + hkey = HK, + encrypt = Encrypt, + decrypt = Decrypt, + send_mac = SendMac, + recv_mac = RecvMac, + compress = Compression, + decompress = Decompression, + c_lng = C_Lng, + s_lng = S_Lng, + send_ext_info = SendExtInfo, + recv_ext_info = RecvExtInfo + }}. %%% It is an agreed problem with RFC 5674 that if the selection is @@ -928,45 +980,66 @@ select_compression_decompression(server, Client, Server) -> Server#ssh_msg_kexinit.compression_algorithms_server_to_client), {Compression, Decompression}. -install_alg(SSH) -> - SSH1 = alg_final(SSH), - SSH2 = alg_setup(SSH1), - alg_init(SSH2). +%% DIr = rcv | snd +install_alg(Dir, SSH) -> + SSH1 = alg_final(Dir, SSH), + SSH2 = alg_setup(Dir, SSH1), + alg_init(Dir, SSH2). -alg_setup(SSH) -> +alg_setup(snd, SSH) -> ALG = SSH#ssh.algorithms, SSH#ssh{kex = ALG#alg.kex, hkey = ALG#alg.hkey, encrypt = ALG#alg.encrypt, - decrypt = ALG#alg.decrypt, send_mac = ALG#alg.send_mac, send_mac_size = mac_digest_size(ALG#alg.send_mac), + compress = ALG#alg.compress, + c_lng = ALG#alg.c_lng, + s_lng = ALG#alg.s_lng, + send_ext_info = ALG#alg.send_ext_info, + recv_ext_info = ALG#alg.recv_ext_info + }; + +alg_setup(rcv, SSH) -> + ALG = SSH#ssh.algorithms, + SSH#ssh{kex = ALG#alg.kex, + hkey = ALG#alg.hkey, + decrypt = ALG#alg.decrypt, recv_mac = ALG#alg.recv_mac, recv_mac_size = mac_digest_size(ALG#alg.recv_mac), - compress = ALG#alg.compress, decompress = ALG#alg.decompress, c_lng = ALG#alg.c_lng, s_lng = ALG#alg.s_lng, - algorithms = undefined + send_ext_info = ALG#alg.send_ext_info, + recv_ext_info = ALG#alg.recv_ext_info }. -alg_init(SSH0) -> + +alg_init(snd, SSH0) -> {ok,SSH1} = send_mac_init(SSH0), - {ok,SSH2} = recv_mac_init(SSH1), - {ok,SSH3} = encrypt_init(SSH2), - {ok,SSH4} = decrypt_init(SSH3), - {ok,SSH5} = compress_init(SSH4), - {ok,SSH6} = decompress_init(SSH5), - SSH6. - -alg_final(SSH0) -> + {ok,SSH2} = encrypt_init(SSH1), + {ok,SSH3} = compress_init(SSH2), + SSH3; + +alg_init(rcv, SSH0) -> + {ok,SSH1} = recv_mac_init(SSH0), + {ok,SSH2} = decrypt_init(SSH1), + {ok,SSH3} = decompress_init(SSH2), + SSH3. + + +alg_final(snd, SSH0) -> {ok,SSH1} = send_mac_final(SSH0), - {ok,SSH2} = recv_mac_final(SSH1), - {ok,SSH3} = encrypt_final(SSH2), - {ok,SSH4} = decrypt_final(SSH3), - {ok,SSH5} = compress_final(SSH4), - {ok,SSH6} = decompress_final(SSH5), - SSH6. + {ok,SSH2} = encrypt_final(SSH1), + {ok,SSH3} = compress_final(SSH2), + SSH3; + +alg_final(rcv, SSH0) -> + {ok,SSH1} = recv_mac_final(SSH0), + {ok,SSH2} = decrypt_final(SSH1), + {ok,SSH3} = decompress_final(SSH2), + SSH3. + select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS -> A = CL -- SL, %% algortihms only used by client diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index 19b3f5c437..faae6008f2 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -48,6 +48,7 @@ -define(SSH_MSG_DEBUG, 4). -define(SSH_MSG_SERVICE_REQUEST, 5). -define(SSH_MSG_SERVICE_ACCEPT, 6). +-define(SSH_MSG_EXT_INFO, 7). -define(SSH_MSG_KEXINIT, 20). -define(SSH_MSG_NEWKEYS, 21). @@ -88,6 +89,20 @@ name %% string }). +-record(ssh_msg_ext_info, + { + nr_extensions, %% uint32 + + %% repeat the following 2 fields "nr-extensions" times: + %% string extension-name + %% string extension-value + + data %% [{extension-name, %% string + %% extension-value}, %% string + %% ... + %% ] + }). + -record(ssh_msg_kexinit, { cookie, %% random(16) -- cgit v1.2.3 From b7cba805e37e591d8fa7d7df06f9563a9f926e23 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 26 Apr 2017 12:08:01 +0200 Subject: ssh: state machine fixes for calls during re-negotiation --- lib/ssh/src/ssh_connection_handler.erl | 47 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 27 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 0ff7c9b3a1..128a9175f5 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -365,7 +365,7 @@ init_connection_handler(Role, Socket, Opts) -> {ok, StartState, D} -> process_flag(trap_exit, true), gen_statem:enter_loop(?MODULE, - [], %%[{debug,[trace,log,statistics,debug]} || Role==server], + [], %%[{debug,[trace,log,statistics,debug]} ], %% [] StartState, D); @@ -504,6 +504,10 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> -type handle_event_result() :: gen_statem:handle_event_result(). +-define(CONNECTED(StateName), + (element(1,StateName) == connected orelse + element(1,StateName) == ext_info ) ). + -spec handle_event(gen_statem:event_type(), event_content(), state_name(), @@ -1020,12 +1024,10 @@ handle_event(cast, data_size, _, _) -> -handle_event(cast, _, StateName, _) when StateName /= {connected,server}, - StateName /= {connected,client} -> +handle_event(cast, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; - -handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) -> +handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTED(StateName) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_window_pending = Pending, @@ -1051,7 +1053,7 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) -> keep_state_and_data end; -handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) -> +handle_event(cast, {reply_request,success,ChannelId}, StateName, D) when ?CONNECTED(StateName) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{remote_id = RemoteId} -> Msg = ssh_connection:channel_success_msg(RemoteId), @@ -1062,13 +1064,13 @@ handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) -> keep_state_and_data end; -handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, {connected,_}, D) -> +handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, StateName, D) when ?CONNECTED(StateName) -> {keep_state, handle_request(ChannelPid, ChannelId, Type, Data, false, none, D)}; -handle_event(cast, {request,ChannelId,Type,Data}, {connected,_}, D) -> +handle_event(cast, {request,ChannelId,Type,Data}, StateName, D) when ?CONNECTED(StateName) -> {keep_state, handle_request(ChannelId, Type, Data, false, none, D)}; -handle_event(cast, {unknown,Data}, {connected,_}, D) -> +handle_event(cast, {unknown,Data}, StateName, D) when ?CONNECTED(StateName) -> Msg = #ssh_msg_unimplemented{sequence = Data}, {keep_state, send_msg(Msg,D)}; @@ -1129,29 +1131,25 @@ handle_event({call,From}, stop, StateName, D0) -> {Repls,D} = send_replies(Replies, D0), {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}}; -handle_event({call,_}, _, StateName, _) when StateName /= {connected,server}, - StateName /= {connected,client} -> +handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0), %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> D = handle_request(ChannelId, Type, Data, true, From, D0), %% Note reply to channel will happen later when reply is recived from peer on the socket start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> {{replies, Replies}, Connection} = ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), {Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}), @@ -1159,8 +1157,7 @@ handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) {keep_state, D, Repls}; handle_event({call,From}, {eof, ChannelId}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id, sent_close = false} -> D = send_msg(ssh_connection:channel_eof_msg(Id), D0), @@ -1172,8 +1169,7 @@ handle_event({call,From}, {eof, ChannelId}, StateName, D0) handle_event({call,From}, {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, StateName, - D0) when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + D0) when ?CONNECTED(StateName) -> erlang:monitor(process, ChannelPid), {ChannelId, D1} = new_channel_id(D0), D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId, @@ -1194,8 +1190,7 @@ handle_event({call,From}, {keep_state, cache_cancel_idle_timer(D)}; handle_event({call,From}, {send_window, ChannelId}, StateName, D) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{send_window_size = WinSize, send_packet_size = Packsize} -> @@ -1206,8 +1201,7 @@ handle_event({call,From}, {send_window, ChannelId}, StateName, D) {keep_state_and_data, [{reply,From,Reply}]}; handle_event({call,From}, {recv_window, ChannelId}, StateName, D) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{recv_window_size = WinSize, recv_packet_size = Packsize} -> @@ -1218,8 +1212,7 @@ handle_event({call,From}, {recv_window, ChannelId}, StateName, D) {keep_state_and_data, [{reply,From,Reply}]}; handle_event({call,From}, {close, ChannelId}, StateName, D0) - when element(1,StateName) == connected ; - element(1,StateName) == ext_info -> + when ?CONNECTED(StateName) -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of #channel{remote_id = Id} = Channel -> D1 = send_msg(ssh_connection:channel_close_msg(Id), D0), -- cgit v1.2.3 From 519f89016e7ce755775a88730814fa34af21676c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 19 Apr 2017 12:01:26 +0200 Subject: ssh: server-sig-algs, client side --- lib/ssh/src/ssh.hrl | 2 +- lib/ssh/src/ssh_auth.erl | 85 +++++++++++++++------------------- lib/ssh/src/ssh_connection_handler.erl | 23 +++++++-- lib/ssh/src/ssh_options.erl | 20 ++------ 4 files changed, 61 insertions(+), 69 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 1a95bb27e7..cf2a359e6c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -38,7 +38,6 @@ -define(MAX_RND_PADDING_LEN, 15). -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). --define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']). -define(FALSE, 0). -define(TRUE, 1). @@ -201,6 +200,7 @@ userauth_quiet_mode, % boolean() userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"] userauth_supported_methods, % string() eg "keyboard-interactive,password" + userauth_pubkeys, kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive" userauth_preference, available_host_keys, diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 51df54341f..aadd1ad6dc 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -136,34 +136,40 @@ keyboard_interactive_msg([#ssh{user = User, Ssh) end. -publickey_msg([Alg, #ssh{user = User, +publickey_msg([SigAlg, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> - Hash = ssh_transport:sha(Alg), + Hash = ssh_transport:sha(SigAlg), + KeyAlg = key_alg(SigAlg), {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:user_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of {ok, PrivKey} -> - StrAlgo = atom_to_list(Alg), - case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of - not_ok -> - {not_ok, Ssh}; + SigAlgStr = atom_to_list(SigAlg), + try + Key = ssh_transport:extract_public_key(PrivKey), + public_key:ssh_encode(Key, ssh2_pubkey) + of PubKeyBlob -> - SigData = build_sig_data(SessionId, - User, Service, PubKeyBlob, StrAlgo), + SigData = build_sig_data(SessionId, User, Service, + PubKeyBlob, SigAlgStr), Sig = ssh_transport:sign(SigData, Hash, PrivKey), - SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]), + SigBlob = list_to_binary([?string(SigAlgStr), + ?binary(Sig)]), ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, service = Service, method = "publickey", data = [?TRUE, - ?string(StrAlgo), + ?string(SigAlgStr), ?binary(PubKeyBlob), ?binary(SigBlob)]}, Ssh) - end; + catch + _:_ -> + {not_ok, Ssh} + end; _Error -> {not_ok, Ssh} end. @@ -190,8 +196,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> method = "none", data = <<>>}, Ssh#ssh{user = User, - userauth_preference = - method_preference(?GET_OPT(pref_public_key_algs, Opts)), + userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys), userauth_methods = none, service = "ssh-connection"} ) @@ -265,8 +270,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, #ssh{opts = Opts, userauth_supported_methods = Methods} = Ssh) -> - case pre_verify_sig(User, binary_to_list(BAlg), - KeyBlob, Opts) of + case pre_verify_sig(User, KeyBlob, Opts) of true -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet( @@ -446,10 +450,10 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -method_preference(PubKeyAlgs) -> +method_preference(SigKeyAlgs) -> %% PubKeyAlgs: List of user (client) public key algorithms to try to use. %% All of the acceptable algorithms is the default values. - PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- PubKeyAlgs], + PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- SigKeyAlgs], NonPKmethods = [{"password", ?MODULE, password_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} ], @@ -492,9 +496,9 @@ get_password_option(Opts, User) -> false -> ?GET_OPT(password, Opts) end. -pre_verify_sig(User, Alg, KeyBlob, Opts) -> +pre_verify_sig(User, KeyBlob, Opts) -> try - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) @@ -505,21 +509,19 @@ pre_verify_sig(User, Alg, KeyBlob, Opts) -> verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> try - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) of - true -> - PlainText = build_sig_data(SessionId, User, - Service, KeyBlob, Alg), - <> = SigWLen, - <> = AlgSig, - ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key); - false -> - false - end + Key0 = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception + true = KeyCb:is_auth_key(Key0, User, [{key_cb_private,KeyCbOpts}|UserOpts]), + Key0 + of + Key -> + PlainText = build_sig_data(SessionId, User, Service, + KeyBlob, Alg), + <> = SigWLen, + <> = AlgSig, + ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key) catch _:_ -> false @@ -591,18 +593,7 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> language = "en"}}) end. -decode_public_key_v2(Bin, _Type) -> - try - public_key:ssh_decode(Bin, ssh2_pubkey) - of - Key -> {ok, Key} - catch - _:_ -> {error, bad_format} - end. -encode_public_key(_Alg, Key) -> - try - public_key:ssh_encode(Key, ssh2_pubkey) - catch - _:_ -> not_ok - end. +key_alg('rsa-sha2-256') -> 'ssh-rsa'; +key_alg('rsa-sha2-512') -> 'ssh-rsa'; +key_alg(Alg) -> Alg. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 128a9175f5..ac1b792f32 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -464,6 +464,7 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> true -> ssh_io; false -> ssh_no_io end, + userauth_pubkeys = ?GET_OPT(pref_public_key_algs, Opts), userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), peer = {PeerName, PeerAddr} }; @@ -711,7 +712,7 @@ handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) -> %% If something else arrives, goto next state and handle the event in that one {next_state, {service_request,Role}, D, [postpone]}; -handle_event(internal, Msg, {ext_info,Role,renegotiate}, D) when is_tuple(Msg) -> +handle_event(internal, Msg, {ext_info,Role,_ReNegFlag}, D) when is_tuple(Msg) -> %% If something else arrives, goto next state and handle the event in that one {next_state, {connected,Role}, D, [postpone]}; @@ -1131,6 +1132,7 @@ handle_event({call,From}, stop, StateName, D0) -> {Repls,D} = send_replies(Replies, D0), {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}}; + handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; @@ -1380,12 +1382,16 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> handle_event(internal, {disconnect,Msg,_Reason}, StateName, D) -> disconnect(Msg, StateName, D); +handle_event(_Type, _Msg, {ext_info,Role,_ReNegFlag}, D) -> + %% If something else arrives, goto next state and handle the event in that one + {next_state, {connected,Role}, D, [postpone]}; + handle_event(Type, Ev, StateName, D) -> Descr = case catch atom_to_list(element(1,Ev)) of "ssh_msg_" ++_ when Type==internal -> %% "Message in wrong state"; -lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); + lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); _ -> "Internal error" end, @@ -1689,11 +1695,20 @@ cache(#data{connection_state=C}) -> C#connection.channel_cache. handle_ssh_msg_ext_info(#ssh_msg_ext_info{}, D=#data{ssh_params = #ssh{recv_ext_info=false}} ) -> % The peer sent this although we didn't allow it! D; + handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> lists:foldl(fun ext_info/2, D0, Data). -%% ext_info({ExtName,ExtValue}, D0) -> -%% D0; + +ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client}=Ssh0}) -> + %% Make strings to eliminate risk of beeing bombed with odd strings that fills the atom table: + SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), + Ssh = Ssh0#ssh{userauth_pubkeys = + [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), + lists:member(SigAlg, SupportedAlgs) + ]}, + D0#data{ssh_params = Ssh}; + ext_info(_, D0) -> %% Not implemented D0. diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6e898b4fde..0886d5b34d 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -437,9 +437,7 @@ default(client) -> {pref_public_key_algs, def} => #{default => - %% Get dynamically supported keys in the order of the ?SUPPORTED_USER_KEYS - [A || A <- ?SUPPORTED_USER_KEYS, - lists:member(A, ssh_transport:supported_algorithms(public_key))], + ssh_transport:supported_algorithms(public_key), chk => fun check_pref_public_key_algs/1, class => @@ -670,20 +668,8 @@ check_pref_public_key_algs(V) -> PKs = ssh_transport:supported_algorithms(public_key), CHK = fun(A, Ack) -> case lists:member(A, PKs) of - true -> - [A|Ack]; - false -> - %% Check with the documented options, that is, - %% the one we can handle - case lists:member(A,?SUPPORTED_USER_KEYS) of - false -> - %% An algorithm ssh never can handle - error_in_check(A, "Not supported public key"); - true -> - %% An algorithm ssh can handle, but not in - %% this very call - Ack - end + true -> [A|Ack]; + false -> error_in_check(A, "Not supported public key") end end, case lists:foldr( -- cgit v1.2.3 From a053401a7a7142d4d2a068b2945ef91cb7957f89 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 19 Apr 2017 14:04:05 +0200 Subject: ssh: server-sig-algs, server side --- lib/ssh/src/ssh_transport.erl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index d623d24529..3c2c345261 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -710,12 +710,12 @@ kex_ext_info(Role, Opts) -> end. ext_info_message(#ssh{role=client, - algorithms=#alg{send_ext_info=true}} = Ssh0) -> - %% FIXME: no extensions implemented for clients + send_ext_info=true} = Ssh0) -> + %% FIXME: no extensions implemented {ok, "", Ssh0}; ext_info_message(#ssh{role=server, - algorithms=#alg{send_ext_info=true}} = Ssh0) -> + send_ext_info=true} = Ssh0) -> AlgsList = lists:map(fun erlang:atom_to_list/1, ssh_transport:default_algorithms(public_key)), Msg = #ssh_msg_ext_info{nr_extensions = 1, @@ -729,10 +729,8 @@ ext_info_message(Ssh0) -> %%%---------------------------------------------------------------- %% select session id -sid(#ssh{session_id = undefined}, H) -> - H; -sid(#ssh{session_id = Id}, _) -> - Id. +sid(#ssh{session_id = undefined}, H) -> H; +sid(#ssh{session_id = Id}, _) -> Id. %% %% The host key should be read from storage -- cgit v1.2.3 From 2e55f44545504aa1ba109e072e6833f5c045b58f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 19 Apr 2017 14:10:29 +0200 Subject: ssh: Implement signature algorithms rsa-sha2-*. draft-ietf-curdle-rsa-sha2 --- lib/ssh/src/ssh_connection_handler.erl | 53 +++++---- lib/ssh/src/ssh_file.erl | 24 ++-- lib/ssh/src/ssh_message.erl | 19 ++- lib/ssh/src/ssh_transport.erl | 205 ++++++++++++++++++--------------- 4 files changed, 167 insertions(+), 134 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ac1b792f32..220b05e6b0 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -490,20 +490,32 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> -type renegotiate_flag() :: init | renegotiate. -type state_name() :: - {hello, role()} - | {kexinit, role(), renegotiate_flag()} - | {key_exchange, role(), renegotiate_flag()} - | {key_exchange_dh_gex_init, server, renegotiate_flag()} + {hello, role() } + | {kexinit, role(), renegotiate_flag()} + | {key_exchange, role(), renegotiate_flag()} + | {key_exchange_dh_gex_init, server, renegotiate_flag()} | {key_exchange_dh_gex_reply, client, renegotiate_flag()} - | {new_keys, role()} - | {ext_info, role(), renegotiate_flag()} - | {service_request, role()} - | {userauth, role()} - | {userauth_keyboard_interactive, role()} - | {connected, role()} + | {new_keys, role(), renegotiate_flag()} + | {ext_info, role(), renegotiate_flag()} + | {service_request, role() } + | {userauth, role() } + | {userauth_keyboard_interactive, role() } + | {userauth_keyboard_interactive_extra, server } + | {userauth_keyboard_interactive_info_response, client } + | {connected, role() } . --type handle_event_result() :: gen_statem:handle_event_result(). +%% The state names must fulfill some rules regarding +%% where the role() and the renegotiate_flag() is placed: + +-spec role(state_name()) -> role(). +role({_,Role}) -> Role; +role({_,Role,_}) -> Role. + +-spec renegotiation(state_name()) -> boolean(). +renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; +renegotiation(_) -> false. + -define(CONNECTED(StateName), (element(1,StateName) == connected orelse @@ -513,7 +525,7 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> event_content(), state_name(), #data{} - ) -> handle_event_result(). + ) -> gen_statem:event_handler_result(state_name()) . %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1530,16 +1542,6 @@ finalize_termination(_StateName, #data{transport_cb = Transport, peer_role(client) -> server; peer_role(server) -> client. -%%-------------------------------------------------------------------- -%% StateName to Role -role({_,Role}) -> Role; -role({_,Role,_}) -> Role. - -%%-------------------------------------------------------------------- -%% Check the StateName to see if we are in the renegotiation phase -renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; -renegotiation(_) -> false. - %%-------------------------------------------------------------------- supported_host_keys(client, _, Options) -> try @@ -1576,8 +1578,11 @@ find_sup_hkeys(Options) -> %% Alg :: atom() available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) -> UserOpts = ?GET_OPT(user_options, Opts), - element(1, - catch KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts])) == ok. + case KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + {ok,_} -> true; + _ -> false + end. + send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 88f4d10792..4498c70d34 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -75,17 +75,12 @@ host_key(Algorithm, Opts) -> Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), case decode(File, Password) of {ok,Key} -> - case {Key,Algorithm} of - {#'RSAPrivateKey'{}, 'ssh-rsa'} -> {ok,Key}; - {#'DSAPrivateKey'{}, 'ssh-dss'} -> {ok,Key}; - {#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}, 'ecdsa-sha2-nistp256'} -> {ok,Key}; - {#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}, 'ecdsa-sha2-nistp384'} -> {ok,Key}; - {#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}, 'ecdsa-sha2-nistp521'} -> {ok,Key}; - _ -> - {error,bad_keytype_in_file} + case ssh_transport:valid_key_sha_alg(Key,Algorithm) of + true -> {ok,Key}; + false -> {error,bad_keytype_in_file} end; - Other -> - Other + {error,DecodeError} -> + {error,DecodeError} end. is_auth_key(Key, User,Opts) -> @@ -115,6 +110,9 @@ user_key(Algorithm, Opts) -> %% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key"; +file_base_name('rsa-sha2-256' ) -> "ssh_host_rsa_key"; +file_base_name('rsa-sha2-384' ) -> "ssh_host_rsa_key"; +file_base_name('rsa-sha2-512' ) -> "ssh_host_rsa_key"; file_base_name('ssh-dss' ) -> "ssh_host_dsa_key"; file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key"; file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key"; @@ -253,12 +251,18 @@ do_lookup_host_key(KeyToMatch, Host, Alg, Opts) -> identity_key_filename('ssh-dss' ) -> "id_dsa"; identity_key_filename('ssh-rsa' ) -> "id_rsa"; +identity_key_filename('rsa-sha2-256' ) -> "id_rsa"; +identity_key_filename('rsa-sha2-384' ) -> "id_rsa"; +identity_key_filename('rsa-sha2-512' ) -> "id_rsa"; identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa"; identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa"; identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa". identity_pass_phrase("ssh-dss" ) -> dsa_pass_phrase; identity_pass_phrase("ssh-rsa" ) -> rsa_pass_phrase; +identity_pass_phrase("rsa-sha2-256" ) -> rsa_pass_phrase; +identity_pass_phrase("rsa-sha2-384" ) -> rsa_pass_phrase; +identity_pass_phrase("rsa-sha2-512" ) -> rsa_pass_phrase; identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase; identity_pass_phrase(P) when is_atom(P) -> identity_pass_phrase(atom_to_list(P)). diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 56f678876c..21c0eabcd3 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -602,11 +602,22 @@ decode_signature(<>) -> Signature. -encode_signature(#'RSAPublicKey'{}, Signature) -> - <>), ?Ebinary(Signature)>>; -encode_signature({_, #'Dss-Parms'{}}, Signature) -> +encode_signature({#'RSAPublicKey'{},Sign}, Signature) -> + SignName = list_to_binary(atom_to_list(Sign)), + <>; +encode_signature({{_, #'Dss-Parms'{}},_}, Signature) -> <>), ?Ebinary(Signature)>>; -encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) -> +encode_signature({{#'ECPoint'{}, {namedCurve,OID}},_}, Signature) -> CurveName = public_key:oid2ssh_curvename(OID), <>), ?Ebinary(Signature)>>. +%% encode_signature(#'RSAPublicKey'{}, Signature) -> +%% SignName = <<"ssh-rsa">>, +%% <>; +%% encode_signature({_, #'Dss-Parms'{}}, Signature) -> +%% <>), ?Ebinary(Signature)>>; +%% encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) -> +%% CurveName = public_key:oid2ssh_curvename(OID), +%% <>), ?Ebinary(Signature)>>. + + diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 3c2c345261..3cf1e60634 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -48,6 +48,7 @@ parallell_gen_key/1, extract_public_key/1, ssh_packet/2, pack/2, + valid_key_sha_alg/2, sha/1, sign/3, verify/4]). %%% For test suites @@ -117,6 +118,9 @@ supported_algorithms(public_key) -> {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]}, + {'rsa-sha2-384', [{public_keys,rsa}, {hashs,sha384} ]}, + {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1 ]); @@ -377,7 +381,8 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ; %%% diffie-hellman-group18-sha512 %%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, - Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) -> + Ssh0 = #ssh{algorithms = #alg{kex=Kex, + hkey=SignAlg} = Algs}) -> %% server {G, P} = dh_group(Kex), if @@ -385,12 +390,12 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Sz = dh_bits(Algs), {Public, Private} = generate_key(dh, [P,G,2*Sz]), K = compute_key(dh, E, Private, [P,G]), - MyPrivHostKey = get_host_key(Ssh0), + MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_h(Ssh0, MyPubHostKey, E, Public, K), - H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), + H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {E,Public,K}), + H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh1} = - ssh_packet(#ssh_msg_kexdh_reply{public_host_key = MyPubHostKey, + ssh_packet(#ssh_msg_kexdh_reply{public_host_key = {MyPubHostKey,SignAlg}, f = Public, h_sig = H_SIG }, Ssh0), @@ -411,13 +416,14 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, f = F, h_sig = H_SIG}, - #ssh{keyex_key = {{Private, Public}, {G, P}}} = Ssh0) -> + #ssh{keyex_key = {{Private, Public}, {G, P}}, + algorithms = #alg{kex=Kex, + hkey=SignAlg}} = Ssh0) -> %% client if 1= K = compute_key(dh, F, Private, [P,G]), - H = kex_h(Ssh0, PeerPubHostKey, Public, F, K), - + H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Public,F,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -493,7 +499,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0), {ok, SshPacket, Ssh#ssh{keyex_key = {x, {G, P}}, - keyex_info = {-1, -1, NBits} % flag for kex_h hash calc + keyex_info = {-1, -1, NBits} % flag for kex_hash calc }}; {error,_} -> ssh_connection_handler:disconnect( @@ -539,20 +545,21 @@ handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) -> handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, #ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits}} = - Ssh0) -> + keyex_info = {Min, Max, NBits}, + algorithms = #alg{kex=Kex, + hkey=SignAlg}} = Ssh0) -> %% server if 1= K = compute_key(dh, E, Private, [P,G]), if 1 - MyPrivHostKey = get_host_key(Ssh0), + MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_h(Ssh0, MyPubHostKey, Min, NBits, Max, P, G, E, Public, K), - H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), + H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,E,Public,K}), + H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh} = - ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey, + ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = {MyPubHostKey,SignAlg}, f = Public, h_sig = H_SIG}, Ssh0), {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K), @@ -578,7 +585,9 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK f = F, h_sig = H_SIG}, #ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits}} = + keyex_info = {Min, Max, NBits}, + algorithms = #alg{kex=Kex, + hkey=SignAlg}} = Ssh0) -> %% client if @@ -586,8 +595,7 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK K = compute_key(dh, F, Private, [P,G]), if 1 - H = kex_h(Ssh0, PeerPubHostKey, Min, NBits, Max, P, G, Public, F, K), - + H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,Public,F,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -623,7 +631,8 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK %%% diffie-hellman-ecdh-sha2-* %%% handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, - Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) -> + Ssh0 = #ssh{algorithms = #alg{kex=Kex, + hkey=SignAlg}}) -> %% at server Curve = ecdh_curve(Kex), {MyPublic, MyPrivate} = generate_key(ecdh, Curve), @@ -631,12 +640,12 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, compute_key(ecdh, PeerPublic, MyPrivate, Curve) of K -> - MyPrivHostKey = get_host_key(Ssh0), + MyPrivHostKey = get_host_key(Ssh0, SignAlg), MyPubHostKey = extract_public_key(MyPrivHostKey), - H = kex_h(Ssh0, Curve, MyPubHostKey, PeerPublic, MyPublic, K), - H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), + H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Curve), {PeerPublic, MyPublic, K}), + H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), {SshPacket, Ssh1} = - ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = MyPubHostKey, + ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = {MyPubHostKey,SignAlg}, q_s = MyPublic, h_sig = H_SIG}, Ssh0), @@ -656,14 +665,15 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, q_s = PeerPublic, h_sig = H_SIG}, - #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}} = Ssh0 + #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}, + algorithms = #alg{hkey=SignAlg}} = Ssh0 ) -> %% at client try compute_key(ecdh, PeerPublic, MyPrivate, Curve) of K -> - H = kex_h(Ssh0, Curve, PeerPubHostKey, MyPublic, PeerPublic, K), + H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Curve), {MyPublic,PeerPublic,K}), case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), @@ -735,25 +745,14 @@ sid(#ssh{session_id = Id}, _) -> Id. %% %% The host key should be read from storage %% -get_host_key(SSH) -> - #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts, algorithms = ALG} = SSH, +get_host_key(SSH, SignAlg) -> + #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts} = SSH, UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:host_key(ALG#alg.hkey, [{key_cb_private,KeyCbOpts}|UserOpts]) of - {ok, #'RSAPrivateKey'{} = Key} -> Key; - {ok, #'DSAPrivateKey'{} = Key} -> Key; - {ok, #'ECPrivateKey'{} = Key} -> Key; - Result -> - exit({error, {Result, unsupported_key_type}}) + case KeyCb:host_key(SignAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + {ok, PrivHostKey} -> PrivHostKey; + Result -> exit({error, {Result, unsupported_key_type}}) end. -sign_host_key(_Ssh, PrivateKey, H) -> - sign(H, sign_host_key_sha(PrivateKey), PrivateKey). - -sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve,OID}}) -> sha(OID); -sign_host_key_sha(#'RSAPrivateKey'{}) -> sha; -sign_host_key_sha(#'DSAPrivateKey'{}) -> sha. - - extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> #'RSAPublicKey'{modulus = N, publicExponent = E}; extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> @@ -763,8 +762,8 @@ extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID}, {#'ECPoint'{point=Q}, {namedCurve,OID}}. -verify_host_key(SSH, PublicKey, Digest, Signature) -> - case verify(Digest, host_key_sha(PublicKey), Signature, PublicKey) of +verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, Signature) -> + case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of false -> {error, bad_signature}; true -> @@ -772,16 +771,6 @@ verify_host_key(SSH, PublicKey, Digest, Signature) -> end. -host_key_sha(#'RSAPublicKey'{}) -> sha; -host_key_sha({_, #'Dss-Parms'{}}) -> sha; -host_key_sha({#'ECPoint'{},{namedCurve,OID}}) -> sha(OID). - -public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; -public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; -public_algo({#'ECPoint'{},{namedCurve,OID}}) -> - Curve = public_key:oid2ssh_curvename(OID), - list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). - accepted_host(Ssh, PeerName, Public, Opts) -> case ?GET_OPT(silently_accept_hosts, Opts) of @@ -1201,29 +1190,29 @@ payload(<>) -> <> = PayloadAndPadding, Payload. -sign(SigData, Hash, #'DSAPrivateKey'{} = Key) -> - DerSignature = public_key:sign(SigData, Hash, Key), +sign(SigData, HashAlg, #'DSAPrivateKey'{} = Key) -> + DerSignature = public_key:sign(SigData, HashAlg, Key), #'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature), <>; -sign(SigData, Hash, Key = #'ECPrivateKey'{}) -> - DerEncodedSign = public_key:sign(SigData, Hash, Key), +sign(SigData, HashAlg, Key = #'ECPrivateKey'{}) -> + DerEncodedSign = public_key:sign(SigData, HashAlg, Key), #'ECDSA-Sig-Value'{r=R, s=S} = public_key:der_decode('ECDSA-Sig-Value', DerEncodedSign), <>; -sign(SigData, Hash, Key) -> - public_key:sign(SigData, Hash, Key). +sign(SigData, HashAlg, Key) -> + public_key:sign(SigData, HashAlg, Key). -verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) -> +verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) -> <> = Sig, Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), - public_key:verify(PlainText, Hash, Signature, Key); -verify(PlainText, Hash, Sig, {#'ECPoint'{},_} = Key) -> + public_key:verify(PlainText, HashAlg, Signature, Key); +verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key) -> <> = Sig, Sval = #'ECDSA-Sig-Value'{r=R, s=S}, DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), - public_key:verify(PlainText, Hash, DerEncodedSig, Key); -verify(PlainText, Hash, Sig, Key) -> - public_key:verify(PlainText, Hash, Sig, Key). + public_key:verify(PlainText, HashAlg, DerEncodedSig, Key); +verify(PlainText, HashAlg, Sig, Key) -> + public_key:verify(PlainText, HashAlg, Sig, Key). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1730,39 +1719,63 @@ hash(K, H, Ki, N, HashAlg) -> hash(K, H, <>, N-128, HashAlg). %%%---------------------------------------------------------------- -kex_h(SSH, Key, E, F, K) -> - KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), - L = <>, - crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). - -kex_h(SSH, Curve, Key, Q_c, Q_s, K) -> - KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), - L = <>, - crypto:hash(sha(Curve), L). - -kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) -> - KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), - L = if Min==-1; Max==-1 -> - %% flag from 'ssh_msg_kex_dh_gex_request_old' - %% It was like this before that message was supported, - %% why? - <>; - true -> - <> - end, - crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). - +kex_hash(SSH, Key, SignAlg, HashAlg, Args) -> + crypto:hash(HashAlg, kex_plaintext(SSH,Key,SignAlg,Args)). + +kex_plaintext(SSH, Key, SignAlg, Args) -> + EncodedKey = public_key:ssh_encode({Key,SignAlg}, ssh2_pubkey), + <>. + +kex_alg_dependent({E, F, K}) -> + %% diffie-hellman and ec diffie-hellman (with E = Q_c, F = Q_s) + <>; + +kex_alg_dependent({-1, _, -1, _, _, E, F, K}) -> + %% ssh_msg_kex_dh_gex_request_old + <>; + +kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) -> + %% diffie-hellman group exchange + <>. + +%%%---------------------------------------------------------------- + +valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-512') -> true; +valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-384') -> true; +valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-256') -> true; +valid_key_sha_alg(#'RSAPublicKey'{}, 'ssh-rsa' ) -> true; + +valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-512') -> true; +valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-384') -> true; +valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-256') -> true; +valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true; + +valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true; +valid_key_sha_alg(#'DSAPrivateKey'{}, 'ssh-dss') -> true; + +valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg); +valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg); +valid_key_sha_alg(_, _) -> false. + + + +public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-curdle-rsa-sha2 +public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; +public_algo({#'ECPoint'{},{namedCurve,OID}}) -> + Curve = public_key:oid2ssh_curvename(OID), + list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). + + + sha('ssh-rsa') -> sha; +sha('rsa-sha2-256') -> sha256; +sha('rsa-sha2-384') -> sha384; +sha('rsa-sha2-512') -> sha512; sha('ssh-dss') -> sha; sha('ecdsa-sha2-nistp256') -> sha(secp256r1); sha('ecdsa-sha2-nistp384') -> sha(secp384r1); -- cgit v1.2.3 From 9bcd621df2abf35394cd9f68b42c446d3ab83f11 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 26 Apr 2017 16:52:05 +0200 Subject: ssh: Codenomicon/Defensics fixes --- lib/ssh/src/ssh_auth.erl | 24 ++++++++++-------------- lib/ssh/src/ssh_connection_handler.erl | 13 ++++++++----- lib/ssh/src/ssh_transport.erl | 27 ++++++++++++++++++--------- 3 files changed, 36 insertions(+), 28 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index aadd1ad6dc..9eb11a53dc 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -296,8 +296,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, userauth_supported_methods = Methods} = Ssh) -> case verify_sig(SessionId, User, "ssh-connection", - binary_to_list(BAlg), - KeyBlob, SigWLen, Opts) of + BAlg, KeyBlob, SigWLen, Opts) of true -> {authorized, User, ssh_transport:ssh_packet( @@ -507,21 +506,18 @@ pre_verify_sig(User, KeyBlob, Opts) -> false end. -verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> +verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, Opts) -> try + Alg = binary_to_list(AlgBin), {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), - Key0 = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception - true = KeyCb:is_auth_key(Key0, User, [{key_cb_private,KeyCbOpts}|UserOpts]), - Key0 - of - Key -> - PlainText = build_sig_data(SessionId, User, Service, - KeyBlob, Alg), - <> = SigWLen, - <> = AlgSig, - ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key) + Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception + true = KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]), + PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg), + <> = SigWLen, + <> = AlgSig, + ssh_transport:verify(PlainText, ssh_transport:sha(Alg), Sig, Key) catch _:_ -> false diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 220b05e6b0..74e14a233f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1710,6 +1710,7 @@ ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client}=Ss SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), Ssh = Ssh0#ssh{userauth_pubkeys = [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), + %% length of SigAlg is implicitly checked by member: lists:member(SigAlg, SupportedAlgs) ]}, D0#data{ssh_params = Ssh}; @@ -2008,12 +2009,14 @@ handshake(Pid, Ref, Timeout) -> end. update_inet_buffers(Socket) -> - {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]), - MinVal = 655360, - case - [{Tag,MinVal} || {Tag,Val} <- BufSzs0, - Val < MinVal] + try + {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]), + MinVal = 655360, + [{Tag,MinVal} || {Tag,Val} <- BufSzs0, + Val < MinVal] of [] -> ok; NewOpts -> inet:setopts(Socket, NewOpts) + catch + _:_ -> ok end. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 3cf1e60634..09b5d1ac81 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1202,15 +1202,23 @@ sign(SigData, HashAlg, Key) -> public_key:sign(SigData, HashAlg, Key). verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) -> - <> = Sig, - Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), - public_key:verify(PlainText, HashAlg, Signature, Key); + case Sig of + <> -> + Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), + public_key:verify(PlainText, HashAlg, Signature, Key); + _ -> + false + end; verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key) -> - <> = Sig, - Sval = #'ECDSA-Sig-Value'{r=R, s=S}, - DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), - public_key:verify(PlainText, HashAlg, DerEncodedSig, Key); + case Sig of + <> -> + Sval = #'ECDSA-Sig-Value'{r=R, s=S}, + DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), + public_key:verify(PlainText, HashAlg, DerEncodedSig, Key); + _ -> + false + end; verify(PlainText, HashAlg, Sig, Key) -> public_key:verify(PlainText, HashAlg, Sig, Key). @@ -1795,7 +1803,8 @@ sha(?'secp384r1') -> sha(secp384r1); sha(?'secp521r1') -> sha(secp521r1); sha('ecdh-sha2-nistp256') -> sha(secp256r1); sha('ecdh-sha2-nistp384') -> sha(secp384r1); -sha('ecdh-sha2-nistp521') -> sha(secp521r1). +sha('ecdh-sha2-nistp521') -> sha(secp521r1); +sha(Str) when is_list(Str), length(Str)<50 -> sha(list_to_atom(Str)). mac_key_bytes('hmac-sha1') -> 20; -- cgit v1.2.3 From 6f26467274a77d0838596775f3e7e6a33aad7273 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 27 Apr 2017 14:58:47 +0200 Subject: ssh: Don't expose new rsa-sha2-* as default --- lib/ssh/src/ssh_transport.erl | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 09b5d1ac81..7c7dda7a1e 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -92,6 +92,10 @@ default_algorithms(cipher) -> default_algorithms(mac) -> supported_algorithms(mac, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); +default_algorithms(public_key) -> + supported_algorithms(public_key, ['rsa-sha2-256', + 'rsa-sha2-384', + 'rsa-sha2-512']); default_algorithms(Alg) -> supported_algorithms(Alg, []). -- cgit v1.2.3 From 83e20c62057ebc1d8064bf57b01be560cd244e1d Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Thu, 4 May 2017 15:42:21 +0200 Subject: Update copyright year --- lib/ssh/src/Makefile | 2 +- lib/ssh/src/ssh.erl | 2 +- lib/ssh/src/ssh.hrl | 2 +- lib/ssh/src/ssh_acceptor.erl | 2 +- lib/ssh/src/ssh_acceptor_sup.erl | 2 +- lib/ssh/src/ssh_auth.erl | 2 +- lib/ssh/src/ssh_cli.erl | 2 +- lib/ssh/src/ssh_connect.hrl | 2 +- lib/ssh/src/ssh_connection.erl | 2 +- lib/ssh/src/ssh_connection_handler.erl | 2 +- lib/ssh/src/ssh_connection_sup.erl | 2 +- lib/ssh/src/ssh_dbg.erl | 2 +- lib/ssh/src/ssh_file.erl | 2 +- lib/ssh/src/ssh_io.erl | 2 +- lib/ssh/src/ssh_message.erl | 2 +- lib/ssh/src/ssh_sftp.erl | 2 +- lib/ssh/src/ssh_sftpd.erl | 2 +- lib/ssh/src/ssh_sftpd_file_api.erl | 2 +- lib/ssh/src/ssh_subsystem_sup.erl | 2 +- lib/ssh/src/ssh_sup.erl | 2 +- lib/ssh/src/ssh_system_sup.erl | 2 +- lib/ssh/src/ssh_transport.erl | 2 +- lib/ssh/src/ssh_transport.hrl | 2 +- lib/ssh/src/sshc_sup.erl | 2 +- lib/ssh/src/sshd_sup.erl | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index f826fdfd9b..9e8d80c71f 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2004-2016. All Rights Reserved. +# Copyright Ericsson AB 2004-2017. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 3e80a04b70..5ebab43c30 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -1,7 +1,7 @@ % %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index cf2a359e6c..d6d412db43 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index f7fbd7ccad..d66a34c58a 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 26defcfdbd..a24664793b 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 9eb11a53dc..6cf659f830 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 4c4f61e036..62854346b0 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index c91c56435e..a8de5f9a2f 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 930ccecb4c..7e9ee78fd2 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 74e14a233f..342583306b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl index fad796f196..60ee8b7c73 100644 --- a/lib/ssh/src/ssh_connection_sup.erl +++ b/lib/ssh/src/ssh_connection_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 9431bf1817..7dfbfc3b4b 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 4498c70d34..6692432fcf 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 6828fd4760..8ba759ad60 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 21c0eabcd3..609040826f 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index f1f7b57e8d..c1558a19b1 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index b879116393..427edf01ab 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl index e444e52ac0..81f181f1fc 100644 --- a/lib/ssh/src/ssh_sftpd_file_api.erl +++ b/lib/ssh/src/ssh_sftpd_file_api.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index cf409ade6b..8db051095c 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl index 26574763e4..eaec7a54e4 100644 --- a/lib/ssh/src/ssh_sup.erl +++ b/lib/ssh/src/ssh_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 84b4cd3241..e70abf59c2 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7c7dda7a1e..25c64a4f25 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index faae6008f2..87c3719514 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index c71b81dc6d..133b2c6450 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index 449ba20d02..c23e65d955 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From efbae4afb84ef03364a1de349d98413211946ad4 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 9 May 2017 18:38:35 +0200 Subject: ssh: Option 'auth_methods' available not only in server but also in client --- lib/ssh/src/ssh_connection_handler.erl | 6 +----- lib/ssh/src/ssh_options.erl | 12 ++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 342583306b..39bd54869f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -434,11 +434,7 @@ init_ssh_record(Role, Socket, Opts) -> init_ssh_record(Role, _Socket, PeerAddr, Opts) -> KeyCb = ?GET_OPT(key_cb, Opts), - AuthMethods = - case Role of - server -> ?GET_OPT(auth_methods, Opts); - client -> undefined - end, + AuthMethods = ?GET_OPT(auth_methods, Opts), S0 = #ssh{role = Role, key_cb = KeyCb, opts = Opts, diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 0886d5b34d..6bd6ab74c3 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -293,12 +293,6 @@ default(server) -> class => user_options }, - {auth_methods, def} => - #{default => ?SUPPORTED_AUTH_METHODS, - chk => fun check_string/1, - class => user_options - }, - {auth_method_kb_interactive_data, def} => #{default => undefined, % Default value can be constructed when User is known chk => fun({S1,S2,S3,B}) -> @@ -583,6 +577,12 @@ default(common) -> }, %%%%% Undocumented + {auth_methods, def} => + #{default => ?SUPPORTED_AUTH_METHODS, + chk => fun check_string/1, + class => user_options + }, + {transport, def} => #{default => ?DEFAULT_TRANSPORT, chk => fun({A,B,C}) -> -- cgit v1.2.3 From 6e9f9cbfc1f69735788651369bf6e288e23fbced Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 10 May 2017 12:39:02 +0200 Subject: ssh: Doc option 'auth_methods' for client --- lib/ssh/src/ssh_options.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6bd6ab74c3..f98422c324 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -576,13 +576,13 @@ default(common) -> class => user_options }, -%%%%% Undocumented {auth_methods, def} => #{default => ?SUPPORTED_AUTH_METHODS, chk => fun check_string/1, class => user_options }, +%%%%% Undocumented {transport, def} => #{default => ?DEFAULT_TRANSPORT, chk => fun({A,B,C}) -> -- cgit v1.2.3 From 242dddbc918c87571013e7e0acd29b4abbe12911 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 10 May 2017 11:42:09 +0200 Subject: ssh: Better error checking for option 'auth_methods' --- lib/ssh/src/ssh_options.erl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index f98422c324..78f68dbcb1 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -578,7 +578,16 @@ default(common) -> {auth_methods, def} => #{default => ?SUPPORTED_AUTH_METHODS, - chk => fun check_string/1, + chk => fun(As) -> + try + Sup = string:tokens(?SUPPORTED_AUTH_METHODS, ","), + New = string:tokens(As, ","), + [] == [X || X <- New, + not lists:member(X,Sup)] + catch + _:_ -> false + end + end, class => user_options }, -- cgit v1.2.3 From de3c2e70b3bf3387877b6624b6772395664039d6 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 12 May 2017 16:11:13 +0200 Subject: ssh: Tests for ext-info extension (ext-info-c) --- lib/ssh/src/ssh_transport.erl | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 25c64a4f25..bd1cb4bd22 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -724,9 +724,21 @@ kex_ext_info(Role, Opts) -> end. ext_info_message(#ssh{role=client, - send_ext_info=true} = Ssh0) -> - %% FIXME: no extensions implemented - {ok, "", Ssh0}; + send_ext_info=true, + opts=Opts} = Ssh0) -> + %% Since no extension sent by the client is implemented, we add a fake one + %% to be able to test the framework. + %% Remove this when there is one and update ssh_protocol_SUITE whare it is used. + case proplists:get_value(ext_info_client, ?GET_OPT(tstflg,Opts)) of + true -> + Msg = #ssh_msg_ext_info{nr_extensions = 1, + data = [{"test@erlang.org", "Testing,PleaseIgnore"}] + }, + {SshPacket, Ssh} = ssh_packet(Msg, Ssh0), + {ok, SshPacket, Ssh}; + _ -> + {ok, "", Ssh0} + end; ext_info_message(#ssh{role=server, send_ext_info=true} = Ssh0) -> -- cgit v1.2.3 From 77371ab686d408e13dc8549085c0fdb9a5b30733 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 15 May 2017 13:58:27 +0200 Subject: ssh: ssh_file:user_key/2 checks ec keytype --- lib/ssh/src/ssh_file.erl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 6692432fcf..33792da38f 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -75,10 +75,7 @@ host_key(Algorithm, Opts) -> Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), case decode(File, Password) of {ok,Key} -> - case ssh_transport:valid_key_sha_alg(Key,Algorithm) of - true -> {ok,Key}; - false -> {error,bad_keytype_in_file} - end; + check_key_type(Key, Algorithm); {error,DecodeError} -> {error,DecodeError} end. @@ -104,10 +101,20 @@ is_host_key(Key, PeerName, Algorithm, Opts) -> user_key(Algorithm, Opts) -> File = file_name(user, identity_key_filename(Algorithm), Opts), Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), - decode(File, Password). + case decode(File, Password) of + {ok, Key} -> + check_key_type(Key, Algorithm); + Error -> + Error + end. %% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +check_key_type(Key, Algorithm) -> + case ssh_transport:valid_key_sha_alg(Key,Algorithm) of + true -> {ok,Key}; + false -> {error,bad_keytype_in_file} + end. file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key"; file_base_name('rsa-sha2-256' ) -> "ssh_host_rsa_key"; -- cgit v1.2.3 From df8ec436495f62cff4f433aaf9129505ee41e189 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 15 May 2017 19:30:14 +0200 Subject: ssh: Enable rsa-sha2-* Conflicts: lib/ssh/src/ssh_transport.erl --- lib/ssh/src/ssh_transport.erl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index bd1cb4bd22..aaec733f3c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -92,10 +92,7 @@ default_algorithms(cipher) -> default_algorithms(mac) -> supported_algorithms(mac, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); -default_algorithms(public_key) -> - supported_algorithms(public_key, ['rsa-sha2-256', - 'rsa-sha2-384', - 'rsa-sha2-512']); + default_algorithms(Alg) -> supported_algorithms(Alg, []). @@ -122,10 +119,9 @@ supported_algorithms(public_key) -> {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]}, - {'rsa-sha2-384', [{public_keys,rsa}, {hashs,sha384} ]}, {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]}, - {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1 ]); -- cgit v1.2.3 From f4cf6605e8ddf4accb553c155a77878031850128 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 5 May 2017 16:18:00 +0200 Subject: ssh: fix broken preferred_algorithms and pref_public_key_algs options --- lib/ssh/src/ssh_options.erl | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 78f68dbcb1..aebb5a7062 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -430,12 +430,9 @@ default(client) -> }, {pref_public_key_algs, def} => - #{default => - ssh_transport:supported_algorithms(public_key), - chk => - fun check_pref_public_key_algs/1, - class => - ssh + #{default => ssh_transport:default_algorithms(public_key), + chk => fun check_pref_public_key_algs/1, + class => user_options }, {dh_gex_limits, def} => @@ -817,16 +814,23 @@ 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())], + try alg_duplicates(Algs, [], []) of [] -> {true, - [try ssh_transport:supported_algorithms(Key) - of - DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs) - catch - _:_ -> error_in_check(Key,"Bad preferred_algorithms key") - end || {Key,Vals} <- Algs] + [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 -> -- cgit v1.2.3 From ebd2baf9c433d489aff66f14505b5c221ba04165 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 8 May 2017 14:57:11 +0200 Subject: ssh: Use 'server-sig-algs' for client's selection of algs --- lib/ssh/src/ssh_connection_handler.erl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 39bd54869f..6a6b9896cb 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1701,15 +1701,18 @@ handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> lists:foldl(fun ext_info/2, D0, Data). -ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client}=Ssh0}) -> +ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client, + userauth_pubkeys=ClientSigAlgs}=Ssh0}) -> %% Make strings to eliminate risk of beeing bombed with odd strings that fills the atom table: SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), - Ssh = Ssh0#ssh{userauth_pubkeys = - [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), - %% length of SigAlg is implicitly checked by member: - lists:member(SigAlg, SupportedAlgs) - ]}, - D0#data{ssh_params = Ssh}; + ServerSigAlgs = [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), + %% length of SigAlg is implicitly checked by the comparison + %% in member/2: + lists:member(SigAlg, SupportedAlgs) + ], + CommonAlgs = [Alg || Alg <- ServerSigAlgs, + lists:member(Alg, ClientSigAlgs)], + D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = CommonAlgs} }; ext_info(_, D0) -> %% Not implemented -- cgit v1.2.3 From 90de09b680d33bf4e048771381134ac8d7e0fa70 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 11 May 2017 15:19:18 +0200 Subject: ssh: select server-sig-algs from configured algos (preferred_algorithms) --- lib/ssh/src/ssh_transport.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index bd1cb4bd22..cebbec7792 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -741,9 +741,11 @@ ext_info_message(#ssh{role=client, end; ext_info_message(#ssh{role=server, - send_ext_info=true} = Ssh0) -> + send_ext_info=true, + opts = Opts} = Ssh0) -> AlgsList = lists:map(fun erlang:atom_to_list/1, - ssh_transport:default_algorithms(public_key)), + proplists:get_value(public_key, + ?GET_OPT(preferred_algorithms, Opts))), Msg = #ssh_msg_ext_info{nr_extensions = 1, data = [{"server-sig-algs", string:join(AlgsList,",")}] }, -- cgit v1.2.3 From 4d7ff0a8169141d18335638cf7c6e48d4c18cdf2 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 16 May 2017 11:52:45 +0200 Subject: ssh: disable rsa-sha2-* for clients because there is a bug in the client verification code for those algorithms --- lib/ssh/src/ssh_options.erl | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index aebb5a7062..12c0190082 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -392,6 +392,12 @@ default(server) -> class => user_options }, + {preferred_algorithms, def} => + #{default => ssh:default_algorithms(), + chk => fun check_preferred_algorithms/1, + class => user_options + }, + %%%%% Undocumented {infofun, def} => #{default => fun(_,_,_) -> void end, @@ -430,11 +436,26 @@ default(client) -> }, {pref_public_key_algs, def} => - #{default => ssh_transport:default_algorithms(public_key), + #{default => ssh_transport:default_algorithms(public_key) -- ['rsa-sha2-256', + 'rsa-sha2-512'], chk => fun check_pref_public_key_algs/1, class => user_options }, + {preferred_algorithms, def} => + #{default => [{K,Vs} || {K,Vs0} <- ssh:default_algorithms(), + Vs <- [case K of + public_key -> + Vs0 -- ['rsa-sha2-256', + 'rsa-sha2-512']; + _ -> + Vs0 + end] + ], + chk => fun check_preferred_algorithms/1, + class => user_options + }, + {dh_gex_limits, def} => #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? chk => fun({Min,I,Max}) -> @@ -500,12 +521,6 @@ default(common) -> class => user_options }, - {preferred_algorithms, def} => - #{default => ssh:default_algorithms(), - chk => fun check_preferred_algorithms/1, - class => user_options - }, - {id_string, def} => #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 chk => fun(random) -> -- cgit v1.2.3 From 594d84311dd22658df695f238ac562fdcba9f060 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 May 2017 20:52:53 +0200 Subject: Revert "ssh: disable rsa-sha2-* for clients" This reverts commit 4d7ff0a8169141d18335638cf7c6e48d4c18cdf2. --- lib/ssh/src/ssh_options.erl | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 12c0190082..aebb5a7062 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -392,12 +392,6 @@ default(server) -> class => user_options }, - {preferred_algorithms, def} => - #{default => ssh:default_algorithms(), - chk => fun check_preferred_algorithms/1, - class => user_options - }, - %%%%% Undocumented {infofun, def} => #{default => fun(_,_,_) -> void end, @@ -436,26 +430,11 @@ default(client) -> }, {pref_public_key_algs, def} => - #{default => ssh_transport:default_algorithms(public_key) -- ['rsa-sha2-256', - 'rsa-sha2-512'], + #{default => ssh_transport:default_algorithms(public_key), chk => fun check_pref_public_key_algs/1, class => user_options }, - {preferred_algorithms, def} => - #{default => [{K,Vs} || {K,Vs0} <- ssh:default_algorithms(), - Vs <- [case K of - public_key -> - Vs0 -- ['rsa-sha2-256', - 'rsa-sha2-512']; - _ -> - Vs0 - end] - ], - chk => fun check_preferred_algorithms/1, - class => user_options - }, - {dh_gex_limits, def} => #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? chk => fun({Min,I,Max}) -> @@ -521,6 +500,12 @@ default(common) -> class => user_options }, + {preferred_algorithms, def} => + #{default => ssh:default_algorithms(), + chk => fun check_preferred_algorithms/1, + class => user_options + }, + {id_string, def} => #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0 chk => fun(random) -> -- cgit v1.2.3 From c99b6f0aa70457453b37533adf6d3872f7009fac Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 May 2017 10:03:34 +0200 Subject: ssh: Handle if server-sig-algs and client has empty intersection In case server-sig-algs names only algorithms unknown to the client, the client will try with the ones it knows --- lib/ssh/src/ssh_connection_handler.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 6a6b9896cb..a77cfe51b5 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1712,7 +1712,12 @@ ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client, ], CommonAlgs = [Alg || Alg <- ServerSigAlgs, lists:member(Alg, ClientSigAlgs)], - D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = CommonAlgs} }; + SelectedAlgs = + case CommonAlgs of + [] -> ClientSigAlgs; % server-sig-algs value is just an advice + _ -> CommonAlgs + end, + D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = SelectedAlgs} }; ext_info(_, D0) -> %% Not implemented -- cgit v1.2.3 From 3507ea008839ad68dc16060a2696e3efde551684 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 May 2017 20:33:14 +0200 Subject: ssh: fix the rsa-sha2-* hostkey verify error --- lib/ssh/src/ssh_message.erl | 4 ++-- lib/ssh/src/ssh_transport.erl | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 609040826f..4f2eeca026 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -598,8 +598,8 @@ decode_kex_init(<>, Acc, N) -> %%% Signature decode/encode %%% -decode_signature(<>) -> - Signature. +decode_signature(<>) -> + {binary_to_list(Alg), Signature}. encode_signature({#'RSAPublicKey'{},Sign}, Signature) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 1a15798080..412f5de9de 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -776,16 +776,20 @@ extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID}, {#'ECPoint'{point=Q}, {namedCurve,OID}}. -verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, Signature) -> - case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of - false -> - {error, bad_signature}; - true -> - known_host_key(SSH, PublicKey, public_algo(PublicKey)) +verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature}) -> + case atom_to_list(Alg#alg.hkey) of + AlgStr -> + case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of + false -> + {error, bad_signature}; + true -> + known_host_key(SSH, PublicKey, public_algo(PublicKey)) + end; + _ -> + {error, bad_signature_name} end. - accepted_host(Ssh, PeerName, Public, Opts) -> case ?GET_OPT(silently_accept_hosts, Opts) of -- cgit v1.2.3 From 8343f1a9c8b6b9e506a298d286eb5e480f516fd3 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 18 May 2017 17:53:27 +0200 Subject: ssh: remove extra options copy from internal state --- lib/ssh/src/ssh_connection_handler.erl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 6a6b9896cb..5ca040f7ec 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -346,7 +346,7 @@ renegotiate_data(ConnectionHandler) -> | undefined, last_size_rekey = 0 :: non_neg_integer(), event_queue = [] :: list(), - opts :: ssh_options:options(), +% opts :: ssh_options:options(), inet_initial_recbuf_size :: pos_integer() | undefined }). @@ -398,8 +398,7 @@ init([Role,Socket,Opts]) -> transport_protocol = Protocol, transport_cb = Callback, transport_close_tag = CloseTag, - ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts), - opts = Opts + ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts) }, D = case Role of client -> @@ -1012,7 +1011,7 @@ handle_event(cast, renegotiate, _, _) -> handle_event(cast, data_size, {connected,Role}, D) -> {ok, [{send_oct,Sent0}]} = inet:getstat(D#data.socket, [send_oct]), Sent = Sent0 - D#data.last_size_rekey, - MaxSent = ?GET_OPT(rekey_limit, D#data.opts), + MaxSent = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), case Sent >= MaxSent of true -> @@ -1862,7 +1861,7 @@ get_repl(X, Acc) -> exit({get_repl,X,Acc}). %%%---------------------------------------------------------------- --define(CALL_FUN(Key,D), catch (?GET_OPT(Key, D#data.opts)) ). +-define(CALL_FUN(Key,D), catch (?GET_OPT(Key, (D#data.ssh_params)#ssh.opts)) ). disconnect_fun({disconnect,Msg}, D) -> ?CALL_FUN(disconnectfun,D)(Msg); disconnect_fun(Reason, D) -> ?CALL_FUN(disconnectfun,D)(Reason). @@ -1912,7 +1911,7 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts, %%% channels open for a while. cache_init_idle_timer(D) -> - case ?GET_OPT(idle_time, D#data.opts) of + case ?GET_OPT(idle_time, (D#data.ssh_params)#ssh.opts) of infinity -> D#data{idle_timer_value = infinity, idle_timer_ref = infinity % A flag used later... -- cgit v1.2.3 From 580dc012238b4fd2839730c0c44edaef55dc9b4b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 22 May 2017 15:03:09 +0200 Subject: ssh: fix ssh_property_test --- lib/ssh/src/ssh_message.erl | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 4f2eeca026..b1fc05ae33 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -611,13 +611,3 @@ encode_signature({{#'ECPoint'{}, {namedCurve,OID}},_}, Signature) -> CurveName = public_key:oid2ssh_curvename(OID), <>), ?Ebinary(Signature)>>. -%% encode_signature(#'RSAPublicKey'{}, Signature) -> -%% SignName = <<"ssh-rsa">>, -%% <>; -%% encode_signature({_, #'Dss-Parms'{}}, Signature) -> -%% <>), ?Ebinary(Signature)>>; -%% encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) -> -%% CurveName = public_key:oid2ssh_curvename(OID), -%% <>), ?Ebinary(Signature)>>. - - -- cgit v1.2.3 From 917712f10dd5e8dea17d12f7c9835680ad32ba9f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 19 May 2017 16:48:08 +0200 Subject: ssh: ssh_dbg print some server-sig-algs info --- lib/ssh/src/ssh_dbg.erl | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 7dfbfc3b4b..820d7ec61b 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -58,7 +58,8 @@ dbg_ssh_messages() -> 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:tp(ssh_transport,handle_hello_version,1, x), + dbg:tpl(ssh_connection_handler,ext_info,2, x). %%%---------------------------------------------------------------- stop() -> @@ -90,6 +91,28 @@ msg_formater({trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS} msg_formater({trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> D; +msg_formater({trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",_SigAlgs},State]},TS}, D) -> + try lists:keyfind(ssh, 1, tuple_to_list(State)) of + false -> + D; + #ssh{userauth_pubkeys = PKs} -> + fmt("~n~s ~p Client suggests ~p~n", [ts(TS),Pid,PKs], D) + catch + _:_ -> + D + end; + +msg_formater({trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},State,TS}, D) -> + try lists:keyfind(ssh, 1, tuple_to_list(State)) of + false -> + D; + #ssh{userauth_pubkeys = PKs} -> + fmt("~n~s ~p Client will try public keys ~p~n", [ts(TS),Pid,PKs], D) + catch + _:_ -> + D + end; + msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D); -- cgit v1.2.3 From d1a31c78d75c16a360ee1de973660b9ec1caeb58 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 22 May 2017 16:49:37 +0200 Subject: ssh: Undocumented ssh_dbg extended with auth/0 auth/1 auth/2 ct_auth/0 ct_messages/0 --- lib/ssh/src/ssh_dbg.erl | 134 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 102 insertions(+), 32 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 820d7ec61b..003b3856e6 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -22,9 +22,10 @@ -module(ssh_dbg). --export([messages/0, - messages/1, - messages/2, +-export([messages/0, messages/1, messages/2, + ct_messages/0, + auth/0, auth/1, auth/2, + ct_auth/0, stop/0 ]). @@ -43,15 +44,33 @@ messages() -> messages(fun(String,_D) -> io:format(String) end). +ct_messages() -> + messages(fun(String,_D) -> ct:log(String,[]) end). + messages(Write) when is_function(Write,2) -> messages(Write, fun(X) -> X end). messages(Write, MangleArg) when is_function(Write,2), is_function(MangleArg,1) -> - catch dbg:start(), - setup_tracer(Write, MangleArg), - dbg:p(new,[c,timestamp]), - dbg_ssh_messages(). + cond_start(msg, Write, MangleArg), + dbg_ssh_messages(), + dbg_ssh_auth(). + + +auth() -> + auth(fun(String,_D) -> io:format(String) end). + +ct_auth() -> + auth(fun(String,_D) -> ct:log(String,[]) end). + +auth(Write) when is_function(Write,2) -> + auth(Write, fun(X) -> X end). + +auth(Write, MangleArg) when is_function(Write,2), + is_function(MangleArg,1) -> + cond_start(auth, Write, MangleArg), + dbg_ssh_auth(). + dbg_ssh_messages() -> dbg:tp(ssh_message,encode,1, x), @@ -61,80 +80,131 @@ dbg_ssh_messages() -> 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]). + %%%---------------------------------------------------------------- stop() -> dbg:stop(). %%%================================================================ -msg_formater({trace_ts,Pid,call,{ssh_message,encode,[Msg]},TS}, D) -> +cond_start(Type, Write, MangleArg) -> + try + dbg:start(), + setup_tracer(Type, Write, MangleArg), + dbg:p(new,[c,timestamp]) + catch + _:_ -> ok + end. + + +msg_formater(msg, {trace_ts,Pid,call,{ssh_message,encode,[Msg]},TS}, D) -> fmt("~n~s SEND ~p ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D); -msg_formater({trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) -> +msg_formater(msg, {trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) -> D; -msg_formater({trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> +msg_formater(msg, {trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) -> D; -msg_formater({trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,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) -> + fmt("~n~s ~p Client login FAILURE. Try ~s~n", [ts(TS),Pid,As], D); -msg_formater({trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_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); + + +msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) -> D; -msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) -> +msg_formater(_, {trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) -> fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D); -msg_formater({trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> +msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) -> D; -msg_formater({trace_ts,Pid,return_from,{ssh_transport,hello_version_msg,1},Hello,TS}, D) -> +msg_formater(_, {trace_ts,Pid,return_from,{ssh_transport,hello_version_msg,1},Hello,TS}, D) -> fmt("~n~s ~p TCP SEND HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); -msg_formater({trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS}, D) -> +msg_formater(_, {trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS}, D) -> fmt("~n~s ~p RECV HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D); -msg_formater({trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> +msg_formater(_, {trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> D; -msg_formater({trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",_SigAlgs},State]},TS}, D) -> +msg_formater(_, {trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",_SigAlgs},State]},TS}, D) -> try lists:keyfind(ssh, 1, tuple_to_list(State)) of false -> D; #ssh{userauth_pubkeys = PKs} -> - fmt("~n~s ~p Client suggests ~p~n", [ts(TS),Pid,PKs], D) + fmt("~n~s ~p Client got suggestion to use user public key sig-algs~n ~p~n", [ts(TS),Pid,PKs], D) catch _:_ -> D end; -msg_formater({trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},State,TS}, D) -> +msg_formater(_, {trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},State,TS}, D) -> try lists:keyfind(ssh, 1, tuple_to_list(State)) of false -> D; #ssh{userauth_pubkeys = PKs} -> - fmt("~n~s ~p Client will try public keys ~p~n", [ts(TS),Pid,PKs], D) + fmt("~n~s ~p Client will try user public key sig-algs~n ~p~n", [ts(TS),Pid,PKs], D) catch _:_ -> D end; -msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> +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,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); +msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,password_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> + fmt("~s ~p User ~p can't login with password~n", [ts(TS),Pid,User], D); + +msg_formater(_, {trace_ts,Pid,call,{ssh_auth,keyboard_interactive_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); +msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,keyboard_interactive_msg,1},{not_ok,#ssh{user=User}},TS}, D) -> + fmt("~s ~p User ~p can't login with keyboard_interactive password~n", [ts(TS),Pid,User], D); + +msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) -> fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D); -msg_formater({trace_ts,Pid,send,{tcp,Sock,Bytes},Dest,TS}, D) -> +msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Dest,TS}, D) -> fmt("~n~s ~p TCP SEND from ~p TO ~p~n ~p~n", [ts(TS),Pid,Sock,Dest, shrink_bin(Bytes)], D); -msg_formater({trace_ts,Pid,send,ErlangMsg,Dest,TS}, D) -> +msg_formater(msg, {trace_ts,Pid,send,ErlangMsg,Dest,TS}, D) -> fmt("~n~s ~p ERL MSG SEND TO ~p~n ~p~n", [ts(TS),Pid,Dest, shrink_bin(ErlangMsg)], D); -msg_formater({trace_ts,Pid,'receive',{tcp,Sock,Bytes},TS}, D) -> +msg_formater(msg, {trace_ts,Pid,'receive',{tcp,Sock,Bytes},TS}, D) -> fmt("~n~s ~p TCP RECEIVE on ~p~n ~p~n", [ts(TS),Pid,Sock,shrink_bin(Bytes)], D); -msg_formater({trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> +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(M, D) -> - fmt("~nDBG ~n~p~n", [shrink_bin(M)], 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(_, _, D) -> + D. fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) -> @@ -146,9 +216,9 @@ ts({_,_,Usec}=Now) -> ts(_) -> "-". %%%---------------------------------------------------------------- -setup_tracer(Write, MangleArg) -> +setup_tracer(Type, Write, MangleArg) -> Handler = fun(Arg, D) -> - msg_formater(MangleArg(Arg), D) + msg_formater(Type, MangleArg(Arg), D) end, InitialData = #data{writer = Write}, {ok,_} = dbg:tracer(process, {Handler, InitialData}), -- cgit v1.2.3 From 8611454d37da15627a79507ca62bf25843e62493 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 23 May 2017 13:46:43 +0200 Subject: ssh: Improve 'server-sig-algs' handling in client --- lib/ssh/src/ssh_auth.erl | 59 +++++++++++++---------- lib/ssh/src/ssh_connection_handler.erl | 88 +++++++++++++++++++++++----------- 2 files changed, 95 insertions(+), 52 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 6cf659f830..ac64a7bf14 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -28,7 +28,8 @@ -include("ssh_auth.hrl"). -include("ssh_transport.hrl"). --export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, +-export([get_public_key/2, + 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/2, handle_userauth_info_response/2 @@ -136,41 +137,49 @@ keyboard_interactive_msg([#ssh{user = User, Ssh) end. -publickey_msg([SigAlg, #ssh{user = User, - session_id = SessionId, - service = Service, - opts = Opts} = Ssh]) -> - Hash = ssh_transport:sha(SigAlg), + +get_public_key(SigAlg, #ssh{opts = Opts}) -> KeyAlg = key_alg(SigAlg), {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of - {ok, PrivKey} -> - SigAlgStr = atom_to_list(SigAlg), + {ok, PrivKey} -> try Key = ssh_transport:extract_public_key(PrivKey), public_key:ssh_encode(Key, ssh2_pubkey) of - PubKeyBlob -> - SigData = build_sig_data(SessionId, User, Service, - PubKeyBlob, SigAlgStr), - Sig = ssh_transport:sign(SigData, Hash, PrivKey), - SigBlob = list_to_binary([?string(SigAlgStr), - ?binary(Sig)]), - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "publickey", - data = [?TRUE, - ?string(SigAlgStr), - ?binary(PubKeyBlob), - ?binary(SigBlob)]}, - Ssh) + PubKeyBlob -> {ok,{PrivKey,PubKeyBlob}} catch _:_ -> - {not_ok, Ssh} + not_ok end; - _Error -> + _Error -> + not_ok + end. + + +publickey_msg([SigAlg, #ssh{user = User, + session_id = SessionId, + service = Service} = Ssh]) -> + case get_public_key(SigAlg, Ssh) of + {ok, {PrivKey,PubKeyBlob}} -> + SigAlgStr = atom_to_list(SigAlg), + SigData = build_sig_data(SessionId, User, Service, + PubKeyBlob, SigAlgStr), + Hash = ssh_transport:sha(SigAlg), + Sig = ssh_transport:sign(SigData, Hash, PrivKey), + SigBlob = list_to_binary([?string(SigAlgStr), + ?binary(Sig)]), + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "publickey", + data = [?TRUE, + ?string(SigAlgStr), + ?binary(PubKeyBlob), + ?binary(SigBlob)]}, + Ssh); + _ -> {not_ok, Ssh} end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f1ce337947..4c6aff5c24 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -453,16 +453,20 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> PeerName0 when is_list(PeerName0) -> PeerName0 end, - S0#ssh{c_vsn = Vsn, - c_version = Version, - io_cb = case ?GET_OPT(user_interaction, Opts) of - true -> ssh_io; - false -> ssh_no_io - end, - userauth_pubkeys = ?GET_OPT(pref_public_key_algs, Opts), - userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), - peer = {PeerName, PeerAddr} - }; + S1 = + S0#ssh{c_vsn = Vsn, + c_version = Version, + io_cb = case ?GET_OPT(user_interaction, Opts) of + true -> ssh_io; + false -> ssh_no_io + end, + userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), + peer = {PeerName, PeerAddr} + }, + S1#ssh{userauth_pubkeys = [K || K <- ?GET_OPT(pref_public_key_algs, Opts), + is_usable_user_pubkey(K, S1) + ] + }; server -> S0#ssh{s_vsn = Vsn, @@ -1700,28 +1704,58 @@ handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> lists:foldl(fun ext_info/2, D0, Data). -ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client, - userauth_pubkeys=ClientSigAlgs}=Ssh0}) -> - %% Make strings to eliminate risk of beeing bombed with odd strings that fills the atom table: - SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), - ServerSigAlgs = [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), - %% length of SigAlg is implicitly checked by the comparison - %% in member/2: - lists:member(SigAlg, SupportedAlgs) - ], - CommonAlgs = [Alg || Alg <- ServerSigAlgs, - lists:member(Alg, ClientSigAlgs)], - SelectedAlgs = - case CommonAlgs of - [] -> ClientSigAlgs; % server-sig-algs value is just an advice - _ -> CommonAlgs - end, - D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = SelectedAlgs} }; +ext_info({"server-sig-algs",SigAlgsStr}, + D0 = #data{ssh_params=#ssh{role=client, + userauth_pubkeys=ClientSigAlgs}=Ssh0}) -> + %% ClientSigAlgs are the pub_key algortithms that: + %% 1) is usable, that is, the user has such a public key and + %% 2) is either the default list or set by the caller + %% with the client option 'pref_public_key_algs' + %% + %% The list is already checked for duplicates. + + SigAlgs = [A || Astr <- string:tokens(SigAlgsStr, ","), + A <- try [list_to_existing_atom(Astr)] + %% list_to_existing_atom will fail for unknown algorithms + catch _:_ -> [] + end], + + CommonAlgs = [A || A <- SigAlgs, + lists:member(A, ClientSigAlgs)], + + %% Re-arrange the client supported public-key algorithms so that the server + %% preferred ones are tried first. + %% Trying algorithms not mentioned by the server is ok, since the server can't know + %% if the client supports 'server-sig-algs' or not. + + D0#data{ + ssh_params = + Ssh0#ssh{ + userauth_pubkeys = + CommonAlgs ++ (ClientSigAlgs -- CommonAlgs) + }}; + + %% If there are algorithms common to the client and the server, use them. + %% Otherwise try with ones that the client supports. The server-sig-alg + %% list is a suggestion, not an order. + %% case CommonAlgs of + %% [_|_] -> + %% D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = CommonAlgs}}; + %% [] -> + %% D0 + %% end; ext_info(_, D0) -> %% Not implemented D0. +%%%---------------------------------------------------------------- +is_usable_user_pubkey(A, Ssh) -> + case ssh_auth:get_public_key(A, Ssh) of + {ok,_} -> true; + _ -> false + end. + %%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of -- cgit v1.2.3 From 9c4d91f4726ff84df8877fc6c73edcd116775a52 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 24 May 2017 15:04:43 +0200 Subject: ssh: ssh_options checks 'pref_public_key_algs' for dubblets --- lib/ssh/src/ssh_options.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index aebb5a7062..7eeed70739 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -674,7 +674,11 @@ check_pref_public_key_algs(V) -> PKs = ssh_transport:supported_algorithms(public_key), CHK = fun(A, Ack) -> case lists:member(A, PKs) of - true -> [A|Ack]; + true -> + case lists:member(A,Ack) of + false -> [A|Ack]; + true -> Ack % Remove duplicates + end; false -> error_in_check(A, "Not supported public key") end end, -- cgit v1.2.3 From b4327e257147a64fc088d1448132f5794bad879f Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 24 May 2017 15:11:03 +0200 Subject: ssh: Change printouts for ssh_dbg:auth() This reverts commit 4ee80fd8738393bf581e0393416befda1ca621b6. --- lib/ssh/src/ssh_dbg.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 003b3856e6..d5d4ab04c3 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -136,12 +136,13 @@ msg_formater(_, {trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]}, msg_formater(_, {trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) -> D; -msg_formater(_, {trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",_SigAlgs},State]},TS}, D) -> +msg_formater(_, {trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",SigAlgs},State]},TS}, D) -> try lists:keyfind(ssh, 1, tuple_to_list(State)) of false -> D; #ssh{userauth_pubkeys = PKs} -> - fmt("~n~s ~p Client got suggestion to use user public key sig-algs~n ~p~n", [ts(TS),Pid,PKs], D) + fmt("~n~s ~p Client got suggestion to use user public key sig-algs~n ~p~n and can use~n ~p~n", + [ts(TS),Pid,string:tokens(SigAlgs,","),PKs], D) catch _:_ -> D -- cgit v1.2.3 From a17b94cc89dd14cf1027e05a0b3def68f2c1e96c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Jun 2017 13:36:24 +0200 Subject: ssh: Removed out-commented code --- lib/ssh/src/ssh_connection_handler.erl | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4c6aff5c24..8d3ddb09a4 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1735,16 +1735,6 @@ ext_info({"server-sig-algs",SigAlgsStr}, CommonAlgs ++ (ClientSigAlgs -- CommonAlgs) }}; - %% If there are algorithms common to the client and the server, use them. - %% Otherwise try with ones that the client supports. The server-sig-alg - %% list is a suggestion, not an order. - %% case CommonAlgs of - %% [_|_] -> - %% D0#data{ssh_params = Ssh0#ssh{userauth_pubkeys = CommonAlgs}}; - %% [] -> - %% D0 - %% end; - ext_info(_, D0) -> %% Not implemented D0. -- cgit v1.2.3 From 8ce5d8239bc49ca72df11ca0a614dfa01fbf931c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 1 Jun 2017 16:02:12 +0200 Subject: ssh: Restructure internal tool ssh_dbg The need for more trace patterns requires a somewhat different structure. It was previoiusly a bit difficult to use in e.g. test suites. Now it is easier. --- lib/ssh/src/ssh_dbg.erl | 88 ++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) (limited to 'lib/ssh/src') diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index d5d4ab04c3..3f742ad9b6 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -22,10 +22,8 @@ -module(ssh_dbg). --export([messages/0, messages/1, messages/2, - ct_messages/0, - auth/0, auth/1, auth/2, - ct_auth/0, +-export([messages/0, messages/1, messages/2, messages/3, + auth/0, auth/1, auth/2, auth/3, stop/0 ]). @@ -37,50 +35,52 @@ -include("ssh_connect.hrl"). -include("ssh_auth.hrl"). --record(data, { - writer, - acc = []}). %%%================================================================ -messages() -> - messages(fun(String,_D) -> io:format(String) end). +messages() -> start(msg). +messages(F) -> start(msg,F). +messages(F,X) -> start(msg,F,X). +messages(F,M,I) -> start(msg,F,M,I). -ct_messages() -> - messages(fun(String,_D) -> ct:log(String,[]) end). +auth() -> start(auth). +auth(F) -> start(auth,F). +auth(F,X) -> start(auth,F,X). +auth(F,M,I) -> start(auth,F,M,I). -messages(Write) when is_function(Write,2) -> - messages(Write, fun(X) -> X end). - -messages(Write, MangleArg) when is_function(Write,2), - is_function(MangleArg,1) -> - cond_start(msg, Write, MangleArg), - dbg_ssh_messages(), - dbg_ssh_auth(). +stop() -> dbg:stop(). +%%%---------------------------------------------------------------- +start(Type) -> start(Type, fun io:format/2). -auth() -> - auth(fun(String,_D) -> io:format(String) end). +start(Type, F) when is_function(F,2) -> start(Type, fmt_fun(F)); +start(Type, F) when is_function(F,3) -> start(Type, F, id_fun()). -ct_auth() -> - auth(fun(String,_D) -> ct:log(String,[]) end). +start(Type, WriteFun, MangleArgFun) when is_function(WriteFun, 3), + is_function(MangleArgFun, 1) -> + start(Type, WriteFun, MangleArgFun, []); +start(Type, WriteFun, InitValue) -> + start(Type, WriteFun, id_fun(), InitValue). -auth(Write) when is_function(Write,2) -> - auth(Write, fun(X) -> X end). +start(Type, WriteFun, MangleArgFun, InitValue) when is_function(WriteFun, 3), + is_function(MangleArgFun, 1) -> + cond_start(Type, WriteFun, MangleArgFun, InitValue), + dbg_ssh(Type). -auth(Write, MangleArg) when is_function(Write,2), - is_function(MangleArg,1) -> - cond_start(auth, Write, MangleArg), - dbg_ssh_auth(). +%%%---------------------------------------------------------------- +fmt_fun(F) -> fun(Fmt,Args,Data) -> F(Fmt,Args), Data end. +id_fun() -> fun(X) -> X end. -dbg_ssh_messages() -> +%%%---------------------------------------------------------------- +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:tpl(ssh_connection_handler,ext_info,2, x); -dbg_ssh_auth() -> +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), @@ -89,15 +89,11 @@ dbg_ssh_auth() -> lists:foreach(fun(F) -> dbg:tp(ssh_auth, F, x) end, [publickey_msg, password_msg, keyboard_interactive_msg]). -%%%---------------------------------------------------------------- -stop() -> - dbg:stop(). - %%%================================================================ -cond_start(Type, Write, MangleArg) -> +cond_start(Type, WriteFun, MangleArgFun, Init) -> try dbg:start(), - setup_tracer(Type, Write, MangleArg), + setup_tracer(Type, WriteFun, MangleArgFun, Init), dbg:p(new,[c,timestamp]) catch _:_ -> ok @@ -207,21 +203,25 @@ msg_formater(msg, {trace_ts,Pid,'receive',ErlangMsg,TS}, D) -> msg_formater(_, _, D) -> D. +%%%---------------------------------------------------------------- +-record(data, {writer, + acc}). -fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) -> - D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}. +fmt(Fmt, Args, D=#data{writer=Write, acc=Acc}) -> + D#data{acc = Write(Fmt,Args,Acc)}. ts({_,_,Usec}=Now) -> {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now), io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); ts(_) -> "-". -%%%---------------------------------------------------------------- -setup_tracer(Type, Write, MangleArg) -> + +setup_tracer(Type, WriteFun, MangleArgFun, Init) -> Handler = fun(Arg, D) -> - msg_formater(Type, MangleArg(Arg), D) + msg_formater(Type, MangleArgFun(Arg), D) end, - InitialData = #data{writer = Write}, + InitialData = #data{writer = WriteFun, + acc = Init}, {ok,_} = dbg:tracer(process, {Handler, InitialData}), ok. -- cgit v1.2.3