blob: 91c40e7e5748a30d6dae298feb85042be827c1d3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.gogo.jline.ssh;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Map;
import org.apache.felix.gogo.jline.Shell;
import org.apache.felix.gogo.jline.Shell.Context;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.CommandSession;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.channel.PtyMode;
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.Signal;
import org.apache.sshd.server.session.ServerSession;
import org.jline.terminal.Attributes;
import org.jline.terminal.Attributes.ControlChar;
import org.jline.terminal.Attributes.InputFlag;
import org.jline.terminal.Attributes.LocalFlag;
import org.jline.terminal.Attributes.OutputFlag;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
/**
* SSHD {@link org.apache.sshd.server.Command} factory which provides access to
* Shell.
*/
public class ShellFactoryImpl implements Factory<Command> {
private final CommandProcessor processor;
public ShellFactoryImpl(CommandProcessor processor) {
this.processor = processor;
}
private static void flush(OutputStream... streams) {
for (OutputStream s : streams) {
try {
s.flush();
} catch (IOException e) {
// Ignore
}
}
}
static void close(Closeable... closeables) {
for (Closeable c : closeables) {
try {
c.close();
} catch (IOException e) {
// Ignore
}
}
}
public Command create() {
return new ShellImpl();
}
public class ShellImpl implements Command, SessionAware {
private InputStream in;
private OutputStream out;
private OutputStream err;
private ExitCallback callback;
@SuppressWarnings("unused")
private ServerSession session;
private boolean closed;
public void setInputStream(final InputStream in) {
this.in = in;
}
public void setOutputStream(final OutputStream out) {
this.out = out;
}
public void setErrorStream(final OutputStream err) {
this.err = err;
}
public void setExitCallback(ExitCallback callback) {
this.callback = callback;
}
public void setSession(ServerSession session) {
this.session = session;
}
public void start(final Environment env) throws IOException {
try {
new Thread(() -> {
try {
ShellImpl.this.run(env);
} catch (Throwable t) {
t.printStackTrace();
}
}).start();
} catch (Exception e) {
throw (IOException) new IOException("Unable to start shell", e);
}
}
public void run(Environment env) {
try {
Terminal terminal = TerminalBuilder.builder()
.name("gogo")
.type(env.getEnv().get("TERM"))
.system(false)
.streams(in, out)
.build();
terminal.setSize(new Size(Integer.parseInt(env.getEnv().get("COLUMNS")),
Integer.parseInt(env.getEnv().get("LINES"))));
Attributes attr = terminal.getAttributes();
for (Map.Entry<PtyMode, Integer> e : env.getPtyModes().entrySet()) {
switch (e.getKey()) {
case VINTR:
attr.setControlChar(ControlChar.VINTR, e.getValue());
break;
case VQUIT:
attr.setControlChar(ControlChar.VQUIT, e.getValue());
break;
case VERASE:
attr.setControlChar(ControlChar.VERASE, e.getValue());
break;
case VKILL:
attr.setControlChar(ControlChar.VKILL, e.getValue());
break;
case VEOF:
attr.setControlChar(ControlChar.VEOF, e.getValue());
break;
case VEOL:
attr.setControlChar(ControlChar.VEOL, e.getValue());
break;
case VEOL2:
attr.setControlChar(ControlChar.VEOL2, e.getValue());
break;
case VSTART:
attr.setControlChar(ControlChar.VSTART, e.getValue());
break;
case VSTOP:
attr.setControlChar(ControlChar.VSTOP, e.getValue());
break;
case VSUSP:
attr.setControlChar(ControlChar.VSUSP, e.getValue());
break;
case VDSUSP:
attr.setControlChar(ControlChar.VDSUSP, e.getValue());
break;
case VREPRINT:
attr.setControlChar(ControlChar.VREPRINT, e.getValue());
break;
case VWERASE:
attr.setControlChar(ControlChar.VWERASE, e.getValue());
break;
case VLNEXT:
attr.setControlChar(ControlChar.VLNEXT, e.getValue());
break;
/*
case VFLUSH:
attr.setControlChar(ControlChar.VMIN, e.getValue());
break;
case VSWTCH:
attr.setControlChar(ControlChar.VTIME, e.getValue());
break;
*/
case VSTATUS:
attr.setControlChar(ControlChar.VSTATUS, e.getValue());
break;
case VDISCARD:
attr.setControlChar(ControlChar.VDISCARD, e.getValue());
break;
case ECHO:
attr.setLocalFlag(LocalFlag.ECHO, e.getValue() != 0);
break;
case ICANON:
attr.setLocalFlag(LocalFlag.ICANON, e.getValue() != 0);
break;
case ISIG:
attr.setLocalFlag(LocalFlag.ISIG, e.getValue() != 0);
break;
case ICRNL:
attr.setInputFlag(InputFlag.ICRNL, e.getValue() != 0);
break;
case INLCR:
attr.setInputFlag(InputFlag.INLCR, e.getValue() != 0);
break;
case IGNCR:
attr.setInputFlag(InputFlag.IGNCR, e.getValue() != 0);
break;
case OCRNL:
attr.setOutputFlag(OutputFlag.OCRNL, e.getValue() != 0);
break;
case ONLCR:
attr.setOutputFlag(OutputFlag.ONLCR, e.getValue() != 0);
break;
case ONLRET:
attr.setOutputFlag(OutputFlag.ONLRET, e.getValue() != 0);
break;
case OPOST:
attr.setOutputFlag(OutputFlag.OPOST, e.getValue() != 0);
break;
default:
}
}
terminal.setAttributes(attr);
PrintStream pout = new PrintStream(terminal.output());
final CommandSession session = processor.createSession(terminal.input(), pout, pout);
session.put(Shell.VAR_TERMINAL, terminal);
for (Map.Entry<String, String> e : env.getEnv().entrySet()) {
session.put(e.getKey(), e.getValue());
}
env.addSignalListener(signals -> {
terminal.setSize(new Size(Integer.parseInt(env.getEnv().get("COLUMNS")),
Integer.parseInt(env.getEnv().get("LINES"))));
terminal.raise(Terminal.Signal.WINCH);
}, Signal.WINCH);
Context context = new Context() {
@Override
public String getProperty(String name) {
return System.getProperty(name);
}
@Override
public void exit() {
destroy();
}
};
new Shell(context, processor).gosh(session, new String[]{"--login"});
} catch (Throwable t) {
t.printStackTrace();
}
}
public void destroy() {
if (!closed) {
closed = true;
ShellFactoryImpl.flush(out, err);
ShellFactoryImpl.close(in, out, err);
callback.onExit(0);
}
}
}
}