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' TabWidth: '4'
IndentWidth: '4' IndentWidth: '4'
UseTab: 'Always' UseTab: 'Always'
@ -7,6 +7,7 @@ AlignOperands: 'true'
AlignAfterOpenBracket: 'Align' AlignAfterOpenBracket: 'Align'
AlignConsecutiveAssignments: 'true' AlignConsecutiveAssignments: 'true'
AlignConsecutiveDeclarations: 'true' AlignConsecutiveDeclarations: 'true'
AlignConsecutiveMacros: 'true'
AlignEscapedNewlines: 'Left' AlignEscapedNewlines: 'Left'
AlignTrailingComments: 'true' AlignTrailingComments: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'true' AllowAllParametersOfDeclarationOnNextLine: 'true'
@ -20,35 +21,20 @@ AlwaysBreakTemplateDeclarations: 'true'
AlwaysBreakBeforeMultilineStrings: 'false' AlwaysBreakBeforeMultilineStrings: 'false'
BinPackArguments: 'false' BinPackArguments: 'false'
BinPackParameters: 'false' BinPackParameters: 'false'
BreakBeforeBraces: 'Custom' BreakBeforeBraces: 'Allman'
BraceWrapping:
AfterEnum: 'true'
AfterClass: 'true'
AfterControlStatement: 'true'
AfterStruct: 'true'
AfterFunction: 'true'
AfterNamespace: 'true'
AfterUnion: 'true'
AfterExternBlock: 'true'
BeforeCatch: 'true'
BeforeElse: 'true'
SplitEmptyRecord: 'false'
SplitEmptyNamespace: 'false'
SplitEmptyFunction: 'false'
BreakBeforeBinaryOperators: 'true' BreakBeforeBinaryOperators: 'true'
BreakBeforeTernaryOperators: 'false' BreakBeforeTernaryOperators: 'false'
BreakConstructorInitializersBeforeComma: 'false' BreakConstructorInitializers: 'AfterColon'
BreakBeforeInheritanceComma: 'false' BreakBeforeInheritanceComma: 'false'
BreakAfterJavaFieldAnnotations: 'true'
BreakStringLiterals: 'true' BreakStringLiterals: 'true'
ColumnLimit: '140' ColumnLimit: '140'
CompactNamespaces: 'false' CompactNamespaces: 'false'
Cpp11BracedListStyle: 'true'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
DerivePointerAlignment: 'false' DerivePointerAlignment: 'false'
IndentCaseLabels: 'true' IndentCaseLabels: 'true'
IndentPPDirectives: 'AfterHash' IndentPPDirectives: 'AfterHash'
KeepEmptyLinesAtTheStartOfBlocks: 'true' KeepEmptyLinesAtTheStartOfBlocks: 'true'
Language: 'Java'
NamespaceIndentation: 'All' NamespaceIndentation: 'All'
PointerAlignment: 'Right' PointerAlignment: 'Right'
ReflowComments: 'true' ReflowComments: 'true'

View File

@ -1,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) [![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/Spigot-SSHD.svg?label=Release&maxAge=60)](https://github.com/Justasic/Spigot-SSHD/releases/latest) [![Release](https://img.shields.io/github/release/Justasic/Minecraft-SSHD.svg?label=Release&maxAge=60)](https://github.com/Justasic/Minecraft-SSHD/releases/latest)
[![GitHub license](https://img.shields.io/github/license/Justasic/Spigot-SSHD)](https://github.com/Justasic/Spigot-SSHD/blob/master/LICENSE) [![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. - Compatible with all ssh clients, regardless of operating system.
- Remotely view your server log in real-time. - 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. - Supports multiple concurrent remote connections.
- Strong identity support using public key authentication. - Strong identity support using public key authentication.
- Audit history who is running commands in the console - Audit history who is running commands in the console
- Run Spigot without using screen or tmux (by adding `-noconsole`) - Run BungeeCord without using screen or tmux (by adding `-noconsole`)
- Remotely script your server by issuing one-off console commands with ssh. - Remotely script your server by issuing one-off console commands with ssh.
### Why should I use SSHD? ### Why should I use Minecraft-SSHD?
- You are in a shared hosting environment that only gives you access to the - log files. - 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 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
==================================== ====================================
Setting up public key authentication with SSH requires first generating a public and private key pair and then installing just the public key on your Spigot server. This plugin supports all modern SSH key algoritms as OpenSSH. You can paste as many public keys from the methods below into each user's authorization file if they have multiple private keys. You can read [this guide from ssh.com](https://www.ssh.com/ssh/keygen/) if you want a better explanation on different key files. Setting up public key authentication with SSH requires first generating a public and private key pair and then installing just the public key on your BungeeCord server. This plugin supports all modern SSH key algoritms as OpenSSH. You can paste as many public keys from the methods below into each user's authorization file if they have multiple private keys. You can read [this guide from ssh.com](https://www.ssh.com/ssh/keygen/) if you want a better explanation on different key files.
## Generating New Keys ## Generating New Keys
@ -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.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 ## 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

27
pom.xml
View File

@ -5,9 +5,9 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.ryanmichela</groupId> <groupId>com.ryanmichela</groupId>
<artifactId>sshd</artifactId> <artifactId>sshd-bungee</artifactId>
<version>1.3.6.1</version> <version>2.0.0</version>
<url>https://github.com/Justasic/Bukkit-SSHD/</url> <url>https://github.com/Justasic/Minecraft-SSHD/</url>
<properties> <properties>
<java.version>1.8</java.version> <java.version>1.8</java.version>
@ -33,9 +33,10 @@
<!-- Dependencies --> <!-- Dependencies -->
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.bukkit</groupId> <groupId>net.md-5</groupId>
<artifactId>bukkit</artifactId> <artifactId>bungeecord-api</artifactId>
<version>1.14.4-R0.1-SNAPSHOT</version> <version>1.14-SNAPSHOT</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -100,14 +101,12 @@
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>2.0</version> <version>2.0</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>2.1</version> <version>2.1</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -116,6 +115,18 @@
<version>1.10</version> <version>1.10</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
</dependencies> </dependencies>
<!-- Build --> <!-- Build -->

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ package com.ryanmichela.sshd;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
import org.bukkit.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -15,12 +15,13 @@ import java.util.Map;
import java.util.logging.Formatter; import java.util.logging.Formatter;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
public class ConsoleLogFormatter extends Formatter { public class ConsoleLogFormatter extends Formatter
{
private SimpleDateFormat dateFormat; private SimpleDateFormat dateFormat;
private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class); private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
public ConsoleLogFormatter() { public ConsoleLogFormatter()
{
this.dateFormat = new SimpleDateFormat("HH:mm:ss"); this.dateFormat = new SimpleDateFormat("HH:mm:ss");
} }
@ -96,7 +97,7 @@ public class ConsoleLogFormatter extends Formatter {
stringbuilder.append(stringwriter.toString()); stringbuilder.append(stringwriter.toString());
} }
return stringbuilder.toString(); return stringbuilder.toString().replace("\n", "\r\n");
} }
private void colorize(LogRecord logrecord) 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.FlushyStreamHandler;
import com.ryanmichela.sshd.SshTerminal; import com.ryanmichela.sshd.SshTerminal;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
import com.ryanmichela.sshd.StreamHandlerAppender;
import com.ryanmichela.sshd.implementations.SSHDCommandSender; import com.ryanmichela.sshd.implementations.SSHDCommandSender;
import com.ryanmichela.sshd.ConsoleLogFormatter; import com.ryanmichela.sshd.ConsoleLogFormatter;
import jline.console.ConsoleReader; import jline.console.ConsoleReader;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.Logger;
import org.apache.sshd.common.Factory; import org.apache.sshd.common.Factory;
@ -18,7 +20,6 @@ import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.channel.ChannelSession; import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.Environment; import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.ExitCallback;
import org.bukkit.Bukkit;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -32,6 +33,8 @@ import java.util.StringTokenizer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.StreamHandler; import java.util.logging.StreamHandler;
import static com.ryanmichela.sshd.SshdPlugin.instance;
public class ConsoleShellFactory implements ShellFactory { public class ConsoleShellFactory implements ShellFactory {
public Command createShell(ChannelSession cs) { public Command createShell(ChannelSession cs) {
@ -48,7 +51,7 @@ public class ConsoleShellFactory implements ShellFactory {
private Thread thread; private Thread thread;
private String Username; private String Username;
StreamHandlerAppender streamHandlerAppender; StreamHandler streamHandler;
public ConsoleReader ConsoleReader; public ConsoleReader ConsoleReader;
public SSHDCommandSender SshdCommandSender; public SSHDCommandSender SshdCommandSender;
@ -93,10 +96,9 @@ public class ConsoleShellFactory implements ShellFactory {
this.ConsoleReader.setExpandEvents(true); this.ConsoleReader.setExpandEvents(true);
this.ConsoleReader.addCompleter(new ConsoleCommandCompleter()); this.ConsoleReader.addCompleter(new ConsoleCommandCompleter());
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader); streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader);
this.streamHandlerAppender = new StreamHandlerAppender(streamHandler);
((Logger)LogManager.getRootLogger()).addAppender(this.streamHandlerAppender); SshdPlugin.instance.getProxy().getLogger().addHandler(this.streamHandler);
this.environment = env; this.environment = env;
this.Username = env.getEnv().get(Environment.ENV_USER); this.Username = env.getEnv().get(Environment.ENV_USER);
@ -107,22 +109,23 @@ public class ConsoleShellFactory implements ShellFactory {
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace();
throw new IOException("Error starting shell", e); throw new IOException("Error starting shell", e);
} }
} }
@Override @Override
public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(this.streamHandlerAppender); } public void destroy(ChannelSession cs) { SshdPlugin.instance.getProxy().getLogger().removeHandler(this.streamHandler); }
public void run() public void run()
{ {
try try
{ {
if (!SshdPlugin.instance.getConfig().getString("Mode").equals("RPC")) if (!instance.configuration.getString("Mode").equals("RPC"))
printPreamble(this.ConsoleReader); printPreamble(this.ConsoleReader);
while (true) while (true)
{ {
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. // The user sent CTRL+D to close the shell, terminate the session.
if (command == null) if (command == null)
break; break;
@ -141,39 +144,42 @@ public class ConsoleShellFactory implements ShellFactory {
// Hide the mkpasswd command input from other users. // Hide the mkpasswd command input from other users.
Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd"); Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd");
Bukkit.getScheduler().runTask( instance.getProxy().getScheduler().runAsync(
SshdPlugin.instance, () -> instance, () ->
{ {
if (SshdPlugin.instance.getConfig().getString("Mode").equals("RPC") && command.startsWith("rpc")) if (instance.configuration.getString("Mode").equals("RPC") && command.startsWith("rpc"))
{ {
// NO ECHO NO PREAMBLE AND SHIT // NO ECHO NO PREAMBLE AND SHIT
String cmd = command.substring("rpc".length() + 1, command.length()); String cmd = command.substring("rpc".length() + 1, command.length());
Bukkit.dispatchCommand(this.SshdCommandSender, cmd); if (!instance.getProxy().getPluginManager().dispatchCommand(this.SshdCommandSender, cmd))
instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
} }
else else
{ {
if (!mkpasswd) if (!mkpasswd)
SshdPlugin.instance.getLogger().info("<" + this.Username + "> " + command); instance.getLogger().info("<" + this.Username + "> " + command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); if (!instance.getProxy().getPluginManager().dispatchCommand(this.SshdCommandSender, command))
instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
} }
}); });
} }
} }
catch (IOException e) catch (IOException e)
{ {
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e); e.printStackTrace();
instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
} }
finally finally
{ {
SshdPlugin.instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH."); instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH.");
callback.onExit(0); callback.onExit(0);
} }
} }
private void printPreamble(ConsoleReader cr) throws IOException private void printPreamble(ConsoleReader cr) throws IOException
{ {
File f = new File(SshdPlugin.instance.getDataFolder(), "motd.txt"); File f = new File(instance.getDataFolder(), "motd.txt");
try try
{ {
BufferedReader br = new BufferedReader(new FileReader(f)); BufferedReader br = new BufferedReader(new FileReader(f));
@ -184,16 +190,18 @@ public class ConsoleShellFactory implements ShellFactory {
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {
SshdPlugin.instance.getLogger().log(Level.WARNING, "Could not open " + f + ": File does not exist."); instance.getLogger().log(Level.WARNING, "Could not open " + f + ": File does not exist.");
// Not showing the SSH motd is not a fatal failure, let the session continue. // Not showing the SSH motd is not a fatal failure, let the session continue.
} }
// Doesn't really guarantee our actual system hostname but // Doesn't really guarantee our actual system hostname but
// it's better than not having one at all. // it's better than not having one at all.
cr.println("Connected to: " + InetAddress.getLocalHost().getHostName() + " (" + Bukkit.getServer().getName() + ")\r"); cr.println("Connected to: " + InetAddress.getLocalHost().getHostName() + " (BungeeCord)\r");
cr.println(ConsoleLogFormatter.ColorizeString(Bukkit.getServer().getMotd()).replaceAll("\n", "\r\n")); // Since BungeeCord is stupid, we have to parse the config file and the the MOTD from it that way...
// If you try to use the method getMotd() it returns that it can't be referenced from a non-static context, which is stupid.
cr.println(ConsoleLogFormatter.ColorizeString(instance.configuration.getString("motd")).replaceAll("\n", "\r\n"));
cr.println("\r"); cr.println("\r");
cr.println("Type 'exit' to exit the shell." + "\r"); cr.println("Type 'exit' or press Ctrl+D exit the shell." + "\r");
cr.println("===============================================" + "\r"); cr.println("===============================================" + "\r");
} }
} }

View File

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

View File

@ -1,19 +1,33 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.Command;
import org.bukkit.entity.Player;
import java.util.Arrays; import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import com.ryanmichela.sshd.Cryptography; import com.ryanmichela.sshd.Cryptography;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
class MkpasswdCommand implements CommandExecutor public class MkpasswdCommand extends Command
{ {
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 @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) public void execute(CommandSender sender, String[] args)
{ {
String algoritm, password; String algoritm, password;
try try
@ -22,78 +36,50 @@ class MkpasswdCommand implements CommandExecutor
// spaces in their passwords otherwise it won't be as strong as it should be. // spaces in their passwords otherwise it won't be as strong as it should be.
algoritm = args[0]; algoritm = args[0];
password = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); password = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (password.length() == 0)
throw new ArrayIndexOutOfBoundsException(); // shortcut
} }
catch (ArrayIndexOutOfBoundsException e) catch (ArrayIndexOutOfBoundsException e)
{ {
// ignore it. this.SendSyntax(sender, false);
return false; return;
} }
// If they're console, allow regardless. // If they're a player, check and make sure they have a permission
if (!(sender instanceof Player)) // If they're not a player (aka, the console), just return true.
{ boolean hasperm = (sender instanceof ProxiedPlayer) ? ((ProxiedPlayer)sender).hasPermission("sshd.mkpasswd") : true;
if (label.equalsIgnoreCase("mkpasswd"))
if (hasperm)
{ {
try try
{ {
String hash = "";
// Dumb but whatever. Some people are really dense. // Dumb but whatever. Some people are really dense.
if (algoritm.equalsIgnoreCase("PLAIN")) if (algoritm.equalsIgnoreCase("PLAIN"))
{ {
// I mean c'mon... // I mean c'mon...
sender.sendMessage("Bro really? it's literally your unencrypted password..."); sender.sendMessage("\u00A79Your Hash: \u00A7cIt's literally your unhashed password.");
return;
} }
else if (algoritm.equalsIgnoreCase("pbkdf2")) else if (algoritm.equalsIgnoreCase("pbkdf2"))
sender.sendMessage("Your hash: " + Cryptography.PBKDF2_HashPassword(password)); hash = Cryptography.PBKDF2_HashPassword(password);
else if (algoritm.equalsIgnoreCase("bcrypt")) else if (algoritm.equalsIgnoreCase("bcrypt"))
sender.sendMessage("Your hash: " + Cryptography.BCrypt_HashPassword(password)); hash = Cryptography.BCrypt_HashPassword(password);
else if (algoritm.equalsIgnoreCase("sha256")) else if (algoritm.equalsIgnoreCase("sha256"))
sender.sendMessage("Your hash: " + Cryptography.SHA256_HashPassword(password)); hash = Cryptography.SHA256_HashPassword(password);
else if (algoritm.equalsIgnoreCase("help"))
sender.sendMessage("Supported hash algorithms: pbkdf2, bcrypt, sha256, plain");
else else
return false; {
this.SendSyntax(sender, !algoritm.equalsIgnoreCase("help"));
return;
}
sender.sendMessage(new ComponentBuilder("Your Hash: " + hash).color(ChatColor.BLUE).create());
} }
catch (Exception e) catch (Exception e)
{ {
// We're console, just print the stack trace. // We're console, just print the stack trace.
e.printStackTrace(); e.printStackTrace();
return false;
}
return true;
} }
} }
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; package com.ryanmichela.sshd;
import net.md_5.bungee.config.Configuration;
import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.ArrayUtils;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
@ -20,14 +21,13 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator
{ {
private File authorizedKeysDir; private File authorizedKeysDir;
private Map<String, Integer> FailCounts = new HashMap<String, Integer>(); private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; } public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
@Override public boolean authenticate(String username, PublicKey key, ServerSession session) @Override public boolean authenticate(String username, PublicKey key, ServerSession session)
{ {
byte[] keyBytes = key.getEncoded(); byte[] keyBytes = key.getEncoded();
File keyFile = new File(authorizedKeysDir, username); File keyFile = new File(authorizedKeysDir, username);
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries"); Integer tries = SshdPlugin.instance.configuration.getInt("LoginRetries");
if (keyFile.exists()) if (keyFile.exists())
{ {

View File

@ -1,96 +0,0 @@
package com.ryanmichela.sshd;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Copyright 2013 Ryan Michela
*/
public class ReflectionUtil {
public static void setProtectedValue(Object o, String field, Object newValue) {
setProtectedValue(o.getClass(), o, field, newValue);
}
public static void setProtectedValue(Class c, String field, Object newValue) {
setProtectedValue(c, null, field, newValue);
}
public static void setProtectedValue(Class c, Object o, String field, Object newValue) {
try {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.set(o, newValue);
} catch (NoSuchFieldException | IllegalAccessException ex) {
System.out.println("*** " + c.getName() + ":" + ex);
}
}
public static <T> T getProtectedValue(Object obj, String fieldName) {
try {
Class c = obj.getClass();
while (c != Object.class) {
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
if (f.getName() == fieldName) {
f.setAccessible(true);
return (T) f.get(obj);
}
}
c = c.getSuperclass();
}
System.out.println("*** " + obj.getClass().getName() + ":No such field");
return null;
} catch (Exception ex) {
System.out.println("*** " + obj.getClass().getName() + ":" + ex);
return null;
}
}
public static <T> T getProtectedValue(Class c, String field) {
try {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
return (T) f.get(c);
} catch (Exception ex) {
System.out.println("*** " + c.getName() + ":" + ex);
return null;
}
}
public static Object invokeProtectedMethod(Class c, String method, Object... args) {
return invokeProtectedMethod(c, null, method, args);
}
public static Object invokeProtectedMethod(Object o, String method, Object... args) {
return invokeProtectedMethod(o.getClass(), o, method, args);
}
public static Object invokeProtectedMethod(Class c, Object o, String method, Object... args) {
try {
Class[] pTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Integer) {
pTypes[i] = int.class;
} else {
pTypes[i] = args[i].getClass();
}
}
Method m = c.getDeclaredMethod(method, pTypes);
m.setAccessible(true);
return m.invoke(o, args);
} catch (Exception ex) {
System.out.println("*** " + c.getName() + "." + method + "(): " + ex);
return null;
}
}
}

View File

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

View File

@ -1,92 +0,0 @@
package com.ryanmichela.sshd;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.ErrorHandler;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import java.io.Serializable;
import java.util.UUID;
import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;
/**
* Copyright 2014 Ryan Michela
*/
public class StreamHandlerAppender implements Appender {
private StreamHandler streamHandler;
private UUID uuid;
public StreamHandlerAppender(StreamHandler streamHandler) {
this.streamHandler = streamHandler;
uuid = UUID.randomUUID();
}
@Override
public void append(LogEvent logEvent) {
java.util.logging.Level level;
if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.DEBUG)) {
level = java.util.logging.Level.FINE;
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.INFO)) {
level = java.util.logging.Level.INFO;
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.WARN)) {
level = java.util.logging.Level.WARNING;
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.ERROR)) {
level = java.util.logging.Level.SEVERE;
} else {
level = java.util.logging.Level.INFO;
}
String message = logEvent.getMessage().getFormattedMessage();
LogRecord logRecord = new LogRecord(level, message);
streamHandler.publish(logRecord);
}
@Override
public String getName() {
return "StreamHandlerAppender:" + uuid.toString();
}
@Override
public Layout<? extends Serializable> getLayout() {
return null;
}
@Override
public boolean ignoreExceptions() {
return false;
}
@Override
public ErrorHandler getHandler() {
return null;
}
@Override
public void setHandler(ErrorHandler errorHandler) {
}
@Override
public void start() {
}
@Override
public void stop() {
}
@Override
public boolean isStarted() {
return true;
}
@Override
public boolean isStopped() {
return false;
}
}

View File

@ -1,46 +1,50 @@
package com.ryanmichela.sshd.implementations; package com.ryanmichela.sshd.implementations;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
import org.bukkit.Bukkit; import lombok.AccessLevel;
import org.bukkit.ChatColor; import lombok.NoArgsConstructor;
import org.bukkit.Server; import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.CommandSender; import net.md_5.bungee.api.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import net.md_5.bungee.api.ProxyServer;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationAbandonedEvent;
import org.bukkit.conversations.ManuallyAbandonedConversationCanceller;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import com.ryanmichela.sshd.ConsoleShellFactory; import com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.ConsoleLogFormatter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set; import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level; import java.util.logging.Level;
public class SSHDCommandSender implements ConsoleCommandSender, CommandSender { public final class SSHDCommandSender implements CommandSender
{
private final PermissibleBase perm = new PermissibleBase(this);
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
// Set by the upstream allocating function
public ConsoleShellFactory.ConsoleShell console; public ConsoleShellFactory.ConsoleShell console;
public void sendMessage(String message) { @Override
this.sendRawMessage(message); public void sendMessage(String message)
{
this.sendRawMessage(message + "\r");
} }
public void sendRawMessage(String message) public void sendRawMessage(String message)
{ {
// What the fuck does this code even do? Are we sending to one client or all of them?
if (this.console.ConsoleReader == null) if (this.console.ConsoleReader == null)
return; return;
try try
{ {
this.console.ConsoleReader.println(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) catch (IOException e)
{ {
@ -48,92 +52,63 @@ public class SSHDCommandSender implements ConsoleCommandSender, CommandSender {
} }
} }
public void sendMessage(String[] messages) { @Override
public void sendMessages(String... messages)
{
Arrays.asList(messages).forEach(this::sendMessage); Arrays.asList(messages).forEach(this::sendMessage);
} }
public String getName() { @Override
return "SSHD Console"; public void sendMessage(BaseComponent... message)
{
sendMessage(BaseComponent.toLegacyText(message));
} }
public boolean isOp() { @Override
public void sendMessage(BaseComponent message)
{
sendMessage(message.toLegacyText());
}
@Override
public String getName()
{
return "SSHD CONSOLE";
}
@Override
public Collection<String> getGroups()
{
return Collections.emptySet();
}
@Override
public void addGroups(String... groups)
{
throw new UnsupportedOperationException("Console may not have groups");
}
@Override
public void removeGroups(String... groups)
{
throw new UnsupportedOperationException("Console may not have groups");
}
@Override
public boolean hasPermission(String permission)
{
return true; return true;
} }
public void setOp(boolean value) { @Override
throw new UnsupportedOperationException("Cannot change operator status of server console"); public void setPermission(String permission, boolean value)
{
throw new UnsupportedOperationException("Console has all permissions");
} }
public boolean beginConversation(Conversation conversation) { @Override
return this.conversationTracker.beginConversation(conversation); public Collection<String> getPermissions()
{
return Collections.emptySet();
} }
public void abandonConversation(Conversation conversation) {
this.conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
}
public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
this.conversationTracker.abandonConversation(conversation, details);
}
public void acceptConversationInput(String input) {
this.conversationTracker.acceptConversationInput(input);
}
public boolean isConversing() {
return this.conversationTracker.isConversing();
}
public boolean isPermissionSet(String name) {
return this.perm.isPermissionSet(name);
}
public boolean isPermissionSet(Permission perm) {
return this.perm.isPermissionSet(perm);
}
public boolean hasPermission(String name) {
return this.perm.hasPermission(name);
}
public boolean hasPermission(Permission perm) {
return this.perm.hasPermission(perm);
}
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
return this.perm.addAttachment(plugin, name, value);
}
public PermissionAttachment addAttachment(Plugin plugin) {
return this.perm.addAttachment(plugin);
}
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) {
return this.perm.addAttachment(plugin, name, value, ticks);
}
public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
return this.perm.addAttachment(plugin, ticks);
}
public void removeAttachment(PermissionAttachment attachment) {
this.perm.removeAttachment(attachment);
}
public void recalculatePermissions() {
this.perm.recalculatePermissions();
}
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
return this.perm.getEffectivePermissions();
}
public boolean isPlayer() {
return false;
}
public Server getServer() {
return Bukkit.getServer();
}
} }

View File

@ -1,78 +0,0 @@
package com.ryanmichela.sshd.implementations;
import org.bukkit.Bukkit;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationAbandonedEvent;
import org.bukkit.conversations.ManuallyAbandonedConversationCanceller;
import java.util.LinkedList;
import java.util.logging.Level;
public class SSHDConversationTracker {
private LinkedList<Conversation> conversationQueue = new LinkedList<>();
synchronized boolean beginConversation(Conversation conversation) {
if (!this.conversationQueue.contains(conversation)) {
this.conversationQueue.addLast(conversation);
if (this.conversationQueue.getFirst() == conversation) {
conversation.begin();
conversation.outputNextPrompt();
return true;
}
}
return true;
}
synchronized void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
if (!this.conversationQueue.isEmpty()) {
if (this.conversationQueue.getFirst() == conversation) {
conversation.abandon(details);
}
if (this.conversationQueue.contains(conversation)) {
this.conversationQueue.remove(conversation);
}
if (!this.conversationQueue.isEmpty()) {
this.conversationQueue.getFirst().outputNextPrompt();
}
}
}
public synchronized void abandonAllConversations() {
LinkedList<Conversation> oldQueue = this.conversationQueue;
this.conversationQueue = new LinkedList<>();
for (Conversation conversation : oldQueue) {
try {
conversation.abandon(new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
} catch (Throwable var5) {
Bukkit.getLogger().log(Level.SEVERE, "Unexpected exception while abandoning a conversation", var5);
}
}
}
synchronized void acceptConversationInput(String input) {
if (this.isConversing()) {
Conversation conversation = this.conversationQueue.getFirst();
try {
conversation.acceptInput(input);
} catch (Throwable var4) {
conversation.getContext().getPlugin().getLogger().log(Level.WARNING, String.format("Plugin %s generated an exception whilst handling conversation input", conversation.getContext().getPlugin().getDescription().getFullName()), var4);
}
}
}
synchronized boolean isConversing() {
return !this.conversationQueue.isEmpty();
}
public synchronized boolean isConversingModaly() {
return this.isConversing() && this.conversationQueue.getFirst().isModal();
}
}

View File

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