diff options
Diffstat (limited to 'spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote')
3 files changed, 204 insertions, 0 deletions
diff --git a/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/AbstractRemoteInterface.java b/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/AbstractRemoteInterface.java new file mode 100644 index 0000000..1c03aa8 --- /dev/null +++ b/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/AbstractRemoteInterface.java @@ -0,0 +1,98 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.standalone.remote; + +import me.lucko.spark.common.command.CommandResponseHandler; +import me.lucko.spark.standalone.StandaloneCommandSender; +import me.lucko.spark.standalone.StandaloneSparkPlugin; +import net.kyori.adventure.text.Component; +import org.jline.reader.Candidate; +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.UserInterruptException; +import org.jline.terminal.Terminal; +import org.jline.terminal.impl.AbstractTerminal; + +public abstract class AbstractRemoteInterface implements RemoteInterface { + + protected final StandaloneSparkPlugin spark; + + public AbstractRemoteInterface(StandaloneSparkPlugin spark) { + this.spark = spark; + } + + private static String stripSlashSpark(String command) { + if (command.startsWith("/")) { + command = command.substring(1); + } + if (command.startsWith("spark ")) { + command = command.substring(6); + } + return command; + } + + public void processSession(Terminal terminal, Runnable closer) { + LineReader reader = LineReaderBuilder.builder() + .terminal(terminal) + .completer((lineReader, parsedLine, list) -> { + String command = stripSlashSpark(parsedLine.line()); + String[] args = command.split(" ", -1); + for (String suggestion : this.spark.suggest(args, StandaloneCommandSender.NO_OP)) { + list.add(new Candidate(suggestion)); + } + }) + .build(); + + StandaloneCommandSender sender = new StandaloneCommandSender(reader::printAbove); + + this.spark.addSender(sender); + ((AbstractTerminal) terminal).setOnClose(() -> this.spark.removeSender(sender)); + + CommandResponseHandler resp = this.spark.createResponseHandler(sender); + resp.replyPrefixed(Component.text("spark remote interface - " + this.spark.getVersion())); + resp.replyPrefixed(Component.text("Use '/spark' commands as usual, or run 'exit' to exit.")); + + while (true) { + try { + String line = reader.readLine("> "); + if (line.trim().isEmpty()) { + continue; + } + + String command = stripSlashSpark(line); + if (command.equals("exit")) { + closer.run(); + return; + } + + this.spark.execute(command.split(" ", 0), sender); + + } catch (UserInterruptException e) { + // ignore + } catch (EndOfFileException e) { + this.spark.removeSender(sender); + return; + } + } + } + +} diff --git a/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/RemoteInterface.java b/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/RemoteInterface.java new file mode 100644 index 0000000..ce6a8dc --- /dev/null +++ b/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/RemoteInterface.java @@ -0,0 +1,28 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.standalone.remote; + +public interface RemoteInterface extends AutoCloseable { + + @Override + void close(); + +} diff --git a/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/SshRemoteInterface.java b/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/SshRemoteInterface.java new file mode 100644 index 0000000..025dadb --- /dev/null +++ b/spark-standalone-agent/src/main/java/me/lucko/spark/standalone/remote/SshRemoteInterface.java @@ -0,0 +1,78 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.standalone.remote; + +import me.lucko.spark.standalone.StandaloneSparkPlugin; +import org.apache.sshd.server.SshServer; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.jline.builtins.ssh.ShellFactoryImpl; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.logging.Level; + +public class SshRemoteInterface extends AbstractRemoteInterface { + private final String password; + private final SshServer sshd; + + public SshRemoteInterface(StandaloneSparkPlugin spark, int port) { + super(spark); + this.password = new SecureRandom().ints(48, 122) + .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) + .limit(32) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + + this.sshd = SshServer.setUpDefaultServer(); + if (port > 0) { + this.sshd.setPort(port); + } + this.sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider()); + this.sshd.setPasswordAuthenticator((username, password, session) -> "spark".equals(username) && MessageDigest.isEqual(this.password.getBytes(), password.getBytes())); + this.sshd.setShellFactory(new ShellFactoryImpl(shellParams -> this.processSession(shellParams.getTerminal(), shellParams.getCloser()))); + + new Thread(() -> { + try { + this.start(); + } catch (IOException e) { + this.spark.log(Level.SEVERE, "Error whilst starting SSH server", e); + } + }, "spark-ssh-server").start(); + } + + private void start() throws IOException { + this.sshd.start(); + this.spark.log(Level.INFO, "SSH Server started on port " + this.sshd.getPort()); + this.spark.log(Level.INFO, "Connect using: ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p " + this.sshd.getPort() + " spark@localhost"); + this.spark.log(Level.INFO, "When prompted, enter the password: " + this.password); + } + + @Override + public void close() { + try { + this.sshd.stop(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +}
\ No newline at end of file |