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