/* * Copyright 2012 aquenos GmbH. * All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html. */ package com.aquenos.scm.ssh.server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import org.apache.sshd.server.Command; import org.apache.sshd.server.Environment; import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.SessionAware; import org.apache.sshd.server.session.ServerSession; /** * Base class for SSH commands. Handles common tasks like storing the stream * objects, environment and server session and starting and stopping a command * thread. Classes derived from this class should simply implement the * {@link #run()} method. * * @author Sebastian Marsching */ public abstract class AbstractCommand implements Command, SessionAware { private Thread commandThread; private boolean interruptRequested = false; private final Object threadLock = new Object(); private InputStream inputStream; private OutputStream outputStream; private OutputStream errorStream; private Environment environment; private ExitCallback exitCallback; private ServerSession session; @Override public void destroy() { synchronized (threadLock) { if (commandThread == null) { return; } interruptRequested = true; commandThread.interrupt(); } } /** * Returns the stream for command error messages (stderr). * * @return standard error stream or <code>null</code> if no special error * stream is available. */ protected OutputStream getErrorStream() { return this.errorStream; } @Override public void setErrorStream(OutputStream oes) { this.errorStream = oes; } @Override public void setExitCallback(ExitCallback exitCallback) { this.exitCallback = exitCallback; } /** * Returns the stream for command input (stdin). * * @return standard input stream or <code>null</code> if no input stream is * available. */ protected InputStream getInputStream() { return this.inputStream; } @Override public void setInputStream(InputStream is) { this.inputStream = is; } /** * Returns the stream for command output (stdout). * * @return standard output stream or <code>null</code> if no output stream * is available. */ protected OutputStream getOutputStream() { return this.outputStream; } @Override public void setOutputStream(OutputStream os) { this.outputStream = os; } /** * Returns the session in which this command is executed. * * @return server session for this command. */ protected ServerSession getSession() { return this.session; } @Override public void setSession(ServerSession session) { this.session = session; } @Override public void start(Environment environment) throws IOException { synchronized (threadLock) { if (commandThread != null) { throw new IllegalStateException( "Command has already been started."); } Runnable commandRunner = new Runnable() { @Override public void run() { int exitCode = -1; try { exitCode = AbstractCommand.this.run(); } finally { synchronized (threadLock) { commandThread = null; interruptRequested = false; exitCallback.onExit(exitCode); } } } }; this.environment = environment; this.commandThread = new Thread(commandRunner, getThreadName()); this.commandThread.start(); } } /** * Returns the environment in which this command is executed. This is mainly * used to access environment variables. * * @return environment for this command. */ protected Environment getEnvironment() { return this.environment; } /** * Returns the name used for the command thread. This can be overridden by * child-classes in order to provide a more meaningful name. * * @return name for the command thread. */ protected String getThreadName() { return "SSH-Command-Thread"; } /** * Returns true if the command thread should be stopped. This flag is set by * {@link #destroy()}. * * @return <code>true</code> if the command thread shall be stopped, * <code>false</code> otherwise. */ protected boolean isInterruptRequested() { synchronized (threadLock) { return interruptRequested; } } /** * Writes an error message. The message is usually written to stderr. If * stderr is not availble, the message is written to stdout. If stdout is * not available either, the message is discarded and the passed status code * is returned. * * @param statusCode * statusCode to return from this method. * @param message * message to write to stderr or stdout. * @return the <code>statusCode</code> passed to this method. */ protected int errorMessage(int statusCode, String message) { OutputStream os = getErrorStream(); if (os == null) { os = getOutputStream(); } if (os == null) { return statusCode; } try { PrintStream printStream = new PrintStream(os, true, "UTF-8"); printStream.println(message); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unexpected exception: " + e.getMessage(), e); } return statusCode; } /** * Runs the actual command code. This method is called from the command * thread. Child classes should implement their command logic in this * method. * * @return exit code to return to the client. */ protected abstract int run(); }