1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
19
%%% Description : SSH connection protocol manager
24
-include("ssh_connect.hrl").
26
-define(DEFAULT_PACKET_SIZE, 32768).
27
-define(DEFAULT_WINDOW_SIZE, 2*?DEFAULT_PACKET_SIZE).
28
-define(DEFAULT_TIMEOUT, 5000).
30
-behaviour(gen_server).
32
-import(lists, [reverse/1, foreach/2]).
34
%% gen_server callbacks
35
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
38
-export([connect/1, connect/2, connect/3]).
39
-export([listen/2, listen/3, listen/4, stop_listener/1]).
41
%%-export([dist_start/1, dist_start/2]).
42
-export([encode_ip/1]).
45
-export([adjust_window/3, attach/2, detach/2,
46
tcpip_forward/3, cancel_tcpip_forward/3, direct_tcpip/6,
49
shell/2, exec/4, i/1, i/2, info/1, info/2,
50
recv_window/3, send/3, send/4, renegotiate/1, renegotiate/2,
51
request_success/2, send_ack/3, send_ack/4, send_ack/5, send_eof/2,
52
send_window/3, session_open/2, session_open/4, subsystem/4,
53
open_pty/3, open_pty/7, open_pty/9,
54
set_user_ack/4, set_user/4,
55
setenv/5, signal/3, winch/4,
59
%% Special for ssh_userauth (and similar)
60
%%-export([set_ssh_msg_handler/2, reset_ssh_msg_handler/1]).
63
%% -export([listen_init/7, connect_init/6]).
65
-define(DBG_SSHMSG, true).
66
-define(DBG_SSHCM, true).
67
-define(DBG_USER, true).
71
type, %% "session", "x11", "forwarded-tcpip", "direct-tcpip"
72
sys, %% "none", "shell", "exec" "subsystem"
73
user, %% "user" process id (default to cm user)
74
user_ack = false, %% user want ack packet when data is sent
76
local_id, %% local channel id
83
remote_id, %% remote channel id
101
requests = [], %% [{Channel, Pid}...] awaiting reply on request
102
authhandle %% for session termination
105
%%====================================================================
107
%%====================================================================
109
%%--------------------------------------------------------------------
110
%% Function: connect(...) -> {ok,Pid} | {error,Error}
111
%% Description: Starts the server (as an ssh-client)
112
%%--------------------------------------------------------------------
115
connect(Host, Opts) ->
116
connect(Host, ?SSH_DEFAULT_PORT, Opts).
117
connect(Host, Port, Opts) ->
118
gen_server:start_link(?MODULE, [client, self(), Host, Port, Opts], []).
120
%%--------------------------------------------------------------------
121
%% Function: listen(...) -> Pid | {error,Error}
122
%% Description: Starts a listening server (as an ssh-server)
123
%%--------------------------------------------------------------------
124
listen(UserFun, Port) ->
125
listen(UserFun, Port, []).
126
listen(UserFun, Port, Opts) ->
127
listen(UserFun, any, Port, Opts).
128
listen(UserFun, Addr, Port, Opts) ->
130
ssh_userauth:reg_user_auth_server(),
131
ssh_transport:listen(
134
gen_server:start_link(
135
?MODULE, [server, Self, UserFun, SSH, Opts], []),
137
end, Addr, Port, Opts).
139
%%--------------------------------------------------------------------
140
%% Function: stop_listener(Pid) -> ok
141
%% Description: Stops the listener
142
%%--------------------------------------------------------------------
143
stop_listener(Pid) ->
144
ssh_transport:stop_listener(Pid).
147
%% %% special ssh distribution version
149
%% dist_start(Node) ->
150
%% Opts1 = case init:get_argument('ssh_password') of
151
%% {ok, [[Passwd]]} -> [{password, Passwd}];
154
%% Opts2 = case init:get_argument('ssh_user') of
155
%% {ok, [[User]]} -> [{user, User}];
158
%% dist_start(Node, Opts1++Opts2).
160
%% dist_start(Node, Opts) when atom(Node), list(Opts) ->
161
%% case string:tokens(atom_to_list(Node), "@") of
162
%% [_, "ssh:"++Host] ->
163
%% CMHost = list_to_atom(Host),
164
%% case whereis(CMHost) of
166
%% start(CMHost, Host, Opts);
167
%% Pid when pid(Pid) ->
175
%% dist_start(_, _) ->
179
%%====================================================================
180
%% gen_server callbacks
181
%%====================================================================
183
%%--------------------------------------------------------------------
184
%% Function: init(Args) -> {ok, State} |
185
%% {ok, State, Timeout} |
188
%% Description: Initiates the server
189
%%--------------------------------------------------------------------
190
init([server, _Caller, UserFun, SSH, Opts]) ->
191
SSH ! {ssh_install, connect_messages()},
192
process_flag(trap_exit, true),
194
%% Caller ! {self(), {ok, self()}},
195
CTab = ets:new(cm_tab, [set,{keypos, #channel.local_id}]),
196
State = #state{role = server, ctab = CTab, ssh = SSH, opts = Opts,
198
NewState = add_user(User, State), %% add inital user
200
init([client, User, Host, Port, Opts]) ->
201
case ssh_transport:connect(Host, Port, Opts) of
203
case user_auth(SSH, Opts) of
205
SSH ! {ssh_install, connect_messages()},
206
process_flag(trap_exit, true),
207
CTab = ets:new(cm_tab, [set,{keypos,#channel.local_id}]),
208
State = #state{role = client, ctab = CTab, ssh = SSH,
209
opts = Opts, requests = []},
210
NewState = add_user(User, State), %% add inital user
213
ssh_transport:disconnect(
214
SSH, ?SSH_DISCONNECT_BY_APPLICATION),
225
case info(CM, User) of
227
Cs1 = lists:keysort(#channel.user, Cs),
230
io:format("~10p ~w ~s/~s ~w/~w ~w/~w\n",
233
C#channel.type, C#channel.sys,
234
C#channel.recv_window_size,
235
C#channel.recv_packet_size,
236
C#channel.send_window_size,
237
C#channel.send_packet_size])
247
gen_server:call(CM, {info, User}).
249
%% CM Client commands
250
session_open(CM, TMO) ->
251
session_open(CM, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, TMO).
253
session_open(CM, InitialWindowSize, MaxPacketSize, TMO) ->
254
case gen_server:call(CM, {open, self(), "session",
255
InitialWindowSize, MaxPacketSize, <<>>}, TMO) of
256
{open, C} -> {ok, C};
260
direct_tcpip(CM, RemoteHost, RemotePort, OrigIP, OrigPort, TMO) ->
261
direct_tcpip(CM, RemoteHost, RemotePort, OrigIP, OrigPort,
262
?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, TMO).
264
direct_tcpip(CM, RemoteIP, RemotePort, OrigIP, OrigPort,
265
InitialWindowSize, MaxPacketSize, TMO) ->
266
case {encode_ip(RemoteIP), encode_ip(OrigIP)} of
267
{false, _} -> {error, einval};
268
{_, false} -> {error, einval};
270
gen_server:call(CM, {open, self(), "direct-tcpip",
271
InitialWindowSize, MaxPacketSize,
272
[?string(RIP), ?uint32(RemotePort),
273
?string(OIP), ?uint32(OrigPort)] }, TMO)
275
%%% {ssh_cm, CM, {open, Channel}} ->
277
%%% {ssh_cm, CM, {open_error, _Reason, Descr, _Lang}} ->
282
tcpip_forward(CM, BindIP, BindPort) ->
283
case encode_ip(BindIP) of
284
false -> {error, einval};
286
global_request(CM, "tcpip-forward", true,
291
cancel_tcpip_forward(CM, BindIP, Port) ->
292
case encode_ip(BindIP) of
293
false -> {error, einval};
295
global_request(CM, "cancel-tcpip-forward", true,
300
open_pty(CM, Channel, TMO) ->
301
open_pty(CM, Channel, os:getenv("TERM"), 80, 24, [], TMO).
303
open_pty(CM, Channel, Term, Width, Height, PtyOpts, TMO) ->
304
open_pty(CM, Channel, Term, Width, Height, 0, 0, PtyOpts, TMO).
307
open_pty(CM, Channel, Term, Width, Height, PixWidth, PixHeight, PtyOpts, TMO) ->
308
request(CM, Channel, "pty-req", true,
310
?uint32(Width), ?uint32(Height),
311
?uint32(PixWidth),?uint32(PixHeight),
312
encode_pty_opts(PtyOpts)], TMO).
314
setenv(CM, Channel, Var, Value, TMO) ->
315
request(CM, Channel, "env", true, [?string(Var), ?string(Value)], TMO).
317
shell(CM, Channel) ->
318
request(CM, Channel, "shell", false, <<>>, 0).
320
exec(CM, Channel, Command, TMO) ->
321
request(CM, Channel, "exec", true, [?string(Command)], TMO).
323
subsystem(CM, Channel, SubSystem, TMO) ->
324
request(CM, Channel, "subsystem", true, [?string(SubSystem)], TMO).
326
winch(CM, Channel, Width, Height) ->
327
winch(CM, Channel, Width, Height, 0, 0).
328
winch(CM, Channel, Width, Height, PixWidth, PixHeight) ->
329
request(CM, Channel, "window-change", false,
330
[?uint32(Width), ?uint32(Height),
331
?uint32(PixWidth), ?uint32(PixHeight)], 0).
333
signal(CM, Channel, Sig) ->
334
request(CM, Channel, "signal", false,
338
gen_server:call(CM, {attach, self()}, TMO).
341
gen_server:call(CM, {detach, self()}, TMO).
346
renegotiate(CM,Opts) ->
347
gen_server:cast(CM, {renegotiate,Opts}).
349
%% Setup user ack on data messages (i.e signal when the data has been sent)
350
set_user_ack(CM, Channel, Ack, TMO) ->
351
gen_server:call(CM, {set_user_ack, Channel, Ack}, TMO).
353
get_authhandle(CM) ->
354
gen_server:call(CM, get_authhandle).
357
gen_server:call(CM, get_peer_addr).
359
set_user(CM, Channel, User, TMO) ->
360
gen_server:call(CM, {set_user, Channel, User}, TMO).
362
send_window(CM, Channel, TMO) ->
363
gen_server:call(CM, {send_window, Channel}, TMO).
365
recv_window(CM, Channel, TMO) ->
366
gen_server:call(CM, {recv_window, Channel}, TMO).
368
adjust_window(CM, Channel, Bytes) ->
369
gen_server:cast(CM, {adjust_window, Channel, Bytes}).
371
close(CM, Channel) ->
372
gen_server:cast(CM, {close, Channel}).
375
gen_server:call(CM, stop).
377
send_eof(CM, Channel) ->
378
gen_server:cast(CM, {eof, Channel}).
380
send(CM, Channel, Data) ->
381
CM ! {ssh_cm, self(), {data, Channel, 0, Data}}.
383
send(CM, Channel, Type, Data) ->
384
CM ! {ssh_cm, self(), {data, Channel, Type, Data}}.
386
send_ack(CM, Channel, Data) ->
387
send_ack(CM, Channel, 0, Data, infinity).
389
send_ack(CM, Channel, Type, Data) ->
390
send_ack(CM, Channel, Type, Data, infinity).
392
send_ack(CM, Channel, Type, Data, Timeout) ->
393
send(CM, Channel, Type, Data),
395
{ssh_cm, CM, {ack, Channel}} ->
401
request(CM, Channel, Type, Reply, Data, TMO) ->
403
true -> gen_server:call(CM, {request, Channel, Type, Data}, TMO);
404
false -> gen_server:cast(CM, {request, Channel, Type, Data})
407
global_request(CM, Type, Reply, Data) ->
408
CM ! {ssh_cm, self(), {global_request,self(),Type,Reply,Data}},
411
{ssh_cm, CM, {success, _Channel}} ->
413
{ssh_cm, CM, {failure, _Channel}} ->
420
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
422
%% CM command encode/decode table
424
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
426
connect_messages() ->
427
[ {ssh_msg_global_request, ?SSH_MSG_GLOBAL_REQUEST,
432
{ssh_msg_request_success, ?SSH_MSG_REQUEST_SUCCESS,
435
{ssh_msg_request_failure, ?SSH_MSG_REQUEST_FAILURE,
438
{ssh_msg_channel_open, ?SSH_MSG_CHANNEL_OPEN,
445
{ssh_msg_channel_open_confirmation, ?SSH_MSG_CHANNEL_OPEN_CONFIRMATION,
452
{ssh_msg_channel_open_failure, ?SSH_MSG_CHANNEL_OPEN_FAILURE,
458
{ssh_msg_channel_window_adjust, ?SSH_MSG_CHANNEL_WINDOW_ADJUST,
462
{ssh_msg_channel_data, ?SSH_MSG_CHANNEL_DATA,
466
{ssh_msg_channel_extended_data, ?SSH_MSG_CHANNEL_EXTENDED_DATA,
471
{ssh_msg_channel_eof, ?SSH_MSG_CHANNEL_EOF,
474
{ssh_msg_channel_close, ?SSH_MSG_CHANNEL_CLOSE,
477
{ssh_msg_channel_request, ?SSH_MSG_CHANNEL_REQUEST,
483
{ssh_msg_channel_success, ?SSH_MSG_CHANNEL_SUCCESS,
486
{ssh_msg_channel_failure, ?SSH_MSG_CHANNEL_FAILURE,
491
%%--------------------------------------------------------------------
492
%% Function: handle_cast/2
493
%% Description: Handling cast messages
494
%% Returns: {noreply, State} |
495
%% {noreply, State, Timeout} |
496
%% {stop, Reason, State} (terminate/2 is called)
497
%%--------------------------------------------------------------------
498
handle_cast({request, Channel, Type, Data}, State) ->
499
{noreply, do_request(Channel, Type, Data, false, undefined, State)};
500
handle_cast({renegotiate, Opts}, State) ->
501
State#state.ssh ! {ssh_renegotiate, false, Opts},
503
handle_cast({adjust_window, Channel, Bytes}, State) ->
504
#state{ssh = SSH, ctab = CTab} = State,
508
WSz = C#channel.recv_window_size + Bytes,
509
channel_adjust_window(SSH, C#channel.remote_id, Bytes),
510
ets:insert(CTab, C#channel { recv_window_size = WSz})
513
handle_cast({close, Channel}, State) ->
514
#state{ssh = SSH, ctab = CTab} = State,
515
with_channel(State, Channel,
517
channel_close(SSH, C#channel.remote_id),
518
ets:insert(CTab, C#channel{sent_close = true})
521
handle_cast({eof, Channel}, State) ->
522
%%#state{ssh = SSH, ctab = _CTab} = State,
523
SSH = State#state.ssh,
524
with_channel(State, Channel,
526
channel_eof(SSH, C#channel.remote_id)%,
527
%%ets:insert(CTab, C#channel{sent_eof = true})
530
handle_cast(_Cast, State) ->
531
?dbg(true, "handle_cast: BAD cast ~p\n(State ~p)\n", [_Cast, State]),
534
%%--------------------------------------------------------------------
535
%% Function: handle_info(Info, State) -> {noreply, State} |
536
%% {noreply, State, Timeout} |
537
%% {stop, Reason, State}
538
%% Description: Handling all non call/cast messages
539
%%--------------------------------------------------------------------
540
handle_info({ssh_msg,SSH,#ssh_msg_service_request{name="ssh-userauth"}},
541
State) when State#state.role == server->
542
case ssh_userauth:auth_remote(SSH, "ssh-connection", State#state.opts) of
544
{noreply, State#state{authhandle = Handle}};
546
ssh_transport:disconnect(SSH, ?SSH_DISCONNECT_BY_APPLICATION),
547
%{stop, {error, Error}, State}
548
{stop, shutdown, State}
550
handle_info({ssh_msg, SSH, Msg}, State) ->
551
%%SSH = State#state.ssh,
552
?dbg(?DBG_SSHMSG, "handle_info<~p>: ssh_msg ~p\n", [SSH, Msg]),
553
case ssh_message(SSH, Msg, State) of
554
{disconnected, _Reason} ->
555
ssh_userauth:disconnect(State#state.authhandle, State#state.opts),
556
%{stop, {error, {disconnected, Reason}}, State};
557
{stop, shutdown, State};
561
handle_info({ssh_cm, Sender, Msg}, State) ->
562
SSH = State#state.ssh,
563
?dbg(?DBG_SSHCM, "handle_info<~p>: sender=~p, ssh_cm ~p\n", [SSH, Sender, Msg]),
564
%% only allow attached users (+ initial user)
565
NewState = case is_user(Sender, State) of
569
cm_message(SSH, Msg, State)
572
handle_info({'EXIT', SSH, Reason}, State) when SSH == State#state.ssh ->
573
error_logger:format("SSH_CM ~p EXIT ~p\n", [SSH, Reason]),
575
handle_info({'EXIT', _Pid, normal}, State) ->
577
handle_info({'EXIT', _Pid, shutdown}, State) ->
579
handle_info({'EXIT', Pid, Reason}, State) ->
580
error_logger:format("ssh_cm: Pid ~p EXIT ~p\n", [Pid, Reason]),
583
handle_info({'DOWN', _Ref, process, Pid, normal}, State) ->
584
NewState = down_user(Pid, State),
586
handle_info({'DOWN', _Ref, process, Pid, Reason}, State) ->
587
error_logger:format("Pid ~p DOWN ~p\n", [Pid, Reason]),
588
NewState = down_user(Pid, State),
592
handle_info(_Info, State) ->
593
?dbg(true, "ssh_cm:handle_info: BAD info ~p\n(State ~p)\n", [_Info, State]),
596
%%--------------------------------------------------------------------
597
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
598
%% {reply, Reply, State, Timeout} |
599
%% {noreply, State} |
600
%% {noreply, State, Timeout} |
601
%% {stop, Reason, Reply, State} |
602
%% {stop, Reason, State}
603
%% Description: Handling call messages
604
%%--------------------------------------------------------------------
605
handle_call({attach, User}, _From, State) ->
606
{reply, ok, add_user(User, State)};
607
handle_call({detach, User}, _From, State) ->
608
{reply, ok, del_user(User, State)};
609
handle_call({send_window, Channel}, _From, State) ->
610
Reply = case ets:lookup(State#state.ctab, Channel) of
612
{ok, {C#channel.send_window_size,
613
C#channel.send_packet_size}};
617
{reply, Reply, State};
618
handle_call({request, Channel, Type, Data}, From, State) ->
619
{noreply, do_request(Channel, Type, Data, true, From, State)};
620
handle_call({recv_window, Channel}, _From, State) ->
621
Reply = case ets:lookup(State#state.ctab, Channel) of
623
{ok, {C#channel.recv_window_size,
624
C#channel.recv_packet_size}};
628
{reply, Reply, State};
629
handle_call({set_user, Channel, User}, _From, State) ->
630
Reply = case is_user(User, State) of
631
false -> {error, einval};
633
CTab = State#state.ctab,
634
case ets:lookup(CTab, Channel) of
636
ets:insert(CTab, C#channel { user = User }),
642
{reply, Reply, State};
643
handle_call(get_authhandle, _From, State) ->
644
{reply, {State#state.authhandle,State#state.ssh}, State};
645
handle_call(get_peer_addr, _From, State) ->
646
{reply, ssh_transport:peername(State#state.ssh), State};
647
handle_call({set_user_ack, Channel,Ack}, _From, State) ->
648
CTab = State#state.ctab,
649
Reply = case ets:lookup(CTab, Channel) of
651
ets:insert(CTab, C#channel { user_ack = Ack }),
656
{reply, Reply, State};
657
handle_call({info,User}, _From, State) ->
659
fun(C, Acc) when User == all; C#channel.user == User ->
663
end, [], State#state.ctab),
664
{reply, {ok, Result}, State};
665
handle_call({open, User, Type, InitialWindowSize, MaxPacketSize, Data}, From, State) ->
666
case is_user(User, State) of
668
{reply, {error, einval}, State};
670
{Channel, State1} = new_channel_id(State),
671
channel_open(State#state.ssh, Type, Channel, InitialWindowSize,
672
MaxPacketSize, Data),
673
C = #channel { type = Type,
677
recv_window_size = InitialWindowSize,
678
recv_packet_size = MaxPacketSize },
679
ets:insert(State#state.ctab, C),
680
State2 = add_request(true, Channel, From, State1),
683
handle_call(stop, _From, State) ->
684
ssh_userauth:disconnect(State#state.authhandle, State#state.opts),
685
ssh_transport:close(State#state.ssh),
686
{stop, normal, ok, State};
687
handle_call(_Call, _From, State) ->
688
?dbg(true, "handle_call: BAD call ~p\n(State ~p)\n", [_Call, State]),
689
{reply, {error, bad_call}, State}.
692
%%--------------------------------------------------------------------
693
%% Function: terminate/2
694
%% Description: Shutdown the server
695
%% Returns: any (ignored by gen_server)
696
%%--------------------------------------------------------------------
697
terminate(_Reason, _State) ->
700
%%--------------------------------------------------------------------
701
%% Func: code_change/3
702
%% Purpose: Convert process state when code is changed
703
%% Returns: {ok, NewState}
704
%%--------------------------------------------------------------------
705
code_change(_OldVsn, State, _Extra) ->
709
%%--------------------------------------------------------------------
710
%%% Internal functions
711
%%--------------------------------------------------------------------
712
ssh_message(SSH, Msg, State) ->
713
CTab = State#state.ctab,
715
#ssh_msg_channel_open_confirmation { recipient_channel = Channel,
716
sender_channel = RID,
717
initial_window_size = WindowSz,
718
maximum_packet_size = PacketSz
720
with_channel(State, Channel,
722
if C#channel.remote_id == undefined ->
723
ets:insert(CTab, C#channel {
725
send_window_size = WindowSz,
726
send_packet_size = PacketSz });
731
reply_request(Channel, {open, Channel}, State);
733
#ssh_msg_channel_open_failure { recipient_channel = Channel,
737
with_channel(State, Channel,
739
ets:delete(CTab, Channel)
741
reply_request(Channel, {open_error, Reason, Descr, Lang}, State);
743
#ssh_msg_channel_success { recipient_channel = Channel } ->
744
reply_request(Channel, success, State);
746
#ssh_msg_channel_failure { recipient_channel = Channel} ->
747
reply_request(Channel, failure, State);
749
#ssh_msg_channel_eof { recipient_channel = Channel} ->
750
with_channel(State, Channel,
752
send_user(C, {eof, Channel})
753
%% if C#channel.sent_eof == true ->
754
%% reply_request(C#channel.local_id,
756
%% io:format("delete Channel b ~p\n", [Channel]),
757
%% ets:delete(CTab, Channel);
759
%% ets:insert(CTab, C#channel { recv_eof = true })
763
#ssh_msg_channel_close { recipient_channel = Channel } ->
764
with_channel(State, Channel,
766
if C#channel.sent_close == false ->
767
channel_close(SSH,C#channel.remote_id),
768
reply_request(C#channel.local_id,
773
ets:delete(CTab, Channel)
775
reply_request(Channel, closed, State);
777
#ssh_msg_channel_data { recipient_channel = Channel,
779
with_channel(State, Channel,
781
WSz = C#channel.recv_window_size - size(Data),
782
send_user(C, {data, Channel, 0, Data}),
783
ets:insert(CTab, C#channel { recv_window_size = WSz})
786
#ssh_msg_channel_extended_data { recipient_channel = Channel,
787
data_type_code = DataType,
789
with_channel(State, Channel,
791
WSz = C#channel.recv_window_size - size(Data),
792
send_user(C, {data, Channel, DataType, Data}),
793
ets:insert(CTab, C#channel { recv_window_size = WSz})
796
#ssh_msg_channel_window_adjust { recipient_channel = Channel,
797
bytes_to_add = Add } ->
798
with_channel(State, Channel,
800
update_send_window(SSH, CTab, C, Add)
803
#ssh_msg_channel_open { channel_type = Type,
804
sender_channel = RID,
805
initial_window_size = RWindowSz,
806
maximum_packet_size = RPacketSz,
810
%% FIXME: check that we requested this !
811
%% (install a listener & user somehow)
812
% <<?UINT32(ALen), Address:ALen/binary, ?UINT32(Port),
813
% ?UINT32(OLen), Orig:OLen/binary, ?UINT32(OrigPort)>> = Data,
814
case State#state.users of
816
{Channel, NewState} = new_channel_id(State),
817
LWindowSz = ?DEFAULT_WINDOW_SIZE,
818
LPacketSz = ?DEFAULT_PACKET_SIZE,
819
C = #channel { type = Type,
823
recv_window_size = LWindowSz,
824
recv_packet_size = LPacketSz,
825
send_window_size = RWindowSz,
826
send_packet_size = RPacketSz,
829
channel_open_confirmation(SSH, RID, Channel,
830
LWindowSz, LPacketSz),
831
send_user(C, {open, Channel, RID, {session}}),
834
channel_open_failure(SSH, RID,
835
?SSH_OPEN_CONNECT_FAILED,
836
"Connection refused", "en"),
841
%% FIXME: check that we requested this !
842
%% (install a listener & user somehow)
843
<<?UINT32(ALen), Address:ALen/binary, ?UINT32(Port),
844
?UINT32(OLen), Orig:OLen/binary, ?UINT32(OrigPort)>> = Data,
845
case get_bind(Address, Port, State) of
847
channel_open_failure(SSH, RID,
848
?SSH_OPEN_CONNECT_FAILED,
849
"Connection refused", "en"),
852
{Channel, NewState} = new_channel_id(State),
853
LWindowSz = ?DEFAULT_WINDOW_SIZE,
854
LPacketSz = ?DEFAULT_PACKET_SIZE,
855
C = #channel { type = Type,
859
recv_window_size = LWindowSz,
860
recv_packet_size = LPacketSz,
861
send_window_size = RWindowSz,
862
send_packet_size = RPacketSz },
864
channel_open_confirmation(SSH, RID, Channel,
865
LWindowSz, LPacketSz),
866
send_user(C, {open, Channel, {forwarded_tcpip,
867
decode_ip(Address), Port,
868
decode_ip(Orig), OrigPort}}),
872
channel_open_failure(SSH, RID,
873
?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
874
"Not allowed", "en"),
878
#ssh_msg_channel_request { recipient_channel = Channel,
880
want_reply = WantReply,
884
<<?UINT32(Status)>> = Data,
885
send_user(CTab, Channel, {exit_status,Channel,Status});
887
<<?UINT32(SigLen), SigName:SigLen/binary,
889
?UINT32(ErrLen), Err:ErrLen/binary,
890
?UINT32(LangLen), Lang:LangLen/binary>> = Data,
891
send_user(CTab, Channel, {exit_signal, Channel,
892
binary_to_list(SigName),
894
binary_to_list(Lang)});
896
<<?BOOLEAN(CDo)>> = Data,
897
send_user(CTab, Channel, {xon_xoff, Channel, CDo=/= 0});
900
<<?UINT32(Width),?UINT32(Height),
901
?UINT32(PixWidth), ?UINT32(PixHeight)>> = Data,
902
send_user(CTab, Channel, {window_change,Channel,
904
PixWidth, PixHeight});
906
<<?UINT32(SigLen), SigName:SigLen/binary>> = Data,
907
send_user(CTab, Channel, {signal,Channel,
908
binary_to_list(SigName)});
910
<<?UINT32(SsLen), SsName:SsLen/binary>> = Data,
911
send_user(CTab, Channel, {subsystem,Channel, WantReply,
912
binary_to_list(SsName)});
914
<<?UINT32(TermLen), BTermName:TermLen/binary,
915
?UINT32(Width),?UINT32(Height),
916
?UINT32(PixWidth), ?UINT32(PixHeight),
917
Modes/binary>> = Data,
918
TermName = binary_to_list(BTermName),
919
?dbg(?DBG_USER, "ssh_msg pty-req: TermName=~p Modes=~p\n", [TermName, Modes]),
920
Pty = #ssh_pty{term = TermName,
921
width = not_zero(Width, 80),
922
height = not_zero(Height, 24),
923
pixel_width = PixWidth,
924
pixel_height = PixHeight,
925
modes = decode_pty_opts(Modes)},
926
send_user(CTab, Channel, {pty, Channel, WantReply, Pty});
929
send_user(CTab, Channel, {shell});
932
<<?UINT32(Len), Command:Len/binary>> = Data,
933
send_user(CTab, Channel, {exec, binary_to_list(Command)});
936
?dbg(true, "ssh_msg ssh_msg_channel_request: Other=~p\n",
938
if WantReply == true ->
939
channel_failure(SSH, Channel);
946
#ssh_msg_global_request { name = _Type,
947
want_reply = WantReply,
949
if WantReply == true ->
950
request_failure(SSH);
957
#ssh_msg_disconnect { code = Code,
958
description = Description,
959
language = _Lang } ->
960
%% close all channels
963
reply_request(C#channel.local_id, closed, State)
966
{disconnected, {Code, Description}};
969
?dbg(true, "ssh_connection: ~p\n", [Msg]),
973
cm_message(SSH, Msg, State) ->
974
CTab = State#state.ctab,
976
{data, Channel, Type, Data} ->
977
send_data(SSH, CTab, Channel, Type, Data),
980
{global_request, User, Type, WantReply, Data} ->
983
<<?UINT32(IPLen), IP:IPLen/binary, ?UINT32(Port)>> = Data,
984
State1 = add_user(User, State), %% auto attach user
985
State2 = put_bind(IP, Port, User, State1),
986
send_global_request(SSH, Type, WantReply, Data),
988
"cancel-tcpip-forward" ->
989
<<?UINT32(IPLen), IP:IPLen/binary, ?UINT32(Port)>> = Data,
990
%% note can not erase user!
991
send_global_request(SSH, Type, WantReply, Data),
992
del_bind(IP, Port, State);
994
send_global_request(SSH, Type, WantReply, Data),
998
{success, Channel} ->
999
channel_success(SSH, Channel),
1003
?dbg(true, "ssh_connection: ~p\n", [Msg]),
1007
update_sys(CTab, C, Type) ->
1010
ets:insert(CTab, C#channel { sys = "subsystem" });
1012
ets:insert(CTab, C#channel { sys = "subsystem" });
1014
ets:insert(CTab, C#channel { sys = "shell" });
1019
user_auth(SSH, Opts) ->
1020
case ssh_transport:service_request(SSH, "ssh-userauth") of
1022
ssh_userauth:auth(SSH, "ssh-connection", Opts);
1028
down_user(User, State) ->
1029
#state{ctab = CTab, ssh = SSH} = State,
1030
case is_user(User, State) of
1033
fun(C, _) when C#channel.user == User ->
1034
channel_close(SSH, C#channel.remote_id),
1035
ets:delete(CTab, C#channel.local_id);
1039
del_user(User, State);
1044
%% reply to request, or send to user, depending on whether the
1045
%% Channel is in requests
1046
reply_request(Channel, Reply, State) ->
1047
#state{ctab = CTab, requests = Requests} = State,
1048
case lists:keysearch(Channel, 1, Requests) of
1049
{value, {Channel, From}} ->
1050
gen_server:reply(From, Reply),
1051
State#state{requests = lists:keydelete(Channel, 1, Requests)};
1053
send_user(CTab, Channel, {Reply, Channel}),
1057
%% Send ssh_cm messages to the 'user'
1058
send_user(C, Msg) when record(C, channel) ->
1059
C#channel.user ! {ssh_cm, self(), Msg}.
1061
send_user(CTab, Channel, Msg) ->
1062
case ets:lookup(CTab, Channel) of
1069
%% Update the send window with Data
1070
%% adjust output window
1072
%% buffer is on form [{DataType,UserAck,User,Data}]
1073
%% DataType = 0 regular data
1075
%% UserAck = true if "User" wants ack when data was sent
1078
send_data(SSH, CTab, LID, DataType, Data0) ->
1079
case ets:lookup(CTab, LID) of
1081
Data = if binary(Data0) -> Data0;
1082
list(Data0) -> list_to_binary(Data0)
1084
send_window(SSH,CTab,C, DataType,
1085
C#channel.user_ack, C#channel.user,
1091
update_send_window(SSH, CTab, C, Bytes) ->
1092
WSz0 = C#channel.send_window_size,
1093
send_window(SSH, CTab, C#channel { send_window_size = WSz0+Bytes},
1094
0, false, undefined, <<>>).
1097
send_window(SSH, CTab, C, DataType, UserAck, User, Data) ->
1099
fun({Type,Ack,Usr,Data1}) ->
1100
channel_data(SSH, C#channel.remote_id, Type, Data1),
1102
Usr ! {ssh_cm, self(), {ack, C#channel.local_id}};
1106
end, remove_from_send_window(CTab, C, DataType, UserAck, User, Data)).
1109
%% Get data from the send buffer
1110
%% each buffer sent must be less than packet size
1111
remove_from_send_window(CTab, C, DataType, UserAck, User, Data) ->
1112
Buf0 = if Data == <<>> ->
1115
C#channel.send_buf ++ [{DataType,UserAck,User,Data}]
1117
{Buf1,NewSz,Buf2} = get_window(Buf0,
1118
C#channel.send_packet_size,
1119
C#channel.send_window_size),
1120
ets:insert(CTab, C#channel { send_window_size = NewSz,
1124
get_window(Bs, PSz, WSz) ->
1125
get_window(Bs, PSz, WSz, []).
1127
get_window(Bs, _PSz, 0, Acc) ->
1128
{reverse(Acc), 0, Bs};
1129
get_window([B0 = {DataType,_UserAck,_User,Bin} | Bs], PSz, WSz, Acc) ->
1131
if BSz =< WSz -> %% will fit into window
1132
if BSz =< PSz -> %% will fit into a packet
1133
get_window(Bs, PSz, WSz-BSz, [B0|Acc]);
1134
true -> %% split into packet size
1135
<<Bin1:PSz/binary, Bin2/binary>> = Bin,
1136
get_window([setelement(4, B0, Bin2) | Bs],
1138
[{DataType,false,undefined,Bin1}|Acc])
1140
WSz =< PSz -> %% use rest of window
1141
<<Bin1:WSz/binary, Bin2/binary>> = Bin,
1142
get_window([setelement(4, B0, Bin2) | Bs],
1144
[{DataType,false,undefined,Bin1}|Acc]);
1145
true -> %% use packet size
1146
<<Bin1:PSz/binary, Bin2/binary>> = Bin,
1147
get_window([setelement(4, B0, Bin2) | Bs],
1149
[{DataType,false,undefined,Bin1}|Acc])
1151
get_window([], _PSz, WSz, Acc) ->
1152
{reverse(Acc), WSz, []}.
1158
channel_eof(SSH, Channel) ->
1159
SSH ! {ssh_msg, self(),
1160
#ssh_msg_channel_eof { recipient_channel = Channel }}.
1162
channel_close(SSH, Channel) ->
1163
SSH ! {ssh_msg, self(),
1164
#ssh_msg_channel_close { recipient_channel = Channel }}.
1166
channel_success(SSH, Channel) ->
1167
SSH ! {ssh_msg, self(),
1168
#ssh_msg_channel_success { recipient_channel = Channel }}.
1170
channel_failure(SSH, Channel) ->
1171
SSH ! {ssh_msg, self(),
1172
#ssh_msg_channel_failure { recipient_channel = Channel }}.
1175
channel_adjust_window(SSH, Channel, Bytes) ->
1176
SSH ! {ssh_msg, self(),
1177
#ssh_msg_channel_window_adjust { recipient_channel = Channel,
1178
bytes_to_add = Bytes }}.
1181
channel_data(SSH, Channel, 0, Data) ->
1182
SSH ! {ssh_msg, self(),
1183
#ssh_msg_channel_data { recipient_channel = Channel,
1185
channel_data(SSH, Channel, Type, Data) ->
1186
SSH ! {ssh_msg, self(),
1187
#ssh_msg_channel_extended_data { recipient_channel = Channel,
1188
data_type_code = Type,
1191
channel_open(SSH, Type, Channel, WindowSize, MaxPacketSize, Data) ->
1192
SSH ! {ssh_msg, self(),
1193
#ssh_msg_channel_open { channel_type = Type,
1194
sender_channel = Channel,
1195
initial_window_size = WindowSize,
1196
maximum_packet_size = MaxPacketSize,
1200
channel_open_confirmation(SSH, RID, LID, WindowSize, PacketSize) ->
1201
SSH ! {ssh_msg, self(),
1202
#ssh_msg_channel_open_confirmation { recipient_channel = RID,
1203
sender_channel = LID,
1204
initial_window_size = WindowSize,
1205
maximum_packet_size = PacketSize}}.
1207
channel_open_failure(SSH, RID, Reason, Description, Lang) ->
1208
SSH ! {ssh_msg, self(),
1209
#ssh_msg_channel_open_failure { recipient_channel = RID,
1211
description = Description,
1216
channel_request(SSH, Channel, Type, WantReply, Data) ->
1217
SSH ! {ssh_msg, self(),
1218
#ssh_msg_channel_request { recipient_channel = Channel,
1219
request_type = Type,
1220
want_reply = WantReply,
1223
send_global_request(SSH, Type, WantReply, Data) ->
1224
SSH ! {ssh_msg, self(),
1225
#ssh_msg_global_request { name = Type,
1226
want_reply = WantReply,
1229
request_failure(SSH) ->
1230
SSH ! {ssh_msg, self(), #ssh_msg_request_failure {}}.
1232
request_success(SSH,Data) ->
1233
SSH ! {ssh_msg, self(), #ssh_msg_request_success { data=Data }}.
1237
decode_pty_opts(<<>>) ->
1239
decode_pty_opts(<<0, 0, 0, 0>>) ->
1241
decode_pty_opts(<<?UINT32(Len), Modes:Len/binary>>) ->
1242
decode_pty_opts2(Modes);
1243
decode_pty_opts(Binary) ->
1244
decode_pty_opts2(Binary).
1246
decode_pty_opts2(<<?TTY_OP_END>>) ->
1248
decode_pty_opts2(<<Code, ?UINT32(Value), Tail/binary>>) ->
1261
?VREPRINT -> vreprint;
1262
?VWERASE -> vwerase;
1266
?VSTATUS -> vstatus;
1267
?VDISCARD -> vdiscard;
1279
?IMAXBEL -> imaxbel;
1290
?ECHOCTL -> echoctl;
1303
?TTY_OP_ISPEED -> tty_op_ispeed;
1304
?TTY_OP_OSPEED -> tty_op_ospeed
1306
[{Op, Value} | decode_pty_opts2(Tail)].
1310
encode_pty_opts([{Opt,Value} | Opts]) ->
1323
vreprint -> ?VREPRINT;
1324
vwerase -> ?VWERASE;
1328
vstatus -> ?VSTATUS;
1329
vdiscard -> ?VDISCARD;
1341
imaxbel -> ?IMAXBEL;
1352
echoctl -> ?ECHOCTL;
1365
tty_op_ispeed -> ?TTY_OP_ISPEED;
1366
tty_op_ospeed -> ?TTY_OP_OSPEED
1368
[Code, ?uint32(Value) | encode_pty_opts(Opts)];
1369
encode_pty_opts([]) ->
1373
decode_ip(Addr) when tuple(Addr) ->
1375
decode_ip(Addr) when binary(Addr) ->
1376
decode_ip(binary_to_list(Addr));
1377
decode_ip(Addr) when list(Addr) ->
1378
case inet_parse:address(Addr) of
1383
%% return string() | false
1384
encode_ip(Addr) when tuple(Addr) ->
1385
case catch inet_parse:ntoa(Addr) of
1386
{'EXIT',_} -> false;
1389
encode_ip(Addr) when list(Addr) ->
1390
case inet_parse:address(Addr) of
1393
case inet:getaddr(Addr, inet) of
1403
do_request(Channel, Type, Data, WantReply, From, State) ->
1404
#state{ctab = CTab, ssh = SSH} = State,
1408
update_sys(CTab, C, Type),
1409
channel_request(SSH, C#channel.remote_id,
1410
Type, WantReply, Data)
1412
add_request(WantReply, Channel, From, State).
1414
add_request(false, _Channel, _From, State) ->
1416
add_request(true, Channel, From, State) ->
1417
Requests = [{Channel, From} | State#state.requests],
1418
State#state{requests = Requests}.
1426
put_bind(IP, Port, User, State) ->
1427
Binds = [{{IP, Port}, User}
1428
| lists:keydelete({IP, Port}, 1, State#state.binds)],
1429
State#state{binds = Binds}.
1431
del_bind(IP, Port, State) ->
1432
State#state{binds = lists:keydelete({IP, Port}, 1, State#state.binds)}.
1434
del_binds_by_user(User, State) ->
1435
Binds = [{B, U} || {B, U} <- State#state.binds, U =/= User],
1436
State#state{binds = Binds}.
1438
get_bind(IP, Port, State) ->
1439
case lists:keysearch({IP, Port}, 1, State) of
1440
{value, User} -> User;
1444
with_channel(State, Channel, Fun) ->
1445
case ets:lookup(State#state.ctab, Channel) of
1454
add_user(User, State) ->
1455
Users = State#state.users,
1456
case lists:keymember(User, 1, Users) of
1458
Ref = erlang:monitor(process, User),
1459
State#state{users = [{User, Ref} | Users]};
1465
del_user(User, State) ->
1466
#state{users = Users, ssh = SSH} = State,
1467
?dbg(false, "del user: ~p\n",[User]),
1468
case lists:keysearch(User, 1, Users) of
1470
{{error, einval}, State};
1471
{value, {User, Ref}} ->
1472
erlang:demonitor(Ref),
1473
State1 = del_binds_by_user(User, State),
1474
State2 = State1#state{users = lists:keydelete(User, 1, Users)},
1475
%% exit if no more users and we are unregistered
1477
case process_info(self(), registered_name) of
1479
ssh_transport:disconnect(SSH, 0);
1480
{registered_name,_Name} ->
1489
is_user(User, State) ->
1490
lists:keymember(User, 1, State#state.users).
1492
%% Allocate channel ID
1493
new_channel_id(State) ->
1494
ID = State#state.channel_id,
1495
{ID, State#state{channel_id = ID + 1}}.
1497
not_zero(0, B) -> B;
1498
not_zero(A, _) -> A.