Compare commits
4 Commits
bungeecord
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c93b9fc0e | ||
|
|
170a96eb94 | ||
|
|
ec1ed8cb9e | ||
|
|
67146ce589 |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
Language: 'Java'
|
||||
#BasedOnStyle: WebKit
|
||||
TabWidth: '4'
|
||||
IndentWidth: '4'
|
||||
UseTab: 'Always'
|
||||
@@ -7,7 +7,6 @@ AlignOperands: 'true'
|
||||
AlignAfterOpenBracket: 'Align'
|
||||
AlignConsecutiveAssignments: 'true'
|
||||
AlignConsecutiveDeclarations: 'true'
|
||||
AlignConsecutiveMacros: 'true'
|
||||
AlignEscapedNewlines: 'Left'
|
||||
AlignTrailingComments: 'true'
|
||||
AllowAllParametersOfDeclarationOnNextLine: 'true'
|
||||
@@ -21,20 +20,35 @@ AlwaysBreakTemplateDeclarations: 'true'
|
||||
AlwaysBreakBeforeMultilineStrings: 'false'
|
||||
BinPackArguments: 'false'
|
||||
BinPackParameters: 'false'
|
||||
BreakBeforeBraces: 'Allman'
|
||||
BreakBeforeBraces: 'Custom'
|
||||
BraceWrapping:
|
||||
AfterEnum: 'true'
|
||||
AfterClass: 'true'
|
||||
AfterControlStatement: 'true'
|
||||
AfterStruct: 'true'
|
||||
AfterFunction: 'true'
|
||||
AfterNamespace: 'true'
|
||||
AfterUnion: 'true'
|
||||
AfterExternBlock: 'true'
|
||||
BeforeCatch: 'true'
|
||||
BeforeElse: 'true'
|
||||
SplitEmptyRecord: 'false'
|
||||
SplitEmptyNamespace: 'false'
|
||||
SplitEmptyFunction: 'false'
|
||||
BreakBeforeBinaryOperators: 'true'
|
||||
BreakBeforeTernaryOperators: 'false'
|
||||
BreakConstructorInitializers: 'AfterColon'
|
||||
BreakConstructorInitializersBeforeComma: 'false'
|
||||
BreakBeforeInheritanceComma: 'false'
|
||||
BreakAfterJavaFieldAnnotations: 'true'
|
||||
BreakStringLiterals: 'true'
|
||||
ColumnLimit: '140'
|
||||
CompactNamespaces: 'false'
|
||||
Cpp11BracedListStyle: 'true'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
|
||||
DerivePointerAlignment: 'false'
|
||||
IndentCaseLabels: 'true'
|
||||
IndentPPDirectives: 'AfterHash'
|
||||
KeepEmptyLinesAtTheStartOfBlocks: 'true'
|
||||
Language: 'Java'
|
||||
NamespaceIndentation: 'All'
|
||||
PointerAlignment: 'Right'
|
||||
ReflowComments: 'true'
|
||||
@@ -1,5 +1,5 @@
|
||||
Minecraft-SSHD (BungeeCord Edition!)
|
||||
=================================
|
||||
Minecraft-SSHD
|
||||
===========
|
||||
|
||||
[](https://travis-ci.org/Justasic/Minecraft-SSHD)
|
||||
[](https://github.com/Justasic/Minecraft-SSHD/releases/latest)
|
||||
@@ -17,7 +17,7 @@ Minecraft-SSHD securely exposes your BungeeCord admin console and the server fil
|
||||
- Supports multiple concurrent remote connections.
|
||||
- Strong identity support using public key authentication.
|
||||
- Audit history who is running commands in the console
|
||||
- Run BungeeCord without using screen or tmux (by adding `-noconsole`)
|
||||
- Run Spigot without using screen or tmux (by adding `-noconsole`)
|
||||
- Remotely script your server by issuing one-off console commands with ssh.
|
||||
|
||||
### Why should I use Minecraft-SSHD?
|
||||
@@ -40,7 +40,7 @@ Screenshots
|
||||
Setting Up Public Key Authentication
|
||||
====================================
|
||||
|
||||
Setting up public key authentication with SSH requires first generating a public and private key pair and then installing just the public key on your BungeeCord server. This plugin supports all modern SSH key algoritms as OpenSSH. You can paste as many public keys from the methods below into each user's authorization file if they have multiple private keys. You can read [this guide from ssh.com](https://www.ssh.com/ssh/keygen/) if you want a better explanation on different key files.
|
||||
Setting up public key authentication with SSH requires first generating a public and private key pair and then installing just the public key on your Spigot server. This plugin supports all modern SSH key algoritms as OpenSSH. You can paste as many public keys from the methods below into each user's authorization file if they have multiple private keys. You can read [this guide from ssh.com](https://www.ssh.com/ssh/keygen/) if you want a better explanation on different key files.
|
||||
|
||||
## Generating New Keys
|
||||
|
||||
|
||||
30
pom.xml
30
pom.xml
@@ -5,7 +5,8 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.ryanmichela</groupId>
|
||||
<artifactId>sshd-bungee</artifactId>
|
||||
<artifactId>sshd</artifactId>
|
||||
<description>Minecraft-SSHD: The SSH daemon for Minecraft servers.</description>
|
||||
<version>2.0.0</version>
|
||||
<url>https://github.com/Justasic/Minecraft-SSHD/</url>
|
||||
|
||||
@@ -33,10 +34,9 @@
|
||||
<!-- Dependencies -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.14-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
<version>1.14.4-R0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -72,7 +72,7 @@
|
||||
<artifactId>eddsa</artifactId>
|
||||
<version>0.3.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.mina</groupId>
|
||||
<artifactId>mina-core</artifactId>
|
||||
@@ -84,7 +84,7 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.28</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
@@ -101,12 +101,14 @@
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>2.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -115,18 +117,6 @@
|
||||
<version>1.10</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- Build -->
|
||||
@@ -166,7 +156,7 @@
|
||||
</descriptorRefs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
<!-- Compile plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
||||
@@ -19,8 +19,8 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
|
||||
public boolean authenticate(String username, String password, ServerSession ss)
|
||||
{
|
||||
// Depending on our hash type, we have to try and figure out what we're doing.
|
||||
String HashType = SshdPlugin.instance.configuration.getString("PasswordType");
|
||||
String ConfigHash = SshdPlugin.instance.configuration.getString("Credentials." + username.trim());
|
||||
String HashType = SshdPlugin.instance.getConfig().getString("PasswordType");
|
||||
String ConfigHash = SshdPlugin.instance.getConfig().getString("Credentials." + username.trim());
|
||||
|
||||
if (ConfigHash == null)
|
||||
SshdPlugin.instance.getLogger().warning("Config has no such user: " + username);
|
||||
@@ -70,7 +70,7 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
|
||||
}
|
||||
|
||||
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using " + HashType + "-based password authentication.");
|
||||
Integer tries = SshdPlugin.instance.configuration.getInt("LoginRetries");
|
||||
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries");
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -3,60 +3,42 @@ package com.ryanmichela.sshd;
|
||||
/**
|
||||
* Copyright 2013 Ryan Michela
|
||||
*/
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
import jline.console.completer.Completer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandMap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ConsoleCommandCompleter implements Completer
|
||||
{
|
||||
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates)
|
||||
{
|
||||
Waitable<List<String>> waitable = new Waitable<List<String>>()
|
||||
{
|
||||
public class ConsoleCommandCompleter implements Completer {
|
||||
|
||||
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
|
||||
Waitable<List<String>> waitable = new Waitable<List<String>>() {
|
||||
@Override
|
||||
protected List<String> evaluate()
|
||||
{
|
||||
List<String> tabcomplete = new ArrayList<String>();
|
||||
for (Map.Entry<String, Command> map : SshdPlugin.instance.getProxy().getPluginManager().getCommands())
|
||||
{
|
||||
String cmd = map.getKey();
|
||||
Command value = map.getValue();
|
||||
|
||||
if (cmd.startsWith(buffer))
|
||||
tabcomplete.add(cmd);
|
||||
}
|
||||
|
||||
return tabcomplete;
|
||||
protected List<String> evaluate() {
|
||||
CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap");
|
||||
return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer);
|
||||
}
|
||||
};
|
||||
|
||||
SshdPlugin.instance.getProxy().getScheduler().runAsync(SshdPlugin.instance, waitable);
|
||||
try
|
||||
{
|
||||
Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable);
|
||||
try {
|
||||
List<String> offers = waitable.get();
|
||||
if (offers == null)
|
||||
if (offers == null) {
|
||||
return cursor;
|
||||
|
||||
}
|
||||
candidates.addAll(offers);
|
||||
|
||||
final int lastSpace = buffer.lastIndexOf(' ');
|
||||
if (lastSpace == -1)
|
||||
if (lastSpace == -1) {
|
||||
return cursor - buffer.length();
|
||||
else
|
||||
} else {
|
||||
return cursor - (buffer.length() - lastSpace - 1);
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return cursor;
|
||||
|
||||
@@ -5,10 +5,7 @@ import org.apache.sshd.server.command.CommandFactory;
|
||||
import org.apache.sshd.server.channel.ChannelSession;
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.apache.sshd.server.ExitCallback;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -54,22 +51,14 @@ public class ConsoleCommandFactory implements CommandFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ChannelSession cs, Environment environment) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
public void start(ChannelSession cs, Environment environment) throws IOException {
|
||||
try {
|
||||
SshdPlugin.instance.getLogger()
|
||||
.info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command);
|
||||
|
||||
if (!SshdPlugin.instance.getProxy().getPluginManager().dispatchCommand(SshdPlugin.instance.getProxy().getConsole(), command))
|
||||
SshdPlugin.instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||
} catch (Exception e) {
|
||||
SshdPlugin.instance.getLogger().severe("Error processing command from SSH -" + e.getMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
} finally {
|
||||
callback.onExit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ package com.ryanmichela.sshd;
|
||||
* Copyright 2013 Ryan Michela
|
||||
*/
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.fusesource.jansi.Ansi;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -15,13 +15,12 @@ import java.util.Map;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
public class ConsoleLogFormatter extends Formatter
|
||||
{
|
||||
public class ConsoleLogFormatter extends Formatter {
|
||||
|
||||
private SimpleDateFormat dateFormat;
|
||||
private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
|
||||
|
||||
public ConsoleLogFormatter()
|
||||
{
|
||||
public ConsoleLogFormatter() {
|
||||
this.dateFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@ import com.ryanmichela.sshd.FlushyOutputStream;
|
||||
import com.ryanmichela.sshd.FlushyStreamHandler;
|
||||
import com.ryanmichela.sshd.SshTerminal;
|
||||
import com.ryanmichela.sshd.SshdPlugin;
|
||||
import com.ryanmichela.sshd.StreamHandlerAppender;
|
||||
import com.ryanmichela.sshd.implementations.SSHDCommandSender;
|
||||
import com.ryanmichela.sshd.ConsoleLogFormatter;
|
||||
import jline.console.ConsoleReader;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.Logger;
|
||||
import org.apache.sshd.common.Factory;
|
||||
@@ -20,6 +18,7 @@ import org.apache.sshd.server.command.Command;
|
||||
import org.apache.sshd.server.channel.ChannelSession;
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.apache.sshd.server.ExitCallback;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -33,8 +32,6 @@ import java.util.StringTokenizer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.StreamHandler;
|
||||
|
||||
import static com.ryanmichela.sshd.SshdPlugin.instance;
|
||||
|
||||
public class ConsoleShellFactory implements ShellFactory {
|
||||
|
||||
public Command createShell(ChannelSession cs) {
|
||||
@@ -51,7 +48,7 @@ public class ConsoleShellFactory implements ShellFactory {
|
||||
private Thread thread;
|
||||
private String Username;
|
||||
|
||||
StreamHandler streamHandler;
|
||||
StreamHandlerAppender streamHandlerAppender;
|
||||
public ConsoleReader ConsoleReader;
|
||||
public SSHDCommandSender SshdCommandSender;
|
||||
|
||||
@@ -96,9 +93,10 @@ public class ConsoleShellFactory implements ShellFactory {
|
||||
this.ConsoleReader.setExpandEvents(true);
|
||||
this.ConsoleReader.addCompleter(new ConsoleCommandCompleter());
|
||||
|
||||
streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader);
|
||||
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader);
|
||||
this.streamHandlerAppender = new StreamHandlerAppender(streamHandler);
|
||||
|
||||
SshdPlugin.instance.getProxy().getLogger().addHandler(this.streamHandler);
|
||||
((Logger)LogManager.getRootLogger()).addAppender(this.streamHandlerAppender);
|
||||
|
||||
this.environment = env;
|
||||
this.Username = env.getEnv().get(Environment.ENV_USER);
|
||||
@@ -109,23 +107,22 @@ public class ConsoleShellFactory implements ShellFactory {
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
throw new IOException("Error starting shell", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(ChannelSession cs) { SshdPlugin.instance.getProxy().getLogger().removeHandler(this.streamHandler); }
|
||||
public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(this.streamHandlerAppender); }
|
||||
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!instance.configuration.getString("Mode").equals("RPC"))
|
||||
if (!SshdPlugin.instance.getConfig().getString("Mode").equals("RPC"))
|
||||
printPreamble(this.ConsoleReader);
|
||||
while (true)
|
||||
{
|
||||
String command = this.ConsoleReader.readLine("\r> ", null);
|
||||
String command = this.ConsoleReader.readLine("\r>", null);
|
||||
// The user sent CTRL+D to close the shell, terminate the session.
|
||||
if (command == null)
|
||||
break;
|
||||
@@ -139,47 +136,54 @@ public class ConsoleShellFactory implements ShellFactory {
|
||||
if (command.equals("cls"))
|
||||
{
|
||||
this.ConsoleReader.clearScreen();
|
||||
this.ConsoleReader.drawLine();
|
||||
this.ConsoleReader.flush();
|
||||
continue;
|
||||
}
|
||||
// Hide the mkpasswd command input from other users.
|
||||
Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd");
|
||||
|
||||
instance.getProxy().getScheduler().runAsync(
|
||||
instance, () ->
|
||||
Bukkit.getScheduler().runTask(
|
||||
SshdPlugin.instance, () ->
|
||||
{
|
||||
if (instance.configuration.getString("Mode").equals("RPC") && command.startsWith("rpc"))
|
||||
if (SshdPlugin.instance.getConfig().getString("Mode").equals("RPC") && command.startsWith("rpc"))
|
||||
{
|
||||
// NO ECHO NO PREAMBLE AND SHIT
|
||||
String cmd = command.substring("rpc".length() + 1, command.length());
|
||||
if (!instance.getProxy().getPluginManager().dispatchCommand(this.SshdCommandSender, cmd))
|
||||
instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
|
||||
Bukkit.dispatchCommand(this.SshdCommandSender, cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't send our mkpasswd command output. This will echo passwords back
|
||||
// to the console for all to see. This command is strictly between
|
||||
// our plugin and the connected client.
|
||||
if (!mkpasswd)
|
||||
instance.getLogger().info("<" + this.Username + "> " + command);
|
||||
|
||||
if (!instance.getProxy().getPluginManager().dispatchCommand(this.SshdCommandSender, command))
|
||||
instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
|
||||
{
|
||||
SshdPlugin.instance.getLogger().info("<" + this.Username + "> " + command);
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||
}
|
||||
else
|
||||
{
|
||||
Bukkit.dispatchCommand(this.SshdCommandSender, command);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
|
||||
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH.");
|
||||
SshdPlugin.instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH.");
|
||||
callback.onExit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void printPreamble(ConsoleReader cr) throws IOException
|
||||
{
|
||||
File f = new File(instance.getDataFolder(), "motd.txt");
|
||||
File f = new File(SshdPlugin.instance.getDataFolder(), "motd.txt");
|
||||
try
|
||||
{
|
||||
BufferedReader br = new BufferedReader(new FileReader(f));
|
||||
@@ -190,18 +194,16 @@ public class ConsoleShellFactory implements ShellFactory {
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
instance.getLogger().log(Level.WARNING, "Could not open " + f + ": File does not exist.");
|
||||
SshdPlugin.instance.getLogger().log(Level.WARNING, "Could not open " + f + ": File does not exist.");
|
||||
// Not showing the SSH motd is not a fatal failure, let the session continue.
|
||||
}
|
||||
|
||||
// Doesn't really guarantee our actual system hostname but
|
||||
// it's better than not having one at all.
|
||||
cr.println("Connected to: " + InetAddress.getLocalHost().getHostName() + " (BungeeCord)\r");
|
||||
// Since BungeeCord is stupid, we have to parse the config file and the the MOTD from it that way...
|
||||
// If you try to use the method getMotd() it returns that it can't be referenced from a non-static context, which is stupid.
|
||||
cr.println(ConsoleLogFormatter.ColorizeString(instance.configuration.getString("motd")).replaceAll("\n", "\r\n"));
|
||||
cr.println("Connected to: " + InetAddress.getLocalHost().getHostName() + " (" + Bukkit.getServer().getName() + ")\r");
|
||||
cr.println(ConsoleLogFormatter.ColorizeString(Bukkit.getServer().getMotd()).replaceAll("\n", "\r\n"));
|
||||
cr.println("\r");
|
||||
cr.println("Type 'exit' or press Ctrl+D exit the shell." + "\r");
|
||||
cr.println("Type 'exit' to exit the shell." + "\r");
|
||||
cr.println("===============================================" + "\r");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.ryanmichela.sshd;
|
||||
|
||||
import jline.console.ConsoleReader;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.channel.exception.SshChannelClosedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -11,50 +10,39 @@ import java.util.logging.*;
|
||||
/**
|
||||
* Copyright 2013 Ryan Michela
|
||||
*/
|
||||
public class FlushyStreamHandler extends StreamHandler
|
||||
{
|
||||
public class FlushyStreamHandler extends StreamHandler {
|
||||
|
||||
private ConsoleReader reader;
|
||||
|
||||
public FlushyStreamHandler(OutputStream out, Formatter formatter, ConsoleReader reader)
|
||||
{
|
||||
public FlushyStreamHandler(OutputStream out, Formatter formatter, ConsoleReader reader) {
|
||||
super(out, formatter);
|
||||
this.reader = reader;
|
||||
setLevel(Level.INFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void publish(LogRecord record)
|
||||
{
|
||||
public synchronized void publish(LogRecord record) {
|
||||
record.setMessage(record.getMessage().replace("\n", "\n\r"));
|
||||
super.publish(record);
|
||||
flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void flush()
|
||||
{
|
||||
try
|
||||
{
|
||||
public synchronized void flush() {
|
||||
try {
|
||||
reader.print(ConsoleReader.RESET_LINE + "");
|
||||
reader.flush();
|
||||
super.flush();
|
||||
try
|
||||
{
|
||||
try {
|
||||
reader.drawLine();
|
||||
}
|
||||
catch (Throwable ex)
|
||||
{
|
||||
} catch (Throwable ex) {
|
||||
reader.getCursorBuffer().clear();
|
||||
}
|
||||
reader.flush();
|
||||
super.flush();
|
||||
}
|
||||
catch (SshChannelClosedException ex)
|
||||
{
|
||||
} catch (SshException ex) {
|
||||
// do nothing
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
package com.ryanmichela.sshd;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.ryanmichela.sshd.Cryptography;
|
||||
import com.ryanmichela.sshd.SshdPlugin;
|
||||
|
||||
public class MkpasswdCommand extends Command
|
||||
class MkpasswdCommand implements CommandExecutor
|
||||
{
|
||||
public MkpasswdCommand()
|
||||
{
|
||||
super("mkpasswd");
|
||||
}
|
||||
|
||||
public void SendSyntax(CommandSender sender, boolean invalid)
|
||||
// Because Spigot's failed syntax API is really less than ideal (you should be required to add a
|
||||
// SendSyntax function override), we're just always going to return true even for syntax failures
|
||||
// as we will handle the syntax message internally. This also lets us send the messages more
|
||||
// securely to the client without people knowing we're using the command. This prevents password
|
||||
// or hash leakages from the user to other connected users. Plus this syntax will show how
|
||||
// to both use the command and what hashes we support which is important for people who don't
|
||||
// know how to RTFM. - Justin
|
||||
private void SendSyntax(CommandSender sender, boolean invalid)
|
||||
{
|
||||
if (invalid)
|
||||
sender.sendMessage(new ComponentBuilder("Invalid Syntax").color(ChatColor.RED).create());
|
||||
sender.sendMessage(new ComponentBuilder("/mkpasswd <help|hash> <password>").color(ChatColor.GREEN).create());
|
||||
sender.sendMessage(new ComponentBuilder("Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN").color(ChatColor.BLUE).create());
|
||||
sender.sendMessage("\u00A7cInvalid Syntax\u00A7r");
|
||||
sender.sendMessage("\u00A7a/mkpasswd <help|hash> <password>\u00A7r");
|
||||
sender.sendMessage("\u00A79Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN\u00A7r");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
|
||||
{
|
||||
// If we're not mkpasswd, just fuck off.
|
||||
if (!label.equalsIgnoreCase("mkpasswd"))
|
||||
return false;
|
||||
|
||||
String algoritm, password;
|
||||
try
|
||||
{
|
||||
@@ -36,18 +41,17 @@ public class MkpasswdCommand extends Command
|
||||
// spaces in their passwords otherwise it won't be as strong as it should be.
|
||||
algoritm = args[0];
|
||||
password = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||
if (password.length() == 0)
|
||||
throw new ArrayIndexOutOfBoundsException(); // shortcut
|
||||
if (password.trim().isEmpty()) // Shortcut to the catch statement below.
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException e)
|
||||
{
|
||||
this.SendSyntax(sender, false);
|
||||
return;
|
||||
// ignore it.
|
||||
this.SendSyntax(sender, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If they're a player, check and make sure they have a permission
|
||||
// If they're not a player (aka, the console), just return true.
|
||||
boolean hasperm = (sender instanceof ProxiedPlayer) ? ((ProxiedPlayer)sender).hasPermission("sshd.mkpasswd") : true;
|
||||
boolean hasperm = (sender instanceof Player) ? ((Player)sender).hasPermission("sshd.mkpasswd") : true;
|
||||
|
||||
if (hasperm)
|
||||
{
|
||||
@@ -58,8 +62,8 @@ public class MkpasswdCommand extends Command
|
||||
if (algoritm.equalsIgnoreCase("PLAIN"))
|
||||
{
|
||||
// I mean c'mon...
|
||||
sender.sendMessage("\u00A79Your Hash: \u00A7cIt's literally your unhashed password.");
|
||||
return;
|
||||
sender.sendMessage("Bro really? it's literally your unencrypted password...");
|
||||
return true;
|
||||
}
|
||||
else if (algoritm.equalsIgnoreCase("pbkdf2"))
|
||||
hash = Cryptography.PBKDF2_HashPassword(password);
|
||||
@@ -70,16 +74,19 @@ public class MkpasswdCommand extends Command
|
||||
else
|
||||
{
|
||||
this.SendSyntax(sender, !algoritm.equalsIgnoreCase("help"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(new ComponentBuilder("Your Hash: " + hash).color(ChatColor.BLUE).create());
|
||||
sender.sendMessage("\u00A79Your Hash: " + hash + "\u00A7r");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// We're console, just print the stack trace.
|
||||
e.printStackTrace();
|
||||
sender.sendMessage("\u00A7cAn error occured. Please check console for details.\u00A7r");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.ryanmichela.sshd;
|
||||
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
|
||||
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
|
||||
@@ -21,13 +20,14 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator
|
||||
{
|
||||
private File authorizedKeysDir;
|
||||
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
|
||||
|
||||
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
|
||||
|
||||
@Override public boolean authenticate(String username, PublicKey key, ServerSession session)
|
||||
{
|
||||
byte[] keyBytes = key.getEncoded();
|
||||
File keyFile = new File(authorizedKeysDir, username);
|
||||
Integer tries = SshdPlugin.instance.configuration.getInt("LoginRetries");
|
||||
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries");
|
||||
|
||||
if (keyFile.exists())
|
||||
{
|
||||
|
||||
96
src/main/java/com/ryanmichela/sshd/ReflectionUtil.java
Normal file
96
src/main/java/com/ryanmichela/sshd/ReflectionUtil.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package com.ryanmichela.sshd;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Copyright 2013 Ryan Michela
|
||||
*/
|
||||
public class ReflectionUtil {
|
||||
|
||||
public static void setProtectedValue(Object o, String field, Object newValue) {
|
||||
setProtectedValue(o.getClass(), o, field, newValue);
|
||||
}
|
||||
|
||||
public static void setProtectedValue(Class c, String field, Object newValue) {
|
||||
setProtectedValue(c, null, field, newValue);
|
||||
}
|
||||
|
||||
public static void setProtectedValue(Class c, Object o, String field, Object newValue) {
|
||||
try {
|
||||
|
||||
Field f = c.getDeclaredField(field);
|
||||
|
||||
f.setAccessible(true);
|
||||
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
|
||||
|
||||
f.set(o, newValue);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
System.out.println("*** " + c.getName() + ":" + ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T getProtectedValue(Object obj, String fieldName) {
|
||||
try {
|
||||
Class c = obj.getClass();
|
||||
while (c != Object.class) {
|
||||
Field[] fields = c.getDeclaredFields();
|
||||
for (Field f : fields) {
|
||||
if (f.getName() == fieldName) {
|
||||
f.setAccessible(true);
|
||||
return (T) f.get(obj);
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
System.out.println("*** " + obj.getClass().getName() + ":No such field");
|
||||
return null;
|
||||
} catch (Exception ex) {
|
||||
System.out.println("*** " + obj.getClass().getName() + ":" + ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T getProtectedValue(Class c, String field) {
|
||||
try {
|
||||
Field f = c.getDeclaredField(field);
|
||||
f.setAccessible(true);
|
||||
return (T) f.get(c);
|
||||
} catch (Exception ex) {
|
||||
System.out.println("*** " + c.getName() + ":" + ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object invokeProtectedMethod(Class c, String method, Object... args) {
|
||||
return invokeProtectedMethod(c, null, method, args);
|
||||
}
|
||||
|
||||
public static Object invokeProtectedMethod(Object o, String method, Object... args) {
|
||||
return invokeProtectedMethod(o.getClass(), o, method, args);
|
||||
}
|
||||
|
||||
public static Object invokeProtectedMethod(Class c, Object o, String method, Object... args) {
|
||||
try {
|
||||
Class[] pTypes = new Class[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] instanceof Integer) {
|
||||
pTypes[i] = int.class;
|
||||
} else {
|
||||
pTypes[i] = args[i].getClass();
|
||||
}
|
||||
}
|
||||
|
||||
Method m = c.getDeclaredMethod(method, pTypes);
|
||||
m.setAccessible(true);
|
||||
return m.invoke(o, args);
|
||||
} catch (Exception ex) {
|
||||
System.out.println("*** " + c.getName() + "." + method + "(): " + ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,10 @@
|
||||
package com.ryanmichela.sshd;
|
||||
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.config.*;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.ryanmichela.sshd.ConsoleShellFactory;
|
||||
import com.ryanmichela.sshd.MkpasswdCommand;
|
||||
@@ -25,17 +20,16 @@ import java.util.logging.Level;
|
||||
/**
|
||||
* Copyright 2013 Ryan Michela
|
||||
*/
|
||||
public final class SshdPlugin extends Plugin
|
||||
public
|
||||
class SshdPlugin extends JavaPlugin
|
||||
{
|
||||
|
||||
private SshServer sshd;
|
||||
public static SshdPlugin instance;
|
||||
private File file;
|
||||
public Configuration configuration;
|
||||
|
||||
@Override public void onLoad()
|
||||
{
|
||||
file = new File(getDataFolder(), "config.yml");
|
||||
|
||||
saveDefaultConfig();
|
||||
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
|
||||
if (!authorizedKeys.exists())
|
||||
authorizedKeys.mkdirs();
|
||||
@@ -54,21 +48,6 @@ public final class SshdPlugin extends Plugin
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!file.exists())
|
||||
{
|
||||
// Copy our config file.
|
||||
InputStream link = (getClass().getResourceAsStream("/config.yml"));
|
||||
Files.copy(link, file.getAbsoluteFile().toPath());
|
||||
}
|
||||
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Don't go any lower than INFO or SSHD will cause a stack overflow exception.
|
||||
// SSHD will log that it wrote bites to the output stream, which writes
|
||||
// bytes to the output stream - ad nauseaum.
|
||||
@@ -80,8 +59,8 @@ public final class SshdPlugin extends Plugin
|
||||
instance = this;
|
||||
|
||||
sshd = SshServer.setUpDefaultServer();
|
||||
sshd.setPort(configuration.getInt("Port", 1025));
|
||||
String host = configuration.getString("ListenAddress", "all");
|
||||
sshd.setPort(getConfig().getInt("Port", 1025));
|
||||
String host = getConfig().getString("ListenAddress", "all");
|
||||
sshd.setHost(host.equals("all") ? null : host);
|
||||
|
||||
File hostKey = new File(getDataFolder(), "hostkey");
|
||||
@@ -92,14 +71,14 @@ public final class SshdPlugin extends Plugin
|
||||
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
|
||||
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
|
||||
|
||||
if (configuration.getBoolean("EnableSFTP"))
|
||||
if (getConfig().getBoolean("EnableSFTP"))
|
||||
{
|
||||
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
||||
sshd.setFileSystemFactory(
|
||||
new VirtualFileSystemFactory(FileSystems.getDefault().getPath(getDataFolder().getAbsolutePath()).getParent().getParent()));
|
||||
}
|
||||
|
||||
getProxy().getPluginManager().registerCommand(this, new MkpasswdCommand());
|
||||
this.getCommand("mkpasswd").setExecutor(new MkpasswdCommand());
|
||||
|
||||
sshd.setCommandFactory(new ConsoleCommandFactory());
|
||||
try
|
||||
@@ -123,12 +102,4 @@ public final class SshdPlugin extends Plugin
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
public static SshdPlugin getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static void setInstance(SshdPlugin instance) {
|
||||
SshdPlugin.instance = instance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.ryanmichela.sshd;
|
||||
|
||||
import org.apache.logging.log4j.core.Appender;
|
||||
import org.apache.logging.log4j.core.ErrorHandler;
|
||||
import org.apache.logging.log4j.core.Layout;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.StreamHandler;
|
||||
|
||||
/**
|
||||
* Copyright 2014 Ryan Michela
|
||||
*/
|
||||
public class StreamHandlerAppender implements Appender {
|
||||
|
||||
private StreamHandler streamHandler;
|
||||
private UUID uuid;
|
||||
|
||||
public StreamHandlerAppender(StreamHandler streamHandler) {
|
||||
this.streamHandler = streamHandler;
|
||||
uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(LogEvent logEvent) {
|
||||
java.util.logging.Level level;
|
||||
|
||||
if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.DEBUG)) {
|
||||
level = java.util.logging.Level.FINE;
|
||||
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.INFO)) {
|
||||
level = java.util.logging.Level.INFO;
|
||||
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.WARN)) {
|
||||
level = java.util.logging.Level.WARNING;
|
||||
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.ERROR)) {
|
||||
level = java.util.logging.Level.SEVERE;
|
||||
} else {
|
||||
level = java.util.logging.Level.INFO;
|
||||
}
|
||||
String message = logEvent.getMessage().getFormattedMessage();
|
||||
|
||||
|
||||
LogRecord logRecord = new LogRecord(level, message);
|
||||
streamHandler.publish(logRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "StreamHandlerAppender:" + uuid.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout<? extends Serializable> getLayout() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ignoreExceptions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorHandler getHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandler(ErrorHandler errorHandler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopped() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,45 @@
|
||||
package com.ryanmichela.sshd.implementations;
|
||||
|
||||
import com.ryanmichela.sshd.SshdPlugin;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.conversations.Conversation;
|
||||
import org.bukkit.conversations.ConversationAbandonedEvent;
|
||||
import org.bukkit.conversations.ManuallyAbandonedConversationCanceller;
|
||||
import org.bukkit.permissions.PermissibleBase;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionAttachment;
|
||||
import org.bukkit.permissions.PermissionAttachmentInfo;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.ryanmichela.sshd.ConsoleShellFactory;
|
||||
import com.ryanmichela.sshd.ConsoleLogFormatter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class SSHDCommandSender implements CommandSender
|
||||
public class SSHDCommandSender implements ConsoleCommandSender, CommandSender
|
||||
{
|
||||
private final PermissibleBase perm = new PermissibleBase(this);
|
||||
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
|
||||
// Set by the upstream allocating function
|
||||
public ConsoleShellFactory.ConsoleShell console;
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message)
|
||||
public void sendMessage(String message)
|
||||
{
|
||||
this.sendRawMessage(message + "\r");
|
||||
}
|
||||
|
||||
public void sendRawMessage(String message)
|
||||
{
|
||||
// What the fuck does this code even do? Are we sending to one client or all of them?
|
||||
if (this.console.ConsoleReader == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replace("\n", "\n\r"));
|
||||
@@ -45,70 +54,99 @@ public final class SSHDCommandSender implements CommandSender
|
||||
this.console.ConsoleReader.getCursorBuffer().clear();
|
||||
}
|
||||
this.console.ConsoleReader.flush();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessages(String... messages)
|
||||
{
|
||||
public void sendMessage(String[] messages) {
|
||||
Arrays.asList(messages).forEach(this::sendMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(BaseComponent... message)
|
||||
{
|
||||
sendMessage(BaseComponent.toLegacyText(message));
|
||||
public String getName() {
|
||||
return "SSHD Console";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(BaseComponent message)
|
||||
{
|
||||
sendMessage(message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "SSHD CONSOLE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getGroups()
|
||||
{
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGroups(String... groups)
|
||||
{
|
||||
throw new UnsupportedOperationException("Console may not have groups");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGroups(String... groups)
|
||||
{
|
||||
throw new UnsupportedOperationException("Console may not have groups");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission)
|
||||
{
|
||||
public boolean isOp() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPermission(String permission, boolean value)
|
||||
{
|
||||
throw new UnsupportedOperationException("Console has all permissions");
|
||||
public void setOp(boolean value) {
|
||||
throw new UnsupportedOperationException("Cannot change operator status of server console");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPermissions()
|
||||
{
|
||||
return Collections.emptySet();
|
||||
public boolean beginConversation(Conversation conversation) {
|
||||
return this.conversationTracker.beginConversation(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
public void abandonConversation(Conversation conversation) {
|
||||
this.conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
|
||||
}
|
||||
|
||||
public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
|
||||
this.conversationTracker.abandonConversation(conversation, details);
|
||||
}
|
||||
|
||||
public void acceptConversationInput(String input) {
|
||||
this.conversationTracker.acceptConversationInput(input);
|
||||
}
|
||||
|
||||
public boolean isConversing() {
|
||||
return this.conversationTracker.isConversing();
|
||||
}
|
||||
|
||||
public boolean isPermissionSet(String name) {
|
||||
return this.perm.isPermissionSet(name);
|
||||
}
|
||||
|
||||
public boolean isPermissionSet(Permission perm) {
|
||||
return this.perm.isPermissionSet(perm);
|
||||
}
|
||||
|
||||
public boolean hasPermission(String name) {
|
||||
return this.perm.hasPermission(name);
|
||||
}
|
||||
|
||||
public boolean hasPermission(Permission perm) {
|
||||
return this.perm.hasPermission(perm);
|
||||
}
|
||||
|
||||
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
|
||||
return this.perm.addAttachment(plugin, name, value);
|
||||
}
|
||||
|
||||
public PermissionAttachment addAttachment(Plugin plugin) {
|
||||
return this.perm.addAttachment(plugin);
|
||||
}
|
||||
|
||||
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) {
|
||||
return this.perm.addAttachment(plugin, name, value, ticks);
|
||||
}
|
||||
|
||||
public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
|
||||
return this.perm.addAttachment(plugin, ticks);
|
||||
}
|
||||
|
||||
public void removeAttachment(PermissionAttachment attachment) {
|
||||
this.perm.removeAttachment(attachment);
|
||||
}
|
||||
|
||||
public void recalculatePermissions() {
|
||||
this.perm.recalculatePermissions();
|
||||
}
|
||||
|
||||
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
||||
return this.perm.getEffectivePermissions();
|
||||
}
|
||||
|
||||
public boolean isPlayer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return Bukkit.getServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.ryanmichela.sshd.implementations;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.conversations.Conversation;
|
||||
import org.bukkit.conversations.ConversationAbandonedEvent;
|
||||
import org.bukkit.conversations.ManuallyAbandonedConversationCanceller;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class SSHDConversationTracker {
|
||||
private LinkedList<Conversation> conversationQueue = new LinkedList<>();
|
||||
|
||||
synchronized boolean beginConversation(Conversation conversation) {
|
||||
if (!this.conversationQueue.contains(conversation)) {
|
||||
this.conversationQueue.addLast(conversation);
|
||||
if (this.conversationQueue.getFirst() == conversation) {
|
||||
conversation.begin();
|
||||
conversation.outputNextPrompt();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
synchronized void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
|
||||
if (!this.conversationQueue.isEmpty()) {
|
||||
if (this.conversationQueue.getFirst() == conversation) {
|
||||
conversation.abandon(details);
|
||||
}
|
||||
|
||||
if (this.conversationQueue.contains(conversation)) {
|
||||
this.conversationQueue.remove(conversation);
|
||||
}
|
||||
|
||||
if (!this.conversationQueue.isEmpty()) {
|
||||
this.conversationQueue.getFirst().outputNextPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized void abandonAllConversations() {
|
||||
LinkedList<Conversation> oldQueue = this.conversationQueue;
|
||||
this.conversationQueue = new LinkedList<>();
|
||||
|
||||
for (Conversation conversation : oldQueue) {
|
||||
try {
|
||||
conversation.abandon(new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
|
||||
} catch (Throwable var5) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Unexpected exception while abandoning a conversation", var5);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
synchronized void acceptConversationInput(String input) {
|
||||
if (this.isConversing()) {
|
||||
Conversation conversation = this.conversationQueue.getFirst();
|
||||
|
||||
try {
|
||||
conversation.acceptInput(input);
|
||||
} catch (Throwable var4) {
|
||||
conversation.getContext().getPlugin().getLogger().log(Level.WARNING, String.format("Plugin %s generated an exception whilst handling conversation input", conversation.getContext().getPlugin().getDescription().getFullName()), var4);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
synchronized boolean isConversing() {
|
||||
return !this.conversationQueue.isEmpty();
|
||||
}
|
||||
|
||||
public synchronized boolean isConversingModaly() {
|
||||
return this.isConversing() && this.conversationQueue.getFirst().isModal();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ name: SSHD
|
||||
version: ${project.version}
|
||||
author: Ryan Michela, Haarolean, toxuin, Justin Crawford, Zachery Coleman
|
||||
main: com.ryanmichela.sshd.SshdPlugin
|
||||
load: STARTUP
|
||||
commands:
|
||||
mkpasswd:
|
||||
description: Make a SSHD password hash
|
||||
usage: /mkpasswd <hash|help> <password>
|
||||
usage: /mkpasswd <hash|help> <password>
|
||||
|
||||
Reference in New Issue
Block a user