package org.dcache.services.ssh2;
import jline.TerminalSupport;
import jline.console.ConsoleReader;
import jline.console.history.FileHistory;
import jline.console.history.MemoryHistory;
import jline.console.history.PersistentHistory;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.fusesource.jansi.Ansi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import diskCacheV111.admin.LegacyAdminShell;
import dmg.cells.nucleus.CellEndpoint;
import dmg.cells.nucleus.NoRouteToCellException;
import dmg.cells.nucleus.SerializationException;
import dmg.util.CommandEvaluationException;
import dmg.util.CommandException;
import dmg.util.CommandExitException;
import dmg.util.CommandPanicException;
import dmg.util.CommandSyntaxException;
import dmg.util.command.HelpFormat;
import org.dcache.util.Strings;
import static org.fusesource.jansi.Ansi.Color.CYAN;
import static org.fusesource.jansi.Ansi.Color.RED;
/**
* Implements legacy ssh subsystem of the admin door.
*/
public class LegacyAdminShellCommand implements Command, Runnable
{
private static final Logger _logger =
LoggerFactory.getLogger(LegacyAdminShellCommand.class);
private LegacyAdminShell _shell;
private InputStream _in;
private ExitCallback _exitCallback;
private OutputStream _out;
private Thread _adminShellThread;
private ConsoleReader _console;
private MemoryHistory _history;
private final boolean _useColors;
private final CellEndpoint _endpoint;
private final String _prompt;
public LegacyAdminShellCommand(CellEndpoint endpoint, File historyFile, int historySize, String prompt, boolean useColor)
{
_useColors = useColor;
_endpoint = endpoint;
_prompt = prompt;
if (historyFile != null && (!historyFile.exists() || historyFile.isFile())) {
try {
_history = new FileHistory(historyFile);
_history.setMaxSize(historySize);
} catch (IOException e) {
_logger.warn("History creation failed: " + e.getMessage());
}
}
}
@Override
public void destroy() {
if (_adminShellThread != null) {
_adminShellThread.interrupt();
}
}
@Override
public void setErrorStream(OutputStream err) {
// we don't use the error stream
}
@Override
public void setExitCallback(ExitCallback callback) {
_exitCallback = callback;
}
@Override
public void setInputStream(InputStream in) {
_in = in;
}
@Override
public void setOutputStream(OutputStream out) {
_out = new SshOutputStream(out);
}
@Override
public void start(Environment env) throws IOException {
String user = env.getEnv().get(Environment.ENV_USER);
_shell = new LegacyAdminShell(user, _endpoint, _prompt);
_console = new ConsoleReader(_in, _out, new ConsoleReaderTerminal(env)) {
@Override
public void print(CharSequence s) throws IOException
{
/* See https://github.com/jline/jline2/issues/205 */
getOutput().append(s);
}
};
_adminShellThread = new Thread(this);
_adminShellThread.start();
}
@Override
public void run() {
try {
initAdminShell();
runAsciiMode();
} catch (IOException e) {
_logger.warn(e.getMessage());
} finally {
try {
cleanUp();
} catch (IOException e) {
_logger.warn("Failed to shutdown console cleanly: "
+ e.getMessage());
}
_exitCallback.onExit(0);
}
}
private void initAdminShell() throws IOException {
if (_history != null) {
_console.setHistory(_history);
}
String hello = "";
if (_shell != null) {
_console.addCompleter(_shell);
hello = _shell.getHello();
}
_console.println(hello);
_console.flush();
}
private void runAsciiMode() throws IOException {
Ansi.setEnabled(_console.getTerminal().isAnsiSupported() && _useColors);
while (!_adminShellThread.isInterrupted()) {
String prompt = Ansi.ansi().bold().a(_shell.getPrompt()).boldOff().toString();
String str = _console.readLine(prompt);
Object result;
try {
if (str == null) {
throw new CommandExitException();
}
if (_useColors) {
String trimmed = str.trim();
if (trimmed.startsWith("help ")) {
str = "help -format=" + HelpFormat.ANSI + trimmed.substring(4);
} else if (trimmed.equals("help")) {
str = "help -format=" + HelpFormat.ANSI;
}
}
result = _shell.executeCommand(str);
} catch (IllegalArgumentException e) {
result = e.getMessage()
+ " (Please check the spelling of your command or your config file(s)!)";
} catch (SerializationException e) {
result =
"There is a bug here, please report to support@dcache.org";
_logger.error("This must be a bug, please report to support@dcache.org.", e);
} catch (CommandSyntaxException e) {
result = e;
} catch (CommandEvaluationException e) {
result = e.getMessage();
} catch (CommandExitException e) {
break;
} catch (CommandPanicException e) {
result = "Command '" + str + "' triggered a bug (" + e.getTargetException() +
"); the service log file contains additional information. Please " +
"contact support@dcache.org.";
} catch (CommandException e) {
result = e.getMessage();
} catch (NoRouteToCellException e) {
result =
"Cell name does not exist or cell is not started: "
+ e.getMessage();
_logger.warn("The cell the command was sent to is no "
+ "longer there: {}", e.getMessage());
} catch (InterruptedException e) {
result = e.getMessage();
} catch (RuntimeException e) {
result = String.format("Command '%s' triggered a bug (%s); please" +
" locate this message in the log file of the admin service and" +
" send an email to support@dcache.org with this line and the" +
" following stack-trace", str, e);
_logger.error((String) result, e);
} catch (Exception e) {
result = e.getMessage();
if(result == null) {
result = e.getClass().getSimpleName() + ": (null)";
}
}
if (result != null) {
String s;
if (result instanceof CommandSyntaxException) {
CommandSyntaxException e = (CommandSyntaxException) result;
Ansi sb = Ansi.ansi();
sb.fg(RED).a("Syntax error: ").a(e.getMessage()).newline();
String help = e.getHelpText();
if (help != null) {
sb.fg(CYAN);
sb.a("Help : ").newline();
sb.a(help);
}
s = sb.reset().toString();
} else {
s = Strings.toMultilineString(result);
}
if (!s.isEmpty()) {
_console.println(s);
_console.flush();
}
}
}
}
private void cleanUp() throws IOException {
if (_history instanceof PersistentHistory) {
((PersistentHistory) _history).flush();
}
_console.println();
_console.flush();
}
private static class ConsoleReaderTerminal extends TerminalSupport
{
private final Environment _env;
private ConsoleReaderTerminal(Environment env)
{
super(true);
_env = env;
setAnsiSupported(env.getEnv().get(Environment.ENV_TERM) != null);
setEchoEnabled(false);
}
@Override
public int getHeight() {
String h = _env.getEnv().get(Environment.ENV_LINES);
if (h != null) {
try {
/* The SSH client may report 0 if forced to allocate a pseudo TTY
* even when it got no local TTY.
*/
int i = Integer.parseInt(h);
if (i > 0) {
return i;
}
} catch(NumberFormatException ignored) {
}
}
return Integer.MAX_VALUE;
}
@Override
public int getWidth() {
String w = _env.getEnv().get(Environment.ENV_COLUMNS);
if (w != null) {
try {
/* The SSH client may report 0 if forced to allocate a pseudo TTY
* even when it got no local TTY.
*/
int i = Integer.parseInt(w);
if (i > 0) {
return i;
}
} catch(NumberFormatException ignored) {
}
}
return Integer.MAX_VALUE;
}
}
private static class SshOutputStream extends FilterOutputStream
{
public SshOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int c) throws IOException {
if (c == '\n') {
super.write(0xa);
super.write(0xd);
} else {
super.write(c);
}
}
}
}