diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/ssh/src/ssh_connection_manager.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/ssh/src/ssh_connection_manager.erl')
-rw-r--r-- | lib/ssh/src/ssh_connection_manager.erl | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl new file mode 100644 index 0000000000..3863005e74 --- /dev/null +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -0,0 +1,760 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: Handles multiplexing to ssh channels and global connection +%% requests e.i. the SSH Connection Protocol (RFC 4254), that provides +%% interactive login sessions, remote execution of commands, forwarded +%% TCP/IP connections, and forwarded X11 connections. Details of the +%% protocol is implemented in ssh_connection.erl +%% ---------------------------------------------------------------------- +-module(ssh_connection_manager). + +-behaviour(gen_server). + +-include("ssh.hrl"). +-include("ssh_connect.hrl"). +-include("ssh_transport.hrl"). + +-export([start_link/1]). + +-export([info/1, info/2, + renegotiate/1, connection_info/2, channel_info/3, + peer_addr/1, send_window/3, recv_window/3, adjust_window/3, + close/2, stop/1, send/5, + send_eof/2]). + +-export([open_channel/6, request/6, request/7, global_request/4, event/2, + cast/2]). + +%% Internal application API and spawn +-export([send_msg/1, ssh_channel_info_handler/3]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(DBG_MESSAGE, true). + +-record(state, + { + role, + client, + starter, + connection, % pid() + connection_state, % #connection{} + latest_channel_id = 0, + opts, + channel_args, + connected + }). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +start_link(Opts) -> + gen_server:start_link(?MODULE, Opts, []). + +open_channel(ConnectionManager, ChannelType, ChannelSpecificData, + InitialWindowSize, MaxPacketSize, Timeout) -> + case (catch call(ConnectionManager, {open, self(), ChannelType, + InitialWindowSize, + MaxPacketSize, ChannelSpecificData}, + Timeout)) of + {open, Channel} -> + {ok, Channel}; + Error -> + %% TODO: Best way? + Error + end. + +request(ConnectionManager, ChannelPid, ChannelId, Type, true, Data, Timeout) -> + call(ConnectionManager, {request, ChannelPid, ChannelId, Type, Data}, Timeout); +request(ConnectionManager, ChannelPid, ChannelId, Type, false, Data, _) -> + cast(ConnectionManager, {request, ChannelPid, ChannelId, Type, Data}). + +request(ConnectionManager, ChannelId, Type, true, Data, Timeout) -> + call(ConnectionManager, {request, ChannelId, Type, Data}, Timeout); +request(ConnectionManager, ChannelId, Type, false, Data, _) -> + cast(ConnectionManager, {request, ChannelId, Type, Data}). + +global_request(ConnectionManager, Type, true = Reply, Data) -> + case call(ConnectionManager, + {global_request, self(), Type, Reply, Data}) of + {ssh_cm, ConnectionManager, {success, _}} -> + ok; + {ssh_cm, ConnectionManager, {failure, _}} -> + error + end; + +global_request(ConnectionManager, Type, false = Reply, Data) -> + cast(ConnectionManager, {global_request, self(), Type, Reply, Data}). + +event(ConnectionManager, BinMsg) -> + call(ConnectionManager, {ssh_msg, self(), BinMsg}). + +info(ConnectionManager) -> + info(ConnectionManager, {info, all}). + +info(ConnectionManager, ChannelProcess) -> + call(ConnectionManager, {info, ChannelProcess}). + +%% TODO: Do we really want this function? Should not +%% renegotiation be triggered by configurable timer +%% or amount of data sent counter! +renegotiate(ConnectionManager) -> + cast(ConnectionManager, renegotiate). + +connection_info(ConnectionManager, Options) -> + call(ConnectionManager, {connection_info, Options}). + +channel_info(ConnectionManager, ChannelId, Options) -> + call(ConnectionManager, {channel_info, ChannelId, Options}). + +%% Replaced by option peer to connection_info/2 keep for now +%% for Backwards compatibility! +peer_addr(ConnectionManager) -> + call(ConnectionManager, {peer_addr, self()}). + +%% Backwards compatibility! +send_window(ConnectionManager, Channel, TimeOut) -> + call(ConnectionManager, {send_window, Channel}, TimeOut). +%% Backwards compatibility! +recv_window(ConnectionManager, Channel, TimeOut) -> + call(ConnectionManager, {recv_window, Channel}, TimeOut). + +adjust_window(ConnectionManager, Channel, Bytes) -> + cast(ConnectionManager, {adjust_window, Channel, Bytes}). + +close(ConnectionManager, ChannelId) -> + try call(ConnectionManager, {close, ChannelId}) of + ok -> + ok + catch + exit:{noproc, _} -> + ok + end. + +stop(ConnectionManager) -> + try call(ConnectionManager, stop) of + ok -> + ok + catch + exit:{noproc, _} -> + ok + end. + +send(ConnectionManager, ChannelId, Type, Data, Timeout) -> + call(ConnectionManager, {data, ChannelId, Type, Data}, Timeout). + +send_eof(ConnectionManager, ChannelId) -> + cast(ConnectionManager, {eof, ChannelId}). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([server, _Socket, Opts]) -> + process_flag(trap_exit, true), + ssh_bits:install_messages(ssh_connection:messages()), + Cache = ssh_channel:cache_create(), + {ok, #state{role = server, + connection_state = #connection{channel_cache = Cache, + channel_id_seed = 0, + port_bindings = [], + requests = [], + channel_pids = []}, + opts = Opts, + connected = false}}; + +init([client, Opts]) -> + process_flag(trap_exit, true), + {links, [Parent]} = process_info(self(), links), + ssh_bits:install_messages(ssh_connection:messages()), + Cache = ssh_channel:cache_create(), + Address = proplists:get_value(address, Opts), + Port = proplists:get_value(port, Opts), + SocketOpts = proplists:get_value(socket_opts, Opts), + Options = proplists:get_value(ssh_opts, Opts), + ChannelPid = proplists:get_value(channel_pid, Opts), + self() ! + {start_connection, client, [Parent, Address, Port, + ChannelPid, SocketOpts, Options]}, + {ok, #state{role = client, + client = ChannelPid, + connection_state = #connection{channel_cache = Cache, + channel_id_seed = 0, + port_bindings = [], + requests = [], + channel_pids = []}, + opts = Opts, + connected = false}}. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call({request, ChannelPid, ChannelId, Type, Data}, From, State0) -> + {{replies, Replies}, State} = handle_request(ChannelPid, + ChannelId, Type, Data, + true, From, State0), + %% Sends message to the connection handler process, reply to + %% channel is sent later when reply arrives from the connection + %% handler. + lists:foreach(fun send_msg/1, Replies), + {noreply, State}; + +handle_call({request, ChannelId, Type, Data}, From, State0) -> + {{replies, Replies}, State} = handle_request(ChannelId, Type, Data, + true, From, State0), + %% Sends message to the connection handler process, reply to + %% channel is sent later when reply arrives from the connection + %% handler. + lists:foreach(fun send_msg/1, Replies), + {noreply, State}; + +%% Message from ssh_connection_handler +handle_call({ssh_msg, Pid, Msg}, From, + #state{connection_state = Connection0, + role = Role, opts = Opts, connected = IsConnected, + client = ClientPid} + = State) -> + + %% To avoid that not all data sent by the other side is processes before + %% possible crash in ssh_connection_handler takes down the connection. + gen_server:reply(From, ok), + + ConnectionMsg = decode_ssh_msg(Msg), + try ssh_connection:handle_msg(ConnectionMsg, Connection0, Pid, Role) of + {{replies, Replies}, Connection} -> + lists:foreach(fun send_msg/1, Replies), + {noreply, State#state{connection_state = Connection}}; + {noreply, Connection} -> + {noreply, State#state{connection_state = Connection}}; + {disconnect, {_, Reason}, {{replies, Replies}, Connection}} + when Role == client andalso (not IsConnected) -> + lists:foreach(fun send_msg/1, Replies), + ClientPid ! {self(), not_connected, Reason}, + {stop, normal, State#state{connection = Connection}}; + {disconnect, Reason, {{replies, Replies}, Connection}} -> + lists:foreach(fun send_msg/1, Replies), + SSHOpts = proplists:get_value(ssh_opts, Opts), + disconnect_fun(Reason, SSHOpts), + {stop, normal, State#state{connection_state = Connection}} + catch + exit:{noproc, Reason} -> + Report = io_lib:format("Connection probably terminated:~n~p~n~p~n", + [ConnectionMsg, Reason]), + error_logger:info_report(Report), + {noreply, State}; + error:Error -> + Report = io_lib:format("Connection message returned:~n~p~n~p~n", + [ConnectionMsg, Error]), + error_logger:info_report(Report), + {noreply, State}; + exit:Exit -> + Report = io_lib:format("Connection message returned:~n~p~n~p~n", + [ConnectionMsg, Exit]), + error_logger:info_report(Report), + {noreply, State} + end; + +handle_call({global_request, Pid, _, _, _} = Request, From, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + State1 = handle_global_request(Request, State0), + Channel = ssh_channel:cache_find(Pid, Cache), + State = add_request(true, Channel#channel.local_id, From, State1), + {noreply, State}; + +handle_call({data, ChannelId, Type, Data}, From, + #state{connection_state = #connection{channel_cache = _Cache} + = Connection0, + connection = ConnectionPid} = State) -> + channel_data(ChannelId, Type, Data, Connection0, ConnectionPid, From, + State); + +handle_call({connection_info, Options}, From, + #state{connection = Connection} = State) -> + ssh_connection_handler:connection_info(Connection, From, Options), + %% Reply will be sent by the connection handler by calling + %% ssh_connection_handler:send_msg/1. + {noreply, State}; + +handle_call({channel_info, ChannelId, Options}, From, + #state{connection_state = #connection{channel_cache = Cache}} = State) -> + + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{} = Channel -> + spawn(?MODULE, ssh_channel_info_handler, [Options, Channel, From]), + {noreply, State}; + undefined -> + {reply, []} + end; + +handle_call({info, ChannelPid}, _From, + #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}, State}; + +handle_call({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data}, + From, #state{connection = Pid, + connection_state = + #connection{channel_cache = Cache}} = State0) -> + {ChannelId, State1} = new_channel_id(State0), + Msg = ssh_connection:channel_open_msg(Type, ChannelId, + InitialWindowSize, + MaxPacketSize, Data), + send_msg({connection_reply, Pid, Msg}), + Channel = #channel{type = Type, + sys = "none", + user = ChannelPid, + local_id = ChannelId, + recv_window_size = InitialWindowSize, + recv_packet_size = MaxPacketSize}, + ssh_channel:cache_update(Cache, Channel), + State = add_request(true, ChannelId, From, State1), + {noreply, State}; + +handle_call({send_window, ChannelId}, _From, + #state{connection_state = + #connection{channel_cache = Cache}} = State) -> + Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{send_window_size = WinSize, + send_packet_size = Packsize} -> + {ok, {WinSize, Packsize}}; + undefined -> + {error, einval} + end, + {reply, Reply, State}; + +handle_call({recv_window, ChannelId}, _From, + #state{connection_state = #connection{channel_cache = Cache}} + = State) -> + + Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{recv_window_size = WinSize, + recv_packet_size = Packsize} -> + {ok, {WinSize, Packsize}}; + undefined -> + {error, einval} + end, + {reply, Reply, State}; + +%% Replaced by option peer to connection_info/2 keep for now +%% for Backwards compatibility! +handle_call({peer_addr, _ChannelId}, _From, + #state{connection = Pid} = State) -> + Reply = ssh_connection_handler:peer_address(Pid), + {reply, Reply, State}; + +handle_call(opts, _, #state{opts = Opts} = State) -> + {reply, Opts, State}; + +handle_call({close, ChannelId}, _, + #state{connection = Pid, connection_state = + #connection{channel_cache = Cache}} = State) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = Id} -> + send_msg({connection_reply, Pid, + ssh_connection:channel_close_msg(Id)}), + {reply, ok, State}; + undefined -> + {reply, ok, State} + end; + +handle_call(stop, _, #state{role = _client, + client = ChannelPid, + connection = Pid} = State) -> + DisconnectMsg = + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "Application disconnect", + language = "en"}, + (catch gen_fsm:send_all_state_event(Pid, DisconnectMsg)), +% ssh_connection_handler:send(Pid, DisconnectMsg), + {stop, normal, ok, State}; +handle_call(stop, _, State) -> + {stop, normal, ok, State}; + +%% API violation make it the violaters problem +%% by ignoring it. The violating process will get +%% a timeout or hang. +handle_call(_, _, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast({request, ChannelPid, ChannelId, Type, Data}, State0) -> + {{replies, Replies}, State} = handle_request(ChannelPid, ChannelId, + Type, Data, + false, none, State0), + lists:foreach(fun send_msg/1, Replies), + {noreply, State}; + +handle_cast({request, ChannelId, Type, Data}, State0) -> + {{replies, Replies}, State} = handle_request(ChannelId, Type, Data, + false, none, State0), + lists:foreach(fun send_msg/1, Replies), + {noreply, State}; + +handle_cast({global_request, _, _, _, _} = Request, State0) -> + State = handle_global_request(Request, State0), + {noreply, State}; + +handle_cast(renegotiate, #state{connection = Pid} = State) -> + ssh_connection_handler:renegotiate(Pid), + {noreply, State}; + +handle_cast({adjust_window, ChannelId, Bytes}, + #state{connection = Pid, connection_state = + #connection{channel_cache = Cache}} = 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_msg({connection_reply, Pid, Msg}); + undefined -> + ignore + end, + {noreply, State}; + +handle_cast({eof, ChannelId}, + #state{connection = Pid, connection_state = + #connection{channel_cache = Cache}} = State) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = Id} -> + send_msg({connection_reply, Pid, + ssh_connection:channel_eof_msg(Id)}), + {noreply, State}; + undefined -> + {noreply, State} + end; + +handle_cast({success, ChannelId}, #state{connection = Pid} = State) -> + Msg = ssh_connection:channel_success_msg(ChannelId), + send_msg({connection_reply, Pid, Msg}), + {noreply, State}; + +handle_cast({failure, ChannelId}, #state{connection = Pid} = State) -> + Msg = ssh_connection:channel_failure_msg(ChannelId), + send_msg({connection_reply, Pid, Msg}), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info({start_connection, server, + [Address, Port, Socket, Options]}, + #state{connection_state = CState} = State) -> + {ok, Connection} = ssh_transport:accept(Address, Port, Socket, Options), + Shell = proplists:get_value(shell, Options), + Exec = proplists:get_value(exec, Options), + CliSpec = proplists:get_value(ssh_cli, Options, {ssh_cli, [Shell]}), + {noreply, State#state{connection = Connection, + connection_state = + CState#connection{address = Address, + port = Port, + cli_spec = CliSpec, + options = Options, + exec = Exec}}}; + +handle_info({start_connection, client, + [Parent, Address, Port, ChannelPid, SocketOpts, Options]}, + State) -> + case (catch ssh_transport:connect(Parent, Address, + Port, SocketOpts, Options)) of + {ok, Connection} -> + erlang:monitor(process, ChannelPid), + {noreply, State#state{connection = Connection}}; + Reason -> + ChannelPid ! {self(), not_connected, Reason}, + {stop, normal, State} + end; + +handle_info({ssh_cm, _Sender, Msg}, State0) -> + %% Backwards compatibility! + State = cm_message(Msg, State0), + {noreply, State}; + +%% Nop backwards compatibility +handle_info({same_user, _}, State) -> + {noreply, State}; + +handle_info(ssh_connected, #state{role = client, client = Pid} + = State) -> + Pid ! {self(), is_connected}, + {noreply, State#state{connected = true}}; + +handle_info(ssh_connected, #state{role = server} = State) -> + {noreply, State#state{connected = true}}; + +handle_info({'DOWN', _Ref, process, ChannelPid, normal}, State0) -> + handle_down(handle_channel_down(ChannelPid, State0)); + +handle_info({'DOWN', _Ref, process, ChannelPid, shutdown}, State0) -> + handle_down(handle_channel_down(ChannelPid, State0)); + +handle_info({'DOWN', _Ref, process, ChannelPid, Reason}, State0) -> + Report = io_lib:format("Pid ~p DOWN ~p\n", [ChannelPid, Reason]), + error_logger:error_report(Report), + handle_down(handle_channel_down(ChannelPid, State0)); + +handle_info({'EXIT', _, _}, State) -> + %% Handled in 'DOWN' + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(Reason, #state{connection_state = + #connection{requests = Requests}, + opts = Opts}) -> + SSHOpts = proplists:get_value(ssh_opts, Opts), + disconnect_fun(Reason, SSHOpts), + (catch lists:foreach(fun({_, From}) -> + gen_server:reply(From, {error, connection_closed}) + end, Requests)), + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +channel_data(Id, Type, Data, Connection0, ConnectionPid, From, State) -> + case ssh_connection:channel_data(Id, Type, Data, Connection0, + ConnectionPid, From) of + {{replies, Replies}, Connection} -> + lists:foreach(fun send_msg/1, Replies), + {noreply, State#state{connection_state = Connection}}; + {noreply, Connection} -> + {noreply, State#state{connection_state = Connection}} + end. + +call(Pid, Msg) -> + call(Pid, Msg, infinity). +call(Pid, Msg, Timeout) -> + try gen_server:call(Pid, Msg, Timeout) of + Result -> + Result + catch + exit:{timeout, _} -> + {error, timeout} + end. + +cast(Pid, Msg) -> + gen_server:cast(Pid, Msg). + +decode_ssh_msg(BinMsg) when is_binary(BinMsg)-> + ssh_bits:decode(BinMsg); +decode_ssh_msg(Msg) -> + Msg. + + +send_msg(Msg) -> + case catch do_send_msg(Msg) of + {'EXIT', Reason}-> + Report = io_lib:format("Connection Manager fail to send:~n~p~n" + "Reason why it failed was:~n~p~n", + [Msg, Reason]), + error_logger:info_report(Report); + _ -> + ok + end. + +do_send_msg({channel_data, Pid, Data}) -> + Pid ! {ssh_cm, self(), Data}; +do_send_msg({channel_requst_reply, From, Data}) -> + gen_server:reply(From, Data); +do_send_msg({connection_reply, Pid, Data}) -> + Msg = ssh_bits:encode(Data), + ssh_connection_handler:send(Pid, Msg); +do_send_msg({flow_control, Cache, Channel, From, Msg}) -> + ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), + gen_server:reply(From, Msg). + +handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, + #state{connection = Pid, + connection_state = + #connection{channel_cache = Cache}} = State0) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = Id} = Channel -> + update_sys(Cache, Channel, Type, ChannelPid), + Msg = ssh_connection:channel_request_msg(Id, Type, + WantReply, Data), + Replies = [{connection_reply, Pid, Msg}], + State = add_request(WantReply, ChannelId, From, State0), + {{replies, Replies}, State}; + undefined -> + {{replies, []}, State0} + end. + +handle_request(ChannelId, Type, Data, WantReply, From, + #state{connection = Pid, + connection_state = + #connection{channel_cache = Cache}} = State0) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = Id} -> + Msg = ssh_connection:channel_request_msg(Id, Type, + WantReply, Data), + Replies = [{connection_reply, Pid, Msg}], + State = add_request(WantReply, ChannelId, From, State0), + {{replies, Replies}, State}; + undefined -> + {{replies, []}, State0} + end. + +handle_down({{replies, Replies}, State}) -> + lists:foreach(fun send_msg/1, Replies), + {noreply, State}. + +handle_channel_down(ChannelPid, #state{connection_state = + #connection{channel_cache = Cache}} = + State) -> + ssh_channel:cache_foldl( + fun(Channel, Acc) when Channel#channel.user == ChannelPid -> + ssh_channel:cache_delete(Cache, + Channel#channel.local_id), + Acc; + (_,Acc) -> + Acc + end, [], Cache), + {{replies, []}, State}. + +update_sys(Cache, Channel, Type, ChannelPid) -> + ssh_channel:cache_update(Cache, + Channel#channel{sys = Type, user = ChannelPid}). + +add_request(false, _ChannelId, _From, State) -> + State; +add_request(true, ChannelId, From, #state{connection_state = + #connection{requests = Requests0} = + Connection} = State) -> + Requests = [{ChannelId, From} | Requests0], + State#state{connection_state = Connection#connection{requests = Requests}}. + +new_channel_id(#state{connection_state = #connection{channel_id_seed = Id} = + Connection} + = State) -> + {Id, State#state{connection_state = + Connection#connection{channel_id_seed = Id + 1}}}. + +handle_global_request({global_request, ChannelPid, + "tcpip-forward" = Type, WantReply, + <<?UINT32(IPLen), + IP:IPLen/binary, ?UINT32(Port)>> = Data}, + #state{connection = ConnectionPid, + connection_state = + #connection{channel_cache = Cache} + = Connection0} = State) -> + ssh_channel:cache_update(Cache, #channel{user = ChannelPid, + type = "forwarded-tcpip", + sys = none}), + Connection = ssh_connection:bind(IP, Port, ChannelPid, Connection0), + Msg = ssh_connection:global_request_msg(Type, WantReply, Data), + send_msg({connection_reply, ConnectionPid, Msg}), + State#state{connection_state = Connection}; + +handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type, + WantReply, <<?UINT32(IPLen), + IP:IPLen/binary, ?UINT32(Port)>> = Data}, + #state{connection = Pid, + connection_state = Connection0} = State) -> + Connection = ssh_connection:unbind(IP, Port, Connection0), + Msg = ssh_connection:global_request_msg(Type, WantReply, Data), + send_msg({connection_reply, Pid, Msg}), + State#state{connection_state = Connection}; + +handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type, + WantReply, Data}, #state{connection = Pid} = State) -> + Msg = ssh_connection:global_request_msg(Type, WantReply, Data), + send_msg({connection_reply, Pid, Msg}), + State. + +cm_message(Msg, State) -> + {noreply, NewState} = handle_cast(Msg, State), + NewState. + +disconnect_fun(Reason, Opts) -> + case proplists:get_value(disconnectfun, Opts) of + undefined -> + ok; + Fun -> + catch Fun(Reason) + end. + +ssh_channel_info_handler(Options, Channel, From) -> + Info = ssh_channel_info(Options, Channel, []), + send_msg({channel_requst_reply, From, Info}). + +ssh_channel_info([], _, Acc) -> + Acc; + +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). + + + |