1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22
23%%----------------------------------------------------------------------
24%% Purpose: Details of connection protocol
25%%----------------------------------------------------------------------
26
27-module(ssh_connection).
28
29-include("ssh.hrl").
30-include("ssh_connect.hrl").
31-include("ssh_transport.hrl").
32
33%% API
34-export([session_channel/2, session_channel/4,
35	 exec/4, shell/2, subsystem/4, send/3, send/4, send/5,
36	 send_eof/2, adjust_window/3, setenv/5, close/2, reply_request/4,
37	 ptty_alloc/3, ptty_alloc/4]).
38
39%% Potential API currently unsupported and not tested
40-export([window_change/4, window_change/6,
41	 signal/3, exit_status/3]).
42
43%% Internal SSH application API
44-export([channel_data/5,
45         handle_msg/3,
46         handle_stop/1,
47
48	 channel_adjust_window_msg/2,
49	 channel_close_msg/1,
50	 channel_open_failure_msg/4,
51	 channel_open_msg/5,
52	 channel_status_msg/1,
53         channel_data_msg/3,
54         channel_eof_msg/1,
55         channel_failure_msg/1,
56         channel_open_confirmation_msg/4,
57         channel_request_msg/4,
58         channel_success_msg/1,
59
60	 request_failure_msg/0,
61	 request_success_msg/1,
62
63         bind/4, unbind/3, unbind_channel/2,
64	 bound_channel/3, encode_ip/1
65        ]).
66
67-type connection_ref() :: ssh:connection_ref().
68-type channel_id()     :: ssh:channel_id().
69
70%%--------------------------------------------------------------------
71%%% API
72%%--------------------------------------------------------------------
73
74%%--------------------------------------------------------------------
75%% Description: Opens a channel for a ssh session. A session is a
76%% remote execution of a program. The program may be a shell, an
77%% application, a system command, or some built-in subsystem.
78%% --------------------------------------------------------------------
79
80-spec session_channel(connection_ref(), timeout()) ->
81                             {ok, channel_id()} | {error, timeout | closed}.
82
83session_channel(ConnectionHandler, Timeout) ->
84    session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
85
86-spec session_channel(connection_ref(), integer(), integer(), timeout()) ->
87                             {ok, channel_id()} | {error, timeout | closed}.
88
89session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) ->
90    case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>,
91                                             InitialWindowSize,
92                                             MaxPacketSize, Timeout) of
93	{open, Channel} ->
94	    {ok, Channel};
95	Error ->
96	    Error
97    end.
98
99%%--------------------------------------------------------------------
100%% Description: Will request that the server start the
101%% execution of the given command.
102%%--------------------------------------------------------------------
103-spec exec(connection_ref(), channel_id(), string(), timeout()) ->
104		  success | failure | {error, timeout | closed}.
105
106exec(ConnectionHandler, ChannelId, Command, TimeOut) ->
107    ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec",
108				   true, [?string(Command)], TimeOut).
109
110%%--------------------------------------------------------------------
111%% Description: Will request that the user's default shell (typically
112%% defined in /etc/passwd in UNIX systems) be started at the other
113%% end.
114%%--------------------------------------------------------------------
115-spec shell(connection_ref(), channel_id()) ->
116                   ok | success | failure | {error, timeout}.
117
118shell(ConnectionHandler, ChannelId) ->
119    ssh_connection_handler:request(ConnectionHandler, self(), ChannelId,
120 				   "shell", false, <<>>, 0).
121%%--------------------------------------------------------------------
122%%
123%% Description: Executes a predefined subsystem.
124%%--------------------------------------------------------------------
125-spec subsystem(connection_ref(), channel_id(), string(), timeout()) ->
126		       success | failure | {error, timeout | closed}.
127
128subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) ->
129     ssh_connection_handler:request(ConnectionHandler, self(),
130				    ChannelId, "subsystem",
131				    true, [?string(SubSystem)], TimeOut).
132%%--------------------------------------------------------------------
133%% Description: Sends channel data.
134%%--------------------------------------------------------------------
135-spec send(connection_ref(), channel_id(), iodata()) ->
136		  ok | {error, timeout | closed}.
137send(ConnectionHandler, ChannelId, Data) ->
138    send(ConnectionHandler, ChannelId, 0, Data, infinity).
139
140
141-spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) ->
142		  ok | {error, timeout | closed}.
143
144send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) ->
145    send(ConnectionHandler, ChannelId, 0, Data, TimeOut);
146
147send(ConnectionHandler, ChannelId, Data, infinity) ->
148    send(ConnectionHandler, ChannelId, 0, Data, infinity);
149
150send(ConnectionHandler, ChannelId, Type, Data) ->
151    send(ConnectionHandler, ChannelId, Type, Data, infinity).
152
153
154-spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) ->
155		  ok | {error, timeout | closed}.
156
157send(ConnectionHandler, ChannelId, Type, Data, TimeOut) ->
158    ssh_connection_handler:send(ConnectionHandler, ChannelId,
159				Type, Data, TimeOut).
160%%--------------------------------------------------------------------
161-spec send_eof(connection_ref(), channel_id()) -> ok | {error, closed}.
162%%
163%%
164%% Description: Sends eof on the channel <ChannelId>.
165%%--------------------------------------------------------------------
166send_eof(ConnectionHandler, Channel) ->
167    ssh_connection_handler:send_eof(ConnectionHandler, Channel).
168
169%%--------------------------------------------------------------------
170-spec adjust_window(connection_ref(), channel_id(), integer()) -> ok.
171%%
172%%
173%% Description: Adjusts the ssh flowcontrol window.
174%%--------------------------------------------------------------------
175adjust_window(ConnectionHandler, Channel, Bytes) ->
176    ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes).
177
178%%--------------------------------------------------------------------
179-spec setenv(connection_ref(), channel_id(), string(), string(), timeout()) ->
180		    success | failure | {error, timeout | closed}.
181%%
182%%
183%% Description: Environment variables may be passed to the shell/command to be
184%% started later.
185%%--------------------------------------------------------------------
186setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) ->
187    ssh_connection_handler:request(ConnectionHandler, ChannelId,
188	    "env", true, [?string(Var), ?string(Value)], TimeOut).
189
190
191%%--------------------------------------------------------------------
192-spec close(connection_ref(), channel_id()) -> ok.
193%%
194%%
195%% Description: Sends a close message on the channel <ChannelId>.
196%%--------------------------------------------------------------------
197close(ConnectionHandler, ChannelId) ->
198    ssh_connection_handler:close(ConnectionHandler, ChannelId).
199
200%%--------------------------------------------------------------------
201-spec reply_request(connection_ref(), boolean(), success | failure, channel_id()) -> ok.
202%%
203%%
204%% Description: Send status replies to requests that want such replies.
205%%--------------------------------------------------------------------
206reply_request(ConnectionHandler, true, Status, ChannelId) ->
207    ssh_connection_handler:reply_request(ConnectionHandler, Status, ChannelId);
208reply_request(_,false, _, _) ->
209    ok.
210
211%%--------------------------------------------------------------------
212%% Description: Sends a ssh connection protocol pty_req.
213%%--------------------------------------------------------------------
214-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) ->
215			success | failure | {error, timeout}.
216
217ptty_alloc(ConnectionHandler, Channel, Options) ->
218    ptty_alloc(ConnectionHandler, Channel, Options, infinity).
219
220
221-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) ->
222			success | failure | {error, timeout | closed}.
223
224ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) ->
225    TermData = backwards_compatible(Options0, []), % FIXME
226    {Width, PixWidth} = pty_default_dimensions(width, TermData),
227    {Height, PixHeight} = pty_default_dimensions(height, TermData),
228    pty_req(ConnectionHandler, Channel,
229	    proplists:get_value(term, TermData, os:getenv("TERM", ?DEFAULT_TERMINAL)),
230	    proplists:get_value(width, TermData, Width),
231	    proplists:get_value(height, TermData, Height),
232	    proplists:get_value(pixel_widh, TermData, PixWidth),
233	    proplists:get_value(pixel_height, TermData, PixHeight),
234	    proplists:get_value(pty_opts, TermData, []), TimeOut
235	   ).
236%%--------------------------------------------------------------------
237%% Not yet officialy supported! The following functions are part of the
238%% initial contributed ssh application. They are untested. Do we want them?
239%% Should they be documented and tested?
240%%--------------------------------------------------------------------
241window_change(ConnectionHandler, Channel, Width, Height) ->
242    window_change(ConnectionHandler, Channel, Width, Height, 0, 0).
243window_change(ConnectionHandler, Channel, Width, Height,
244	      PixWidth, PixHeight) ->
245    ssh_connection_handler:request(ConnectionHandler, Channel,
246				   "window-change", false,
247				   [?uint32(Width), ?uint32(Height),
248				    ?uint32(PixWidth), ?uint32(PixHeight)], 0).
249
250signal(ConnectionHandler, Channel, Sig) ->
251    ssh_connection_handler:request(ConnectionHandler, Channel,
252				   "signal", false, [?string(Sig)], 0).
253
254
255exit_status(ConnectionHandler, Channel, Status) ->
256    ssh_connection_handler:request(ConnectionHandler, Channel,
257				   "exit-status", false, [?uint32(Status)], 0).
258
259%%--------------------------------------------------------------------
260%%% Internal, that is, ssh application internal API
261%%--------------------------------------------------------------------
262
263%%%----------------------------------------------------------------
264%%% Send data on a channel/connection as result of for example
265%%% ssh_connection:send (executed in the ssh_connection_state machine)
266%%%
267
268channel_data(ChannelId, DataType, Data0,
269	     #connection{channel_cache = Cache} = Connection,
270	     From) ->
271    case ssh_client_channel:cache_lookup(Cache, ChannelId) of
272	#channel{remote_id = Id, sent_close = false} = Channel0 ->
273            Data =
274               try iolist_to_binary(Data0)
275               catch
276                   _:_ ->
277                       unicode:characters_to_binary(Data0)
278               end,
279	    {SendList, Channel} =
280		update_send_window(Channel0#channel{flow_control = From}, DataType,
281				   Data, Connection),
282	    Replies =
283		lists:map(fun({SendDataType, SendData}) ->
284				  {connection_reply,
285				   channel_data_msg(Id,
286						    SendDataType,
287						    SendData)}
288			  end, SendList),
289	    FlowCtrlMsgs = flow_control(Replies, Channel, Cache),
290	    {Replies ++ FlowCtrlMsgs, Connection};
291	_ ->
292	    {[{channel_request_reply,From,{error,closed}}], Connection}
293    end.
294
295%%%----------------------------------------------------------------
296%%% Handle the channel messages on behalf of the ssh_connection_handler
297%%% state machine.
298%%%
299%%% Replies {Reply, UpdatedConnection}
300%%%
301
302handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId,
303					      sender_channel = RemoteId,
304					      initial_window_size = WindowSz,
305					      maximum_packet_size = PacketSz},
306	   #connection{channel_cache = Cache} = Connection0, _) ->
307
308    #channel{remote_id = undefined} = Channel =
309	ssh_client_channel:cache_lookup(Cache, ChannelId),
310
311    ssh_client_channel:cache_update(Cache, Channel#channel{
312				     remote_id = RemoteId,
313				     recv_packet_size = max(32768, % rfc4254/5.2
314							    min(PacketSz, Channel#channel.recv_packet_size)
315							   ),
316				     send_window_size = WindowSz,
317				     send_packet_size = PacketSz}),
318    reply_msg(Channel, Connection0, {open, ChannelId});
319
320handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId,
321					 reason = Reason,
322					 description = Descr,
323					 lang = Lang},
324	   #connection{channel_cache = Cache} = Connection0, _) ->
325    Channel = ssh_client_channel:cache_lookup(Cache, ChannelId),
326    ssh_client_channel:cache_delete(Cache, ChannelId),
327    reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang});
328
329handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _) ->
330    reply_msg(ChannelId, Connection, success);
331
332handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, Connection, _) ->
333    reply_msg(ChannelId, Connection, failure);
334
335handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _) ->
336    reply_msg(ChannelId, Connection, {eof, ChannelId});
337
338handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId},
339	   #connection{channel_cache = Cache} = Connection0, _) ->
340
341	case ssh_client_channel:cache_lookup(Cache, ChannelId) of
342		#channel{sent_close = Closed, remote_id = RemoteId,
343			 flow_control = FlowControl} = Channel ->
344		ssh_client_channel:cache_delete(Cache, ChannelId),
345		{CloseMsg, Connection} =
346		    reply_msg(Channel, Connection0, {closed, ChannelId}),
347		ConnReplyMsgs =
348		    case Closed of
349			true -> [];
350			false ->
351			    RemoteCloseMsg = channel_close_msg(RemoteId),
352			    [{connection_reply, RemoteCloseMsg}]
353		    end,
354
355		%% if there was a send() in progress, make it fail
356		SendReplyMsgs =
357		    case FlowControl of
358			undefined -> [];
359			From ->
360			    [{flow_control, From, {error, closed}}]
361		    end,
362
363		Replies = ConnReplyMsgs ++ CloseMsg ++ SendReplyMsgs,
364		{Replies, Connection};
365
366	    undefined ->
367		{[], Connection0}
368	end;
369
370handle_msg(#ssh_msg_channel_data{recipient_channel = ChannelId,
371				 data = Data},
372	   Connection, _) ->
373    channel_data_reply_msg(ChannelId, Connection, 0, Data);
374
375handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId,
376					  data_type_code = DataType,
377					  data = Data},
378	   Connection, _) ->
379    channel_data_reply_msg(ChannelId, Connection, DataType, Data);
380
381handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
382					  bytes_to_add = Add},
383	   #connection{channel_cache = Cache} = Connection, _) ->
384    #channel{send_window_size = Size, remote_id = RemoteId} =
385	Channel0 = ssh_client_channel:cache_lookup(Cache, ChannelId),
386
387    {SendList, Channel} =  %% TODO: Datatype 0 ?
388	update_send_window(Channel0#channel{send_window_size = Size + Add},
389			   0, undefined, Connection),
390
391    Replies = lists:map(fun({Type, Data}) ->
392				{connection_reply, channel_data_msg(RemoteId, Type, Data)}
393			end, SendList),
394    FlowCtrlMsgs = flow_control(Channel, Cache),
395    {Replies ++ FlowCtrlMsgs, Connection};
396
397handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type,
398				 sender_channel = RemoteId,
399				 initial_window_size = WindowSz,
400				 maximum_packet_size = PacketSz},
401	   #connection{options = SSHopts} = Connection0,
402	   server) ->
403    MinAcceptedPackSz =
404        ?GET_OPT(minimal_remote_max_packet_size, SSHopts),
405
406    if
407	MinAcceptedPackSz =< PacketSz ->
408	    try setup_session(Connection0, RemoteId,
409			      Type, WindowSz, PacketSz) of
410		Result ->
411		    Result
412	    catch _:_ ->
413		    FailMsg = channel_open_failure_msg(RemoteId,
414						       ?SSH_OPEN_CONNECT_FAILED,
415						       "Connection refused", "en"),
416		    {[{connection_reply, FailMsg}], Connection0}
417	    end;
418
419	MinAcceptedPackSz > PacketSz ->
420	    FailMsg = channel_open_failure_msg(RemoteId,
421					       ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
422					       lists:concat(["Maximum packet size below ",MinAcceptedPackSz,
423							      " not supported"]), "en"),
424	    {[{connection_reply, FailMsg}], Connection0}
425    end;
426
427handle_msg(#ssh_msg_channel_open{channel_type = "session",
428				 sender_channel = RemoteId},
429	   Connection,
430	   client) ->
431    %% Client implementations SHOULD reject any session channel open
432    %% requests to make it more difficult for a corrupt server to attack the
433    %% client. See See RFC 4254 6.1.
434    FailMsg = channel_open_failure_msg(RemoteId,
435				       ?SSH_OPEN_CONNECT_FAILED,
436				       "Connection refused", "en"),
437    {[{connection_reply, FailMsg}], Connection};
438
439handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _) ->
440    FailMsg = channel_open_failure_msg(RemoteId,
441				       ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
442				       "Not allowed", "en"),
443    {[{connection_reply, FailMsg}], Connection};
444
445handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
446				    request_type = "exit-status",
447				    data = Data},
448           Connection, _) ->
449    <<?UINT32(Status)>> = Data,
450    reply_msg(ChannelId, Connection, {exit_status, ChannelId, Status});
451
452handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
453				    request_type = "exit-signal",
454				    want_reply = false,
455				    data = Data},
456           #connection{channel_cache = Cache} = Connection0, _) ->
457    <<?DEC_BIN(SigName, _SigLen),
458      ?BOOLEAN(_Core),
459      ?DEC_BIN(Err, _ErrLen),
460      ?DEC_BIN(Lang, _LangLen)>> = Data,
461    Channel = ssh_client_channel:cache_lookup(Cache, ChannelId),
462    RemoteId =  Channel#channel.remote_id,
463    {Reply, Connection} =  reply_msg(Channel, Connection0,
464				     {exit_signal, ChannelId,
465				      binary_to_list(SigName),
466				      binary_to_list(Err),
467				      binary_to_list(Lang)}),
468    CloseMsg = channel_close_msg(RemoteId),
469    {[{connection_reply, CloseMsg}|Reply], Connection};
470
471handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
472				    request_type = "xon-xoff",
473				    want_reply = false,
474				    data = Data},
475           Connection, _) ->
476    <<?BOOLEAN(CDo)>> = Data,
477    reply_msg(ChannelId, Connection, {xon_xoff, ChannelId, CDo=/= 0});
478
479handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
480				    request_type = "window-change",
481				    want_reply = false,
482				    data = Data},
483           Connection0, _) ->
484    <<?UINT32(Width),?UINT32(Height),
485      ?UINT32(PixWidth), ?UINT32(PixHeight)>> = Data,
486    reply_msg(ChannelId, Connection0, {window_change, ChannelId,
487                                       Width, Height,
488                                       PixWidth, PixHeight});
489
490handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
491				    request_type = "signal",
492				    data = Data},
493           Connection0, _) ->
494    <<?DEC_BIN(SigName, _SigLen)>> = Data,
495    reply_msg(ChannelId, Connection0, {signal, ChannelId,
496                                       binary_to_list(SigName)});
497
498handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
499				    request_type = "subsystem",
500				    want_reply = WantReply,
501				    data = Data},
502	   #connection{channel_cache = Cache} = Connection, server) ->
503    <<?DEC_BIN(SsName,_SsLen)>> = Data,
504    #channel{remote_id=RemoteId} = Channel =
505	ssh_client_channel:cache_lookup(Cache, ChannelId),
506    Reply =
507        try
508            start_subsystem(SsName, Connection, Channel,
509                            {subsystem, ChannelId, WantReply, binary_to_list(SsName)})
510        of
511            {ok, Pid} ->
512                erlang:monitor(process, Pid),
513                ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}),
514                channel_success_msg(RemoteId);
515            {error,_Error} ->
516                channel_failure_msg(RemoteId)
517        catch
518            _:_ ->
519                channel_failure_msg(RemoteId)
520        end,
521    {[{connection_reply,Reply}], Connection};
522
523handle_msg(#ssh_msg_channel_request{request_type = "subsystem"},
524	   Connection, client) ->
525    %% The client SHOULD ignore subsystem requests. See RFC 4254 6.5.
526    {[], Connection};
527
528handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
529				    request_type = "pty-req",
530				    want_reply = WantReply,
531				    data = Data},
532	   Connection, server) ->
533    <<?DEC_BIN(BTermName,_TermLen),
534      ?UINT32(Width),?UINT32(Height),
535      ?UINT32(PixWidth), ?UINT32(PixHeight),
536      Modes/binary>> = Data,
537    TermName = binary_to_list(BTermName),
538    PtyRequest = {TermName, Width, Height,
539		  PixWidth, PixHeight, decode_pty_opts(Modes)},
540    handle_cli_msg(Connection, ChannelId,
541		   {pty, ChannelId, WantReply, PtyRequest});
542
543handle_msg(#ssh_msg_channel_request{request_type = "pty-req"},
544	   Connection, client) ->
545    %% The client SHOULD ignore pty requests. See RFC 4254 6.2.
546    {[], Connection};
547
548handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
549				    request_type = "shell",
550				    want_reply = WantReply},
551	   Connection, server) ->
552    handle_cli_msg(Connection, ChannelId,
553		   {shell, ChannelId, WantReply});
554
555handle_msg(#ssh_msg_channel_request{request_type = "shell"},
556	   Connection, client) ->
557    %% The client SHOULD ignore shell requests. See RFC 4254 6.5.
558    {[], Connection};
559
560handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
561				    request_type = "exec",
562				    want_reply = WantReply,
563				    data = Data},
564	   Connection, server) ->
565    <<?DEC_BIN(Command, _Len)>> = Data,
566    handle_cli_msg(Connection, ChannelId,
567		   {exec, ChannelId, WantReply, binary_to_list(Command)});
568
569handle_msg(#ssh_msg_channel_request{request_type = "exec"},
570	   Connection, client) ->
571    %% The client SHOULD ignore exec requests. See RFC 4254 6.5.
572    {[], Connection};
573
574handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
575  				    request_type = "env",
576  				    want_reply = WantReply,
577  				    data = Data},
578	   Connection, server) ->
579    <<?DEC_BIN(Var,_VarLen), ?DEC_BIN(Value,_ValLen)>> = Data,
580    handle_cli_msg(Connection, ChannelId,
581 		   {env, ChannelId, WantReply, Var, Value});
582
583handle_msg(#ssh_msg_channel_request{request_type = "env"},
584	   Connection, client) ->
585    %% The client SHOULD ignore env requests.
586    {[], Connection};
587
588handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
589				    request_type = _Other,
590				    want_reply = WantReply},
591	   #connection{channel_cache = Cache} = Connection, _) ->
592    if WantReply == true ->
593		case ssh_client_channel:cache_lookup(Cache, ChannelId) of
594		    #channel{remote_id = RemoteId}  ->
595			FailMsg = channel_failure_msg(RemoteId),
596			{[{connection_reply, FailMsg}], Connection};
597		    undefined -> %% Chanel has been closed
598			{[], Connection}
599		end;
600       true ->
601	    {[], Connection}
602    end;
603
604handle_msg(#ssh_msg_global_request{name = _Type,
605				   want_reply = WantReply,
606				   data = _Data}, Connection, _) ->
607    if WantReply == true ->
608	    FailMsg = request_failure_msg(),
609	    {[{connection_reply, FailMsg}], Connection};
610       true ->
611	    {[], Connection}
612    end;
613
614handle_msg(#ssh_msg_request_failure{},
615	   #connection{requests = [{_, From} | Rest]} = Connection, _) ->
616    {[{channel_request_reply, From, {failure, <<>>}}],
617     Connection#connection{requests = Rest}};
618
619handle_msg(#ssh_msg_request_success{data = Data},
620	   #connection{requests = [{_, From} | Rest]} = Connection, _) ->
621    {[{channel_request_reply, From, {success, Data}}],
622     Connection#connection{requests = Rest}};
623
624handle_msg(#ssh_msg_disconnect{code = Code,
625			       description = Description},
626	   Connection, _) ->
627    {disconnect, {Code, Description}, handle_stop(Connection)}.
628
629
630%%%----------------------------------------------------------------
631%%% Returns pending responses to be delivered to the peer when a
632%%% Channel/Connection closes
633%%%
634handle_stop(#connection{channel_cache = Cache} = Connection0) ->
635    {Connection, Replies} =
636	ssh_client_channel:cache_foldl(
637          fun(Channel, {Connection1, Acc}) ->
638                  {Reply, Connection2} =
639                      reply_msg(Channel, Connection1,
640                                {closed, Channel#channel.local_id}),
641                  {Connection2, Reply ++ Acc}
642          end, {Connection0, []}, Cache),
643    ssh_client_channel:cache_delete(Cache),
644    {Replies, Connection}.
645
646%%%----------------------------------------------------------------
647%%% channel_*_msg(...)
648%%% Returns a #ssh_msg_....{} for channel operations.
649%%%
650channel_adjust_window_msg(ChannelId, Bytes) ->
651    #ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
652				   bytes_to_add = Bytes}.
653
654channel_close_msg(ChannelId) ->
655    #ssh_msg_channel_close {recipient_channel = ChannelId}.
656
657channel_data_msg(ChannelId, 0, Data) ->
658    #ssh_msg_channel_data{recipient_channel = ChannelId,
659			  data = Data};
660channel_data_msg(ChannelId, Type, Data) ->
661    #ssh_msg_channel_extended_data{recipient_channel = ChannelId,
662				    data_type_code = Type,
663				    data = Data}.
664
665channel_eof_msg(ChannelId) ->
666    #ssh_msg_channel_eof{recipient_channel = ChannelId}.
667
668channel_failure_msg(ChannelId) ->
669    #ssh_msg_channel_failure{recipient_channel = ChannelId}.
670
671channel_open_msg(Type, ChannelId, WindowSize, MaxPacketSize, Data) ->
672    #ssh_msg_channel_open{channel_type = Type,
673			  sender_channel = ChannelId,
674			  initial_window_size = WindowSize,
675			  maximum_packet_size = MaxPacketSize,
676			  data = Data
677			 }.
678
679channel_open_confirmation_msg(RemoteId, LID, WindowSize, PacketSize) ->
680    #ssh_msg_channel_open_confirmation{recipient_channel = RemoteId,
681				       sender_channel = LID,
682				       initial_window_size = WindowSize,
683				       maximum_packet_size = PacketSize}.
684
685channel_open_failure_msg(RemoteId, Reason, Description, Lang) ->
686    #ssh_msg_channel_open_failure{recipient_channel = RemoteId,
687				  reason = Reason,
688				  description = Description,
689				  lang = Lang}.
690
691channel_status_msg({success, ChannelId}) ->
692    channel_success_msg(ChannelId);
693
694channel_status_msg({failure, ChannelId}) ->
695    channel_failure_msg(ChannelId).
696
697channel_request_msg(ChannelId, Type, WantReply, Data) ->
698    #ssh_msg_channel_request{recipient_channel = ChannelId,
699			     request_type = Type,
700			     want_reply = WantReply,
701			     data = Data}.
702
703channel_success_msg(ChannelId) ->
704    #ssh_msg_channel_success{recipient_channel = ChannelId}.
705
706%%%----------------------------------------------------------------
707%%% request_*_msg(...)
708%%% Returns a #ssh_msg_....{} for request responses.
709%%%
710request_failure_msg() ->
711    #ssh_msg_request_failure{}.
712
713request_success_msg(Data) ->
714    #ssh_msg_request_success{data = Data}.
715
716%%%----------------------------------------------------------------
717%%%
718%%%
719bind(IP, Port, ChannelPid, Connection) ->
720    Binds = [{{IP, Port}, ChannelPid}
721	     | lists:keydelete({IP, Port}, 1,
722			       Connection#connection.port_bindings)],
723    Connection#connection{port_bindings = Binds}.
724
725unbind(IP, Port, Connection) ->
726    Connection#connection{
727      port_bindings =
728      lists:keydelete({IP, Port}, 1,
729		      Connection#connection.port_bindings)}.
730unbind_channel(ChannelPid, Connection) ->
731    Binds = [{Bind, ChannelP} || {Bind, ChannelP}
732				     <- Connection#connection.port_bindings,
733				 ChannelP =/= ChannelPid],
734    Connection#connection{port_bindings = Binds}.
735
736bound_channel(IP, Port, Connection) ->
737    case lists:keysearch({IP, Port}, 1, Connection#connection.port_bindings) of
738	{value, {{IP, Port}, ChannelPid}} -> ChannelPid;
739	_ -> undefined
740    end.
741
742encode_ip(Addr) when is_tuple(Addr) ->
743    case catch inet_parse:ntoa(Addr) of
744	{'EXIT',_} -> false;
745	A -> A
746    end;
747encode_ip(Addr) when is_list(Addr) ->
748    case inet_parse:address(Addr) of
749	{ok, _} -> Addr;
750	Error ->
751	    case inet:getaddr(Addr, inet) of
752		{ok, A} ->
753		    inet_parse:ntoa(A);
754		Error -> false
755	    end
756    end.
757
758%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
759%%%
760%%% Internal functions
761%%%
762%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
763
764%%%----------------------------------------------------------------
765%%% Create the channel data when an ssh_msg_open_channel message
766%%% of "session" typ is handled
767%%%
768setup_session(#connection{channel_cache = Cache,
769                          channel_id_seed = NewChannelID
770			 } = C,
771	      RemoteId, Type, WindowSize, PacketSize) ->
772    NextChannelID = NewChannelID + 1,
773    Channel =
774        #channel{type = Type,
775                 sys = "ssh",
776                 local_id = NewChannelID,
777                 recv_window_size = ?DEFAULT_WINDOW_SIZE,
778                 recv_packet_size = ?DEFAULT_PACKET_SIZE,
779                 send_window_size = WindowSize,
780                 send_packet_size = PacketSize,
781                 send_buf = queue:new(),
782                 remote_id = RemoteId
783                },
784    ssh_client_channel:cache_update(Cache, Channel),
785    OpenConfMsg = channel_open_confirmation_msg(RemoteId, NewChannelID,
786						?DEFAULT_WINDOW_SIZE,
787						?DEFAULT_PACKET_SIZE),
788    Reply = {connection_reply, OpenConfMsg},
789    {[Reply], C#connection{channel_id_seed = NextChannelID}}.
790
791
792%%%----------------------------------------------------------------
793%%% Start a cli or subsystem
794%%%
795start_cli(#connection{options = Options,
796		      cli_spec = CliSpec,
797		      exec = Exec,
798		      sub_system_supervisor = SubSysSup}, ChannelId) ->
799    case CliSpec of
800        no_cli ->
801            {error, cli_disabled};
802        {CbModule, Args} ->
803            start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options)
804    end.
805
806
807start_subsystem(BinName, #connection{options = Options,
808                                     sub_system_supervisor = SubSysSup},
809	       #channel{local_id = ChannelId}, _ReplyMsg) ->
810    Name = binary_to_list(BinName),
811    case check_subsystem(Name, Options) of
812	{Callback, Opts} when is_atom(Callback), Callback =/= none ->
813	    start_channel(Callback, ChannelId, Opts, SubSysSup, Options);
814	{Other, _} when Other =/= none ->
815	    {error, legacy_option_not_supported}
816    end.
817
818
819%%% Helpers for starting cli/subsystems
820start_channel(Cb, Id, Args, SubSysSup, Opts) ->
821    start_channel(Cb, Id, Args, SubSysSup, undefined, Opts).
822
823start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) ->
824    ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup),
825    case max_num_channels_not_exceeded(ChannelSup, Opts) of
826        true ->
827            case ssh_server_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec) of
828                {error,{Error,_Info}} ->
829                    throw(Error);
830                Others ->
831                    Others
832            end;
833        false ->
834	    throw(max_num_channels_exceeded)
835    end.
836
837max_num_channels_not_exceeded(ChannelSup, Opts) ->
838    MaxNumChannels = ?GET_OPT(max_channels, Opts),
839    NumChannels = length([x || {_,_,worker,[ssh_server_channel]} <-
840				   supervisor:which_children(ChannelSup)]),
841    %% Note that NumChannels is BEFORE starting a new one
842    NumChannels < MaxNumChannels.
843
844check_subsystem("sftp"= SsName, Options) ->
845    case ?GET_OPT(subsystems, Options) of
846	no_subsys -> 	% FIXME: Can 'no_subsys' ever be matched?
847	    {SsName, {Cb, Opts}} = ssh_sftpd:subsystem_spec([]),
848	    {Cb, Opts};
849	SubSystems ->
850	    proplists:get_value(SsName, SubSystems, {none, []})
851    end;
852
853check_subsystem(SsName, Options) ->
854    Subsystems = ?GET_OPT(subsystems, Options),
855    case proplists:get_value(SsName, Subsystems, {none, []}) of
856	Fun when is_function(Fun) ->
857	    {Fun, []};
858	{_, _} = Value ->
859	    Value
860    end.
861
862%%%----------------------------------------------------------------
863%%%
864%%% Send-window handling
865%%%
866
867update_send_window(Channel, _, undefined,
868		   #connection{channel_cache = Cache}) ->
869    do_update_send_window(Channel, Cache);
870
871update_send_window(#channel{send_buf = SendBuffer} = Channel, DataType, Data,
872		   #connection{channel_cache = Cache}) ->
873    do_update_send_window(Channel#channel{send_buf = queue:in({DataType, Data}, SendBuffer)},
874			  Cache).
875
876do_update_send_window(Channel0, Cache) ->
877    {SendMsgs, Channel} = get_window(Channel0, []),
878    ssh_client_channel:cache_update(Cache, Channel),
879    {SendMsgs, Channel}.
880
881get_window(#channel{send_window_size = 0
882		   } = Channel, Acc) ->
883    {lists:reverse(Acc), Channel};
884get_window(#channel{send_packet_size = 0
885		   } = Channel, Acc) ->
886    {lists:reverse(Acc), Channel};
887get_window(#channel{send_buf = Buffer,
888		    send_packet_size = PacketSize,
889		    send_window_size = WindowSize0
890		   } = Channel, Acc0) ->
891    case queue:out(Buffer) of
892	{{value, {_, Data} = Msg}, NewBuffer} ->
893	    case handle_send_window(Msg, size(Data), PacketSize, WindowSize0, Acc0) of
894		{WindowSize, Acc, {_, <<>>}} ->
895		    {lists:reverse(Acc), Channel#channel{send_window_size = WindowSize,
896							 send_buf = NewBuffer}};
897		{WindowSize, Acc, Rest} ->
898		    get_window(Channel#channel{send_window_size = WindowSize,
899					       send_buf = queue:in_r(Rest, NewBuffer)}, Acc)
900	    end;
901	{empty, NewBuffer} ->
902	    {[], Channel#channel{send_buf = NewBuffer}}
903    end.
904
905handle_send_window(Msg = {Type, Data}, Size, PacketSize, WindowSize, Acc) when Size =< WindowSize ->
906    case Size =< PacketSize of
907	true ->
908	    {WindowSize - Size, [Msg | Acc], {Type, <<>>}};
909	false ->
910	    <<Msg1:PacketSize/binary, Msg2/binary>> = Data,
911	    {WindowSize - PacketSize, [{Type, Msg1} | Acc], {Type, Msg2}}
912    end;
913handle_send_window({Type, Data}, _, PacketSize, WindowSize, Acc) when WindowSize =< PacketSize ->
914    <<Msg1:WindowSize/binary, Msg2/binary>> = Data,
915    {WindowSize - WindowSize, [{Type, Msg1} | Acc], {Type, Msg2}};
916handle_send_window({Type, Data}, _, PacketSize, WindowSize, Acc) ->
917    <<Msg1:PacketSize/binary, Msg2/binary>> = Data,
918    {WindowSize - PacketSize, [{Type, Msg1} | Acc], {Type, Msg2}}.
919
920%%%----------------------------------------------------------------
921%%%
922%%% Flow control
923%%%
924
925flow_control(Channel, Cache) ->
926    flow_control([window_adjusted], Channel, Cache).
927
928flow_control([], Channel, Cache) ->
929    ssh_client_channel:cache_update(Cache, Channel),
930    [];
931flow_control([_|_], #channel{flow_control = From,
932			     send_buf = Buffer} = Channel, Cache) when From =/= undefined ->
933    case queue:is_empty(Buffer) of
934	true ->
935	    ssh_client_channel:cache_update(Cache, Channel#channel{flow_control = undefined}),
936	    [{flow_control, Cache, Channel, From, ok}];
937	false ->
938	    []
939    end;
940flow_control(_,_,_) ->
941    [].
942
943%%%----------------------------------------------------------------
944%%%
945%%% Pseudo terminal stuff
946%%%
947
948pty_req(ConnectionHandler, Channel, Term, Width, Height,
949	 PixWidth, PixHeight, PtyOpts, TimeOut) ->
950    ssh_connection_handler:request(ConnectionHandler,
951				   Channel, "pty-req", true,
952				   [?string(Term),
953				    ?uint32(Width), ?uint32(Height),
954				    ?uint32(PixWidth),?uint32(PixHeight),
955				    encode_pty_opts(PtyOpts)], TimeOut).
956
957pty_default_dimensions(Dimension, TermData) ->
958    case proplists:get_value(Dimension, TermData, 0) of
959	N when is_integer(N), N > 0 ->
960	    {N, 0};
961	_ ->
962            PixelDim = list_to_atom("pixel_" ++ atom_to_list(Dimension)),
963	    case proplists:get_value(PixelDim, TermData, 0) of
964		N when is_integer(N), N > 0 ->
965		    {0, N};
966		_ ->
967		    {?TERMINAL_WIDTH, 0}
968	    end
969    end.
970
971encode_pty_opts(Opts) ->
972    Bin = list_to_binary(encode_pty_opts2(Opts)),
973    <<?STRING(Bin)>>.
974
975encode_pty_opts2([]) ->
976    [?TTY_OP_END];
977encode_pty_opts2([{vintr,Value} | Opts]) ->
978    [?VINTR, ?uint32(Value) | encode_pty_opts2(Opts)];
979encode_pty_opts2([{vquit,Value} | Opts]) ->
980    [?VQUIT, ?uint32(Value) | encode_pty_opts2(Opts)];
981encode_pty_opts2([{verase,Value} | Opts]) ->
982    [?VERASE, ?uint32(Value) | encode_pty_opts2(Opts)];
983encode_pty_opts2([{vkill,Value} | Opts]) ->
984    [?VKILL, ?uint32(Value) | encode_pty_opts2(Opts)];
985encode_pty_opts2([{veof,Value} | Opts]) ->
986    [?VEOF, ?uint32(Value) | encode_pty_opts2(Opts)];
987encode_pty_opts2([{veol,Value} | Opts]) ->
988    [?VEOL, ?uint32(Value) | encode_pty_opts2(Opts)];
989encode_pty_opts2([{veol2,Value} | Opts]) ->
990    [?VEOL2, ?uint32(Value) | encode_pty_opts2(Opts)];
991encode_pty_opts2([{vstart,Value} | Opts]) ->
992    [?VSTART, ?uint32(Value) | encode_pty_opts2(Opts)];
993encode_pty_opts2([{vstop,Value} | Opts]) ->
994    [?VSTOP, ?uint32(Value) | encode_pty_opts2(Opts)];
995encode_pty_opts2([{vsusp,Value} | Opts]) ->
996    [?VSUSP, ?uint32(Value) | encode_pty_opts2(Opts)];
997encode_pty_opts2([{vdsusp,Value} | Opts]) ->
998    [?VDSUSP, ?uint32(Value) | encode_pty_opts2(Opts)];
999encode_pty_opts2([{vreprint,Value} | Opts]) ->
1000    [?VREPRINT, ?uint32(Value) | encode_pty_opts2(Opts)];
1001encode_pty_opts2([{vwerase,Value} | Opts]) ->
1002    [ ?VWERASE, ?uint32(Value) | encode_pty_opts2(Opts)];
1003encode_pty_opts2([{vlnext,Value} | Opts]) ->
1004    [?VLNEXT, ?uint32(Value) | encode_pty_opts2(Opts)];
1005encode_pty_opts2([{vflush,Value} | Opts]) ->
1006    [?VFLUSH, ?uint32(Value) | encode_pty_opts2(Opts)];
1007encode_pty_opts2([{vswtch,Value} | Opts]) ->
1008    [?VSWTCH, ?uint32(Value) | encode_pty_opts2(Opts)];
1009encode_pty_opts2([{vstatus,Value} | Opts]) ->
1010    [?VSTATUS, ?uint32(Value) | encode_pty_opts2(Opts)];
1011encode_pty_opts2([{vdiscard,Value} | Opts]) ->
1012    [?VDISCARD, ?uint32(Value) | encode_pty_opts2(Opts)];
1013encode_pty_opts2([{ignpar,Value} | Opts]) ->
1014    [?IGNPAR, ?uint32(Value) | encode_pty_opts2(Opts)];
1015encode_pty_opts2([{parmrk,Value} | Opts]) ->
1016    [?PARMRK, ?uint32(Value) | encode_pty_opts2(Opts)];
1017encode_pty_opts2([{inpck,Value} | Opts]) ->
1018    [?INPCK, ?uint32(Value) | encode_pty_opts2(Opts)];
1019encode_pty_opts2([{istrip,Value} | Opts]) ->
1020    [?ISTRIP, ?uint32(Value) | encode_pty_opts2(Opts)];
1021encode_pty_opts2([{inlcr,Value} | Opts]) ->
1022    [?INLCR, ?uint32(Value) | encode_pty_opts2(Opts)];
1023encode_pty_opts2([{igncr,Value} | Opts]) ->
1024    [?IGNCR, ?uint32(Value) | encode_pty_opts2(Opts)];
1025encode_pty_opts2([{icrnl,Value} | Opts]) ->
1026    [?ICRNL, ?uint32(Value) | encode_pty_opts2(Opts)];
1027encode_pty_opts2([{iuclc,Value} | Opts]) ->
1028    [?IUCLC, ?uint32(Value) | encode_pty_opts2(Opts)];
1029encode_pty_opts2([{ixon,Value} | Opts]) ->
1030    [?IXON, ?uint32(Value) | encode_pty_opts2(Opts)];
1031encode_pty_opts2([{ixany,Value} | Opts]) ->
1032    [?IXANY, ?uint32(Value) | encode_pty_opts2(Opts)];
1033encode_pty_opts2([{ixoff,Value} | Opts]) ->
1034    [?IXOFF, ?uint32(Value) | encode_pty_opts2(Opts)];
1035encode_pty_opts2([{imaxbel,Value} | Opts]) ->
1036    [?IMAXBEL, ?uint32(Value) | encode_pty_opts2(Opts)];
1037encode_pty_opts2([{isig,Value} | Opts]) ->
1038    [?ISIG, ?uint32(Value) | encode_pty_opts2(Opts)];
1039encode_pty_opts2([{icanon,Value} | Opts]) ->
1040    [?ICANON, ?uint32(Value) | encode_pty_opts2(Opts)];
1041encode_pty_opts2([{xcase,Value} | Opts]) ->
1042    [?XCASE, ?uint32(Value) | encode_pty_opts2(Opts)];
1043encode_pty_opts2([{echo,Value} | Opts]) ->
1044    [?ECHO, ?uint32(Value) | encode_pty_opts2(Opts)];
1045encode_pty_opts2([{echoe,Value} | Opts]) ->
1046    [?ECHOE, ?uint32(Value) | encode_pty_opts2(Opts)];
1047encode_pty_opts2([{echok,Value} | Opts]) ->
1048    [?ECHOK, ?uint32(Value) | encode_pty_opts2(Opts)];
1049encode_pty_opts2([{echonl,Value} | Opts]) ->
1050    [?ECHONL, ?uint32(Value) | encode_pty_opts2(Opts)];
1051encode_pty_opts2([{noflsh,Value} | Opts]) ->
1052    [?NOFLSH, ?uint32(Value) | encode_pty_opts2(Opts)];
1053encode_pty_opts2([{tostop,Value} | Opts]) ->
1054    [?TOSTOP, ?uint32(Value) | encode_pty_opts2(Opts)];
1055encode_pty_opts2([{iexten,Value} | Opts]) ->
1056    [?IEXTEN, ?uint32(Value) | encode_pty_opts2(Opts)];
1057encode_pty_opts2([{echoctl,Value} | Opts]) ->
1058    [?ECHOCTL, ?uint32(Value) | encode_pty_opts2(Opts)];
1059encode_pty_opts2([{echoke,Value} | Opts]) ->
1060    [?ECHOKE, ?uint32(Value) | encode_pty_opts2(Opts)];
1061encode_pty_opts2([{pendin,Value} | Opts]) ->
1062    [?PENDIN, ?uint32(Value) | encode_pty_opts2(Opts)];
1063encode_pty_opts2([{opost,Value} | Opts]) ->
1064    [?OPOST, ?uint32(Value) | encode_pty_opts2(Opts)];
1065encode_pty_opts2([{olcuc,Value} | Opts]) ->
1066    [?OLCUC, ?uint32(Value) | encode_pty_opts2(Opts)];
1067encode_pty_opts2([{onlcr,Value} | Opts]) ->
1068    [?ONLCR, ?uint32(Value) | encode_pty_opts2(Opts)];
1069encode_pty_opts2([{ocrnl,Value} | Opts]) ->
1070    [?OCRNL, ?uint32(Value) | encode_pty_opts2(Opts)];
1071encode_pty_opts2([{onocr,Value} | Opts]) ->
1072    [?ONOCR, ?uint32(Value) | encode_pty_opts2(Opts)];
1073encode_pty_opts2([{onlret,Value} | Opts]) ->
1074    [?ONLRET, ?uint32(Value) | encode_pty_opts2(Opts)];
1075encode_pty_opts2([{cs7,Value} | Opts]) ->
1076    [?CS7, ?uint32(Value) | encode_pty_opts2(Opts)];
1077encode_pty_opts2([{cs8,Value} | Opts]) ->
1078    [?CS8, ?uint32(Value) | encode_pty_opts2(Opts)];
1079encode_pty_opts2([{parenb,Value} | Opts]) ->
1080    [?PARENB, ?uint32(Value) | encode_pty_opts2(Opts)];
1081encode_pty_opts2([{parodd,Value} | Opts]) ->
1082    [?PARODD, ?uint32(Value) | encode_pty_opts2(Opts)];
1083encode_pty_opts2([{tty_op_ispeed,Value} | Opts]) ->
1084    [?TTY_OP_ISPEED, ?uint32(Value) | encode_pty_opts2(Opts)];
1085encode_pty_opts2([{tty_op_ospeed,Value} | Opts]) ->
1086    [?TTY_OP_OSPEED, ?uint32(Value) | encode_pty_opts2(Opts)].
1087
1088decode_pty_opts(<<>>) ->
1089    [];
1090decode_pty_opts(<<0, 0, 0, 0>>) ->
1091    [];
1092decode_pty_opts(<<?DEC_BIN(Modes,_Len)>>) ->
1093    decode_pty_opts2(Modes);
1094decode_pty_opts(Binary) ->
1095    decode_pty_opts2(Binary).
1096
1097decode_pty_opts2(<<?TTY_OP_END>>) ->
1098    [];
1099decode_pty_opts2(<<Code, ?UINT32(Value), Tail/binary>>) ->
1100    Op = case Code of
1101	     ?VINTR -> vintr;
1102	     ?VQUIT -> vquit;
1103	     ?VERASE -> verase;
1104	     ?VKILL -> vkill;
1105	     ?VEOF -> veof;
1106	     ?VEOL -> veol;
1107	     ?VEOL2 -> veol2;
1108	     ?VSTART -> vstart;
1109	     ?VSTOP -> vstop;
1110	     ?VSUSP -> vsusp;
1111	     ?VDSUSP -> vdsusp;
1112	     ?VREPRINT -> vreprint;
1113	     ?VWERASE -> vwerase;
1114	     ?VLNEXT -> vlnext;
1115	     ?VFLUSH -> vflush;
1116	     ?VSWTCH -> vswtch;
1117	     ?VSTATUS -> vstatus;
1118	     ?VDISCARD -> vdiscard;
1119	     ?IGNPAR -> ignpar;
1120	     ?PARMRK -> parmrk;
1121	     ?INPCK -> inpck;
1122	     ?ISTRIP -> istrip;
1123	     ?INLCR -> inlcr;
1124	     ?IGNCR -> igncr;
1125	     ?ICRNL -> icrnl;
1126	     ?IUCLC -> iuclc;
1127	     ?IXON -> ixon;
1128	     ?IXANY -> ixany;
1129	     ?IXOFF -> ixoff;
1130	     ?IMAXBEL -> imaxbel;
1131	     ?ISIG -> isig;
1132	     ?ICANON -> icanon;
1133	     ?XCASE -> xcase;
1134	     ?ECHO -> echo;
1135	     ?ECHOE -> echoe;
1136	     ?ECHOK -> echok;
1137	     ?ECHONL -> echonl;
1138	     ?NOFLSH -> noflsh;
1139	     ?TOSTOP -> tostop;
1140	     ?IEXTEN -> iexten;
1141	     ?ECHOCTL -> echoctl;
1142	     ?ECHOKE -> echoke;
1143	     ?PENDIN -> pendin;
1144	     ?OPOST -> opost;
1145	     ?OLCUC -> olcuc;
1146	     ?ONLCR -> onlcr;
1147	     ?OCRNL -> ocrnl;
1148	     ?ONOCR -> onocr;
1149	     ?ONLRET -> onlret;
1150	     ?CS7 -> cs7;
1151	     ?CS8 -> cs8;
1152	     ?PARENB -> parenb;
1153	     ?PARODD -> parodd;
1154	     ?TTY_OP_ISPEED -> tty_op_ispeed;
1155	     ?TTY_OP_OSPEED -> tty_op_ospeed;
1156	     _ -> Code
1157	 end,
1158    [{Op, Value} | decode_pty_opts2(Tail)].
1159
1160
1161backwards_compatible([], Acc) ->
1162    Acc;
1163backwards_compatible([{hight, Value} | Rest], Acc) ->
1164    backwards_compatible(Rest, [{height, Value} | Acc]);
1165backwards_compatible([{pixel_hight, Value} | Rest], Acc) ->
1166    backwards_compatible(Rest, [{height, Value} | Acc]);
1167backwards_compatible([Value| Rest], Acc) ->
1168    backwards_compatible(Rest, [ Value | Acc]).
1169
1170
1171%%%----------------------------------------------------------------
1172%%%
1173%%% Common part of handling channel messages meant for a cli (like "env", "exec" etc)
1174%%% Called at the finnish of handle_msg(#ssh_msg_channel_request,...)
1175%%%
1176
1177handle_cli_msg(C0, ChId, Reply0) ->
1178    Cache = C0#connection.channel_cache,
1179    Ch0 = ssh_client_channel:cache_lookup(Cache, ChId),
1180    case Ch0#channel.user of
1181        undefined ->
1182            case (catch start_cli(C0, ChId)) of
1183                {ok, Pid} ->
1184                    erlang:monitor(process, Pid),
1185                    Ch = Ch0#channel{user = Pid},
1186                    ssh_client_channel:cache_update(Cache, Ch),
1187                    reply_msg(Ch, C0, Reply0);
1188                _Other ->
1189                    Reply = {connection_reply, channel_failure_msg(Ch0#channel.remote_id)},
1190                    {[Reply], C0}
1191            end;
1192
1193        _ ->
1194            reply_msg(Ch0, C0, Reply0)
1195    end.
1196
1197%%%----------------------------------------------------------------
1198%%%
1199%%% Request response handling on return to the calling ssh_connection_handler
1200%%% state machine.
1201%%%
1202
1203channel_data_reply_msg(ChannelId, Connection, DataType, Data) ->
1204    case ssh_client_channel:cache_lookup(Connection#connection.channel_cache, ChannelId) of
1205	#channel{recv_window_size = Size} = Channel ->
1206	    WantedSize = Size - size(Data),
1207	    ssh_client_channel:cache_update(Connection#connection.channel_cache,
1208                                     Channel#channel{recv_window_size = WantedSize}),
1209            reply_msg(Channel, Connection, {data, ChannelId, DataType, Data});
1210	undefined ->
1211	    {[], Connection}
1212    end.
1213
1214
1215reply_msg(ChId, C, Reply) when is_integer(ChId) ->
1216    reply_msg(ssh_client_channel:cache_lookup(C#connection.channel_cache, ChId), C, Reply);
1217
1218reply_msg(Channel, Connection, {open, _} = Reply) ->
1219    request_reply_or_data(Channel, Connection, Reply);
1220reply_msg(Channel, Connection, {open_error, _, _, _} = Reply) ->
1221    request_reply_or_data(Channel, Connection, Reply);
1222reply_msg(Channel, Connection, success = Reply) ->
1223    request_reply_or_data(Channel, Connection, Reply);
1224reply_msg(Channel, Connection, failure = Reply) ->
1225    request_reply_or_data(Channel, Connection, Reply);
1226reply_msg(Channel, Connection, {closed, _} = Reply) ->
1227    request_reply_or_data(Channel, Connection, Reply);
1228reply_msg(undefined, Connection, _Reply) ->
1229    {[], Connection};
1230reply_msg(#channel{user = ChannelPid}, Connection, Reply) ->
1231    {[{channel_data, ChannelPid, Reply}], Connection}.
1232
1233
1234request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid},
1235		      #connection{requests = Requests} =
1236		      Connection, Reply) ->
1237    case lists:keysearch(ChannelId, 1, Requests) of
1238	{value, {ChannelId, From}} ->
1239	    {[{channel_request_reply, From, Reply}],
1240	     Connection#connection{requests =
1241				       lists:keydelete(ChannelId, 1, Requests)}};
1242	false when (Reply == success) or (Reply == failure) ->
1243	    {[], Connection};
1244	false ->
1245	    {[{channel_data, ChannelPid, Reply}], Connection}
1246    end.
1247