11 Commits

Author SHA1 Message Date
Zachery
766fb45c46 Update MkpasswdCommand.java 2019-10-16 00:48:24 -05:00
Justin Crawford
08528c5127 Increment project version for sponge release 2019-10-13 01:03:22 -07:00
Justin Crawford
8f9cbd55bc Update for new version release 2019-10-10 19:55:31 -07:00
Zachery
33f34ad54d Get this to a release state
- Fixed CommandCompleter so tab completion works again.
- Fixed most of the mkpasswd command, it's also now more secure as it
only shows in your SSH session and not other's consoles.
- Removed some now unused code.
2019-10-10 01:12:38 -05:00
Justin Crawford
b51a03e2d5 Add new logo 2019-10-09 23:18:06 -05:00
Zachery
d38c75dd9e SSH on bungee now works.
Had to rewrite the mkpasswd command and how the console was handled
in the SSH session as many areas didn't use CRLF compared to Spigot
which seems to work better. The config had to be done manually for
the plugin since BungeeCord doesn't handle plugin configs as conveniently.

Removed the test server I accidentally added to the git repo.
2019-10-09 22:57:57 -05:00
Zachery
1c6028199b Most of the way there. 2019-10-09 00:20:17 -05:00
Zachery
047369105e Delete SshdPlugin.java 2019-10-08 16:01:23 -05:00
Zachery
586910a75c PHEW! A lot of the conversion to bungeecord is complete, there's only one more class to complete, but I suspect there will be quite a few bugs. 2019-10-08 15:36:48 -05:00
Justin Crawford
7618333bf3 Update the readme for BungeeCord 2019-10-07 23:42:56 -07:00
Justin Crawford
96bb1a5fa8 Begin the port to BungeeCord 2019-10-07 23:31:20 -07:00
17 changed files with 274 additions and 521 deletions

View File

@@ -1,5 +1,5 @@
--- ---
#BasedOnStyle: WebKit Language: 'Java'
TabWidth: '4' TabWidth: '4'
IndentWidth: '4' IndentWidth: '4'
UseTab: 'Always' UseTab: 'Always'
@@ -7,6 +7,7 @@ AlignOperands: 'true'
AlignAfterOpenBracket: 'Align' AlignAfterOpenBracket: 'Align'
AlignConsecutiveAssignments: 'true' AlignConsecutiveAssignments: 'true'
AlignConsecutiveDeclarations: 'true' AlignConsecutiveDeclarations: 'true'
AlignConsecutiveMacros: 'true'
AlignEscapedNewlines: 'Left' AlignEscapedNewlines: 'Left'
AlignTrailingComments: 'true' AlignTrailingComments: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'true' AllowAllParametersOfDeclarationOnNextLine: 'true'
@@ -20,35 +21,20 @@ AlwaysBreakTemplateDeclarations: 'true'
AlwaysBreakBeforeMultilineStrings: 'false' AlwaysBreakBeforeMultilineStrings: 'false'
BinPackArguments: 'false' BinPackArguments: 'false'
BinPackParameters: 'false' BinPackParameters: 'false'
BreakBeforeBraces: 'Custom' BreakBeforeBraces: 'Allman'
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' BreakBeforeBinaryOperators: 'true'
BreakBeforeTernaryOperators: 'false' BreakBeforeTernaryOperators: 'false'
BreakConstructorInitializersBeforeComma: 'false' BreakConstructorInitializers: 'AfterColon'
BreakBeforeInheritanceComma: 'false' BreakBeforeInheritanceComma: 'false'
BreakAfterJavaFieldAnnotations: 'true'
BreakStringLiterals: 'true' BreakStringLiterals: 'true'
ColumnLimit: '140' ColumnLimit: '140'
CompactNamespaces: 'false' CompactNamespaces: 'false'
Cpp11BracedListStyle: 'true'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
DerivePointerAlignment: 'false' DerivePointerAlignment: 'false'
IndentCaseLabels: 'true' IndentCaseLabels: 'true'
IndentPPDirectives: 'AfterHash' IndentPPDirectives: 'AfterHash'
KeepEmptyLinesAtTheStartOfBlocks: 'true' KeepEmptyLinesAtTheStartOfBlocks: 'true'
Language: 'Java'
NamespaceIndentation: 'All' NamespaceIndentation: 'All'
PointerAlignment: 'Right' PointerAlignment: 'Right'
ReflowComments: 'true' ReflowComments: 'true'

View File

@@ -1,5 +1,5 @@
Minecraft-SSHD Minecraft-SSHD (BungeeCord Edition!)
=========== =================================
[![Build Status](https://travis-ci.org/Justasic/Minecraft-SSHD.svg?branch=master)](https://travis-ci.org/Justasic/Minecraft-SSHD) [![Build Status](https://travis-ci.org/Justasic/Minecraft-SSHD.svg?branch=master)](https://travis-ci.org/Justasic/Minecraft-SSHD)
[![Release](https://img.shields.io/github/release/Justasic/Minecraft-SSHD.svg?label=Release&maxAge=60)](https://github.com/Justasic/Minecraft-SSHD/releases/latest) [![Release](https://img.shields.io/github/release/Justasic/Minecraft-SSHD.svg?label=Release&maxAge=60)](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. - Supports multiple concurrent remote connections.
- Strong identity support using public key authentication. - Strong identity support using public key authentication.
- Audit history who is running commands in the console - Audit history who is running commands in the console
- Run Spigot without using screen or tmux (by adding `-noconsole`) - Run BungeeCord without using screen or tmux (by adding `-noconsole`)
- Remotely script your server by issuing one-off console commands with ssh. - Remotely script your server by issuing one-off console commands with ssh.
### Why should I use Minecraft-SSHD? ### Why should I use Minecraft-SSHD?
@@ -40,7 +40,7 @@ Screenshots
Setting Up Public Key Authentication 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 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. 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.
## Generating New Keys ## Generating New Keys

24
pom.xml
View File

@@ -5,8 +5,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.ryanmichela</groupId> <groupId>com.ryanmichela</groupId>
<artifactId>sshd</artifactId> <artifactId>sshd-bungee</artifactId>
<description>Minecraft-SSHD: The SSH daemon for Minecraft servers.</description>
<version>2.0.0</version> <version>2.0.0</version>
<url>https://github.com/Justasic/Minecraft-SSHD/</url> <url>https://github.com/Justasic/Minecraft-SSHD/</url>
@@ -34,9 +33,10 @@
<!-- Dependencies --> <!-- Dependencies -->
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.bukkit</groupId> <groupId>net.md-5</groupId>
<artifactId>bukkit</artifactId> <artifactId>bungeecord-api</artifactId>
<version>1.14.4-R0.1-SNAPSHOT</version> <version>1.14-SNAPSHOT</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -101,14 +101,12 @@
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>2.0</version> <version>2.0</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>2.1</version> <version>2.1</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -117,6 +115,18 @@
<version>1.10</version> <version>1.10</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </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> </dependencies>
<!-- Build --> <!-- Build -->

View File

@@ -19,8 +19,8 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
public boolean authenticate(String username, String password, ServerSession ss) 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. // Depending on our hash type, we have to try and figure out what we're doing.
String HashType = SshdPlugin.instance.getConfig().getString("PasswordType"); String HashType = SshdPlugin.instance.configuration.getString("PasswordType");
String ConfigHash = SshdPlugin.instance.getConfig().getString("Credentials." + username.trim()); String ConfigHash = SshdPlugin.instance.configuration.getString("Credentials." + username.trim());
if (ConfigHash == null) if (ConfigHash == null)
SshdPlugin.instance.getLogger().warning("Config has no such user: " + username); 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."); SshdPlugin.instance.getLogger().info("Failed login for " + username + " using " + HashType + "-based password authentication.");
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries"); Integer tries = SshdPlugin.instance.configuration.getInt("LoginRetries");
try try
{ {

View File

@@ -3,42 +3,60 @@ package com.ryanmichela.sshd;
/** /**
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
import net.md_5.bungee.api.plugin.Command;
import jline.console.completer.Completer; import jline.console.completer.Completer;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
public class ConsoleCommandCompleter implements Completer { public class ConsoleCommandCompleter implements Completer
{
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) { public int complete(final String buffer, final int cursor, final List<CharSequence> candidates)
Waitable<List<String>> waitable = new Waitable<List<String>>() { {
Waitable<List<String>> waitable = new Waitable<List<String>>()
{
@Override @Override
protected List<String> evaluate() { protected List<String> evaluate()
CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap"); {
return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer); 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;
} }
}; };
Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable);
try { SshdPlugin.instance.getProxy().getScheduler().runAsync(SshdPlugin.instance, waitable);
try
{
List<String> offers = waitable.get(); List<String> offers = waitable.get();
if (offers == null) { if (offers == null)
return cursor; return cursor;
}
candidates.addAll(offers); candidates.addAll(offers);
final int lastSpace = buffer.lastIndexOf(' '); final int lastSpace = buffer.lastIndexOf(' ');
if (lastSpace == -1) { if (lastSpace == -1)
return cursor - buffer.length(); return cursor - buffer.length();
} else { else
return cursor - (buffer.length() - lastSpace - 1); return cursor - (buffer.length() - lastSpace - 1);
} }
} catch (ExecutionException e) { catch (ExecutionException e)
{
SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e);
} catch (InterruptedException e) { }
catch (InterruptedException e)
{
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
return cursor; return cursor;

View File

@@ -5,7 +5,10 @@ import org.apache.sshd.server.command.CommandFactory;
import org.apache.sshd.server.channel.ChannelSession; import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.Environment; import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.ExitCallback;
import org.bukkit.Bukkit;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -51,14 +54,22 @@ public class ConsoleCommandFactory implements CommandFactory {
} }
@Override @Override
public void start(ChannelSession cs, Environment environment) throws IOException { public void start(ChannelSession cs, Environment environment) throws IOException
try { {
try
{
SshdPlugin.instance.getLogger() SshdPlugin.instance.getLogger()
.info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command); .info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
} catch (Exception e) { 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)
{
SshdPlugin.instance.getLogger().severe("Error processing command from SSH -" + e.getMessage()); SshdPlugin.instance.getLogger().severe("Error processing command from SSH -" + e.getMessage());
} finally { }
finally
{
callback.onExit(0); callback.onExit(0);
} }
} }

View File

@@ -4,7 +4,7 @@ package com.ryanmichela.sshd;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
import org.bukkit.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -15,12 +15,13 @@ import java.util.Map;
import java.util.logging.Formatter; import java.util.logging.Formatter;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
public class ConsoleLogFormatter extends Formatter { public class ConsoleLogFormatter extends Formatter
{
private SimpleDateFormat dateFormat; private SimpleDateFormat dateFormat;
private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class); private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
public ConsoleLogFormatter() { public ConsoleLogFormatter()
{
this.dateFormat = new SimpleDateFormat("HH:mm:ss"); this.dateFormat = new SimpleDateFormat("HH:mm:ss");
} }

View File

@@ -6,10 +6,12 @@ import com.ryanmichela.sshd.FlushyOutputStream;
import com.ryanmichela.sshd.FlushyStreamHandler; import com.ryanmichela.sshd.FlushyStreamHandler;
import com.ryanmichela.sshd.SshTerminal; import com.ryanmichela.sshd.SshTerminal;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
import com.ryanmichela.sshd.StreamHandlerAppender;
import com.ryanmichela.sshd.implementations.SSHDCommandSender; import com.ryanmichela.sshd.implementations.SSHDCommandSender;
import com.ryanmichela.sshd.ConsoleLogFormatter; import com.ryanmichela.sshd.ConsoleLogFormatter;
import jline.console.ConsoleReader; 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.LogManager;
import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.Logger;
import org.apache.sshd.common.Factory; import org.apache.sshd.common.Factory;
@@ -18,7 +20,6 @@ import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.channel.ChannelSession; import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.Environment; import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.ExitCallback;
import org.bukkit.Bukkit;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -32,6 +33,8 @@ import java.util.StringTokenizer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.StreamHandler; import java.util.logging.StreamHandler;
import static com.ryanmichela.sshd.SshdPlugin.instance;
public class ConsoleShellFactory implements ShellFactory { public class ConsoleShellFactory implements ShellFactory {
public Command createShell(ChannelSession cs) { public Command createShell(ChannelSession cs) {
@@ -48,7 +51,7 @@ public class ConsoleShellFactory implements ShellFactory {
private Thread thread; private Thread thread;
private String Username; private String Username;
StreamHandlerAppender streamHandlerAppender; StreamHandler streamHandler;
public ConsoleReader ConsoleReader; public ConsoleReader ConsoleReader;
public SSHDCommandSender SshdCommandSender; public SSHDCommandSender SshdCommandSender;
@@ -93,10 +96,9 @@ public class ConsoleShellFactory implements ShellFactory {
this.ConsoleReader.setExpandEvents(true); this.ConsoleReader.setExpandEvents(true);
this.ConsoleReader.addCompleter(new ConsoleCommandCompleter()); this.ConsoleReader.addCompleter(new ConsoleCommandCompleter());
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader); streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader);
this.streamHandlerAppender = new StreamHandlerAppender(streamHandler);
((Logger)LogManager.getRootLogger()).addAppender(this.streamHandlerAppender); SshdPlugin.instance.getProxy().getLogger().addHandler(this.streamHandler);
this.environment = env; this.environment = env;
this.Username = env.getEnv().get(Environment.ENV_USER); this.Username = env.getEnv().get(Environment.ENV_USER);
@@ -107,18 +109,19 @@ public class ConsoleShellFactory implements ShellFactory {
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace();
throw new IOException("Error starting shell", e); throw new IOException("Error starting shell", e);
} }
} }
@Override @Override
public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(this.streamHandlerAppender); } public void destroy(ChannelSession cs) { SshdPlugin.instance.getProxy().getLogger().removeHandler(this.streamHandler); }
public void run() public void run()
{ {
try try
{ {
if (!SshdPlugin.instance.getConfig().getString("Mode").equals("RPC")) if (!instance.configuration.getString("Mode").equals("RPC"))
printPreamble(this.ConsoleReader); printPreamble(this.ConsoleReader);
while (true) while (true)
{ {
@@ -136,54 +139,47 @@ public class ConsoleShellFactory implements ShellFactory {
if (command.equals("cls")) if (command.equals("cls"))
{ {
this.ConsoleReader.clearScreen(); this.ConsoleReader.clearScreen();
this.ConsoleReader.drawLine();
this.ConsoleReader.flush();
continue; continue;
} }
// Hide the mkpasswd command input from other users. // Hide the mkpasswd command input from other users.
Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd"); Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd");
Bukkit.getScheduler().runTask( instance.getProxy().getScheduler().runAsync(
SshdPlugin.instance, () -> instance, () ->
{ {
if (SshdPlugin.instance.getConfig().getString("Mode").equals("RPC") && command.startsWith("rpc")) if (instance.configuration.getString("Mode").equals("RPC") && command.startsWith("rpc"))
{ {
// NO ECHO NO PREAMBLE AND SHIT // NO ECHO NO PREAMBLE AND SHIT
String cmd = command.substring("rpc".length() + 1, command.length()); String cmd = command.substring("rpc".length() + 1, command.length());
Bukkit.dispatchCommand(this.SshdCommandSender, cmd); if (!instance.getProxy().getPluginManager().dispatchCommand(this.SshdCommandSender, cmd))
instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
} }
else 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) if (!mkpasswd)
{ instance.getLogger().info("<" + this.Username + "> " + command);
SshdPlugin.instance.getLogger().info("<" + this.Username + "> " + command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); if (!instance.getProxy().getPluginManager().dispatchCommand(this.SshdCommandSender, command))
} instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
else
{
Bukkit.dispatchCommand(this.SshdCommandSender, command);
}
} }
}); });
} }
} }
catch (IOException e) catch (IOException e)
{ {
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e); e.printStackTrace();
instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
} }
finally finally
{ {
SshdPlugin.instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH."); instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH.");
callback.onExit(0); callback.onExit(0);
} }
} }
private void printPreamble(ConsoleReader cr) throws IOException private void printPreamble(ConsoleReader cr) throws IOException
{ {
File f = new File(SshdPlugin.instance.getDataFolder(), "motd.txt"); File f = new File(instance.getDataFolder(), "motd.txt");
try try
{ {
BufferedReader br = new BufferedReader(new FileReader(f)); BufferedReader br = new BufferedReader(new FileReader(f));
@@ -194,16 +190,18 @@ public class ConsoleShellFactory implements ShellFactory {
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {
SshdPlugin.instance.getLogger().log(Level.WARNING, "Could not open " + f + ": File does not exist."); 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. // Not showing the SSH motd is not a fatal failure, let the session continue.
} }
// Doesn't really guarantee our actual system hostname but // Doesn't really guarantee our actual system hostname but
// it's better than not having one at all. // it's better than not having one at all.
cr.println("Connected to: " + InetAddress.getLocalHost().getHostName() + " (" + Bukkit.getServer().getName() + ")\r"); cr.println("Connected to: " + InetAddress.getLocalHost().getHostName() + " (BungeeCord)\r");
cr.println(ConsoleLogFormatter.ColorizeString(Bukkit.getServer().getMotd()).replaceAll("\n", "\r\n")); // 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("\r"); cr.println("\r");
cr.println("Type 'exit' to exit the shell." + "\r"); cr.println("Type 'exit' or press Ctrl+D exit the shell." + "\r");
cr.println("===============================================" + "\r"); cr.println("===============================================" + "\r");
} }
} }

View File

@@ -2,6 +2,7 @@ package com.ryanmichela.sshd;
import jline.console.ConsoleReader; import jline.console.ConsoleReader;
import org.apache.sshd.common.SshException; import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.exception.SshChannelClosedException;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@@ -10,39 +11,50 @@ import java.util.logging.*;
/** /**
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public class FlushyStreamHandler extends StreamHandler { public class FlushyStreamHandler extends StreamHandler
{
private ConsoleReader reader; private ConsoleReader reader;
public FlushyStreamHandler(OutputStream out, Formatter formatter, ConsoleReader reader) { public FlushyStreamHandler(OutputStream out, Formatter formatter, ConsoleReader reader)
{
super(out, formatter); super(out, formatter);
this.reader = reader; this.reader = reader;
setLevel(Level.INFO); setLevel(Level.INFO);
} }
@Override @Override
public synchronized void publish(LogRecord record) { public synchronized void publish(LogRecord record)
{
record.setMessage(record.getMessage().replace("\n", "\n\r")); record.setMessage(record.getMessage().replace("\n", "\n\r"));
super.publish(record); super.publish(record);
flush(); flush();
} }
@Override @Override
public synchronized void flush() { public synchronized void flush()
try { {
try
{
reader.print(ConsoleReader.RESET_LINE + ""); reader.print(ConsoleReader.RESET_LINE + "");
reader.flush(); reader.flush();
super.flush(); super.flush();
try { try
{
reader.drawLine(); reader.drawLine();
} catch (Throwable ex) { }
catch (Throwable ex)
{
reader.getCursorBuffer().clear(); reader.getCursorBuffer().clear();
} }
reader.flush(); reader.flush();
super.flush(); super.flush();
} catch (SshException ex) { }
catch (SshChannelClosedException ex)
{
// do nothing // do nothing
} catch (IOException ex) { }
catch (IOException ex)
{
Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex);
} }
} }

View File

@@ -1,39 +1,34 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.Command;
import org.bukkit.entity.Player;
import java.util.Arrays; import java.util.Arrays;
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 com.ryanmichela.sshd.Cryptography; import com.ryanmichela.sshd.Cryptography;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
class MkpasswdCommand implements CommandExecutor public class MkpasswdCommand extends Command
{ {
// Because Spigot's failed syntax API is really less than ideal (you should be required to add a public MkpasswdCommand()
// 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 super("mkpasswd");
// 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 public void SendSyntax(CommandSender sender, boolean invalid)
// know how to RTFM. - Justin
private void SendSyntax(CommandSender sender, boolean invalid)
{ {
if (invalid) if (invalid)
sender.sendMessage("\u00A7cInvalid Syntax\u00A7r"); sender.sendMessage(new ComponentBuilder("Invalid Syntax").color(ChatColor.RED).create());
sender.sendMessage("\u00A7a/mkpasswd <help|hash> <password>\u00A7r"); sender.sendMessage(new ComponentBuilder("/mkpasswd <help|hash> <password>").color(ChatColor.GREEN).create());
sender.sendMessage("\u00A79Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN\u00A7r"); sender.sendMessage(new ComponentBuilder("Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN").color(ChatColor.BLUE).create());
} }
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) public void execute(CommandSender sender, String[] args)
{ {
// If we're not mkpasswd, just fuck off.
if (!label.equalsIgnoreCase("mkpasswd"))
return false;
String algoritm, password; String algoritm, password;
try try
{ {
@@ -41,17 +36,18 @@ class MkpasswdCommand implements CommandExecutor
// spaces in their passwords otherwise it won't be as strong as it should be. // spaces in their passwords otherwise it won't be as strong as it should be.
algoritm = args[0]; algoritm = args[0];
password = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); password = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (password.trim().isEmpty()) // Shortcut to the catch statement below. if (password.length() == 0)
throw new ArrayIndexOutOfBoundsException(); throw new ArrayIndexOutOfBoundsException(); // shortcut
} }
catch (ArrayIndexOutOfBoundsException e) catch (ArrayIndexOutOfBoundsException e)
{ {
// ignore it. this.SendSyntax(sender, false);
this.SendSyntax(sender, true); return;
return true;
} }
boolean hasperm = (sender instanceof Player) ? ((Player)sender).hasPermission("sshd.mkpasswd") : 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;
if (hasperm) if (hasperm)
{ {
@@ -62,8 +58,8 @@ class MkpasswdCommand implements CommandExecutor
if (algoritm.equalsIgnoreCase("PLAIN")) if (algoritm.equalsIgnoreCase("PLAIN"))
{ {
// I mean c'mon... // I mean c'mon...
sender.sendMessage("Bro really? it's literally your unencrypted password..."); sender.sendMessage("\u00A79Your Hash: \u00A7cIt's literally your unhashed password.");
return true; return;
} }
else if (algoritm.equalsIgnoreCase("pbkdf2")) else if (algoritm.equalsIgnoreCase("pbkdf2"))
hash = Cryptography.PBKDF2_HashPassword(password); hash = Cryptography.PBKDF2_HashPassword(password);
@@ -74,19 +70,16 @@ class MkpasswdCommand implements CommandExecutor
else else
{ {
this.SendSyntax(sender, !algoritm.equalsIgnoreCase("help")); this.SendSyntax(sender, !algoritm.equalsIgnoreCase("help"));
return true; return;
} }
sender.sendMessage("\u00A79Your Hash: " + hash + "\u00A7r"); sender.sendMessage(new ComponentBuilder("Your Hash: " + hash).color(ChatColor.BLUE).create());
} }
catch (Exception e) catch (Exception e)
{ {
// We're console, just print the stack trace. // We're console, just print the stack trace.
e.printStackTrace(); e.printStackTrace();
sender.sendMessage("\u00A7cAn error occured. Please check console for details.\u00A7r");
} }
} }
return true;
} }
} }

View File

@@ -1,5 +1,6 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import net.md_5.bungee.config.Configuration;
import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.ArrayUtils;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
@@ -20,14 +21,13 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator
{ {
private File authorizedKeysDir; private File authorizedKeysDir;
private Map<String, Integer> FailCounts = new HashMap<String, Integer>(); private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; } public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
@Override public boolean authenticate(String username, PublicKey key, ServerSession session) @Override public boolean authenticate(String username, PublicKey key, ServerSession session)
{ {
byte[] keyBytes = key.getEncoded(); byte[] keyBytes = key.getEncoded();
File keyFile = new File(authorizedKeysDir, username); File keyFile = new File(authorizedKeysDir, username);
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries"); Integer tries = SshdPlugin.instance.configuration.getInt("LoginRetries");
if (keyFile.exists()) if (keyFile.exists())
{ {

View File

@@ -1,96 +0,0 @@
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;
}
}
}

View File

@@ -1,10 +1,15 @@
package com.ryanmichela.sshd; 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.common.file.virtualfs.VirtualFileSystemFactory;
import org.apache.sshd.server.SshServer; import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory; import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.bukkit.plugin.java.JavaPlugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.config.*;
import com.ryanmichela.sshd.ConsoleShellFactory; import com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.MkpasswdCommand; import com.ryanmichela.sshd.MkpasswdCommand;
@@ -20,16 +25,17 @@ import java.util.logging.Level;
/** /**
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public public final class SshdPlugin extends Plugin
class SshdPlugin extends JavaPlugin
{ {
private SshServer sshd; private SshServer sshd;
public static SshdPlugin instance; public static SshdPlugin instance;
private File file;
public Configuration configuration;
@Override public void onLoad() @Override public void onLoad()
{ {
saveDefaultConfig(); file = new File(getDataFolder(), "config.yml");
File authorizedKeys = new File(getDataFolder(), "authorized_keys"); File authorizedKeys = new File(getDataFolder(), "authorized_keys");
if (!authorizedKeys.exists()) if (!authorizedKeys.exists())
authorizedKeys.mkdirs(); authorizedKeys.mkdirs();
@@ -48,6 +54,21 @@ class SshdPlugin extends JavaPlugin
e.printStackTrace(); 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. // 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 // SSHD will log that it wrote bites to the output stream, which writes
// bytes to the output stream - ad nauseaum. // bytes to the output stream - ad nauseaum.
@@ -59,8 +80,8 @@ class SshdPlugin extends JavaPlugin
instance = this; instance = this;
sshd = SshServer.setUpDefaultServer(); sshd = SshServer.setUpDefaultServer();
sshd.setPort(getConfig().getInt("Port", 1025)); sshd.setPort(configuration.getInt("Port", 1025));
String host = getConfig().getString("ListenAddress", "all"); String host = configuration.getString("ListenAddress", "all");
sshd.setHost(host.equals("all") ? null : host); sshd.setHost(host.equals("all") ? null : host);
File hostKey = new File(getDataFolder(), "hostkey"); File hostKey = new File(getDataFolder(), "hostkey");
@@ -71,14 +92,14 @@ class SshdPlugin extends JavaPlugin
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator()); sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys)); sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
if (getConfig().getBoolean("EnableSFTP")) if (configuration.getBoolean("EnableSFTP"))
{ {
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory())); sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
sshd.setFileSystemFactory( sshd.setFileSystemFactory(
new VirtualFileSystemFactory(FileSystems.getDefault().getPath(getDataFolder().getAbsolutePath()).getParent().getParent())); new VirtualFileSystemFactory(FileSystems.getDefault().getPath(getDataFolder().getAbsolutePath()).getParent().getParent()));
} }
this.getCommand("mkpasswd").setExecutor(new MkpasswdCommand()); getProxy().getPluginManager().registerCommand(this, new MkpasswdCommand());
sshd.setCommandFactory(new ConsoleCommandFactory()); sshd.setCommandFactory(new ConsoleCommandFactory());
try try
@@ -102,4 +123,12 @@ class SshdPlugin extends JavaPlugin
// do nothing // do nothing
} }
} }
public static SshdPlugin getInstance() {
return instance;
}
private static void setInstance(SshdPlugin instance) {
SshdPlugin.instance = instance;
}
} }

View File

@@ -1,92 +0,0 @@
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;
}
}

View File

@@ -1,35 +1,26 @@
package com.ryanmichela.sshd.implementations; package com.ryanmichela.sshd.implementations;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
import org.bukkit.Bukkit; import lombok.AccessLevel;
import org.bukkit.ChatColor; import lombok.NoArgsConstructor;
import org.bukkit.Server; import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.CommandSender; import net.md_5.bungee.api.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import net.md_5.bungee.api.ProxyServer;
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.ConsoleShellFactory;
import com.ryanmichela.sshd.ConsoleLogFormatter; import com.ryanmichela.sshd.ConsoleLogFormatter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set; import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level; import java.util.logging.Level;
public class SSHDCommandSender implements ConsoleCommandSender, CommandSender public final class SSHDCommandSender implements CommandSender
{ {
private final PermissibleBase perm = new PermissibleBase(this);
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
// Set by the upstream allocating function
public ConsoleShellFactory.ConsoleShell console; public ConsoleShellFactory.ConsoleShell console;
@Override
public void sendMessage(String message) public void sendMessage(String message)
{ {
this.sendRawMessage(message + "\r"); this.sendRawMessage(message + "\r");
@@ -37,9 +28,9 @@ public class SSHDCommandSender implements ConsoleCommandSender, CommandSender
public void sendRawMessage(String message) 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) if (this.console.ConsoleReader == null)
return; return;
try try
{ {
this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replace("\n", "\n\r")); this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replace("\n", "\n\r"));
@@ -61,92 +52,63 @@ public class SSHDCommandSender implements ConsoleCommandSender, CommandSender
} }
} }
public void sendMessage(String[] messages) { @Override
public void sendMessages(String... messages)
{
Arrays.asList(messages).forEach(this::sendMessage); Arrays.asList(messages).forEach(this::sendMessage);
} }
public String getName() { @Override
return "SSHD Console"; public void sendMessage(BaseComponent... message)
{
sendMessage(BaseComponent.toLegacyText(message));
} }
public boolean isOp() { @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)
{
return true; return true;
} }
public void setOp(boolean value) { @Override
throw new UnsupportedOperationException("Cannot change operator status of server console"); public void setPermission(String permission, boolean value)
{
throw new UnsupportedOperationException("Console has all permissions");
} }
public boolean beginConversation(Conversation conversation) { @Override
return this.conversationTracker.beginConversation(conversation); public Collection<String> getPermissions()
{
return Collections.emptySet();
} }
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();
}
} }

View File

@@ -1,78 +0,0 @@
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();
}
}

View File

@@ -2,7 +2,6 @@ name: SSHD
version: ${project.version} version: ${project.version}
author: Ryan Michela, Haarolean, toxuin, Justin Crawford, Zachery Coleman author: Ryan Michela, Haarolean, toxuin, Justin Crawford, Zachery Coleman
main: com.ryanmichela.sshd.SshdPlugin main: com.ryanmichela.sshd.SshdPlugin
load: STARTUP
commands: commands:
mkpasswd: mkpasswd:
description: Make a SSHD password hash description: Make a SSHD password hash