58
48
%%====================================================================
60
%%====================================================================
62
child_spec(Shell, 22).
64
child_spec(Shell, Port) ->
65
child_spec(Shell, Port, []).
67
child_spec(Shell, Port, Opts) ->
68
child_spec(Shell, any, Port, Opts).
70
child_spec(Shell, Address, Port, Opts) ->
72
StartFunc = {gen_server,
74
[Shell, Address, Port, Opts], []]},
79
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
81
%%--------------------------------------------------------------------
82
%% Function: listen(...) -> {ok,Pid} | ignore | {error,Error}
83
%% Description: Starts a listening server
84
%% Note that the pid returned is NOT the pid of this gen_server;
85
%% this server is started when an SSH connection is made on the
87
%%--------------------------------------------------------------------
91
listen(Shell, Port) ->
92
listen(Shell, Port, []).
94
listen(Shell, Port, Opts) ->
95
listen(Shell, any, Port, Opts).
97
listen(Shell, HostAddr, Port, Opts) ->
98
ssh:daemon(HostAddr, Port, [{shell, Shell} | Opts]).
101
%%--------------------------------------------------------------------
102
%% Function: stop(Pid) -> ok
103
%% Description: Stops the listener
104
%%--------------------------------------------------------------------
106
ssh:stop_listener(Pid).
108
%%====================================================================
109
%% gen_server callbacks
110
%%====================================================================
112
%%--------------------------------------------------------------------
113
%% Function: init(Args) -> {ok, State} |
114
%% {ok, State, Timeout} |
117
%% Description: Initiates the server
118
%%--------------------------------------------------------------------
119
init([Shell, Address, Port, Opts]) ->
120
{ok, #state{shell = Shell,
125
%%--------------------------------------------------------------------
126
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
127
%% {reply, Reply, State, Timeout} |
128
%% {noreply, State} |
129
%% {noreply, State, Timeout} |
130
%% {stop, Reason, Reply, State} |
131
%% {stop, Reason, State}
132
%% Description: Handling call messages
133
%%--------------------------------------------------------------------
134
handle_call(stop, _From, State) ->
135
Result = ssh_cm:stop(State#state.cm),
136
{stop, normal, Result, State};
137
handle_call(info, _From, State) ->
138
{reply, State, State};
139
handle_call(_Request, _From, State) ->
141
{reply, Reply, State}.
143
%%--------------------------------------------------------------------
144
%% Function: handle_cast(Msg, State) -> {noreply, State} |
145
%% {noreply, State, Timeout} |
146
%% {stop, Reason, State}
147
%% Description: Handling cast messages
148
%%--------------------------------------------------------------------
149
handle_cast(_Msg, State) ->
152
%%--------------------------------------------------------------------
153
%% Function: handle_info(Info, State) -> {noreply, State} |
154
%% {noreply, State, Timeout} |
155
%% {stop, Reason, State}
156
%% Description: Handling all non call/cast messages
157
%%--------------------------------------------------------------------
158
handle_info({ssh_cm, CM, {open, Channel, RemoteChannel, {session}}}, State) ->
160
State#state{cm = CM, channel = Channel, remote_channel = RemoteChannel}};
162
handle_info({ssh_cm, CM, {data, _Channel, _Type, Data}},
163
#state{remote_channel = ChannelId} = State) ->
164
ssh_connection:adjust_window(CM, ChannelId, size(Data)),
165
State#state.group ! {self(), {data, binary_to_list(Data)}},
168
handle_info({ssh_cm, CM, {pty, _Channel, WantReply, Pty}},
169
#state{remote_channel = ChannelId} = State0) ->
170
ssh_connection:reply_request(CM, WantReply, success, ChannelId),
171
State = State0#state{pty = Pty},
49
%% ssh_channel callbacks
50
%%====================================================================
52
%%--------------------------------------------------------------------
53
%% Function: init(Args) -> {ok, State}
55
%% Description: Initiates the CLI
56
%%--------------------------------------------------------------------
58
{ok, #state{shell = Shell}}.
60
%%--------------------------------------------------------------------
61
%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
63
%% Description: Handles channel messages received on the ssh-connection.
64
%%--------------------------------------------------------------------
65
handle_ssh_msg({ssh_cm, _ConnectionManager,
66
{data, _ChannelId, _Type, Data}},
67
#state{group = Group} = State) ->
68
Group ! {self(), {data, binary_to_list(Data)}},
71
handle_ssh_msg({ssh_cm, ConnectionManager,
72
{pty, ChannelId, WantReply,
73
{TermName, Width, Height, PixWidth, PixHeight, Modes}}},
75
State = State0#state{pty =
76
#ssh_pty{term = TermName,
77
width = not_zero(Width, 80),
78
height = not_zero(Height, 24),
79
pixel_width = PixWidth,
80
pixel_height = PixHeight,
175
handle_info({ssh_cm, _CM,
176
{window_change, _Channel, Width, Height, PixWidth, PixHeight}},
178
#state{buf = Buf, pty = Pty, cm = CM, channel = Channel} = State,
179
NewPty = Pty#ssh_pty{width = Width, height = Height,
180
pixel_width = PixWidth,
181
pixel_height = PixHeight},
182
{Chars, NewBuf} = io_request({window_change, Pty}, Buf, NewPty),
183
write_chars(CM, Channel, Chars),
184
{noreply, State#state{pty = NewPty, buf = NewBuf}};
185
handle_info({Group, Req}, State) when Group==State#state.group ->
186
?dbg(?DBG_IO_REQUEST, "io_request: ~w\n", [Req]),
187
#state{buf = Buf, pty = Pty, cm = CM, channel = Channel} = State,
83
ssh_connection:reply_request(ConnectionManager, WantReply,
87
handle_ssh_msg({ssh_cm, ConnectionManager,
88
{env, ChannelId, WantReply, _Var, _Value}}, State) ->
89
ssh_connection:reply_request(ConnectionManager,
90
WantReply, failure, ChannelId),
93
handle_ssh_msg({ssh_cm, ConnectionManager,
94
{window_change, ChannelId, Width, Height, PixWidth, PixHeight}},
95
#state{buf = Buf, pty = Pty0} = State) ->
96
Pty = Pty0#ssh_pty{width = Width, height = Height,
97
pixel_width = PixWidth,
98
pixel_height = PixHeight},
99
{Chars, NewBuf} = io_request({window_change, Pty0}, Buf, Pty),
100
write_chars(ConnectionManager, ChannelId, Chars),
101
{ok, State#state{pty = Pty, buf = NewBuf}};
103
handle_ssh_msg({ssh_cm, ConnectionManager,
104
{shell, ChannelId, WantReply}}, State) ->
105
NewState = start_shell(ConnectionManager, State),
106
ssh_connection:reply_request(ConnectionManager, WantReply,
108
{ok, NewState#state{channel = ChannelId,
109
cm = ConnectionManager}};
111
handle_ssh_msg({ssh_cm, ConnectionManager,
112
{exec, ChannelId, WantReply, Cmd}}, State) ->
113
{Reply, Status} = exec(Cmd),
114
write_chars(ConnectionManager,
115
ChannelId, io_lib:format("~p\n", [Reply])),
116
ssh_connection:reply_request(ConnectionManager, WantReply,
118
ssh_connection:exit_status(ConnectionManager, ChannelId, Status),
119
ssh_connection:send_eof(ConnectionManager, ChannelId),
120
{stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionManager}};
122
handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
125
handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
126
%% Ignore signals according to RFC 4254 section 6.9.
129
handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
130
Report = io_lib:format("Connection closed by peer ~n Error ~p~n",
132
error_logger:error_report(Report),
133
{stop, ChannelId, State};
135
handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
136
{stop, ChannelId, State};
138
handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
140
Report = io_lib:format("Connection closed by peer ~n Status ~p~n",
142
error_logger:error_report(Report),
143
{stop, ChannelId, State}.
145
%%--------------------------------------------------------------------
146
%% Function: handle_msg(Args) -> {ok, State} | {stop, ChannelId, State}
148
%% Description: Handles other channel messages.
149
%%--------------------------------------------------------------------
150
handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
151
#state{channel = ChannelId,
152
cm = ConnectionManager} = State) ->
155
handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
156
cm = ConnectionManager,
157
channel = ChannelId} = State) ->
188
158
{Chars, NewBuf} = io_request(Req, Buf, Pty),
189
write_chars(CM, Channel, Chars),
190
{noreply, State#state{buf = NewBuf}};
191
handle_info({ssh_cm, CM, {shell, WantReply}},
192
#state{remote_channel = ChannelId} = State) ->
193
NewState = start_shell(CM, State),
194
process_flag(trap_exit, true),
195
ssh_connection:reply_request(CM, WantReply, success, ChannelId),
197
handle_info({ssh_cm, CM, {exec, Cmd}}, #state{channel = ChannelId} = State) ->
198
Reply = case erl_scan:string(Cmd) of
199
{ok, Tokens, _EndList} ->
200
case erl_parse:parse_exprs(Tokens) of
202
case (catch erl_eval:exprs(
204
erl_eval:new_bindings())) of
205
{value, Value, _NewBindings} ->
207
{'EXIT', {E, _}} -> E;
214
write_chars(CM, ChannelId, io_lib:format("~p\n", [Reply])),
215
ssh_connection:send_eof(CM, ChannelId),
216
ssh_connection:close(CM, ChannelId),
218
handle_info({get_cm, From}, #state{cm=CM} = State) ->
219
From ! {From, cm, CM},
221
handle_info({ssh_cm, _CM, {eof, _Channel}}, State) ->
222
{stop, normal, State};
223
handle_info({ssh_cm, _CM, {closed, _Channel}}, State) ->
224
%% ignore -- we'll get an {eof, Channel} soon??
226
handle_info({ssh_cm, CM, {subsystem, ChannelId, _WantsReply, SsName}} = Msg,
227
#state{address = Address, port = Port, options = Opts,
228
remote_channel = RemoteChannel} = State) ->
229
case check_subsystem(SsName, Opts) of
232
%% Backwards compatibility
233
Module when is_atom(Module) ->
235
gen_server:start_link(Module, [Opts], []),
237
{ssh_cm, CM, {open, ChannelId, RemoteChannel, {session}}},
239
ssh_connection_manager:controlling_process(CM, ChannelId,
241
{stop, normal, State};
242
Fun when is_function(Fun) ->
245
{ssh_cm, CM, {open, ChannelId, RemoteChannel, {session}}},
247
ssh_connection_manager:controlling_process(CM, ChannelId,
249
{stop, normal, State};
251
SystemSup = ssh_system_sup:system_supervisor(Address, Port),
252
ChannelSup = ssh_system_sup:channel_supervisor(SystemSup),
254
= ssh_channel_sup:start_child(ChannelSup, ChildSpec),
256
{ssh_cm, CM, {open, ChannelId, RemoteChannel, {session}}},
258
ssh_connection_manager:controlling_process(CM, ChannelId,
260
empty_mailbox_workaround(SubSystemD),
261
{stop, normal, State}
264
handle_info({'EXIT', Group, normal},
265
#state{cm=CM, channel=Channel, group=Group} = State) ->
266
ssh_connection:close(CM, Channel),
268
{stop, normal, State};
269
handle_info(Info, State) ->
270
?dbg(true, "~p:handle_info: BAD info ~p\n(State ~p)\n",
271
[?MODULE, Info, State]),
272
{stop, {bad_info, Info}, State}.
159
write_chars(ConnectionManager, ChannelId, Chars),
160
{ok, State#state{buf = NewBuf}};
162
handle_msg({'EXIT', Group, _Reason}, #state{group = Group,
163
channel = ChannelId} = State) ->
164
{stop, ChannelId, State};
166
handle_msg(_, State) ->
274
169
%%--------------------------------------------------------------------
275
170
%% Function: terminate(Reason, State) -> void()
276
%% Description: This function is called by a gen_server when it is about to
277
%% terminate. It should be the opposite of Module:init/1 and do any necessary
278
%% cleaning up. When it returns, the gen_server terminates with Reason.
279
%% The return value is ignored.
171
%% Description: Called when the channel process is trminated
280
172
%%--------------------------------------------------------------------
281
173
terminate(_Reason, _State) ->
284
176
%%--------------------------------------------------------------------
285
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
286
%% Description: Convert process state when code is changed
287
%%--------------------------------------------------------------------
288
code_change(_OldVsn, State, _Extra) ->
291
%%--------------------------------------------------------------------
292
177
%%% Internal functions
293
178
%%--------------------------------------------------------------------
295
%%% io_request, handle io requests from the user process
181
eval(parse(scan(Cmd))).
184
erl_scan:string(Cmd).
186
parse({ok, Tokens, _}) ->
187
erl_parse:parse_exprs(Tokens);
191
eval({ok, Expr_list}) ->
192
case (catch erl_eval:exprs(Expr_list,
193
erl_eval:new_bindings())) of
194
{value, Value, _NewBindings} ->
196
{'EXIT', {Error, _}} ->
204
%%% io_request, handle io requests from the user process,
205
%%% Note, this is not the real I/O-protocol, but the mockup version
206
%%% used between edlin and a user_driver. The protocol tags are
207
%%% similar, but the message set is different.
208
%%% The protocol only exists internally between edlin and a character
209
%%% displaying device...
210
%%% We are *not* really unicode aware yet, we just filter away characters
211
%%% beyond the latin1 range. We however handle the unicode binaries...
296
212
io_request({window_change, OldTty}, Buf, Tty) ->
297
213
window_change(Tty, OldTty, Buf);
298
214
io_request({put_chars, Cs}, Buf, Tty) ->
299
215
put_chars(bin_to_list(Cs), Buf, Tty);
216
io_request({put_chars, unicode, Cs}, Buf, Tty) ->
217
put_chars([Ch || Ch <- unicode:characters_to_list(Cs,unicode), Ch =< 255], Buf, Tty);
300
218
io_request({insert_chars, Cs}, Buf, Tty) ->
301
219
insert_chars(bin_to_list(Cs), Buf, Tty);
220
io_request({insert_chars, unicode, Cs}, Buf, Tty) ->
221
insert_chars([Ch || Ch <- unicode:characters_to_list(Cs,unicode), Ch =< 255], Buf, Tty);
302
222
io_request({move_rel, N}, Buf, Tty) ->
303
223
move_rel(N, Buf, Tty);
304
224
io_request({delete_chars,N}, Buf, Tty) ->