%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2022. 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% %% %% %%---------------------------------------------------------------------- %% Purpose: Details of connection protocol %%---------------------------------------------------------------------- -module(ssh_connection). -include("ssh.hrl"). -include("ssh_connect.hrl"). -include("ssh_transport.hrl"). %% API -export([session_channel/2, session_channel/4, exec/4, shell/2, subsystem/4, send/3, send/4, send/5, send_eof/2, adjust_window/3, setenv/5, close/2, reply_request/4, ptty_alloc/3, ptty_alloc/4]). %% Potential API currently unsupported and not tested -export([window_change/4, window_change/6, signal/3, exit_status/3]). %% Internal SSH application API -export([channel_data/5, handle_msg/4, handle_stop/1, open_channel/4, channel_adjust_window_msg/2, channel_close_msg/1, channel_open_failure_msg/4, channel_open_msg/5, channel_status_msg/1, channel_data_msg/3, channel_eof_msg/1, channel_failure_msg/1, channel_open_confirmation_msg/4, channel_request_msg/4, channel_success_msg/1, request_global_msg/3, request_failure_msg/0, request_success_msg/1, send_environment_vars/3, encode_ip/1 ]). %% For testing only -export([encode_pty_opts/1, decode_pty_opts/1]). -type connection_ref() :: ssh:connection_ref(). -type channel_id() :: ssh:channel_id(). -type req_status() :: success | failure . -type reason() :: closed | timeout . -type result() :: req_status() | {error, reason()} . -type ssh_data_type_code() :: non_neg_integer(). % Only 0 and 1 are used %%% The SSH Connection Protocol -export_type([event/0, channel_msg/0, want_reply/0, data_ch_msg/0, eof_ch_msg/0, signal_ch_msg/0, exit_signal_ch_msg/0, exit_status_ch_msg/0, closed_ch_msg/0, env_ch_msg/0, pty_ch_msg/0, shell_ch_msg/0, window_change_ch_msg/0, exec_ch_msg/0 ]). -type event() :: {ssh_cm, ssh:connection_ref(), channel_msg()}. -type channel_msg() :: data_ch_msg() | eof_ch_msg() | closed_ch_msg() | pty_ch_msg() | env_ch_msg() | shell_ch_msg() | exec_ch_msg() | signal_ch_msg() | window_change_ch_msg() | exit_status_ch_msg() | exit_signal_ch_msg() . -type want_reply() :: boolean(). -type data_ch_msg() :: {data, ssh:channel_id(), ssh_data_type_code(), Data :: binary() } . -type eof_ch_msg() :: {eof, ssh:channel_id() } . -type signal_ch_msg() :: {signal, ssh:channel_id(), SignalName :: string() } . -type exit_signal_ch_msg() :: {exit_signal, ssh:channel_id(), ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()} . -type exit_status_ch_msg() :: {exit_status, ssh:channel_id(), ExitStatus :: non_neg_integer() } . -type closed_ch_msg() :: {closed, ssh:channel_id() } . -type env_ch_msg() :: {env, ssh:channel_id(), want_reply(), Var :: string(), Value :: string() } . -type pty_ch_msg() :: {pty, ssh:channel_id(), want_reply(), {Terminal :: string(), CharWidth :: non_neg_integer(), RowHeight :: non_neg_integer(), PixelWidth :: non_neg_integer(), PixelHeight :: non_neg_integer(), TerminalModes :: [term_mode()] } } . -type term_mode() :: {Opcode :: atom() | byte(), Value :: non_neg_integer()} . -type shell_ch_msg() :: {shell, ssh:channel_id(), want_reply() } . -type window_change_ch_msg() :: {window_change, ssh:channel_id(), CharWidth :: non_neg_integer(), RowHeight :: non_neg_integer(), PixelWidth :: non_neg_integer(), PixelHeight :: non_neg_integer() } . -type exec_ch_msg() :: {exec, ssh:channel_id(), want_reply(), Command :: string() } . %%% This function is solely to convince all %%% checks that the type event() exists... -export([dummy/1]). -spec dummy(event()) -> false. dummy(_) -> false. %%-------------------------------------------------------------------- %%% API %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %% Description: Opens a channel for a ssh session. A session is a %% remote execution of a program. The program may be a shell, an %% application, a system command, or some built-in subsystem. %% -------------------------------------------------------------------- -spec session_channel(ConnectionRef, Timeout) -> Result when ConnectionRef :: ssh:connection_ref(), Timeout :: timeout(), Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, Timeout) -> session_channel(ConnectionHandler, undefined, undefined, Timeout). -spec session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> Result when ConnectionRef :: ssh:connection_ref(), InitialWindowSize :: pos_integer() | undefined, MaxPacketSize :: pos_integer() | undefined, Timeout :: timeout(), Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> open_channel(ConnectionHandler, "session", <<>>, InitialWindowSize, MaxPacketSize, Timeout). %%-------------------------------------------------------------------- %% Description: Opens a channel for the given type. %% -------------------------------------------------------------------- open_channel(ConnectionHandler, Type, ChanData, Timeout) -> open_channel(ConnectionHandler, Type, ChanData, undefined, undefined, Timeout). open_channel(ConnectionHandler, Type, ChanData, InitialWindowSize, MaxPacketSize, Timeout) -> case ssh_connection_handler:open_channel(ConnectionHandler, Type, ChanData, InitialWindowSize, MaxPacketSize, Timeout) of {open, Channel} -> {ok, Channel}; Error -> Error end. %%-------------------------------------------------------------------- %% Description: Will request that the server start the %% execution of the given command. %%-------------------------------------------------------------------- -spec exec(ConnectionRef, ChannelId, Command, Timeout) -> result() when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(), Command :: string(), Timeout :: timeout(). exec(ConnectionHandler, ChannelId, Command, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec", true, [?string(Command)], TimeOut). %%-------------------------------------------------------------------- %% Description: Will request that the user's default shell (typically %% defined in /etc/passwd in UNIX systems) be started at the other %% end. %%-------------------------------------------------------------------- -spec shell(ConnectionRef, ChannelId) -> Result when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(), Result :: ok | success | failure | {error, timeout} . shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "shell", false, <<>>, 0). %%-------------------------------------------------------------------- %% %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- -spec subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> result() when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(), Subsystem :: string(), Timeout :: timeout(). subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "subsystem", true, [?string(SubSystem)], TimeOut). %%-------------------------------------------------------------------- %% Description: Sends channel data. %%-------------------------------------------------------------------- -spec send(connection_ref(), channel_id(), iodata()) -> ok | {error, timeout | closed}. send(ConnectionHandler, ChannelId, Data) -> send(ConnectionHandler, ChannelId, 0, Data, infinity). -spec send(connection_ref(), channel_id(), iodata(), timeout()) -> ok | {error, reason()}; (connection_ref(), channel_id(), ssh_data_type_code(), iodata()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> send(ConnectionHandler, ChannelId, 0, Data, TimeOut); send(ConnectionHandler, ChannelId, Data, infinity) -> send(ConnectionHandler, ChannelId, 0, Data, infinity); send(ConnectionHandler, ChannelId, Type, Data) -> send(ConnectionHandler, ChannelId, Type, Data, infinity). -spec send(connection_ref(), channel_id(), ssh_data_type_code(), iodata(), timeout()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). %%-------------------------------------------------------------------- -spec send_eof(ConnectionRef, ChannelId) -> ok | {error, closed} when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(). %% %% %% Description: Sends eof on the channel . %%-------------------------------------------------------------------- send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- -spec adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(), NumOfBytes :: integer(). %% %% %% Description: Adjusts the ssh flowcontrol window. %%-------------------------------------------------------------------- adjust_window(ConnectionHandler, Channel, Bytes) -> ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes). %%-------------------------------------------------------------------- -spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> success when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(), Var :: string(), Value :: string(), Timeout :: timeout(). %% %% %% Description: Environment variables may be passed to the shell/command to be %% started later. setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) -> setenv(ConnectionHandler, ChannelId, true, Var, Value, TimeOut). setenv(ConnectionHandler, ChannelId, WantReply, Var, Value, TimeOut) -> case ssh_connection_handler:request(ConnectionHandler, ChannelId, "env", WantReply, [?string(Var), ?string(Value)], TimeOut) of ok when WantReply == false -> success; Reply -> Reply end. %%-------------------------------------------------------------------- -spec close(ConnectionRef, ChannelId) -> ok when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(). %% %% %% Description: Sends a close message on the channel . %%-------------------------------------------------------------------- close(ConnectionHandler, ChannelId) -> ssh_connection_handler:close(ConnectionHandler, ChannelId). %%-------------------------------------------------------------------- -spec reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok when ConnectionRef :: ssh:connection_ref(), WantReply :: boolean(), Status :: req_status(), ChannelId :: ssh:channel_id(). %% %% %% Description: Send status replies to requests that want such replies. %%-------------------------------------------------------------------- reply_request(ConnectionHandler, true, Status, ChannelId) -> ssh_connection_handler:reply_request(ConnectionHandler, Status, ChannelId); reply_request(_,false, _, _) -> ok. %%-------------------------------------------------------------------- %% Description: Sends a ssh connection protocol pty_req. %%-------------------------------------------------------------------- -spec ptty_alloc(ConnectionRef, ChannelId, Options) -> result() when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(), Options :: proplists:proplist(). ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). -spec ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> result() when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(), Options :: proplists:proplist(), Timeout :: timeout(). ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> 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, 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 officially supported! The following functions are part of the %% initial contributed ssh application. They are untested. Do we want them? %% Should they be documented and tested? %%-------------------------------------------------------------------- window_change(ConnectionHandler, Channel, Width, Height) -> window_change(ConnectionHandler, Channel, Width, Height, 0, 0). window_change(ConnectionHandler, Channel, Width, Height, PixWidth, PixHeight) -> ssh_connection_handler:request(ConnectionHandler, Channel, "window-change", false, [?uint32(Width), ?uint32(Height), ?uint32(PixWidth), ?uint32(PixHeight)], 0). signal(ConnectionHandler, Channel, Sig) -> ssh_connection_handler:request(ConnectionHandler, Channel, "signal", false, [?string(Sig)], 0). -spec exit_status(ConnectionRef, ChannelId, Status) -> ok when ConnectionRef :: ssh:connection_ref(), ChannelId :: ssh:channel_id(), Status :: integer(). exit_status(ConnectionHandler, Channel, Status) -> ssh_connection_handler:request(ConnectionHandler, Channel, "exit-status", false, [?uint32(Status)], 0). %%-------------------------------------------------------------------- %%% Internal, that is, ssh application internal API %%-------------------------------------------------------------------- %%%---------------------------------------------------------------- %%% Send data on a channel/connection as result of for example %%% ssh_connection:send (executed in the ssh_connection_state machine) %%% channel_data(ChannelId, DataType, Data0, #connection{channel_cache = Cache} = Connection, From) -> case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id, sent_close = false} = Channel0 -> Data = ?to_binary(Data0), {SendList, Channel} = update_send_window(Channel0#channel{flow_control = From}, DataType, Data, Connection), Replies = lists:map(fun({SendDataType, SendData}) -> {connection_reply, channel_data_msg(Id, SendDataType, SendData)} end, SendList), FlowCtrlMsgs = flow_control(Replies, Channel, Cache), {Replies ++ FlowCtrlMsgs, Connection}; _ -> {[{channel_request_reply,From,{error,closed}}], Connection} end. %%%---------------------------------------------------------------- %%% Handle the channel messages on behalf of the ssh_connection_handler %%% state machine. %%% %%% Replies {Reply, UpdatedConnection} %%% handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, sender_channel = RemoteId, initial_window_size = WindowSz, maximum_packet_size = PacketSz}, #connection{channel_cache = Cache} = Connection0, _, _SSH) -> #channel{remote_id = undefined} = Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), ssh_client_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_msg(Channel, Connection0, {open, ChannelId}); handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId, reason = Reason, description = Descr, lang = Lang}, #connection{channel_cache = Cache} = Connection0, _, _SSH) -> Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), ssh_client_channel:cache_delete(Cache, ChannelId), reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang}); handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _, _SSH) -> reply_msg(ChannelId, Connection, success); handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, Connection, _, _SSH) -> reply_msg(ChannelId, Connection, failure); handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _, _SSH) -> reply_msg(ChannelId, Connection, {eof, ChannelId}); handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, #connection{channel_cache = Cache} = Connection0, _, _SSH) -> case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{sent_close = Closed, remote_id = RemoteId, flow_control = FlowControl} = Channel -> ssh_client_channel:cache_delete(Cache, ChannelId), {CloseMsg, Connection} = reply_msg(Channel, Connection0, {closed, ChannelId}), ConnReplyMsgs = case Closed of true -> []; false -> RemoteCloseMsg = channel_close_msg(RemoteId), [{connection_reply, RemoteCloseMsg}] end, %% if there was a send() in progress, make it fail SendReplyMsgs = case FlowControl of undefined -> []; From -> [{flow_control, From, {error, closed}}] end, Replies = ConnReplyMsgs ++ CloseMsg ++ SendReplyMsgs, {Replies, Connection}; undefined -> {[], Connection0} end; handle_msg(#ssh_msg_channel_data{recipient_channel = ChannelId, data = Data}, Connection, _, _SSH) -> channel_data_reply_msg(ChannelId, Connection, 0, Data); handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId, data_type_code = DataType, data = Data}, Connection, _, _SSH) -> channel_data_reply_msg(ChannelId, Connection, DataType, Data); handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, bytes_to_add = Add}, #connection{channel_cache = Cache} = Connection, _, _SSH) -> #channel{send_window_size = Size, remote_id = RemoteId} = Channel0 = ssh_client_channel:cache_lookup(Cache, ChannelId), {SendList, Channel} = %% TODO: Datatype 0 ? update_send_window(Channel0#channel{send_window_size = Size + Add}, 0, undefined, Connection), Replies = lists:map(fun({Type, Data}) -> {connection_reply, channel_data_msg(RemoteId, Type, Data)} end, SendList), FlowCtrlMsgs = flow_control(Channel, Cache), {Replies ++ FlowCtrlMsgs, Connection}; handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, sender_channel = RemoteId, initial_window_size = WindowSz, maximum_packet_size = PacketSz}, #connection{options = SSHopts} = Connection0, server, _SSH) -> MinAcceptedPackSz = ?GET_OPT(minimal_remote_max_packet_size, SSHopts), if MinAcceptedPackSz =< PacketSz -> try setup_session(Connection0, RemoteId, Type, WindowSz, PacketSz) of Result -> Result catch _:_ -> FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, "Connection refused", "en"), {[{connection_reply, FailMsg}], Connection0} end; MinAcceptedPackSz > PacketSz -> FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, lists:concat(["Maximum packet size below ",MinAcceptedPackSz, " not supported"]), "en"), {[{connection_reply, FailMsg}], Connection0} end; handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip", sender_channel = RemoteId, initial_window_size = WindowSize, maximum_packet_size = PacketSize, data = <> }, #connection{channel_cache = Cache, channel_id_seed = ChId, suggest_window_size = WinSz, suggest_packet_size = PktSz, options = Options, sub_system_supervisor = SubSysSup } = C, client, _SSH) -> {ReplyMsg, NextChId} = case ssh_connection_handler:retrieve(C, {tcpip_forward,ConnectedHost,ConnectedPort}) of {ok, {ConnectToHost,ConnectToPort}} -> case gen_tcp:connect(ConnectToHost, ConnectToPort, [{active,false}, binary]) of {ok,Sock} -> {ok,Pid} = ssh_subsystem_sup:start_channel(client, SubSysSup, self(), ssh_tcpip_forward_client, ChId, [Sock], undefined, Options), ssh_client_channel:cache_update(Cache, #channel{type = "forwarded-tcpip", sys = "none", local_id = ChId, remote_id = RemoteId, user = Pid, recv_window_size = WinSz, recv_packet_size = PktSz, send_window_size = WindowSize, send_packet_size = PacketSize, send_buf = queue:new() }), gen_tcp:controlling_process(Sock, Pid), inet:setopts(Sock, [{active,once}]), {channel_open_confirmation_msg(RemoteId, ChId, WinSz, PktSz), ChId + 1}; {error,Error} -> {channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, io_lib:format("Forwarded connection refused: ~p",[Error]), "en"), ChId} end; undefined -> {channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, io_lib:format("No forwarding ordered",[]), "en"), ChId} end, {[{connection_reply, ReplyMsg}], C#connection{channel_id_seed = NextChId}}; handle_msg(#ssh_msg_channel_open{channel_type = "direct-tcpip", sender_channel = RemoteId, initial_window_size = WindowSize, maximum_packet_size = PacketSize, data = <> }, #connection{channel_cache = Cache, channel_id_seed = ChId, suggest_window_size = WinSz, suggest_packet_size = PktSz, options = Options, sub_system_supervisor = SubSysSup } = C, server, _SSH) -> {ReplyMsg, NextChId} = case ?GET_OPT(tcpip_tunnel_in, Options) of %% May add more to the option, like allowed ip/port pairs to connect to false -> {channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, "Forwarding disabled", "en"), ChId}; true -> case gen_tcp:connect(binary_to_list(HostToConnect), PortToConnect, [{active,false}, binary]) of {ok,Sock} -> {ok,Pid} = ssh_subsystem_sup:start_channel(server, SubSysSup, self(), ssh_tcpip_forward_srv, ChId, [Sock], undefined, Options), ssh_client_channel:cache_update(Cache, #channel{type = "direct-tcpip", sys = "none", local_id = ChId, remote_id = RemoteId, user = Pid, recv_window_size = WinSz, recv_packet_size = PktSz, send_window_size = WindowSize, send_packet_size = PacketSize, send_buf = queue:new() }), gen_tcp:controlling_process(Sock, Pid), inet:setopts(Sock, [{active,once}]), {channel_open_confirmation_msg(RemoteId, ChId, WinSz, PktSz), ChId + 1}; {error,Error} -> {channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, io_lib:format("Forwarded connection refused: ~p",[Error]), "en"), ChId} end end, {[{connection_reply, ReplyMsg}], C#connection{channel_id_seed = NextChId}}; handle_msg(#ssh_msg_channel_open{channel_type = "session", sender_channel = RemoteId}, Connection, client, _SSH) -> %% Client implementations SHOULD reject any session channel open %% requests to make it more difficult for a corrupt server to attack the %% client. See See RFC 4254 6.1. FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, "Connection refused", "en"), {[{connection_reply, FailMsg}], Connection}; handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _, _SSH) -> FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "Not allowed", "en"), {[{connection_reply, FailMsg}], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exit-status", data = Data}, Connection, _, _SSH) -> <> = Data, reply_msg(ChannelId, Connection, {exit_status, ChannelId, Status}); handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exit-signal", want_reply = false, data = Data}, #connection{channel_cache = Cache} = Connection0, _, _SSH) -> <> = Data, Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), RemoteId = Channel#channel.remote_id, {Reply, Connection} = reply_msg(Channel, Connection0, {exit_signal, ChannelId, binary_to_list(SigName), binary_to_list(Err), binary_to_list(Lang)}), CloseMsg = channel_close_msg(RemoteId), {[{connection_reply, CloseMsg}|Reply], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "xon-xoff", want_reply = false, data = Data}, Connection, _, _SSH) -> <> = Data, reply_msg(ChannelId, Connection, {xon_xoff, ChannelId, CDo=/= 0}); handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "window-change", want_reply = false, data = Data}, Connection0, _, _SSH) -> <> = Data, reply_msg(ChannelId, Connection0, {window_change, ChannelId, Width, Height, PixWidth, PixHeight}); handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "signal", data = Data}, Connection0, _, _SSH) -> <> = Data, reply_msg(ChannelId, Connection0, {signal, ChannelId, binary_to_list(SigName)}); handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "subsystem", want_reply = WantReply, data = Data}, #connection{channel_cache = Cache} = Connection, server, _SSH) -> <> = Data, #channel{remote_id=RemoteId} = Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), Reply = case start_subsystem(SsName, Connection, Channel, {subsystem, ChannelId, WantReply, binary_to_list(SsName)}) of {ok, Pid} -> erlang:monitor(process, Pid), ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}), channel_success_msg(RemoteId); {error,_Error} -> channel_failure_msg(RemoteId) end, {[{connection_reply,Reply}], Connection}; handle_msg(#ssh_msg_channel_request{request_type = "subsystem"}, Connection, client, _SSH) -> %% The client SHOULD ignore subsystem requests. See RFC 4254 6.5. {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "pty-req", want_reply = WantReply, data = Data}, Connection, server, _SSH) -> <> = Data, TermName = binary_to_list(BTermName), PtyOpts0 = decode_pty_opts(Modes), PtyOpts = case proplists:get_value(onlcr, PtyOpts0, undefined) of undefined -> %% If - peer client asked for pty %% - did not tell if LF->CRLF expansion is wanted %% then %% - do LF->CRLF expansion [{onlcr,1} | PtyOpts0]; _ -> PtyOpts0 end, PtyRequest = {TermName, Width, Height, PixWidth, PixHeight, PtyOpts}, handle_cli_msg(Connection, ChannelId, {pty, ChannelId, WantReply, PtyRequest}); handle_msg(#ssh_msg_channel_request{request_type = "pty-req"}, Connection, client, _SSH) -> %% The client SHOULD ignore pty requests. See RFC 4254 6.2. {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "shell", want_reply = WantReply}, Connection, server, _SSH) -> handle_cli_msg(Connection, ChannelId, {shell, ChannelId, WantReply}); handle_msg(#ssh_msg_channel_request{request_type = "shell"}, Connection, client, _SSH) -> %% The client SHOULD ignore shell requests. See RFC 4254 6.5. {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exec", want_reply = WantReply, data = Data}, Connection, server, _SSH) -> <> = Data, handle_cli_msg(Connection, ChannelId, {exec, ChannelId, WantReply, binary_to_list(Command)}); handle_msg(#ssh_msg_channel_request{request_type = "exec"}, Connection, client, _SSH) -> %% The client SHOULD ignore exec requests. See RFC 4254 6.5. {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "env", want_reply = WantReply, data = Data}, Connection, server, _SSH) -> <> = Data, handle_cli_msg(Connection, ChannelId, {env, ChannelId, WantReply, Var, Value}); handle_msg(#ssh_msg_channel_request{request_type = "env"}, Connection, client, _SSH) -> %% The client SHOULD ignore env requests. {[], Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, want_reply = WantReply}, #connection{channel_cache = Cache} = Connection, _, _SSH) -> %% Not a valid request_type. All valid types are handling the %% parameter checking in their own clauses above. %% %% The special ReqType faulty_msg signals that something went %% wrong found during decoding. %% %% RFC4254 5.4 says: %% "If 'want reply' is FALSE, no response will be sent to the request. %% Otherwise, the recipient responds with either %% SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, or request-specific %% continuation messages. If the request is not recognized or is not %% supported for the channel, SSH_MSG_CHANNEL_FAILURE is returned." %% case ssh_client_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = RemoteId} when WantReply==true -> FailMsg = channel_failure_msg(RemoteId), {[{connection_reply, FailMsg}], Connection}; _ -> %% Channel has been closed or no reply is wanted {[], Connection} end; handle_msg(#ssh_msg_global_request{name = <<"tcpip-forward">>, want_reply = WantReply, data = <>}, #connection{options = Opts} = Connection, server, _SSH) -> case ?GET_OPT(tcpip_tunnel_out, Opts) of false -> %% This daemon instance has not enabled tcpip_forwarding {[{connection_reply, request_failure_msg()}], Connection}; true -> SubSysSup = ?GET_INTERNAL_OPT(subsystem_sup, Opts), FwdSup = ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup), ConnPid = self(), case ssh_tcpip_forward_acceptor:supervised_start(FwdSup, {ListenAddrStr, ListenPort}, undefined, "forwarded-tcpip", ssh_tcpip_forward_srv, ConnPid) of {ok,ListenPort} when WantReply==true -> {[{connection_reply, request_success_msg(<<>>)}], Connection}; {ok,LPort} when WantReply==true -> {[{connection_reply, request_success_msg(<>)}], Connection}; {error,_} when WantReply==true -> {[{connection_reply, request_failure_msg()}], Connection}; _ when WantReply==true -> {[{connection_reply, request_failure_msg()}], Connection}; _ -> {[], Connection} end end; handle_msg(#ssh_msg_global_request{name = _Type, want_reply = WantReply, data = _Data}, Connection, _Role, _SSH) -> if WantReply == true -> FailMsg = request_failure_msg(), {[{connection_reply, FailMsg}], Connection}; true -> {[], Connection} end; handle_msg(#ssh_msg_request_failure{}, #connection{requests = [{_, From} | Rest]} = Connection, _, _SSH) -> {[{channel_request_reply, From, {failure, <<>>}}], Connection#connection{requests = Rest}}; handle_msg(#ssh_msg_request_failure{}, #connection{requests = [{_, From,_} | Rest]} = Connection, _, _SSH) -> {[{channel_request_reply, From, {failure, <<>>}}], Connection#connection{requests = Rest}}; handle_msg(#ssh_msg_request_success{data = Data}, #connection{requests = [{_, From} | Rest]} = Connection, _, _SSH) -> {[{channel_request_reply, From, {success, Data}}], Connection#connection{requests = Rest}}; handle_msg(#ssh_msg_request_success{data = Data}, #connection{requests = [{_, From, Fun} | Rest]} = Connection0, _, _SSH) -> Connection = Fun({success,Data}, Connection0), {[{channel_request_reply, From, {success, Data}}], Connection#connection{requests = Rest}}; handle_msg(#ssh_msg_disconnect{code = Code, description = Description}, Connection, _, _SSH) -> {disconnect, {Code, Description}, handle_stop(Connection)}. %%%---------------------------------------------------------------- %%% Returns pending responses to be delivered to the peer when a %%% Channel/Connection closes %%% handle_stop(#connection{channel_cache = Cache} = Connection0) -> {Connection, Replies} = ssh_client_channel:cache_foldl( fun(Channel, {Connection1, Acc}) -> {Reply, Connection2} = reply_msg(Channel, Connection1, {closed, Channel#channel.local_id}), {Connection2, Reply ++ Acc} end, {Connection0, []}, Cache), ssh_client_channel:cache_delete(Cache), {Replies, Connection}. %%%---------------------------------------------------------------- %%% channel_*_msg(...) %%% Returns a #ssh_msg_....{} for channel operations. %%% channel_adjust_window_msg(ChannelId, Bytes) -> #ssh_msg_channel_window_adjust{recipient_channel = ChannelId, bytes_to_add = Bytes}. channel_close_msg(ChannelId) -> #ssh_msg_channel_close {recipient_channel = ChannelId}. channel_data_msg(ChannelId, 0, Data) -> #ssh_msg_channel_data{recipient_channel = ChannelId, data = Data}; channel_data_msg(ChannelId, Type, Data) -> #ssh_msg_channel_extended_data{recipient_channel = ChannelId, data_type_code = Type, data = Data}. channel_eof_msg(ChannelId) -> #ssh_msg_channel_eof{recipient_channel = ChannelId}. channel_failure_msg(ChannelId) -> #ssh_msg_channel_failure{recipient_channel = ChannelId}. channel_open_msg(Type, ChannelId, WindowSize, MaxPacketSize, Data) -> #ssh_msg_channel_open{channel_type = Type, sender_channel = ChannelId, initial_window_size = WindowSize, maximum_packet_size = MaxPacketSize, data = Data }. channel_open_confirmation_msg(RemoteId, LID, WindowSize, PacketSize) -> #ssh_msg_channel_open_confirmation{recipient_channel = RemoteId, sender_channel = LID, initial_window_size = WindowSize, maximum_packet_size = PacketSize}. channel_open_failure_msg(RemoteId, Reason, Description, Lang) -> #ssh_msg_channel_open_failure{recipient_channel = RemoteId, reason = Reason, description = Description, lang = Lang}. channel_status_msg({success, ChannelId}) -> channel_success_msg(ChannelId); channel_status_msg({failure, ChannelId}) -> channel_failure_msg(ChannelId). channel_request_msg(ChannelId, Type, WantReply, Data) -> #ssh_msg_channel_request{recipient_channel = ChannelId, request_type = Type, want_reply = WantReply, data = Data}. channel_success_msg(ChannelId) -> #ssh_msg_channel_success{recipient_channel = ChannelId}. %%%---------------------------------------------------------------- %%% request_*_msg(...) %%% Returns a #ssh_msg_....{} %%% request_global_msg(Name, WantReply, Data) -> #ssh_msg_global_request{name = Name, want_reply = WantReply, data = Data}. request_failure_msg() -> #ssh_msg_request_failure{}. request_success_msg(Data) -> #ssh_msg_request_success{data = Data}. %%%---------------------------------------------------------------- %%% %%% encode_ip(Addr) when is_tuple(Addr) -> case catch inet_parse:ntoa(Addr) of {'EXIT',_} -> false; A -> A end; encode_ip(Addr) when is_list(Addr) -> case inet_parse:address(Addr) of {ok, _} -> Addr; Error -> case inet:getaddr(Addr, inet) of {ok, A} -> inet_parse:ntoa(A); Error -> false end end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% Internal functions %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%---------------------------------------------------------------- %%% Create the channel data when an ssh_msg_open_channel message %%% of "session" typ is handled %%% setup_session(#connection{channel_cache = Cache, channel_id_seed = NewChannelID, suggest_window_size = WinSz, suggest_packet_size = PktSz } = C, RemoteId, Type, WindowSize, PacketSize) when is_integer(WinSz), is_integer(PktSz) -> NextChannelID = NewChannelID + 1, Channel = #channel{type = Type, sys = "ssh", local_id = NewChannelID, recv_window_size = WinSz, recv_packet_size = PktSz, send_window_size = WindowSize, send_packet_size = PacketSize, send_buf = queue:new(), remote_id = RemoteId }, ssh_client_channel:cache_update(Cache, Channel), OpenConfMsg = channel_open_confirmation_msg(RemoteId, NewChannelID, WinSz, PktSz), Reply = {connection_reply, OpenConfMsg}, {[Reply], C#connection{channel_id_seed = NextChannelID}}. %%%---------------------------------------------------------------- %%% Start a cli or subsystem %%% start_cli(#connection{options = Options, cli_spec = CliSpec, exec = Exec, sub_system_supervisor = SubSysSup}, ChannelId) -> case CliSpec of no_cli -> {error, cli_disabled}; {CbModule, Args} -> ssh_subsystem_sup:start_channel(server, SubSysSup, self(), CbModule, ChannelId, Args, Exec, Options) end. start_subsystem(BinName, #connection{options = Options, sub_system_supervisor = SubSysSup}, #channel{local_id = ChannelId}, _ReplyMsg) -> Name = binary_to_list(BinName), case check_subsystem(Name, Options) of {Callback, Opts} when is_atom(Callback), Callback =/= none -> ssh_subsystem_sup:start_channel(server, SubSysSup, self(), Callback, ChannelId, Opts, undefined, Options); {none, _} -> {error, bad_subsystem}; {_, _} -> {error, legacy_option_not_supported} end. %%% Helpers for starting cli/subsystems check_subsystem("sftp"= SsName, Options) -> 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 -> proplists:get_value(SsName, SubSystems, {none, []}) end; check_subsystem(SsName, Options) -> Subsystems = ?GET_OPT(subsystems, Options), case proplists:get_value(SsName, Subsystems, {none, []}) of Fun when is_function(Fun) -> {Fun, []}; {_, _} = Value -> Value end. %%%---------------------------------------------------------------- %%% %%% Send-window handling %%% update_send_window(Channel, _, undefined, #connection{channel_cache = Cache}) -> do_update_send_window(Channel, Cache); update_send_window(#channel{send_buf = SendBuffer} = Channel, DataType, Data, #connection{channel_cache = Cache}) -> do_update_send_window(Channel#channel{send_buf = queue:in({DataType, Data}, SendBuffer)}, Cache). do_update_send_window(Channel0, Cache) -> {SendMsgs, Channel} = get_window(Channel0, []), ssh_client_channel:cache_update(Cache, Channel), {SendMsgs, Channel}. get_window(#channel{send_window_size = 0 } = Channel, Acc) -> {lists:reverse(Acc), Channel}; get_window(#channel{send_packet_size = 0 } = Channel, Acc) -> {lists:reverse(Acc), Channel}; get_window(#channel{send_buf = Buffer, send_packet_size = PacketSize, send_window_size = WindowSize0 } = Channel, Acc0) -> case queue:out(Buffer) of {{value, {_, Data} = Msg}, NewBuffer} -> case handle_send_window(Msg, size(Data), PacketSize, WindowSize0, Acc0) of {WindowSize, Acc, {_, <<>>}} -> {lists:reverse(Acc), Channel#channel{send_window_size = WindowSize, send_buf = NewBuffer}}; {WindowSize, Acc, Rest} -> get_window(Channel#channel{send_window_size = WindowSize, send_buf = queue:in_r(Rest, NewBuffer)}, Acc) end; {empty, NewBuffer} -> {[], Channel#channel{send_buf = NewBuffer}} end. handle_send_window(Msg = {Type, Data}, Size, PacketSize, WindowSize, Acc) when Size =< WindowSize -> case Size =< PacketSize of true -> {WindowSize - Size, [Msg | Acc], {Type, <<>>}}; false -> <> = Data, {WindowSize - PacketSize, [{Type, Msg1} | Acc], {Type, Msg2}} end; handle_send_window({Type, Data}, _, PacketSize, WindowSize, Acc) when WindowSize =< PacketSize -> <> = Data, {WindowSize - WindowSize, [{Type, Msg1} | Acc], {Type, Msg2}}; handle_send_window({Type, Data}, _, PacketSize, WindowSize, Acc) -> <> = Data, {WindowSize - PacketSize, [{Type, Msg1} | Acc], {Type, Msg2}}. %%%---------------------------------------------------------------- %%% %%% Flow control %%% flow_control(Channel, Cache) -> flow_control([window_adjusted], Channel, Cache). flow_control([], Channel, Cache) -> ssh_client_channel:cache_update(Cache, Channel), []; flow_control([_|_], #channel{flow_control = From, send_buf = Buffer} = Channel, Cache) when From =/= undefined -> case queue:is_empty(Buffer) of true -> ssh_client_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), [{flow_control, Cache, Channel, From, ok}]; false -> [] end; flow_control(_,_,_) -> []. %%%---------------------------------------------------------------- %%% %%% Pseudo terminal stuff %%% pty_req(ConnectionHandler, Channel, Term, Width, Height, PixWidth, PixHeight, PtyOpts, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, Channel, "pty-req", true, [?string(Term), ?uint32(Width), ?uint32(Height), ?uint32(PixWidth),?uint32(PixHeight), encode_pty_opts(PtyOpts)], TimeOut). pty_default_dimensions(Dimension, TermData) -> case proplists:get_value(Dimension, TermData, 0) of N when is_integer(N), N > 0 -> {N, 0}; _ -> 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}; _ -> {?TERMINAL_WIDTH, 0} end end. encode_pty_opts(Opts) -> Bin = list_to_binary(encode_pty_opts2(Opts)), <>. encode_pty_opts2([]) -> [?TTY_OP_END]; encode_pty_opts2([{vintr,Value} | Opts]) -> [?VINTR, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vquit,Value} | Opts]) -> [?VQUIT, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{verase,Value} | Opts]) -> [?VERASE, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vkill,Value} | Opts]) -> [?VKILL, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{veof,Value} | Opts]) -> [?VEOF, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{veol,Value} | Opts]) -> [?VEOL, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{veol2,Value} | Opts]) -> [?VEOL2, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vstart,Value} | Opts]) -> [?VSTART, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vstop,Value} | Opts]) -> [?VSTOP, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vsusp,Value} | Opts]) -> [?VSUSP, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vdsusp,Value} | Opts]) -> [?VDSUSP, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vreprint,Value} | Opts]) -> [?VREPRINT, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vwerase,Value} | Opts]) -> [ ?VWERASE, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vlnext,Value} | Opts]) -> [?VLNEXT, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vflush,Value} | Opts]) -> [?VFLUSH, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vswtch,Value} | Opts]) -> [?VSWTCH, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vstatus,Value} | Opts]) -> [?VSTATUS, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{vdiscard,Value} | Opts]) -> [?VDISCARD, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{ignpar,Value} | Opts]) -> [?IGNPAR, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{parmrk,Value} | Opts]) -> [?PARMRK, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{inpck,Value} | Opts]) -> [?INPCK, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{istrip,Value} | Opts]) -> [?ISTRIP, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{inlcr,Value} | Opts]) -> [?INLCR, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{igncr,Value} | Opts]) -> [?IGNCR, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{icrnl,Value} | Opts]) -> [?ICRNL, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{iuclc,Value} | Opts]) -> [?IUCLC, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{ixon,Value} | Opts]) -> [?IXON, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{ixany,Value} | Opts]) -> [?IXANY, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{ixoff,Value} | Opts]) -> [?IXOFF, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{imaxbel,Value} | Opts]) -> [?IMAXBEL, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{iutf8,Value} | Opts]) -> [?IUTF8, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{isig,Value} | Opts]) -> [?ISIG, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{icanon,Value} | Opts]) -> [?ICANON, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{xcase,Value} | Opts]) -> [?XCASE, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{echo,Value} | Opts]) -> [?ECHO, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{echoe,Value} | Opts]) -> [?ECHOE, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{echok,Value} | Opts]) -> [?ECHOK, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{echonl,Value} | Opts]) -> [?ECHONL, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{noflsh,Value} | Opts]) -> [?NOFLSH, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{tostop,Value} | Opts]) -> [?TOSTOP, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{iexten,Value} | Opts]) -> [?IEXTEN, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{echoctl,Value} | Opts]) -> [?ECHOCTL, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{echoke,Value} | Opts]) -> [?ECHOKE, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{pendin,Value} | Opts]) -> [?PENDIN, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{opost,Value} | Opts]) -> [?OPOST, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{olcuc,Value} | Opts]) -> [?OLCUC, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{onlcr,Value} | Opts]) -> [?ONLCR, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{ocrnl,Value} | Opts]) -> [?OCRNL, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{onocr,Value} | Opts]) -> [?ONOCR, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{onlret,Value} | Opts]) -> [?ONLRET, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{cs7,Value} | Opts]) -> [?CS7, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{cs8,Value} | Opts]) -> [?CS8, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{parenb,Value} | Opts]) -> [?PARENB, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{parodd,Value} | Opts]) -> [?PARODD, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{tty_op_ispeed,Value} | Opts]) -> [?TTY_OP_ISPEED, ?uint32(Value) | encode_pty_opts2(Opts)]; encode_pty_opts2([{tty_op_ospeed,Value} | Opts]) -> [?TTY_OP_OSPEED, ?uint32(Value) | encode_pty_opts2(Opts)]. decode_pty_opts(<<>>) -> []; decode_pty_opts(<<0, 0, 0, 0>>) -> []; decode_pty_opts(<>) -> decode_pty_opts2(Modes); decode_pty_opts(Binary) -> decode_pty_opts2(Binary). decode_pty_opts2(<>) -> []; decode_pty_opts2(<>) -> Op = case Code of ?VINTR -> vintr; ?VQUIT -> vquit; ?VERASE -> verase; ?VKILL -> vkill; ?VEOF -> veof; ?VEOL -> veol; ?VEOL2 -> veol2; ?VSTART -> vstart; ?VSTOP -> vstop; ?VSUSP -> vsusp; ?VDSUSP -> vdsusp; ?VREPRINT -> vreprint; ?VWERASE -> vwerase; ?VLNEXT -> vlnext; ?VFLUSH -> vflush; ?VSWTCH -> vswtch; ?VSTATUS -> vstatus; ?VDISCARD -> vdiscard; ?IGNPAR -> ignpar; ?PARMRK -> parmrk; ?INPCK -> inpck; ?ISTRIP -> istrip; ?INLCR -> inlcr; ?IGNCR -> igncr; ?ICRNL -> icrnl; ?IUCLC -> iuclc; ?IXON -> ixon; ?IXANY -> ixany; ?IXOFF -> ixoff; ?IMAXBEL -> imaxbel; ?IUTF8 -> iutf8; % RFC 8160 ?ISIG -> isig; ?ICANON -> icanon; ?XCASE -> xcase; ?ECHO -> echo; ?ECHOE -> echoe; ?ECHOK -> echok; ?ECHONL -> echonl; ?NOFLSH -> noflsh; ?TOSTOP -> tostop; ?IEXTEN -> iexten; ?ECHOCTL -> echoctl; ?ECHOKE -> echoke; ?PENDIN -> pendin; ?OPOST -> opost; ?OLCUC -> olcuc; ?ONLCR -> onlcr; ?OCRNL -> ocrnl; ?ONOCR -> onocr; ?ONLRET -> onlret; ?CS7 -> cs7; ?CS8 -> cs8; ?PARENB -> parenb; ?PARODD -> parodd; ?TTY_OP_ISPEED -> tty_op_ispeed; ?TTY_OP_OSPEED -> tty_op_ospeed; _ -> Code end, [{Op, Value} | decode_pty_opts2(Tail)]. backwards_compatible([], Acc) -> Acc; backwards_compatible([{hight, Value} | Rest], Acc) -> backwards_compatible(Rest, [{height, Value} | Acc]); backwards_compatible([{pixel_hight, Value} | Rest], Acc) -> backwards_compatible(Rest, [{height, Value} | Acc]); backwards_compatible([Value| Rest], Acc) -> backwards_compatible(Rest, [ Value | Acc]). %%%---------------------------------------------------------------- %%% %%% Common part of handling channel messages meant for a cli (like "env", "exec" etc) %%% Called at the finnish of handle_msg(#ssh_msg_channel_request,...) %%% handle_cli_msg(C0, ChId, Reply0) -> Cache = C0#connection.channel_cache, Ch0 = ssh_client_channel:cache_lookup(Cache, ChId), case Ch0#channel.user of undefined -> case start_cli(C0, ChId) of {ok, Pid} -> erlang:monitor(process, Pid), Ch = Ch0#channel{user = Pid}, ssh_client_channel:cache_update(Cache, Ch), reply_msg(Ch, C0, Reply0); {error, _Error} -> Reply = {connection_reply, channel_failure_msg(Ch0#channel.remote_id)}, {[Reply], C0} end; _ -> reply_msg(Ch0, C0, Reply0) end. %%%---------------------------------------------------------------- %%% %%% TCP/IP forwarding %%%---------------------------------------------------------------- %%% %%% Request response handling on return to the calling ssh_connection_handler %%% state machine. %%% channel_data_reply_msg(ChannelId, Connection, DataType, Data) -> case ssh_client_channel:cache_lookup(Connection#connection.channel_cache, ChannelId) of #channel{recv_window_size = Size} = Channel -> WantedSize = Size - size(Data), ssh_client_channel:cache_update(Connection#connection.channel_cache, Channel#channel{recv_window_size = WantedSize}), reply_msg(Channel, Connection, {data, ChannelId, DataType, Data}); undefined -> {[], Connection} end. reply_msg(ChId, C, Reply) when is_integer(ChId) -> reply_msg(ssh_client_channel:cache_lookup(C#connection.channel_cache, ChId), C, Reply); reply_msg(Channel, Connection, {open, _} = Reply) -> request_reply_or_data(Channel, Connection, Reply); reply_msg(Channel, Connection, {open_error, _, _, _} = Reply) -> request_reply_or_data(Channel, Connection, Reply); reply_msg(Channel, Connection, success = Reply) -> request_reply_or_data(Channel, Connection, Reply); reply_msg(Channel, Connection, failure = Reply) -> request_reply_or_data(Channel, Connection, Reply); reply_msg(Channel, Connection, {closed, _} = Reply) -> request_reply_or_data(Channel, Connection, Reply); reply_msg(undefined, Connection, _Reply) -> {[], Connection}; reply_msg(#channel{user = ChannelPid}, Connection, Reply) -> {[{channel_data, ChannelPid, Reply}], Connection}. request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid}, #connection{requests = Requests} = Connection, Reply) -> case lists:keysearch(ChannelId, 1, Requests) of {value, {ChannelId, From}} -> {[{channel_request_reply, From, Reply}], Connection#connection{requests = lists:keydelete(ChannelId, 1, Requests)}}; false when (Reply == success) or (Reply == failure) -> {[], Connection}; false -> {[{channel_data, ChannelPid, Reply}], Connection} end. %%%---------------------------------------------------------------- send_environment_vars(ConnectionHandler, Channel, VarNames) -> lists:foldl( fun(Var, success) -> case os:getenv(Var) of false -> success; Value -> setenv(ConnectionHandler, Channel, false, Var, Value, infinity) end end, success, VarNames).