Compare commits

...

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
19 changed files with 464 additions and 558 deletions

View File

@ -1,5 +1,5 @@
---
#BasedOnStyle: WebKit
Language: 'Java'
TabWidth: '4'
IndentWidth: '4'
UseTab: 'Always'
@ -7,6 +7,7 @@ AlignOperands: 'true'
AlignAfterOpenBracket: 'Align'
AlignConsecutiveAssignments: 'true'
AlignConsecutiveDeclarations: 'true'
AlignConsecutiveMacros: 'true'
AlignEscapedNewlines: 'Left'
AlignTrailingComments: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'true'
@ -20,35 +21,20 @@ AlwaysBreakTemplateDeclarations: 'true'
AlwaysBreakBeforeMultilineStrings: 'false'
BinPackArguments: 'false'
BinPackParameters: 'false'
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'
BreakBeforeBraces: 'Allman'
BreakBeforeBinaryOperators: 'true'
BreakBeforeTernaryOperators: 'false'
BreakConstructorInitializersBeforeComma: 'false'
BreakConstructorInitializers: 'AfterColon'
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'

View File

@ -1,13 +1,15 @@
Spigot-SSHD
===========
Minecraft-SSHD (BungeeCord Edition!)
=================================
[![Build Status](https://travis-ci.org/Justasic/Spigot-SSHD.svg?branch=master)](https://travis-ci.org/Justasic/Spigot-SSHD)
[![Release](https://img.shields.io/github/release/Justasic/Spigot-SSHD.svg?label=Release&maxAge=60)](https://github.com/Justasic/Spigot-SSHD/releases/latest)
[![GitHub license](https://img.shields.io/github/license/Justasic/Spigot-SSHD)](https://github.com/Justasic/Spigot-SSHD/blob/master/LICENSE)
[![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)
[![GitHub license](https://img.shields.io/github/license/Justasic/Minecraft-SSHD)](https://github.com/Justasic/Minecraft-SSHD/blob/master/LICENSE)
Have you ever wished you could remotely access your server's admin console without having to setup a complex remote access system? Now you can with SSHD.
<img align="left" width="140" height="140" src="docs/ssh_logo.png?raw=true" hspace="5" vspace="5" alt="diskover"><br/>
SSHD securely exposes your Spigot admin console using the SSH protocol - the same protocol that serves as the secure foundation for nearly all remote server administration.
**Have you ever wished you could remotely access your server's admin console without having to setup a complex remote access system? Now you can with Minecraft-SSHD!**
Minecraft-SSHD securely exposes your BungeeCord admin console and the server filesystem using the SSH protocol - the same protocol that serves as the secure foundation for nearly all remote server administration.<br/>
- Compatible with all ssh clients, regardless of operating system.
- Remotely view your server log in real-time.
@ -15,10 +17,10 @@ SSHD securely exposes your Spigot admin console using the SSH protocol - the sam
- Supports multiple concurrent remote connections.
- Strong identity support using public key authentication.
- 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.
### Why should I use SSHD?
### Why should I use Minecraft-SSHD?
- You are in a shared hosting environment that only gives you access to the - log files.
- You want to share access to your server console, but don't want to give anybody access to the machine its running on.
@ -38,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 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
@ -91,8 +93,8 @@ mkpasswd supports the following hash algorithms:
`sshd.mkpasswd` - Checks if the in-game user has access to run the mkpasswd command.
SSHD uses cryptographic certificates or a secure username and password to verify remote access.
Minecraft-SSHD uses cryptographic certificates or a secure username and password to verify remote access.
## Source Code
[Get the source on GitHub](https://github.com/Justasic/Spigot-SSHD "Source Code")
[Get the source on GitHub](https://github.com/Justasic/Minecraft-SSHD "Source Code")

BIN
docs/ssh_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

133
docs/ssh_logo.svg Normal file
View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="800"
height="800"
viewBox="0 0 211.66666 211.66667"
version="1.1"
id="svg8"
sodipodi:docname="ssh_logo.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<defs
id="defs2">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter5273">
<feFlood
flood-opacity="1"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood5263" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite5265" />
<feGaussianBlur
in="composite1"
stdDeviation="3.5"
result="blur"
id="feGaussianBlur5267" />
<feOffset
dx="1"
dy="1"
result="offset"
id="feOffset5269" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite5271" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="375.64503"
inkscape:cy="367.3313"
inkscape:document-units="mm"
inkscape:current-layer="layer3"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1920"
inkscape:window-y="119"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Background"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-85.333343)">
<circle
style="fill:#4c4c4c;fill-opacity:1;stroke-width:0.30369404;filter:url(#filter5273)"
id="path4533"
cx="105"
cy="192.00002"
r="100" />
<circle
style="fill:#242424;fill-opacity:1;stroke-width:0.27332464;"
id="path4533-3"
cx="105"
cy="192.00002"
r="90" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Text"
style="opacity:0.98999999">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:145.88342285px;line-height:0;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.31260732"
x="130.4019"
y="91.524567"
id="text5759"
transform="scale(0.84637592,1.1815081)"><tspan
sodipodi:role="line"
id="tspan5757"
x="130.4019"
y="91.524567"
style="font-size:145.88342285px;line-height:0;fill:#ffffff;fill-opacity:1;stroke-width:0.31260732">_</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:145.88342285px;line-height:0;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;opacity:0.98999999;fill:#20bb00;fill-opacity:1;stroke:none;stroke-width:0.31260732"
x="39.53738"
y="118.43275"
id="text5759-2"
transform="scale(0.84637592,1.1815081)"><tspan
sodipodi:role="line"
id="tspan5757-5"
x="39.53738"
y="118.43275"
style="font-size:123.47222137px;line-height:0;fill:#20bb00;fill-opacity:1;stroke-width:0.31260732">&gt;</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

33
pom.xml
View File

@ -5,9 +5,9 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.ryanmichela</groupId>
<artifactId>sshd</artifactId>
<version>1.3.6.1</version>
<url>https://github.com/Justasic/Bukkit-SSHD/</url>
<artifactId>sshd-bungee</artifactId>
<version>2.0.0</version>
<url>https://github.com/Justasic/Minecraft-SSHD/</url>
<properties>
<java.version>1.8</java.version>
@ -33,9 +33,10 @@
<!-- Dependencies -->
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.14.4-R0.1-SNAPSHOT</version>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -71,7 +72,7 @@
<artifactId>eddsa</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
@ -83,7 +84,7 @@
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
@ -100,14 +101,12 @@
<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>
@ -116,6 +115,18 @@
<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 -->
@ -155,7 +166,7 @@
</descriptorRefs>
</configuration>
</plugin>
<!-- Compile plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -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.getConfig().getString("PasswordType");
String ConfigHash = SshdPlugin.instance.getConfig().getString("Credentials." + username.trim());
String HashType = SshdPlugin.instance.configuration.getString("PasswordType");
String ConfigHash = SshdPlugin.instance.configuration.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.getConfig().getInt("LoginRetries");
Integer tries = SshdPlugin.instance.configuration.getInt("LoginRetries");
try
{

View File

@ -3,42 +3,60 @@ 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() {
CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap");
return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer);
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;
}
};
Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable);
try {
SshdPlugin.instance.getProxy().getScheduler().runAsync(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;

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.Environment;
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.InputStream;
@ -51,14 +54,22 @@ 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);
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());
} finally {
}
finally
{
callback.onExit(0);
}
}

View File

@ -4,7 +4,7 @@ package com.ryanmichela.sshd;
* Copyright 2013 Ryan Michela
*/
import org.bukkit.ChatColor;
import net.md_5.bungee.api.ChatColor;
import org.fusesource.jansi.Ansi;
import java.io.PrintWriter;
@ -15,12 +15,13 @@ 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");
}
@ -96,7 +97,7 @@ public class ConsoleLogFormatter extends Formatter {
stringbuilder.append(stringwriter.toString());
}
return stringbuilder.toString();
return stringbuilder.toString().replace("\n", "\r\n");
}
private void colorize(LogRecord logrecord)

View File

@ -6,10 +6,12 @@ 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;
@ -18,7 +20,6 @@ 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;
@ -32,6 +33,8 @@ 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) {
@ -48,7 +51,7 @@ public class ConsoleShellFactory implements ShellFactory {
private Thread thread;
private String Username;
StreamHandlerAppender streamHandlerAppender;
StreamHandler streamHandler;
public ConsoleReader ConsoleReader;
public SSHDCommandSender SshdCommandSender;
@ -93,10 +96,9 @@ public class ConsoleShellFactory implements ShellFactory {
this.ConsoleReader.setExpandEvents(true);
this.ConsoleReader.addCompleter(new ConsoleCommandCompleter());
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader);
this.streamHandlerAppender = new StreamHandlerAppender(streamHandler);
streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader);
((Logger)LogManager.getRootLogger()).addAppender(this.streamHandlerAppender);
SshdPlugin.instance.getProxy().getLogger().addHandler(this.streamHandler);
this.environment = env;
this.Username = env.getEnv().get(Environment.ENV_USER);
@ -107,22 +109,23 @@ public class ConsoleShellFactory implements ShellFactory {
}
catch (Exception e)
{
e.printStackTrace();
throw new IOException("Error starting shell", e);
}
}
@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()
{
try
{
if (!SshdPlugin.instance.getConfig().getString("Mode").equals("RPC"))
if (!instance.configuration.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;
@ -141,39 +144,42 @@ public class ConsoleShellFactory implements ShellFactory {
// Hide the mkpasswd command input from other users.
Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd");
Bukkit.getScheduler().runTask(
SshdPlugin.instance, () ->
instance.getProxy().getScheduler().runAsync(
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
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
{
if (!mkpasswd)
SshdPlugin.instance.getLogger().info("<" + this.Username + "> " + command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
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());
}
});
}
}
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
{
SshdPlugin.instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH.");
instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH.");
callback.onExit(0);
}
}
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
{
BufferedReader br = new BufferedReader(new FileReader(f));
@ -184,16 +190,18 @@ public class ConsoleShellFactory implements ShellFactory {
}
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.
}
// 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() + " (" + Bukkit.getServer().getName() + ")\r");
cr.println(ConsoleLogFormatter.ColorizeString(Bukkit.getServer().getMotd()).replaceAll("\n", "\r\n"));
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("\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");
}
}

View File

@ -2,6 +2,7 @@ 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;
@ -10,39 +11,50 @@ 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 (SshException ex) {
}
catch (SshChannelClosedException ex)
{
// do nothing
} catch (IOException ex) {
}
catch (IOException ex)
{
Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}

View File

@ -1,19 +1,33 @@
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 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.SshdPlugin;
class MkpasswdCommand implements CommandExecutor
public class MkpasswdCommand extends Command
{
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
public MkpasswdCommand()
{
super("mkpasswd");
}
public 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());
}
@Override
public void execute(CommandSender sender, String[] args)
{
String algoritm, password;
try
@ -22,78 +36,50 @@ class MkpasswdCommand implements CommandExecutor
// 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
}
catch (ArrayIndexOutOfBoundsException e)
{
// ignore it.
return false;
this.SendSyntax(sender, false);
return;
}
// If they're console, allow regardless.
if (!(sender instanceof Player))
{
if (label.equalsIgnoreCase("mkpasswd"))
// 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)
{
try
{
try
String hash = "";
// Dumb but whatever. Some people are really dense.
if (algoritm.equalsIgnoreCase("PLAIN"))
{
// Dumb but whatever. Some people are really dense.
if (algoritm.equalsIgnoreCase("PLAIN"))
{
// I mean c'mon...
sender.sendMessage("Bro really? it's literally your unencrypted password...");
}
else if (algoritm.equalsIgnoreCase("pbkdf2"))
sender.sendMessage("Your hash: " + Cryptography.PBKDF2_HashPassword(password));
else if (algoritm.equalsIgnoreCase("bcrypt"))
sender.sendMessage("Your hash: " + Cryptography.BCrypt_HashPassword(password));
else if (algoritm.equalsIgnoreCase("sha256"))
sender.sendMessage("Your hash: " + Cryptography.SHA256_HashPassword(password));
else if (algoritm.equalsIgnoreCase("help"))
sender.sendMessage("Supported hash algorithms: pbkdf2, bcrypt, sha256, plain");
else
return false;
// I mean c'mon...
sender.sendMessage("\u00A79Your Hash: \u00A7cIt's literally your unhashed password.");
return;
}
catch (Exception e)
else if (algoritm.equalsIgnoreCase("pbkdf2"))
hash = Cryptography.PBKDF2_HashPassword(password);
else if (algoritm.equalsIgnoreCase("bcrypt"))
hash = Cryptography.BCrypt_HashPassword(password);
else if (algoritm.equalsIgnoreCase("sha256"))
hash = Cryptography.SHA256_HashPassword(password);
else
{
// We're console, just print the stack trace.
e.printStackTrace();
return false;
this.SendSyntax(sender, !algoritm.equalsIgnoreCase("help"));
return;
}
return true;
sender.sendMessage(new ComponentBuilder("Your Hash: " + hash).color(ChatColor.BLUE).create());
}
catch (Exception e)
{
// We're console, just print the stack trace.
e.printStackTrace();
}
}
else
{
Player player = (Player) sender;
if (label.equalsIgnoreCase("mkpasswd"))
{
try
{
if (player.hasPermission("sshd.mkpasswd"))
{
// Dumb but whatever. Some people are really dense.
if (algoritm.equalsIgnoreCase("PLAIN"))
sender.sendMessage(password);
else if (algoritm.equalsIgnoreCase("pbkdf2"))
sender.sendMessage(Cryptography.PBKDF2_HashPassword(password));
else if (algoritm.equalsIgnoreCase("bcrypt"))
sender.sendMessage(Cryptography.BCrypt_HashPassword(password));
else if (algoritm.equalsIgnoreCase("sha256"))
sender.sendMessage(Cryptography.SHA256_HashPassword(password));
else
return false;
}
}
catch (Exception e)
{
// since this is a player, send a failure message
sender.sendMessage("An error occured, please check console.");
e.printStackTrace();
return false;
}
return true;
}
}
return false;
}
}
}
}

View File

@ -1,5 +1,6 @@
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;
@ -20,14 +21,13 @@ 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.getConfig().getInt("LoginRetries");
Integer tries = SshdPlugin.instance.configuration.getInt("LoginRetries");
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;
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 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.MkpasswdCommand;
@ -20,16 +25,17 @@ import java.util.logging.Level;
/**
* Copyright 2013 Ryan Michela
*/
public
class SshdPlugin extends JavaPlugin
public final class SshdPlugin extends Plugin
{
private SshServer sshd;
public static SshdPlugin instance;
private File file;
public Configuration configuration;
@Override public void onLoad()
{
saveDefaultConfig();
file = new File(getDataFolder(), "config.yml");
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
if (!authorizedKeys.exists())
authorizedKeys.mkdirs();
@ -48,6 +54,21 @@ class SshdPlugin extends JavaPlugin
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.
@ -59,8 +80,8 @@ class SshdPlugin extends JavaPlugin
instance = this;
sshd = SshServer.setUpDefaultServer();
sshd.setPort(getConfig().getInt("Port", 1025));
String host = getConfig().getString("ListenAddress", "all");
sshd.setPort(configuration.getInt("Port", 1025));
String host = configuration.getString("ListenAddress", "all");
sshd.setHost(host.equals("all") ? null : host);
File hostKey = new File(getDataFolder(), "hostkey");
@ -71,14 +92,14 @@ class SshdPlugin extends JavaPlugin
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
if (getConfig().getBoolean("EnableSFTP"))
if (configuration.getBoolean("EnableSFTP"))
{
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
sshd.setFileSystemFactory(
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());
try
@ -102,4 +123,12 @@ class SshdPlugin extends JavaPlugin
// 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,139 +1,114 @@
package com.ryanmichela.sshd.implementations;
import com.ryanmichela.sshd.SshdPlugin;
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 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 com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.ConsoleLogFormatter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
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 final class SSHDCommandSender implements CommandSender
{
public ConsoleShellFactory.ConsoleShell console;
public void sendMessage(String message) {
this.sendRawMessage(message);
@Override
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(ChatColor.stripColor(message));
}
this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replace("\n", "\n\r"));
this.console.ConsoleReader.print(this.console.ConsoleReader.RESET_LINE + "");
this.console.ConsoleReader.flush();
try
{
this.console.ConsoleReader.drawLine();
}
catch (Throwable ex)
{
this.console.ConsoleReader.getCursorBuffer().clear();
}
this.console.ConsoleReader.flush();
}
catch (IOException e)
{
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
}
}
public void sendMessage(String[] messages) {
@Override
public void sendMessages(String... messages)
{
Arrays.asList(messages).forEach(this::sendMessage);
}
public String getName() {
return "SSHD Console";
@Override
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;
}
public void setOp(boolean value) {
throw new UnsupportedOperationException("Cannot change operator status of server console");
@Override
public void setPermission(String permission, boolean value)
{
throw new UnsupportedOperationException("Console has all permissions");
}
public boolean beginConversation(Conversation conversation) {
return this.conversationTracker.beginConversation(conversation);
@Override
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

@ -1,6 +1,6 @@
name: SSHD
version: ${project.version}
author: Ryan Michela, Haarolean, toxuin, Justin Crawford
author: Ryan Michela, Haarolean, toxuin, Justin Crawford, Zachery Coleman
main: com.ryanmichela.sshd.SshdPlugin
commands:
mkpasswd: