diff options
Diffstat (limited to 'lib/ssh/src/ssh_connection.erl')
-rw-r--r-- | lib/ssh/src/ssh_connection.erl | 1366 |
1 files changed, 1366 insertions, 0 deletions
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl new file mode 100644 index 0000000000..0aaf1c18d2 --- /dev/null +++ b/lib/ssh/src/ssh_connection.erl @@ -0,0 +1,1366 @@ +%% +%% %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: Details of connection protocol +%%---------------------------------------------------------------------- + +-module(ssh_connection). + +-include("ssh.hrl"). +-include("ssh_connect.hrl"). +-include("ssh_transport.hrl"). + +-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, open_pty/3, open_pty/7, + open_pty/9, setenv/5, window_change/4, window_change/6, + direct_tcpip/6, direct_tcpip/8, tcpip_forward/3, + cancel_tcpip_forward/3, signal/3, exit_status/3, encode_ip/1, close/2, + reply_request/4]). + +-export([channel_data/6, handle_msg/4, channel_eof_msg/1, + channel_close_msg/1, channel_success_msg/1, channel_failure_msg/1, + channel_adjust_window_msg/2, channel_data_msg/3, + channel_open_msg/5, channel_open_confirmation_msg/4, + channel_open_failure_msg/4, channel_request_msg/4, + global_request_msg/3, request_failure_msg/0, + request_success_msg/1, bind/4, unbind/3, unbind_channel/2, + bound_channel/3, messages/0]). + +%%-------------------------------------------------------------------- +%%% Internal application API +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Function: session_channel(ConnectionManager +%% [, InitialWindowSize, MaxPacketSize], +%% Timeout) -> {ok, } +%% ConnectionManager = pid() +%% InitialWindowSize = integer() +%% MaxPacketSize = integer() +%% +%% 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. +%% -------------------------------------------------------------------- +session_channel(ConnectionManager, Timeout) -> + session_channel(ConnectionManager, + ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, + Timeout). +session_channel(ConnectionManager, InitialWindowSize, + MaxPacketSize, Timeout) -> + ssh_connection_manager:open_channel(ConnectionManager, "session", <<>>, + InitialWindowSize, + MaxPacketSize, Timeout). +%%-------------------------------------------------------------------- +%% Function: exec(ConnectionManager, ChannelId, Command, Timeout) -> +%% +%% ConnectionManager = pid() +%% ChannelId = integer() +%% Cmd = string() +%% Timeout = integer() +%% +%% Description: Will request that the server start the +%% execution of the given command. +%%-------------------------------------------------------------------- +exec(ConnectionManager, ChannelId, Command, TimeOut) -> + ssh_connection_manager:request(ConnectionManager, self(), ChannelId, "exec", + true, [?string(Command)], TimeOut). +%%-------------------------------------------------------------------- +%% Function: shell(ConnectionManager, ChannelId) -> +%% +%% ConnectionManager = pid() +%% ChannelId = integer() +%% +%% Description: Will request that the user's default shell (typically +%% defined in /etc/passwd in UNIX systems) be started at the other +%% end. +%%-------------------------------------------------------------------- +shell(ConnectionManager, ChannelId) -> + ssh_connection_manager:request(ConnectionManager, self(), ChannelId, + "shell", false, <<>>, 0). +%%-------------------------------------------------------------------- +%% Function: subsystem(ConnectionManager, ChannelId, SubSystem, TimeOut) -> +%% +%% ConnectionManager = pid() +%% ChannelId = integer() +%% SubSystem = string() +%% TimeOut = integer() +%% +%% +%% Description: Executes a predefined subsystem. +%%-------------------------------------------------------------------- +subsystem(ConnectionManager, ChannelId, SubSystem, TimeOut) -> + ssh_connection_manager:request(ConnectionManager, self(), + ChannelId, "subsystem", + true, [?string(SubSystem)], TimeOut). +%%-------------------------------------------------------------------- +%% Function: send(ConnectionManager, ChannelId, Type, Data, [TimeOut]) -> +%% +%% +%% Description: Sends channel data. +%%-------------------------------------------------------------------- +send(ConnectionManager, ChannelId, Data) -> + send(ConnectionManager, ChannelId, 0, Data, infinity). +send(ConnectionManager, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> + send(ConnectionManager, ChannelId, 0, Data, TimeOut); +send(ConnectionManager, ChannelId, Type, Data) -> + send(ConnectionManager, ChannelId, Type, Data, infinity). +send(ConnectionManager, ChannelId, Type, Data, TimeOut) -> + ssh_connection_manager:send(ConnectionManager, ChannelId, + Type, Data, TimeOut). +%%-------------------------------------------------------------------- +%% Function: send_eof(ConnectionManager, ChannelId) -> +%% +%% +%% Description: Sends eof on the channel <ChannelId>. +%%-------------------------------------------------------------------- +send_eof(ConnectionManager, Channel) -> + ssh_connection_manager:send_eof(ConnectionManager, Channel). + +%%-------------------------------------------------------------------- +%% Function: adjust_window(ConnectionManager, Channel, Bytes) -> +%% +%% +%% Description: Adjusts the ssh flowcontrol window. +%%-------------------------------------------------------------------- +adjust_window(ConnectionManager, Channel, Bytes) -> + ssh_connection_manager:adjust_window(ConnectionManager, Channel, Bytes). + +%%-------------------------------------------------------------------- +%% Function: setenv(ConnectionManager, ChannelId, Var, Value, TimeOut) -> +%% +%% +%% Description: Environment variables may be passed to the shell/command to be +%% started later. +%%-------------------------------------------------------------------- +setenv(ConnectionManager, ChannelId, Var, Value, TimeOut) -> + ssh_connection_manager:request(ConnectionManager, ChannelId, + "env", true, [?string(Var), ?string(Value)], TimeOut). + + +%%-------------------------------------------------------------------- +%% Function: close(ConnectionManager, ChannelId) -> +%% +%% +%% Description: Sends a close message on the channel <ChannelId>. +%%-------------------------------------------------------------------- +close(ConnectionManager, ChannelId) -> + ssh_connection_manager:close(ConnectionManager, ChannelId). + + +%%-------------------------------------------------------------------- +%% Function: reply_request(ConnectionManager, WantReply, Status, CannelId) ->_ +%% +%% +%% Description: Send status replies to requests that want such replies. +%%-------------------------------------------------------------------- +reply_request(ConnectionManager, true, Status, ChannelId) -> + ConnectionManager ! {ssh_cm, self(), {Status, ChannelId}}, + ok; +reply_request(_,false, _, _) -> + ok. + + +%%-------------------------------------------------------------------- +%% Function: window_change(ConnectionManager, Channel, Width, Height) -> +%% +%% +%% Description: Not yet officialy supported. +%%-------------------------------------------------------------------- +window_change(ConnectionManager, Channel, Width, Height) -> + window_change(ConnectionManager, Channel, Width, Height, 0, 0). +window_change(ConnectionManager, Channel, Width, Height, + PixWidth, PixHeight) -> + ssh_connection_manager:request(ConnectionManager, Channel, + "window-change", false, + [?uint32(Width), ?uint32(Height), + ?uint32(PixWidth), ?uint32(PixHeight)], 0). +%%-------------------------------------------------------------------- +%% Function: signal(ConnectionManager, Channel, Sig) -> +%% +%% +%% Description: Not yet officialy supported. +%%-------------------------------------------------------------------- +signal(ConnectionManager, Channel, Sig) -> + ssh_connection_manager:request(ConnectionManager, Channel, + "signal", false, [?string(Sig)], 0). + +%%-------------------------------------------------------------------- +%% Function: signal(ConnectionManager, Channel, Status) -> +%% +%% +%% Description: Not yet officialy supported. +%%-------------------------------------------------------------------- +exit_status(ConnectionManager, Channel, Status) -> + ssh_connection_manager:request(ConnectionManager, Channel, + "exit-status", false, [?uint32(Status)], 0). + + +%%-------------------------------------------------------------------- +%% Function: open_pty(ConnectionManager, Channel, TimeOut) -> +%% +%% +%% Description: Not yet officialy supported. +%%-------------------------------------------------------------------- +open_pty(ConnectionManager, Channel, TimeOut) -> + open_pty(ConnectionManager, Channel, + os:getenv("TERM"), 80, 24, [], TimeOut). + +open_pty(ConnectionManager, Channel, Term, Width, Height, PtyOpts, TimeOut) -> + open_pty(ConnectionManager, Channel, Term, Width, + Height, 0, 0, PtyOpts, TimeOut). + +open_pty(ConnectionManager, Channel, Term, Width, Height, + PixWidth, PixHeight, PtyOpts, TimeOut) -> + ssh_connection_manager:request(ConnectionManager, + Channel, "pty-req", true, + [?string(Term), + ?uint32(Width), ?uint32(Height), + ?uint32(PixWidth),?uint32(PixHeight), + encode_pty_opts(PtyOpts)], TimeOut). + + +%%-------------------------------------------------------------------- +%% Function: direct_tcpip(ConnectionManager, RemoteHost, +%% RemotePort, OrigIP, OrigPort, Timeout) -> +%% +%% +%% Description: Not yet officialy supported. +%%-------------------------------------------------------------------- +direct_tcpip(ConnectionManager, RemoteHost, + RemotePort, OrigIP, OrigPort, Timeout) -> + direct_tcpip(ConnectionManager, RemoteHost, RemotePort, OrigIP, OrigPort, + ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). + +direct_tcpip(ConnectionManager, RemoteIP, RemotePort, OrigIP, OrigPort, + InitialWindowSize, MaxPacketSize, Timeout) -> + case {encode_ip(RemoteIP), encode_ip(OrigIP)} of + {false, _} -> + {error, einval}; + {_, false} -> + {error, einval}; + {RIP, OIP} -> + ssh_connection_manager:open_channel(ConnectionManager, + "direct-tcpip", + [?string(RIP), + ?uint32(RemotePort), + ?string(OIP), + ?uint32(OrigPort)], + InitialWindowSize, + MaxPacketSize, + Timeout) + end. +%%-------------------------------------------------------------------- +%% Function: tcpip_forward(ConnectionManager, BindIP, BindPort) -> +%% +%% +%% Description: Not yet officialy supported. +%%-------------------------------------------------------------------- +tcpip_forward(ConnectionManager, BindIP, BindPort) -> + case encode_ip(BindIP) of + false -> + {error, einval}; + IPStr -> + ssh_connection_manager:global_request(ConnectionManager, + "tcpip-forward", true, + [?string(IPStr), + ?uint32(BindPort)]) + end. +%%-------------------------------------------------------------------- +%% Function: cancel_tcpip_forward(ConnectionManager, BindIP, Port) -> +%% +%% +%% Description: Not yet officialy supported. +%%-------------------------------------------------------------------- +cancel_tcpip_forward(ConnectionManager, BindIP, Port) -> + case encode_ip(BindIP) of + false -> + {error, einval}; + IPStr -> + ssh_connection_manager:global_request(ConnectionManager, + "cancel-tcpip-forward", true, + [?string(IPStr), + ?uint32(Port)]) + end. + +%%-------------------------------------------------------------------- +%%% Internal API +%%-------------------------------------------------------------------- +channel_data(ChannelId, DataType, Data, Connection, ConnectionPid, From) + when is_list(Data)-> + channel_data(ChannelId, DataType, + list_to_binary(Data), Connection, ConnectionPid, From); + +channel_data(ChannelId, DataType, Data, + #connection{channel_cache = Cache} = Connection, ConnectionPid, + From) -> + + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = Id} = Channel0 -> + {SendList, Channel} = update_send_window(Channel0, DataType, + Data, Connection), + Replies = + lists:map(fun({SendDataType, SendData}) -> + {connection_reply, ConnectionPid, + channel_data_msg(Id, + SendDataType, + SendData)} + end, SendList), + FlowCtrlMsgs = flow_control(Replies, + Channel#channel{flow_control = From}, + Cache), + {{replies, Replies ++ FlowCtrlMsgs}, Connection}; + undefined -> + {noreply, Connection} + end. + +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, _, _) -> + + #channel{remote_id = undefined} = Channel = + ssh_channel:cache_lookup(Cache, ChannelId), + + ssh_channel:cache_update(Cache, Channel#channel{ + remote_id = RemoteId, + send_window_size = WindowSz, + send_packet_size = PacketSz}), + {Reply, Connection} = reply_msg(Channel, Connection0, {open, ChannelId}), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId, + reason = Reason, + description = Descr, + lang = Lang}, + #connection{channel_cache = Cache} = Connection0, _, _) -> + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + ssh_channel:cache_delete(Cache, ChannelId), + {Reply, Connection} = + reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang}), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, + #connection{channel_cache = Cache} = Connection0, _, _) -> + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + {Reply, Connection} = reply_msg(Channel, Connection0, success), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, + #connection{channel_cache = Cache} = Connection0, _, _) -> + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + {Reply, Connection} = reply_msg(Channel, Connection0, failure), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, + #connection{channel_cache = Cache} = Connection0, _, _) -> + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + {Reply, Connection} = reply_msg(Channel, Connection0, {eof, ChannelId}), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, + #connection{channel_cache = Cache} = Connection0, + ConnectionPid, _) -> + + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{sent_close = Closed, remote_id = RemoteId} = Channel -> + ssh_channel:cache_delete(Cache, ChannelId), + {CloseMsg, Connection} = + reply_msg(Channel, Connection0, {closed, ChannelId}), + case Closed of + true -> + {{replies, [CloseMsg]}, Connection}; + false -> + RemoteCloseMsg = channel_close_msg(RemoteId), + {{replies, + [{connection_reply, + ConnectionPid, RemoteCloseMsg}, + CloseMsg]}, Connection} + end; + undefined -> + {{replies, []}, Connection0} + end; + +handle_msg(#ssh_msg_channel_data{recipient_channel = ChannelId, + data = Data}, + #connection{channel_cache = Cache} = Connection0, _, _) -> + + #channel{recv_window_size = Size} = Channel = + ssh_channel:cache_lookup(Cache, ChannelId), + WantedSize = Size - size(Data), + ssh_channel:cache_update(Cache, Channel#channel{ + recv_window_size = WantedSize}), + {Replies, Connection} = + channel_data_reply(Cache, Channel, Connection0, 0, Data), + {{replies, Replies}, Connection}; + +handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId, + data_type_code = DataType, + data = Data}, + #connection{channel_cache = Cache} = Connection0, _, _) -> + + #channel{recv_window_size = Size} = Channel = + ssh_channel:cache_lookup(Cache, ChannelId), + WantedSize = Size - size(Data), + ssh_channel:cache_update(Cache, Channel#channel{ + recv_window_size = WantedSize}), + {Replies, Connection} = + channel_data_reply(Cache, Channel, Connection0, DataType, Data), + {{replies, Replies}, Connection}; + +handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, + bytes_to_add = Add}, + #connection{channel_cache = Cache} = Connection, + ConnectionPid, _) -> + + #channel{send_window_size = Size} = + Channel0 = ssh_channel:cache_lookup(Cache, ChannelId), + + {SendList, Channel} = %% TODO: Datatype 0 ? + update_send_window(Channel0#channel{send_window_size = Size + Add}, + 0, <<>>, Connection), + + Replies = lists:map(fun({Type, Data}) -> + {connection_reply, ConnectionPid, + channel_data_msg(ChannelId, Type, Data)} + end, SendList), + FlowCtrlMsgs = flow_control(Channel, Cache), + {{replies, Replies ++ FlowCtrlMsgs}, Connection}; + +handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, + sender_channel = ChannelId, + initial_window_size = WindowSz, + maximum_packet_size = PacketSz}, Connection0, + ConnectionPid, server) -> + + try setup_session(Connection0, ConnectionPid, ChannelId, + Type, WindowSz, PacketSz) of + Result -> + Result + catch _:_ -> + FailMsg = channel_open_failure_msg(ChannelId, + ?SSH_OPEN_CONNECT_FAILED, + "Connection refused", "en"), + {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, + Connection0} + end; + +handle_msg(#ssh_msg_channel_open{channel_type = "session", + sender_channel = RemoteId}, + Connection, ConnectionPid, client) -> + %% 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"), + {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, + Connection}; + +handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip" = Type, + sender_channel = RemoteId, + initial_window_size = RWindowSz, + maximum_packet_size = RPacketSz, + data = Data}, + #connection{channel_cache = Cache} = Connection0, + ConnectionPid, server) -> + <<?UINT32(ALen), Address:ALen/binary, ?UINT32(Port), + ?UINT32(OLen), Orig:OLen/binary, ?UINT32(OrigPort)>> = Data, + + case bound_channel(Address, Port, Connection0) of + undefined -> + FailMsg = channel_open_failure_msg(RemoteId, + ?SSH_OPEN_CONNECT_FAILED, + "Connection refused", "en"), + {{replies, + [{connection_reply, ConnectionPid, FailMsg}]}, Connection0}; + ChannelPid -> + {ChannelId, Connection1} = new_channel_id(Connection0), + LWindowSz = ?DEFAULT_WINDOW_SIZE, + LPacketSz = ?DEFAULT_PACKET_SIZE, + Channel = #channel{type = Type, + sys = "none", + user = ChannelPid, + local_id = ChannelId, + recv_window_size = LWindowSz, + recv_packet_size = LPacketSz, + send_window_size = RWindowSz, + send_packet_size = RPacketSz}, + ssh_channel:cache_update(Cache, Channel), + OpenConfMsg = channel_open_confirmation_msg(RemoteId, ChannelId, + LWindowSz, LPacketSz), + {OpenMsg, Connection} = + reply_msg(Channel, Connection1, + {open, Channel, {forwarded_tcpip, + decode_ip(Address), Port, + decode_ip(Orig), OrigPort}}), + {{replies, [{connection_reply, ConnectionPid, OpenConfMsg}, + OpenMsg]}, Connection} + end; + +handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip", + sender_channel = RemoteId}, + Connection, ConnectionPid, client) -> + %% Client implementations SHOULD reject direct TCP/IP open requests for + %% security reasons. See RFC 4254 7.2. + FailMsg = channel_open_failure_msg(RemoteId, + ?SSH_OPEN_CONNECT_FAILED, + "Connection refused", "en"), + {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, Connection}; + + +handle_msg(#ssh_msg_channel_open{sender_channel = ChannelId}, Connection, + ConnectionPid, _) -> + FailMsg = channel_open_failure_msg(ChannelId, + ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "Not allowed", "en"), + {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "exit-status", + data = Data}, + #connection{channel_cache = Cache} = Connection, _, _) -> + <<?UINT32(Status)>> = Data, + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + {Reply, Connection} = + reply_msg(Channel, Connection, {exit_status, ChannelId, Status}), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "exit-signal", + want_reply = false, + data = Data}, + #connection{channel_cache = Cache} = Connection0, + ConnectionPid, _) -> + <<?UINT32(SigLen), SigName:SigLen/binary, + ?BOOLEAN(_Core), + ?UINT32(ErrLen), Err:ErrLen/binary, + ?UINT32(LangLen), Lang:LangLen/binary>> = Data, + Channel = ssh_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), + {{replies, [{connection_reply, ConnectionPid, CloseMsg}, Reply]}, + Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "xon-xoff", + want_reply = false, + data = Data}, + #connection{channel_cache = Cache} = Connection, _, _) -> + <<?BOOLEAN(CDo)>> = Data, + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + {Reply, Connection} = + reply_msg(Channel, Connection, {xon_xoff, ChannelId, CDo=/= 0}), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "window-change", + want_reply = false, + data = Data}, + #connection{channel_cache = Cache} = Connection0, _, _) -> + <<?UINT32(Width),?UINT32(Height), + ?UINT32(PixWidth), ?UINT32(PixHeight)>> = Data, + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + {Reply, Connection} = + reply_msg(Channel, Connection0, {window_change, ChannelId, + Width, Height, + PixWidth, PixHeight}), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "signal", + data = Data}, + #connection{channel_cache = Cache} = Connection0, _, _) -> + <<?UINT32(SigLen), SigName:SigLen/binary>> = Data, + + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + {Reply, Connection} = + reply_msg(Channel, Connection0, {signal, ChannelId, + binary_to_list(SigName)}), + {{replies, [Reply]}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "subsystem", + want_reply = WantReply, + data = Data}, + #connection{channel_cache = Cache} = Connection, + ConnectionPid, server) -> + <<?UINT32(SsLen), SsName:SsLen/binary>> = Data, + + #channel{remote_id = RemoteId} = Channel0 = + ssh_channel:cache_lookup(Cache, ChannelId), + + ReplyMsg = {subsystem, ChannelId, WantReply, binary_to_list(SsName)}, + + try start_subsytem(SsName, Connection, Channel0, ReplyMsg) of + {ok, Pid} -> + erlang:monitor(process, Pid), + Channel = Channel0#channel{user = Pid}, + ssh_channel:cache_update(Cache, Channel), + Reply = {connection_reply, ConnectionPid, + channel_success_msg(RemoteId)}, + {{replies, [Reply]}, Connection} + catch _:_ -> + Reply = {connection_reply, ConnectionPid, + channel_failure_msg(RemoteId)}, + {{replies, [Reply]}, Connection} + end; + +handle_msg(#ssh_msg_channel_request{request_type = "subsystem"}, + Connection, _, client) -> + %% The client SHOULD ignore subsystem requests. See RFC 4254 6.5. + {{replies, []}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "pty-req", + want_reply = WantReply, + data = Data}, + #connection{channel_cache = Cache} = Connection, + ConnectionPid, server) -> + <<?UINT32(TermLen), BTermName:TermLen/binary, + ?UINT32(Width),?UINT32(Height), + ?UINT32(PixWidth), ?UINT32(PixHeight), + Modes/binary>> = Data, + TermName = binary_to_list(BTermName), + + PtyRequest = {TermName, Width, Height, + PixWidth, PixHeight, decode_pty_opts(Modes)}, + + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + + handle_cli_msg(Connection, ConnectionPid, Channel, + {pty, ChannelId, WantReply, PtyRequest}); + +handle_msg(#ssh_msg_channel_request{request_type = "pty-req"}, + Connection, _, client) -> + %% The client SHOULD ignore pty requests. See RFC 4254 6.2. + {{replies, []}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "shell", + want_reply = WantReply}, + #connection{channel_cache = Cache} = Connection, + ConnectionPid, server) -> + + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + + handle_cli_msg(Connection, ConnectionPid, Channel, + {shell, ChannelId, WantReply}); + +handle_msg(#ssh_msg_channel_request{request_type = "shell"}, + Connection, _, client) -> + %% The client SHOULD ignore shell requests. See RFC 4254 6.5. + {{replies, []}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "exec", + want_reply = WantReply, + data = Data}, + #connection{channel_cache = Cache} = Connection, + ConnectionPid, server) -> + <<?UINT32(Len), Command:Len/binary>> = Data, + + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + + handle_cli_msg(Connection, ConnectionPid, Channel, + {exec, ChannelId, WantReply, binary_to_list(Command)}); + +handle_msg(#ssh_msg_channel_request{request_type = "exec"}, + Connection, _, client) -> + %% The client SHOULD ignore exec requests. See RFC 4254 6.5. + {{replies, []}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = "env", + want_reply = WantReply, + data = Data}, + #connection{channel_cache = Cache} = Connection, + ConnectionPid, server) -> + + <<?UINT32(VarLen), + Var:VarLen/binary, ?UINT32(ValueLen), Value:ValueLen/binary>> = Data, + + Channel = ssh_channel:cache_lookup(Cache, ChannelId), + + handle_cli_msg(Connection, ConnectionPid, Channel, + {env, ChannelId, WantReply, Var, Value}); + +handle_msg(#ssh_msg_channel_request{request_type = "env"}, + Connection, _, client) -> + %% The client SHOULD ignore env requests. + {{replies, []}, Connection}; + +handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = _Other, + want_reply = WantReply}, Connection, + ConnectionPid, _) -> + ?dbg(true, "ssh_msg ssh_msg_channel_request: Other=~p\n", + [_Other]), + if WantReply == true -> + FailMsg = channel_failure_msg(ChannelId), + {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, + Connection}; + true -> + {noreply, Connection} + end; + +handle_msg(#ssh_msg_global_request{name = _Type, + want_reply = WantReply, + data = _Data}, Connection, + ConnectionPid, _) -> + if WantReply == true -> + FailMsg = request_failure_msg(), + {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, + Connection}; + true -> + {noreply, Connection} + end; + +%%% This transport message will also be handled at the connection level +handle_msg(#ssh_msg_disconnect{code = Code, + description = Description, + language = _Lang }, + #connection{channel_cache = Cache} = Connection0, _, _) -> + {Connection, Replies} = + ssh_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_channel:cache_delete(Cache), + {disconnect, {Code, Description}, {{replies, Replies}, Connection}}. + +handle_cli_msg(#connection{channel_cache = Cache} = Connection0, + ConnectionPid, + #channel{user = undefined, + local_id = ChannelId} = Channel0, Reply0) -> + + case (catch start_cli(Connection0, ChannelId)) of + {ok, Pid} -> + erlang:monitor(process, Pid), + Channel = Channel0#channel{user = Pid}, + ssh_channel:cache_update(Cache, Channel), + {Reply, Connection} = reply_msg(Channel, Connection0, Reply0), + {{replies, [Reply]}, Connection}; + _ -> + Reply = {connection_reply, ConnectionPid, + request_failure_msg()}, + {{replies, [Reply]}, Connection0} + end; + +handle_cli_msg(Connection0, _, Channel, Reply0) -> + {Reply, Connection} = reply_msg(Channel, Connection0, Reply0), + {{replies, [Reply]}, Connection}. + + +channel_eof_msg(ChannelId) -> + #ssh_msg_channel_eof{recipient_channel = ChannelId}. + +channel_close_msg(ChannelId) -> + #ssh_msg_channel_close {recipient_channel = ChannelId}. + +channel_success_msg(ChannelId) -> + #ssh_msg_channel_success{recipient_channel = ChannelId}. + +channel_failure_msg(ChannelId) -> + #ssh_msg_channel_failure{recipient_channel = ChannelId}. + +channel_adjust_window_msg(ChannelId, Bytes) -> + #ssh_msg_channel_window_adjust{recipient_channel = ChannelId, + bytes_to_add = Bytes}. + +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_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_request_msg(ChannelId, Type, WantReply, Data) -> + #ssh_msg_channel_request{recipient_channel = ChannelId, + request_type = Type, + want_reply = WantReply, + data = Data}. + +global_request_msg(Type, WantReply, Data) -> + #ssh_msg_global_request{name = Type, + want_reply = WantReply, + data = Data}. +request_failure_msg() -> + #ssh_msg_request_failure{}. + +request_success_msg(Data) -> + #ssh_msg_request_success{data = Data}. + +bind(IP, Port, ChannelPid, Connection) -> + Binds = [{{IP, Port}, ChannelPid} + | lists:keydelete({IP, Port}, 1, + Connection#connection.port_bindings)], + Connection#connection{port_bindings = Binds}. + +unbind(IP, Port, Connection) -> + Connection#connection{ + port_bindings = + lists:keydelete({IP, Port}, 1, + Connection#connection.port_bindings)}. +unbind_channel(ChannelPid, Connection) -> + Binds = [{Bind, ChannelP} || {Bind, ChannelP} + <- Connection#connection.port_bindings, + ChannelP =/= ChannelPid], + Connection#connection{port_bindings = Binds}. + +bound_channel(IP, Port, Connection) -> + case lists:keysearch({IP, Port}, 1, Connection#connection.port_bindings) of + {value, {{IP, Port}, ChannelPid}} -> ChannelPid; + _ -> undefined + end. + +messages() -> + [ {ssh_msg_global_request, ?SSH_MSG_GLOBAL_REQUEST, + [string, + boolean, + '...']}, + + {ssh_msg_request_success, ?SSH_MSG_REQUEST_SUCCESS, + ['...']}, + + {ssh_msg_request_failure, ?SSH_MSG_REQUEST_FAILURE, + []}, + + {ssh_msg_channel_open, ?SSH_MSG_CHANNEL_OPEN, + [string, + uint32, + uint32, + uint32, + '...']}, + + {ssh_msg_channel_open_confirmation, ?SSH_MSG_CHANNEL_OPEN_CONFIRMATION, + [uint32, + uint32, + uint32, + uint32, + '...']}, + + {ssh_msg_channel_open_failure, ?SSH_MSG_CHANNEL_OPEN_FAILURE, + [uint32, + uint32, + string, + string]}, + + {ssh_msg_channel_window_adjust, ?SSH_MSG_CHANNEL_WINDOW_ADJUST, + [uint32, + uint32]}, + + {ssh_msg_channel_data, ?SSH_MSG_CHANNEL_DATA, + [uint32, + binary]}, + + {ssh_msg_channel_extended_data, ?SSH_MSG_CHANNEL_EXTENDED_DATA, + [uint32, + uint32, + binary]}, + + {ssh_msg_channel_eof, ?SSH_MSG_CHANNEL_EOF, + [uint32]}, + + {ssh_msg_channel_close, ?SSH_MSG_CHANNEL_CLOSE, + [uint32]}, + + {ssh_msg_channel_request, ?SSH_MSG_CHANNEL_REQUEST, + [uint32, + string, + boolean, + '...']}, + + {ssh_msg_channel_success, ?SSH_MSG_CHANNEL_SUCCESS, + [uint32]}, + + {ssh_msg_channel_failure, ?SSH_MSG_CHANNEL_FAILURE, + [uint32]} + ]. + +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. + +start_channel(Address, Port, Cb, Id, Args) -> + start_channel(Address, Port, Cb, Id, Args, undefined). + +start_channel(Address, Port, Cb, Id, Args, Exec) -> + ChildSpec = child_spec(Cb, Id, Args, Exec), + SystemSup = ssh_system_sup:system_supervisor(Address, Port), + ChannelSup = ssh_system_sup:channel_supervisor(SystemSup), + ssh_channel_sup:start_child(ChannelSup, ChildSpec). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +setup_session(#connection{channel_cache = Cache} = Connection0, + ConnectionPid, RemoteId, + Type, WindowSize, PacketSize) -> + {ChannelId, Connection} = new_channel_id(Connection0), + + Channel = #channel{type = Type, + sys = "ssh", + local_id = ChannelId, + recv_window_size = ?DEFAULT_WINDOW_SIZE, + recv_packet_size = ?DEFAULT_PACKET_SIZE, + send_window_size = WindowSize, + send_packet_size = PacketSize, + remote_id = RemoteId + }, + ssh_channel:cache_update(Cache, Channel), + OpenConfMsg = channel_open_confirmation_msg(RemoteId, ChannelId, + ?DEFAULT_WINDOW_SIZE, + ?DEFAULT_PACKET_SIZE), + + {{replies, [{connection_reply, ConnectionPid, OpenConfMsg}]}, Connection}. + + +check_subsystem("sftp"= SsName, Options) -> + case proplists:get_value(subsystems, Options, no_subsys) of + no_subsys -> + {SsName, {Cb, Opts}} = ssh_sftpd:subsystem_spec([]), + {Cb, Opts}; + SubSystems -> + proplists:get_value(SsName, SubSystems, {none, []}) + end; + +check_subsystem(SsName, Options) -> + Subsystems = proplists:get_value(subsystems, Options, []), + case proplists:get_value(SsName, Subsystems, {none, []}) of + Fun when is_function(Fun) -> + {Fun, []}; + {_, _} = Value -> + Value + end. + +child_spec(Callback, Id, Args, Exec) -> + Name = make_ref(), + StartFunc = {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]}, + Restart = temporary, + Shutdown = 3600, + Type = worker, + {Name, StartFunc, Restart, Shutdown, Type, [ssh_channel]}. + +%% Backwards compatibility +start_cli(#connection{address = Address, port = Port, cli_spec = {Fun, [Shell]}, + options = Options}, + _ChannelId) when is_function(Fun) -> + case Fun(Shell, Address, Port, Options) of + NewFun when is_function(NewFun) -> + {ok, NewFun()}; + Pid when is_pid(Pid) -> + {ok, Pid} + end; + +start_cli(#connection{address = Address, port = Port, + cli_spec = {CbModule, Args}, exec = Exec}, ChannelId) -> + start_channel(Address, Port, CbModule, ChannelId, Args, Exec). + +start_subsytem(BinName, #connection{address = Address, port = Port, + options = Options}, + #channel{local_id = ChannelId, remote_id = RemoteChannelId}, + ReplyMsg) -> + Name = binary_to_list(BinName), + case check_subsystem(Name, Options) of + {Callback, Opts} when is_atom(Callback), Callback =/= none -> + start_channel(Address, Port, Callback, ChannelId, Opts); + {Other, _} when Other =/= none -> + handle_backwards_compatibility(Other, self(), + ChannelId, RemoteChannelId, + Options, Address, Port, + {ssh_cm, self(), ReplyMsg}) + end. + +channel_data_reply(_, #channel{local_id = ChannelId} = Channel, + Connection0, DataType, Data) -> + {Reply, Connection} = + reply_msg(Channel, Connection0, {data, ChannelId, DataType, Data}), + {[Reply], Connection}. + +new_channel_id(Connection) -> + ID = Connection#connection.channel_id_seed, + {ID, Connection#connection{channel_id_seed = ID + 1}}. + +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(#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_requst_reply, From, Reply}, + Connection#connection{requests = + lists:keydelete(ChannelId, 1, Requests)}}; + false -> + {{channel_data, ChannelPid, Reply}, Connection} + end. + +update_send_window(Channel0, DataType, Data, + #connection{channel_cache = Cache}) -> + Buf0 = if Data == <<>> -> + Channel0#channel.send_buf; + true -> + Channel0#channel.send_buf ++ [{DataType, Data}] + end, + {Buf1, NewSz, Buf2} = get_window(Buf0, + Channel0#channel.send_packet_size, + Channel0#channel.send_window_size), + + Channel = Channel0#channel{send_window_size = NewSz, send_buf = Buf2}, + ssh_channel:cache_update(Cache, Channel), + {Buf1, Channel}. + +get_window(Bs, PSz, WSz) -> + get_window(Bs, PSz, WSz, []). + +get_window(Bs, _PSz, 0, Acc) -> + {lists:reverse(Acc), 0, Bs}; +get_window([B0 = {DataType, Bin} | Bs], PSz, WSz, Acc) -> + BSz = size(Bin), + if BSz =< WSz -> %% will fit into window + if BSz =< PSz -> %% will fit into a packet + get_window(Bs, PSz, WSz-BSz, [B0|Acc]); + true -> %% split into packet size + <<Bin1:PSz/binary, Bin2/binary>> = Bin, + get_window([setelement(2, B0, Bin2) | Bs], + PSz, WSz-PSz, + [{DataType, Bin1}|Acc]) + end; + WSz =< PSz -> %% use rest of window + <<Bin1:WSz/binary, Bin2/binary>> = Bin, + get_window([setelement(2, B0, Bin2) | Bs], + PSz, WSz-WSz, + [{DataType, Bin1}|Acc]); + true -> %% use packet size + <<Bin1:PSz/binary, Bin2/binary>> = Bin, + get_window([setelement(2, B0, Bin2) | Bs], + PSz, WSz-PSz, + [{DataType, Bin1}|Acc]) + end; +get_window([], _PSz, WSz, Acc) -> + {lists:reverse(Acc), WSz, []}. + +flow_control(Channel, Cache) -> + flow_control([window_adjusted], Channel, Cache). + +flow_control([], Channel, Cache) -> + ssh_channel:cache_update(Cache, Channel), + []; +flow_control([_|_], #channel{flow_control = From} = Channel, Cache) -> + case From of + undefined -> + []; + _ -> + [{flow_control, Cache, Channel, From, ok}] + end. + +encode_pty_opts(Opts) -> + Bin = list_to_binary(encode_pty_opts2(Opts)), + Len = size(Bin), + <<?UINT32(Len), Bin/binary>>. + +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([{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(<<?UINT32(Len), Modes:Len/binary>>) -> + decode_pty_opts2(Modes); +decode_pty_opts(Binary) -> + decode_pty_opts2(Binary). + +decode_pty_opts2(<<?TTY_OP_END>>) -> + []; +decode_pty_opts2(<<Code, ?UINT32(Value), Tail/binary>>) -> + 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; + ?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)]. + +decode_ip(Addr) when is_binary(Addr) -> + case inet_parse:address(binary_to_list(Addr)) of + {error,_} -> Addr; + {ok,A} -> A + end. + +%% This is really awful and that is why it is beeing phased out. +handle_backwards_compatibility({_,_,_,_,_,_} = ChildSpec, _, _, _, _, + Address, Port, _) -> + SystemSup = ssh_system_sup:system_supervisor(Address, Port), + ChannelSup = ssh_system_sup:channel_supervisor(SystemSup), + ssh_channel_sup:start_child(ChannelSup, ChildSpec); + +handle_backwards_compatibility(Module, ConnectionManager, ChannelId, + RemoteChannelId, Opts, + _, _, Msg) when is_atom(Module) -> + {ok, SubSystemPid} = gen_server:start_link(Module, [Opts], []), + SubSystemPid ! + {ssh_cm, ConnectionManager, + {open, ChannelId, RemoteChannelId, {session}}}, + SubSystemPid ! Msg, + {ok, SubSystemPid}; + +handle_backwards_compatibility(Fun, ConnectionManager, ChannelId, + RemoteChannelId, + _, _, _, Msg) when is_function(Fun) -> + SubSystemPid = Fun(), + SubSystemPid ! + {ssh_cm, ConnectionManager, + {open, ChannelId, RemoteChannelId, {session}}}, + SubSystemPid ! Msg, + {ok, SubSystemPid}; + +handle_backwards_compatibility(ChildSpec, + ConnectionManager, + ChannelId, RemoteChannelId, _, + Address, Port, Msg) -> + SystemSup = ssh_system_sup:system_supervisor(Address, Port), + ChannelSup = ssh_system_sup:channel_supervisor(SystemSup), + {ok, SubSystemPid} + = ssh_channel_sup:start_child(ChannelSup, ChildSpec), + SubSystemPid ! + {ssh_cm, ConnectionManager, + {open, ChannelId, RemoteChannelId, {session}}}, + SubSystemPid ! Msg, + {ok, SubSystemPid}. |