1 /*
2 * SSHTools - Java SSH2 API
3 *
4 * Copyright (C) 2002-2003 Lee David Painter and Contributors.
5 *
6 * Contributions made by:
7 *
8 * Brett Smith
9 * Richard Pernavas
10 * Erwin Bolwidt
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26 package com.sshtools.daemon.session;
27
28 import com.sshtools.daemon.configuration;
29 import com.sshtools.daemon.platform;
30 import com.sshtools.daemon.scp;
31 import com.sshtools.daemon.subsystem;
32
33 import com.sshtools.j2ssh;
34 import com.sshtools.j2ssh.agent;
35 import com.sshtools.j2ssh.configuration;
36 import com.sshtools.j2ssh.connection;
37 import com.sshtools.j2ssh.io;
38 import com.sshtools.j2ssh.util;
39
40 import org.apache.commons.logging;
41
42 import java.io;
43
44 import java.util;
45
46
47 /**
48 *
49 *
50 * @author $author$
51 * @version $Revision: 1.16 $
52 */
53 public class SessionChannelServer extends IOChannel {
54 private static Log log = LogFactory.getLog(SessionChannelServer.class);
55
56 /** */
57 public final static String SESSION_CHANNEL_TYPE = "session";
58 private static Map allowedSubsystems = new HashMap();
59 private Map environment = new HashMap();
60 private NativeProcessProvider processInstance;
61 private SubsystemServer subsystemInstance;
62 private Thread thread;
63 private IOStreamConnector ios;
64 private ChannelOutputStream stderrOut;
65 private InputStream stderrIn;
66 private ProcessMonitorThread processMonitor;
67 private PseudoTerminalWrapper pty;
68 private SshAgentForwardingListener agent;
69 private ServerConfiguration config;
70
71 /**
72 * Creates a new SessionChannelServer object.
73 *
74 * @throws ConfigurationException
75 */
76 public SessionChannelServer() throws ConfigurationException {
77 super();
78
79 // Load the allowed subsystems from the server configuration
80 config = (ServerConfiguration) ConfigurationLoader.getConfiguration(ServerConfiguration.class);
81 allowedSubsystems.putAll(config.getSubsystems());
82 }
83
84 private void bindStderrInputStream(InputStream stderrIn) {
85 this.stderrIn = stderrIn;
86 ios = new IOStreamConnector(stderrIn, stderrOut);
87 }
88
89 /**
90 *
91 *
92 * @param cols
93 * @param rows
94 * @param width
95 * @param height
96 */
97 protected void onChangeTerminalDimensions(int cols, int rows, int width,
98 int height) {
99 }
100
101 /**
102 *
103 *
104 * @throws IOException
105 */
106 protected void onChannelClose() throws IOException {
107 // Remove our reference to the agent
108 if (agent != null) {
109 agent.removeReference(this);
110 }
111
112 if (processInstance != null) {
113 if (processInstance.stillActive()) {
114 processInstance.kill();
115 }
116 }
117
118 if (subsystemInstance != null) {
119 subsystemInstance.stop();
120 }
121
122 // If we have a process monitor then get the exit code
123 // and send before we close the channel
124 if (processMonitor != null) {
125 StartStopState state = processMonitor.getStartStopState();
126
127 try {
128 state.waitForState(StartStopState.STOPPED);
129 } catch (InterruptedException ex) {
130 throw new IOException("The process monitor was interrupted");
131 }
132 }
133 }
134
135 /**
136 *
137 *
138 * @throws IOException
139 */
140 protected void onChannelEOF() throws IOException {
141 }
142
143 /**
144 *
145 *
146 * @param data
147 *
148 * @throws IOException
149 */
150 protected void onChannelExtData(byte[] data) throws IOException {
151 // Do something with the data
152 }
153
154 /**
155 *
156 *
157 * @throws InvalidChannelException
158 */
159 protected void onChannelOpen() throws InvalidChannelException {
160 stderrOut = new ChannelOutputStream(this,
161 new Integer(SshMsgChannelExtendedData.SSH_EXTENDED_DATA_STDERR));
162 }
163
164 /**
165 *
166 *
167 * @param command
168 *
169 * @return
170 *
171 * @throws IOException
172 */
173 protected boolean onExecuteCommand(String command)
174 throws IOException {
175 log.debug("Executing command " + command);
176
177 // Hack for now
178 if (command.startsWith("scp ")) {
179 if (processInstance == null) {
180 processInstance = new ScpServer();
181 }
182 }
183
184 // Create an instance of the native process provider if we n
185 if (processInstance == null) {
186 processInstance = NativeProcessProvider.newInstance();
187 }
188
189 if (processInstance == null) {
190 log.debug("Failed to create process");
191
192 return false;
193 }
194
195 boolean result = processInstance.createProcess(command, environment);
196
197 if (result) {
198 if (pty != null) {
199 // Bind the streams to the pseudo terminal wrapper
200 pty.bindMasterOutputStream(getOutputStream());
201 pty.bindMasterInputStream(getInputStream());
202 pty.bindSlaveInputStream(processInstance.getInputStream());
203 pty.bindSlaveOutputStream(processInstance.getOutputStream());
204
205 // Initialize the terminal
206 pty.initialize();
207
208 // Bind the master output stream of the pty to the session
209 bindInputStream(pty.getMasterInputStream());
210
211 // Bind the processes stderr
212 bindStderrInputStream(processInstance.getStderrInputStream());
213 } else {
214 // Just bind the process streams to the session
215 bindInputStream(processInstance.getInputStream());
216 bindOutputStream(processInstance.getOutputStream());
217 bindStderrInputStream(processInstance.getStderrInputStream());
218 }
219 }
220
221 return result;
222 }
223
224 /**
225 *
226 *
227 * @param term
228 * @param cols
229 * @param rows
230 * @param width
231 * @param height
232 * @param modes
233 *
234 * @return
235 */
236 protected boolean onRequestPseudoTerminal(String term, int cols, int rows,
237 int width, int height, String modes) {
238 try {
239 // Create an instance of the native process provider
240 processInstance = NativeProcessProvider.newInstance();
241
242 if (processInstance.supportsPseudoTerminal(term)) {
243 return processInstance.allocatePseudoTerminal(term, cols, rows,
244 width, height, modes);
245 } else {
246 pty = new PseudoTerminalWrapper(term, cols, rows, width,
247 height, modes);
248
249 return true;
250 }
251 } catch (IOException ioe) {
252 log.warn("Failed to allocate pseudo terminal " + term, ioe);
253
254 return false;
255 }
256 }
257
258 /**
259 *
260 *
261 * @param name
262 * @param value
263 */
264 protected void onSetEnvironmentVariable(String name, String value) {
265 environment.put(name, value);
266 }
267
268 /**
269 *
270 *
271 * @return
272 *
273 * @throws IOException
274 */
275 protected boolean onStartShell() throws IOException {
276 String shell = config.getTerminalProvider();
277
278 if (processInstance == null) {
279 processInstance = NativeProcessProvider.newInstance();
280 }
281
282 if ((shell != null) && !shell.trim().equals("")) {
283 int idx = shell.indexOf("%DEFAULT_TERMINAL%");
284
285 if (idx > -1) {
286 shell = ((idx > 0) ? shell.substring(0, idx) : "") +
287 processInstance.getDefaultTerminalProvider() +
288 (((idx + 18) < shell.length()) ? shell.substring(idx + 18)
289 : "");
290 }
291 } else {
292 shell = processInstance.getDefaultTerminalProvider();
293 }
294
295 return onExecuteCommand(shell);
296 }
297
298 /**
299 *
300 *
301 * @param subsystem
302 *
303 * @return
304 */
305 protected boolean onStartSubsystem(String subsystem) {
306 boolean result = false;
307
308 try {
309 if (!allowedSubsystems.containsKey(subsystem)) {
310 log.error(subsystem + " Subsystem is not available");
311
312 return false;
313 }
314
315 AllowedSubsystem obj = (AllowedSubsystem) allowedSubsystems.get(subsystem);
316
317 if (obj.getType().equals("class")) {
318 // Create the class implementation and start the subsystem
319 Class cls = Class.forName(obj.getProvider());
320 subsystemInstance = (SubsystemServer) cls.newInstance();
321 subsystemInstance.setSession(this);
322 bindInputStream(subsystemInstance.getInputStream());
323 bindOutputStream(subsystemInstance.getOutputStream());
324
325 return true;
326 } else {
327 // Determine the subsystem provider
328 String provider = obj.getProvider();
329 File f = new File(provider);
330
331 if (!f.exists()) {
332 provider = ConfigurationLoader.getHomeDirectory() + "bin" +
333 File.separator + provider;
334 f = new File(provider);
335
336 if (!f.exists()) {
337 log.error("Failed to locate subsystem provider " +
338 obj.getProvider());
339
340 return false;
341 }
342 }
343
344 return onExecuteCommand(provider);
345 }
346 } catch (Exception e) {
347 log.error("Failed to start subsystem " + subsystem, e);
348 }
349
350 return false;
351 }
352
353 /**
354 *
355 *
356 * @return
357 */
358 public byte[] getChannelOpenData() {
359 return null;
360 }
361
362 /**
363 *
364 *
365 * @return
366 */
367 public byte[] getChannelConfirmationData() {
368 return null;
369 }
370
371 /**
372 *
373 *
374 * @return
375 */
376 protected int getMinimumWindowSpace() {
377 return 1024;
378 }
379
380 /**
381 *
382 *
383 * @return
384 */
385 protected int getMaximumWindowSpace() {
386 return 32648;
387 }
388
389 /**
390 *
391 *
392 * @return
393 */
394 protected int getMaximumPacketSize() {
395 return 32648;
396 }
397
398 /**
399 *
400 *
401 * @return
402 */
403 public String getChannelType() {
404 return SESSION_CHANNEL_TYPE;
405 }
406
407 /**
408 *
409 *
410 * @param requestType
411 * @param wantReply
412 * @param requestData
413 *
414 * @throws IOException
415 */
416 protected void onChannelRequest(String requestType, boolean wantReply,
417 byte[] requestData) throws IOException {
418 log.debug("Channel Request received: " + requestType);
419
420 boolean success = false;
421
422 if (requestType.equals("shell")) {
423 success = onStartShell();
424
425 if (success) {
426 if (wantReply) {
427 connection.sendChannelRequestSuccess(this);
428 }
429
430 processInstance.start();
431 processMonitor = new ProcessMonitorThread(processInstance);
432 } else if (wantReply) {
433 connection.sendChannelRequestFailure(this);
434 }
435 }
436
437 if (requestType.equals("env")) {
438 ByteArrayReader bar = new ByteArrayReader(requestData);
439 String name = bar.readString();
440 String value = bar.readString();
441 onSetEnvironmentVariable(name, value);
442
443 if (wantReply) {
444 connection.sendChannelRequestSuccess(this);
445 }
446 }
447
448 if (requestType.equals("exec")) {
449 ByteArrayReader bar = new ByteArrayReader(requestData);
450 String command = bar.readString();
451 success = onExecuteCommand(command);
452
453 if (success) {
454 if (wantReply) {
455 connection.sendChannelRequestSuccess(this);
456 }
457
458 processInstance.start();
459 processMonitor = new ProcessMonitorThread(processInstance);
460 } else if (wantReply) {
461 connection.sendChannelRequestFailure(this);
462 }
463 }
464
465 if (requestType.equals("subsystem")) {
466 ByteArrayReader bar = new ByteArrayReader(requestData);
467 String subsystem = bar.readString();
468 success = onStartSubsystem(subsystem);
469
470 if (success) {
471 if (wantReply) {
472 connection.sendChannelRequestSuccess(this);
473 }
474
475 if (processInstance != null) {
476 processInstance.start();
477 processMonitor = new ProcessMonitorThread(processInstance);
478 } else if (subsystemInstance != null) {
479 subsystemInstance.start();
480 processMonitor = new ProcessMonitorThread(subsystemInstance);
481 }
482 } else if (wantReply) {
483 connection.sendChannelRequestFailure(this);
484 }
485 }
486
487 if (requestType.equals("pty-req")) {
488 ByteArrayReader bar = new ByteArrayReader(requestData);
489 String term = bar.readString();
490 int cols = (int) bar.readInt();
491 int rows = (int) bar.readInt();
492 int width = (int) bar.readInt();
493 int height = (int) bar.readInt();
494 String modes = bar.readString();
495 success = onRequestPseudoTerminal(term, cols, rows, width, height,
496 modes);
497
498 if (wantReply && success) {
499 connection.sendChannelRequestSuccess(this);
500 } else if (wantReply) {
501 connection.sendChannelRequestFailure(this);
502 }
503 }
504
505 if (requestType.equals("window-change")) {
506 ByteArrayReader bar = new ByteArrayReader(requestData);
507 int cols = (int) bar.readInt();
508 int rows = (int) bar.readInt();
509 int width = (int) bar.readInt();
510 int height = (int) bar.readInt();
511 onChangeTerminalDimensions(cols, rows, width, height);
512
513 if (wantReply && success) {
514 connection.sendChannelRequestSuccess(this);
515 } else if (wantReply) {
516 connection.sendChannelRequestFailure(this);
517 }
518 }
519
520 if (requestType.equals("auth-agent-req")) {
521 try {
522 SshThread thread = SshThread.getCurrentThread();
523
524 // Get an agent instance
525 agent = SshAgentForwardingListener.getInstance(thread.getSessionIdString(),
526 connection);
527
528 // Inform the agent we want to track this reference
529 agent.addReference(this);
530
531 // Set the environment so processes can find the agent
532 environment.put("SSH_AGENT_AUTH", agent.getConfiguration());
533
534 // Set a thread property so other services within this server can find it
535 thread.setProperty("sshtools.agent", agent.getConfiguration());
536
537 if (wantReply) {
538 connection.sendChannelRequestSuccess(this);
539 }
540 } catch (Exception ex) {
541 if (wantReply) {
542 connection.sendChannelRequestFailure(this);
543 }
544 }
545 }
546 }
547
548 class ProcessMonitorThread extends Thread {
549 private NativeProcessProvider process;
550 private SubsystemServer subsystem;
551 private StartStopState state;
552
553 public ProcessMonitorThread(NativeProcessProvider process) {
554 this.process = process;
555 state = new StartStopState(StartStopState.STARTED);
556 start();
557 }
558
559 public ProcessMonitorThread(SubsystemServer subsystem) {
560 state = subsystem.getState();
561 }
562
563 public StartStopState getStartStopState() {
564 return state;
565 }
566
567 public void run() {
568 try {
569 log.info("Monitor waiting for process exit code");
570
571 int exitcode = process.waitForExitCode();
572
573 if (exitcode == 9999999) {
574 log.error("Process monitor failed to retrieve exit code");
575 } else {
576 log.debug("Process exit code is " +
577 String.valueOf(exitcode));
578 process.getInputStream().close();
579 process.getOutputStream().close();
580 process.getStderrInputStream().close();
581
582 ByteArrayWriter baw = new ByteArrayWriter();
583 baw.writeInt(exitcode);
584
585 // Send the exit request
586 if (connection.isConnected() &&
587 SessionChannelServer.this.isOpen()) {
588 connection.sendChannelRequest(SessionChannelServer.this,
589 "exit-status", false, baw.toByteArray());
590 }
591
592 // Stop the monitor
593 state.setValue(StartStopState.STOPPED);
594
595 // Close the session
596 SessionChannelServer.this.close();
597 }
598 } catch (IOException ioe) {
599 log.error("Failed to kill process", ioe);
600 }
601 }
602 }
603 }