Compare commits
14 Commits
1.3.6
...
bungeecord
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
766fb45c46 | ||
|
|
08528c5127 | ||
|
|
8f9cbd55bc | ||
|
|
33f34ad54d | ||
|
|
b51a03e2d5 | ||
|
|
d38c75dd9e | ||
|
|
1c6028199b | ||
|
|
047369105e | ||
|
|
586910a75c | ||
|
|
7618333bf3 | ||
|
|
96bb1a5fa8 | ||
|
|
e986cacc50 | ||
|
|
0afba39d57 | ||
|
|
1b4c7c2304 |
@@ -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'
|
||||
108
README.md
108
README.md
@@ -1,70 +1,100 @@
|
||||
Spigot-SSHD
|
||||
===========
|
||||
Minecraft-SSHD (BungeeCord Edition!)
|
||||
=================================
|
||||
|
||||
[](https://travis-ci.org/Justasic/Spigot-SSHD)
|
||||
[](https://travis-ci.org/Justasic/Minecraft-SSHD)
|
||||
[](https://github.com/Justasic/Minecraft-SSHD/releases/latest)
|
||||
[](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.
|
||||
- Remotely issue commands from the server console, just as if you were on the server itself.
|
||||
- Supports multiple concurrent remote connections.
|
||||
- Strong identity support using public key authentication.
|
||||
- Audit history who is running commands in the console
|
||||
- Run BungeeCord without using screen or tmux (by adding `-noconsole`)
|
||||
- Remotely script your server by issuing one-off console commands with ssh.
|
||||
|
||||
## Why should I use SSHD?
|
||||
### Why should I use Minecraft-SSHD?
|
||||
|
||||
- Your server runs on Windows.
|
||||
- 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.
|
||||
- You always wanted to use RCON, but want to see the server log as well.
|
||||
- You are tired of running your server in a Screen session.
|
||||
- You are tired of running your server in a GNU screen or tmux session.
|
||||
- You just want to access your server console using SSH.
|
||||
|
||||
## Configuration
|
||||
|
||||
- **listenAddress** - The network interface(s) SSHD should listen on. (Default all)
|
||||
- **port** - Specify the port SSHD should listen on. (Default 22)
|
||||
- **username/password** - The credentials used to log into the server console. (Default blank)
|
||||
|
||||
Note: By default, only public key authentication is enabled. This is the most secure authentication mode! Setting a username and password will make your server less secure.
|
||||
|
||||
## Setting Up Public Key Authentication
|
||||
Screenshots
|
||||
============
|
||||
|
||||
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.
|
||||
<img align="left" width="390" src="docs/console.png?raw=true" hspace="5" vspace="5" alt="console">
|
||||
<img width="400" src="docs/session.png?raw=true" alt="session"><br>
|
||||
|
||||
On Windows
|
||||
|
||||
1. TODO
|
||||
Setting Up Public Key Authentication
|
||||
====================================
|
||||
|
||||
On Linux/OS X
|
||||
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.
|
||||
|
||||
1. TODO
|
||||
## Generating New Keys
|
||||
|
||||
#### On Windows
|
||||
|
||||
1. Ensure [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) is installed and open up `puttygen` (you can search for it in start search).
|
||||
2. Click `Generate` and follow the directions.
|
||||
3. When it finishes, set your key comment (if you like) and copy the text from the big `Public key for pasting into OpenSSH authorized_keys file`
|
||||
4. Create a new file inside of the `plugins/SSHD/authorized_users` folder and name the file just the username (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
|
||||
5. Paste the key you copied from step 3 into the file you just created.
|
||||
6. SSH into the server and see if your key works
|
||||
|
||||
#### On Linux/OS X
|
||||
|
||||
1. Open a terminal and run `ssh-keygen` then follow the prompts.
|
||||
2. Copy the contents of your `id_<algorithm>.pub` file (example: if your key was generated with rsa, it will be named `id_rsa.pub`). This file is usually located in `/home/YOURUSERNAME/.ssh/`
|
||||
3. Paste the contents of the .pub file into a new file inside the `plugins/SSHD/authorized_users` folder and name the file just the username that the user will use to login with (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
|
||||
|
||||
## Using existing keys
|
||||
|
||||
#### On Windows
|
||||
|
||||
1. Ensure [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) is installed and open up `puttygen` (you can search for it in start search).
|
||||
2. Click `Conversions` then click `Import Key` and select your .ppk file.
|
||||
3. Copy the text from the big `Public key for pasting into OpenSSH authorized_keys file`
|
||||
4. Create a new file inside of the `plugins/SSHD/authorized_users` folder and name the file just the username (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
|
||||
5. Paste the key you copied from step 3 into the file you just created.
|
||||
6. SSH into the server and see if your key works
|
||||
|
||||
#### On Linux/OS X
|
||||
|
||||
1. Copy the contents of your `id_<algorithm>.pub` file (example: if your key was generated with rsa, it will be named `id_rsa.pub`). This file is usually located in `/home/YOURUSERNAME/.ssh/`
|
||||
2. Paste the contents of the .pub file into a new file inside the `plugins/SSHD/authorized_users` folder and name the file just the username that the user will use to login with (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
|
||||
|
||||
Plugin Usage
|
||||
============
|
||||
|
||||
## Commands
|
||||
None - just install and go.
|
||||
|
||||
/mkpasswd <hash|help> <password>
|
||||
|
||||
mkpasswd supports the following hash algorithms:
|
||||
|
||||
- bcrypt - Using the OpenBSD-style Blowfish password hash
|
||||
- sha256 - Using a basic salted sha256 hash
|
||||
- pbkdf2 - Using the [PBKDF2](https://en.wikipedia.org/wiki/Pbkdf2) password hash
|
||||
- PLAIN - Using plain text passwords (very insecure)
|
||||
|
||||
## Permissions
|
||||
|
||||
None - SSHD uses cryptographic certificates or a secure username and password to verify remote access.
|
||||
`sshd.mkpasswd` - Checks if the in-game user has access to run the mkpasswd command.
|
||||
|
||||
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")
|
||||
|
||||
## Metrics
|
||||
|
||||
This plugin utilizes Hidendra's plugin metrics system. the following information is collected and sent to mcstats.org unless opted out:
|
||||
|
||||
- A unique identifier
|
||||
- The server's version of Java
|
||||
- Whether the server is in offline or online mode
|
||||
- Plugin's version
|
||||
- Server's version
|
||||
- OS version/name and architecture
|
||||
- core count for the CPU
|
||||
- Number of players online
|
||||
- Metrics version
|
||||
|
||||
Opting out of this service can be done by editing plugins/Plugin Metrics/config.yml and changing opt-out to true.
|
||||
[Get the source on GitHub](https://github.com/Justasic/Minecraft-SSHD "Source Code")
|
||||
|
||||
BIN
docs/console.png
Normal file
BIN
docs/console.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 355 KiB |
BIN
docs/session.png
Normal file
BIN
docs/session.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
BIN
docs/ssh_logo.png
Normal file
BIN
docs/ssh_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
133
docs/ssh_logo.svg
Normal file
133
docs/ssh_logo.svg
Normal 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">></tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
35
pom.xml
35
pom.xml
@@ -5,9 +5,9 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.ryanmichela</groupId>
|
||||
<artifactId>sshd</artifactId>
|
||||
<version>1.3.6</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>
|
||||
@@ -171,4 +182,4 @@
|
||||
</build>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -67,8 +68,7 @@ public class ConsoleLogFormatter extends Formatter {
|
||||
return result;
|
||||
}
|
||||
|
||||
public
|
||||
String format(LogRecord logrecord)
|
||||
public String format(LogRecord logrecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -97,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)
|
||||
|
||||
@@ -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;
|
||||
@@ -138,47 +141,45 @@ public class ConsoleShellFactory implements ShellFactory {
|
||||
this.ConsoleReader.clearScreen();
|
||||
continue;
|
||||
}
|
||||
// Hide the mkpasswd command input.
|
||||
// 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 + "> <" + (mkpasswd ? "True": "False") + "> " + 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());
|
||||
}
|
||||
});
|
||||
}
|
||||
// This should help stop one of the bugs where bytes are waiting to be written
|
||||
// but the client fucked off already so the plugin throws an exception.
|
||||
((Logger)LogManager.getRootLogger()).removeAppender(this.streamHandlerAppender);
|
||||
}
|
||||
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));
|
||||
@@ -189,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,59 @@
|
||||
package com.ryanmichela.sshd;
|
||||
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.channel.exception.SshChannelClosedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Copyright 2013 Ryan Michela
|
||||
*/
|
||||
public class FlushyOutputStream extends OutputStream {
|
||||
public class FlushyOutputStream extends OutputStream
|
||||
{
|
||||
|
||||
private OutputStream base;
|
||||
private boolean isClosed = false;
|
||||
|
||||
public FlushyOutputStream(OutputStream base) {
|
||||
public FlushyOutputStream(OutputStream base)
|
||||
{
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
if (isClosed) return;
|
||||
base.write(b);
|
||||
base.flush();
|
||||
public void write(int b) throws IOException
|
||||
{
|
||||
this.write(BigInteger.valueOf(b).toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
if (isClosed) return;
|
||||
base.write(b);
|
||||
base.flush();
|
||||
public void write(byte[] b) throws IOException
|
||||
{
|
||||
this.write(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if (isClosed) return;
|
||||
try {
|
||||
public void write(byte[] b, int off, int len) throws IOException
|
||||
{
|
||||
if (isClosed)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
base.write(b, off, len);
|
||||
base.flush();
|
||||
} catch (SshException e) {
|
||||
if (!e.getMessage().contains("channel already closed")) throw e;
|
||||
}
|
||||
catch (SshChannelClosedException e)
|
||||
{
|
||||
// ignored.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
public void close() throws IOException
|
||||
{
|
||||
isClosed = true;
|
||||
base.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,79 +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"))
|
||||
{
|
||||
sender.sendMessage("Your hash: " + password);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -58,10 +79,9 @@ class SshdPlugin extends JavaPlugin
|
||||
{
|
||||
instance = this;
|
||||
|
||||
// pls comment this shit so I know what it does.
|
||||
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");
|
||||
@@ -72,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
|
||||
@@ -103,4 +123,12 @@ class SshdPlugin extends JavaPlugin
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
public static SshdPlugin getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static void setInstance(SshdPlugin instance) {
|
||||
SshdPlugin.instance = instance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user