diff options
Diffstat (limited to 'lib/ssh/src/ssh_connection_handler.erl')
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 326 |
1 files changed, 185 insertions, 141 deletions
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 0f6162db60..ca63d2194f 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -33,7 +33,7 @@ -include("ssh_transport.hrl"). -include("ssh_auth.hrl"). -include("ssh_connect.hrl"). - +-compile(export_all). -export([start_link/3]). %% Internal application API @@ -70,6 +70,8 @@ undecoded_packet_length, % integer() key_exchange_init_msg, % #ssh_msg_kexinit{} renegotiate = false, % boolean() + last_size_rekey = 0, + event_queue = [], connection_queue, address, port, @@ -82,6 +84,11 @@ {next_state, state_name(), term(), timeout()} | {stop, term(), term()}. +-type gen_fsm_sync_return() :: {next_state, state_name(), term()} | + {next_state, state_name(), term(), timeout()} | + {reply, term(), state_name(), term()} | + {stop, term(), term(), term()}. + %%==================================================================== %% Internal application API %%==================================================================== @@ -432,9 +439,7 @@ key_exchange(#ssh_msg_kex_dh_gex_reply{} = Msg, new_keys(#ssh_msg_newkeys{} = Msg, #state{ssh_params = Ssh0} = State0) -> {ok, Ssh} = ssh_transport:handle_new_keys(Msg, Ssh0), - {NextStateName, State} = - after_new_keys(State0#state{ssh_params = Ssh}), - {next_state, NextStateName, next_packet(State)}. + after_new_keys(next_packet(State0#state{ssh_params = Ssh})). %%-------------------------------------------------------------------- -spec userauth(#ssh_msg_service_request{} | #ssh_msg_service_accept{} | @@ -558,11 +563,13 @@ userauth(#ssh_msg_userauth_banner{message = Msg}, -spec connected({#ssh_msg_kexinit{}, binary()}, %%| %% #ssh_msg_kexdh_init{}, #state{}) -> gen_fsm_state_return(). %%-------------------------------------------------------------------- -connected({#ssh_msg_kexinit{}, _Payload} = Event, State) -> - kexinit(Event, State#state{renegotiate = true}). -%% ; -%% connected(#ssh_msg_kexdh_init{} = Event, State) -> -%% key_exchange(Event, State#state{renegotiate = true}). +connected({#ssh_msg_kexinit{}, _Payload} = Event, #state{ssh_params = Ssh0} = State0) -> + {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), + State = State0#state{ssh_params = Ssh, + key_exchange_init_msg = KeyInitMsg, + renegotiate = true}, + send_msg(SshPacket, State), + kexinit(Event, State). %%-------------------------------------------------------------------- -spec handle_event(#ssh_msg_disconnect{} | #ssh_msg_ignore{} | #ssh_msg_debug{} | @@ -591,33 +598,6 @@ handle_event(#ssh_msg_debug{always_display = Display, message = DbgMsg, language handle_event(#ssh_msg_unimplemented{}, StateName, State) -> {next_state, StateName, next_packet(State)}; -handle_event({adjust_window, ChannelId, Bytes}, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> - State = - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{recv_window_size = WinSize, remote_id = Id} = Channel -> - ssh_channel:cache_update(Cache, Channel#channel{recv_window_size = - WinSize + Bytes}), - Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes), - send_replies([{connection_reply, Msg}], State0); - undefined -> - State0 - end, - {next_state, StateName, next_packet(State)}; - -handle_event({reply_request, success, ChannelId}, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> - State = case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = RemoteId} -> - Msg = ssh_connection:channel_success_msg(RemoteId), - send_replies([{connection_reply, Msg}], State0); - undefined -> - State0 - end, - {next_state, StateName, State}; - handle_event(renegotiate, connected, #state{ssh_params = Ssh0} = State) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), @@ -629,13 +609,13 @@ handle_event(renegotiate, connected, #state{ssh_params = Ssh0} renegotiate = true})}; handle_event(renegotiate, StateName, State) -> - timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]), - %% Allready in keyexcahange so ignore + %% Already in key-exchange so safe to ignore {next_state, StateName, State}; %% Rekey due to sent data limit reached? handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) -> - {ok, [{send_oct,Sent}]} = inet:getstat(State#state.socket, [send_oct]), + {ok, [{send_oct,Sent0}]} = inet:getstat(State#state.socket, [send_oct]), + Sent = Sent0 - State#state.last_size_rekey, MaxSent = proplists:get_value(rekey_limit, State#state.opts, 1024000000), timer:apply_after(?REKEY_DATA_TIMOUT, gen_fsm, send_all_state_event, [self(), data_size]), case Sent >= MaxSent of @@ -645,11 +625,44 @@ handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) -> {next_state, kexinit, next_packet(State#state{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg, - renegotiate = true})}; + renegotiate = true, + last_size_rekey = Sent0})}; _ -> {next_state, connected, next_packet(State)} end; handle_event(data_size, StateName, State) -> + %% Already in key-exchange so safe to ignore + {next_state, StateName, State}; + +handle_event(Event, StateName, State) when StateName /= connected -> + Events = [{event, Event} | State#state.event_queue], + {next_state, StateName, State#state{event_queue = Events}}; + +handle_event({adjust_window, ChannelId, Bytes}, StateName, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + State = + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{recv_window_size = WinSize, remote_id = Id} = Channel -> + ssh_channel:cache_update(Cache, Channel#channel{recv_window_size = + WinSize + Bytes}), + Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes), + send_replies([{connection_reply, Msg}], State0); + undefined -> + State0 + end, + {next_state, StateName, next_packet(State)}; + +handle_event({reply_request, success, ChannelId}, StateName, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + State = case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = RemoteId} -> + Msg = ssh_connection:channel_success_msg(RemoteId), + send_replies([{connection_reply, Msg}], State0); + undefined -> + State0 + end, {next_state, StateName, State}; handle_event({request, ChannelPid, ChannelId, Type, Data}, StateName, State0) -> @@ -680,8 +693,65 @@ handle_event({unknown, Data}, StateName, State) -> sockname]} | {channel_info, channel_id(), [recv_window | send_window]} | {close, channel_id()} | stop, term(), state_name(), #state{}) - -> gen_fsm_state_return(). + -> gen_fsm_sync_return(). %%-------------------------------------------------------------------- +handle_sync_event(get_print_info, _From, StateName, State) -> + Reply = + try + {inet:sockname(State#state.socket), + inet:peername(State#state.socket) + } + of + {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])}; + _ -> {{"-",0},"-"} + catch + _:_ -> {{"?",0},"?"} + end, + {reply, Reply, StateName, State}; + +handle_sync_event({connection_info, Options}, _From, StateName, State) -> + Info = ssh_info(Options, State, []), + {reply, Info, StateName, State}; + +handle_sync_event({channel_info, ChannelId, Options}, _From, StateName, + #state{connection_state = #connection{channel_cache = Cache}} = State) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{} = Channel -> + Info = ssh_channel_info(Options, Channel, []), + {reply, Info, StateName, State}; + undefined -> + {reply, [], StateName, State} + end; + +handle_sync_event({info, ChannelPid}, _From, StateName, + #state{connection_state = + #connection{channel_cache = Cache}} = State) -> + Result = ssh_channel:cache_foldl( + fun(Channel, Acc) when ChannelPid == all; + Channel#channel.user == ChannelPid -> + [Channel | Acc]; + (_, Acc) -> + Acc + end, [], Cache), + {reply, {ok, Result}, StateName, State}; + +handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0, + role = Role, + opts = Opts} = State0) -> + {disconnect, Reason, {{replies, Replies}, Connection}} = + ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "User closed down connection", + language = "en"}, Connection0, Role), + State = send_replies(Replies, State0), + SSHOpts = proplists:get_value(ssh_opts, Opts), + disconnect_fun(Reason, SSHOpts), + {stop, normal, ok, State#state{connection_state = Connection}}; + + +handle_sync_event(Event, From, StateName, State) when StateName /= connected -> + Events = [{sync, Event, From} | State#state.event_queue], + {next_state, StateName, State#state{event_queue = Events}}; + handle_sync_event({request, ChannelPid, ChannelId, Type, Data, Timeout}, From, StateName, State0) -> {{replies, Replies}, State1} = handle_request(ChannelPid, ChannelId, Type, Data, @@ -784,46 +854,6 @@ handle_sync_event({recv_window, ChannelId}, _From, StateName, end, {reply, Reply, StateName, next_packet(State)}; -handle_sync_event(get_print_info, _From, StateName, State) -> - Reply = - try - {inet:sockname(State#state.socket), - inet:peername(State#state.socket) - } - of - {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])}; - _ -> {{"-",0},"-"} - catch - _:_ -> {{"?",0},"?"} - end, - {reply, Reply, StateName, State}; - -handle_sync_event({connection_info, Options}, _From, StateName, State) -> - Info = ssh_info(Options, State, []), - {reply, Info, StateName, State}; - -handle_sync_event({channel_info, ChannelId, Options}, _From, StateName, - #state{connection_state = #connection{channel_cache = Cache}} = State) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{} = Channel -> - Info = ssh_channel_info(Options, Channel, []), - {reply, Info, StateName, State}; - undefined -> - {reply, [], StateName, State} - end; - -handle_sync_event({info, ChannelPid}, _From, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State) -> - Result = ssh_channel:cache_foldl( - fun(Channel, Acc) when ChannelPid == all; - Channel#channel.user == ChannelPid -> - [Channel | Acc]; - (_, Acc) -> - Acc - end, [], Cache), - {reply, {ok, Result}, StateName, State}; - handle_sync_event({close, ChannelId}, _, StateName, #state{connection_state = #connection{channel_cache = Cache}} = State0) -> @@ -838,19 +868,7 @@ handle_sync_event({close, ChannelId}, _, StateName, undefined -> State0 end, - {reply, ok, StateName, next_packet(State)}; - -handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0, - role = Role, - opts = Opts} = State0) -> - {disconnect, Reason, {{replies, Replies}, Connection}} = - ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "User closed down connection", - language = "en"}, Connection0, Role), - State = send_replies(Replies, State0), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), - {stop, normal, ok, State#state{connection_state = Connection}}. + {reply, ok, StateName, next_packet(State)}. %%-------------------------------------------------------------------- -spec handle_info({atom(), port(), binary()} | {atom(), port()} | @@ -1138,54 +1156,38 @@ init_ssh(server = Role, Vsn, Version, Options, Socket) -> supported_host_keys(client, _, Options) -> try - case extract_algs(proplists:get_value(pref_public_key_algs, Options, false), []) of - false -> - ["ssh-rsa", "ssh-dss"]; - Algs -> - Algs + case proplists:get_value(public_key, + proplists:get_value(preferred_algorithms,Options,[]) + ) of + undefined -> + ssh_auth:default_public_key_algorithms(); + L -> + L -- (L--ssh_auth:default_public_key_algorithms()) end + of + [] -> + {stop, {shutdown, "No public key algs"}}; + Algs -> + [atom_to_list(A) || A<-Algs] catch exit:Reason -> {stop, {shutdown, Reason}} end; supported_host_keys(server, KeyCb, Options) -> - lists:foldl(fun(Type, Acc) -> - case available_host_key(KeyCb, Type, Options) of - {error, _} -> - Acc; - Alg -> - [Alg | Acc] - end - end, [], - %% Prefered alg last so no need to reverse - ["ssh-dss", "ssh-rsa"]). -extract_algs(false, _) -> - false; -extract_algs([],[]) -> - false; -extract_algs([], NewList) -> - lists:reverse(NewList); -extract_algs([H|T], NewList) -> - case H of - 'ssh-dss' -> - extract_algs(T, ["ssh-dss"|NewList]); - 'ssh-rsa' -> - extract_algs(T, ["ssh-rsa"|NewList]) - end. -available_host_key(KeyCb, "ssh-dss"= Alg, Opts) -> - case KeyCb:host_key('ssh-dss', Opts) of - {ok, _} -> - Alg; - Other -> - Other - end; -available_host_key(KeyCb, "ssh-rsa" = Alg, Opts) -> - case KeyCb:host_key('ssh-rsa', Opts) of - {ok, _} -> - Alg; - Other -> - Other - end. + Algs= + [atom_to_list(A) || A <- proplists:get_value(public_key, + proplists:get_value(preferred_algorithms,Options,[]), + ssh_auth:default_public_key_algorithms() + ), + available_host_key(KeyCb, A, Options) + ], + Algs. + + +%% Alg :: atom() +available_host_key(KeyCb, Alg, Opts) -> + element(1, catch KeyCb:host_key(Alg, Opts)) == ok. + send_msg(Msg, #state{socket = Socket, transport_cb = Transport}) -> Transport:send(Socket, Msg). @@ -1279,8 +1281,17 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName, ConnectionMsg = ssh_message:decode(Msg), State1 = generate_event_new_state(State0, EncData), try ssh_connection:handle_msg(ConnectionMsg, Connection0, Role) of - {{replies, Replies}, Connection} -> - State = send_replies(Replies, State1#state{connection_state = Connection}), + {{replies, Replies0}, Connection} -> + if StateName == connected -> + Replies = Replies0, + State2 = State1; + true -> + {ConnReplies, Replies} = + lists:splitwith(fun not_connected_filter/1, Replies0), + Q = State1#state.event_queue ++ ConnReplies, + State2 = State1#state{ event_queue = Q } + end, + State = send_replies(Replies, State2#state{connection_state = Connection}), {next_state, StateName, next_packet(State)}; {noreply, Connection} -> {next_state, StateName, next_packet(State1#state{connection_state = Connection})}; @@ -1453,15 +1464,43 @@ next_packet(#state{socket = Socket} = State) -> State. after_new_keys(#state{renegotiate = true} = State) -> - {connected, State#state{renegotiate = false}}; + State1 = State#state{renegotiate = false, event_queue = []}, + lists:foldr(fun after_new_keys_events/2, {next_state, connected, State1}, State#state.event_queue); after_new_keys(#state{renegotiate = false, ssh_params = #ssh{role = client} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:service_request_msg(Ssh0), send_msg(Msg, State), - {userauth, State#state{ssh_params = Ssh}}; + {next_state, userauth, State#state{ssh_params = Ssh}}; after_new_keys(#state{renegotiate = false, ssh_params = #ssh{role = server}} = State) -> - {userauth, State}. + {next_state, userauth, State}. + +after_new_keys_events({sync, _Event, From}, {stop, _Reason, _StateData}=Terminator) -> + gen_fsm:reply(From, {error, closed}), + Terminator; +after_new_keys_events(_, {stop, _Reason, _StateData}=Terminator) -> + Terminator; +after_new_keys_events({sync, Event, From}, {next_state, StateName, StateData}) -> + case handle_sync_event(Event, From, StateName, StateData) of + {reply, Reply, NextStateName, NewStateData} -> + gen_fsm:reply(From, Reply), + {next_state, NextStateName, NewStateData}; + {next_state, NextStateName, NewStateData}-> + {next_state, NextStateName, NewStateData}; + {stop, Reason, Reply, NewStateData} -> + gen_fsm:reply(From, Reply), + {stop, Reason, NewStateData} + end; +after_new_keys_events({event, Event}, {next_state, StateName, StateData}) -> + case handle_event(Event, StateName, StateData) of + {next_state, NextStateName, NewStateData}-> + {next_state, NextStateName, NewStateData}; + {stop, Reason, NewStateData} -> + {stop, Reason, NewStateData} + end; +after_new_keys_events({connection_reply, _Data} = Reply, {StateName, State}) -> + NewState = send_replies([Reply], State), + {next_state, StateName, NewState}. handle_ssh_packet_data(RemainingSshPacketLen, DecData, EncData, StateName, State) -> @@ -1622,6 +1661,11 @@ log_error(Reason) -> error_logger:error_report(Report), "Internal error". +not_connected_filter({connection_reply, _Data}) -> + true; +not_connected_filter(_) -> + false. + send_replies([], State) -> State; send_replies([{connection_reply, Data} | Rest], #state{ssh_params = Ssh0} = State) -> |