5 Commits

Author SHA1 Message Date
Justin Crawford
25287b1580 Fix Travis 2019-10-02 19:41:31 -07:00
Justin Crawford
0458179597 Add support for authorized_keys files.
Each user can have a set of authorized keys for public key authentication.
This is better to support as it lets us use different algorithms and not
just RSA. In the age of security, it's good to have variety.

I also added additional libraries to support ed25519-based public keys.

I updated the SSH libraries so any upstream bug fixes are applied, fixed
some warnings and a few other things.
2019-10-02 19:14:56 -07:00
Haarolean
dc76da9ac1 Version bump 2018-05-06 19:50:19 +03:00
Haarolean
7330b1eead Fix codestyle 2018-05-06 19:42:57 +03:00
Haarolean
8f6319d979 Fixed #10
Fixed #6 once more
Added exit command alias
2018-05-06 19:42:39 +03:00
19 changed files with 435 additions and 273 deletions

68
.clangformat Normal file
View File

@@ -0,0 +1,68 @@
---
#BasedOnStyle: WebKit
TabWidth: '4'
IndentWidth: '4'
UseTab: 'Always'
AlignOperands: 'true'
AlignAfterOpenBracket: 'Align'
AlignConsecutiveAssignments: 'true'
AlignConsecutiveDeclarations: 'true'
AlignEscapedNewlines: 'Left'
AlignTrailingComments: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'true'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: 'All'
AllowShortIfStatementsOnASingleLine: 'false'
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterReturnType: 'None'
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'
BreakBeforeBinaryOperators: 'true'
BreakBeforeTernaryOperators: 'false'
BreakConstructorInitializersBeforeComma: 'false'
BreakBeforeInheritanceComma: 'false'
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'
SortIncludes: 'true'
SortUsingDeclarations: 'true'
SpaceAfterCStyleCast: 'false'
SpaceAfterTemplateKeyword: 'false'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: 'false'
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInContainerLiterals: 'false'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: 'Auto'
...

View File

@@ -1,4 +1,5 @@
sudo: false sudo: false
dist: trusty
language: java language: java
jdk: jdk:
- oraclejdk8 - oraclejdk8

View File

@@ -1,6 +1,70 @@
Bukkit-SSHD Spigot-SSHD
=========== ===========
[![Build Status](https://travis-ci.org/rmichela/Bukkit-SSHD.png)](https://travis-ci.org/rmichela/Bukkit-SSHD) [![Build Status](https://travis-ci.org/Justasic/Spigot-SSHD.svg?branch=master)](https://travis-ci.org/Justasic/Spigot-SSHD)
An SSHD daemon embedded in a Bukkit plugin. 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.
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.
- 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.
- Remotely script your server by issuing one-off console commands with ssh.
## Why should I use 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 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
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.
On Windows
1. TODO
On Linux/OS X
1. TODO
## Commands
None - just install and go.
## Permissions
None - 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.

53
pom.xml
View File

@@ -5,9 +5,14 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.ryanmichela</groupId> <groupId>com.ryanmichela</groupId>
<artifactId>SSHD</artifactId> <artifactId>sshd</artifactId>
<version>1.3.4</version> <version>1.3.4.2</version>
<url>http://dev.bukkit.org/server-mods/sshd/</url> <url>https://github.com/Justasic/Bukkit-SSHD/</url>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- Repositories --> <!-- Repositories -->
<repositories> <repositories>
@@ -30,33 +35,57 @@
<dependency> <dependency>
<groupId>org.bukkit</groupId> <groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId> <artifactId>bukkit</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version> <version>1.14.4-R0.1-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.sshd</groupId> <groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId> <artifactId>sshd-core</artifactId>
<version>1.6.0</version> <version>2.3.0</version>
<scope>compile</scope> <scope>compile</scope>
<type>jar</type> <type>jar</type>
</dependency> </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-mina</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-common</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-sftp</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>net.i2p.crypto</groupId>
<artifactId>eddsa</artifactId>
<version>0.3.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.mina</groupId> <groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId> <artifactId>mina-core</artifactId>
<version>2.0.16</version> <version>2.1.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>1.7.25</version> <version>1.7.28</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId> <artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version> <version>1.7.28</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -85,10 +114,12 @@
<version>1.10</version> <version>1.10</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<!-- Build --> <!-- Build -->
<build> <build>
<defaultGoal>clean package</defaultGoal>
<resources> <resources>
<resource> <resource>
<targetPath>.</targetPath> <targetPath>.</targetPath>
@@ -104,7 +135,7 @@
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version> <version>3.1.1</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
@@ -126,7 +157,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version> <version>3.7.0</version>
<configuration> <configuration>
<source>1.8</source> <source>1.8</source>
<target>1.8</target> <target>1.8</target>

View File

@@ -10,6 +10,7 @@ import java.util.Map;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public class ConfigPasswordAuthenticator implements PasswordAuthenticator { public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
private Map<String, Integer> failCounts = new HashMap<String, Integer>(); private Map<String, Integer> failCounts = new HashMap<String, Integer>();
@Override @Override

View File

@@ -4,15 +4,16 @@ package com.ryanmichela.sshd;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
import jline.console.completer.Completer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandMap; import org.bukkit.command.CommandMap;
import jline.console.completer.Completer;
import java.util.List; import java.util.List;
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

View File

@@ -1,7 +1,8 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import org.apache.sshd.server.Command; import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.CommandFactory; 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.Environment;
import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.ExitCallback;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -14,12 +15,14 @@ import java.io.OutputStream;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public class ConsoleCommandFactory implements CommandFactory { public class ConsoleCommandFactory implements CommandFactory {
@Override @Override
public Command createCommand(String command) { public Command createCommand(ChannelSession cs, String command) {
return new ConsoleCommand(command); return new ConsoleCommand(command);
} }
public class ConsoleCommand implements Command { public class ConsoleCommand implements Command {
private String command; private String command;
private InputStream in; private InputStream in;
@@ -48,9 +51,10 @@ public class ConsoleCommandFactory implements CommandFactory {
} }
@Override @Override
public void start(Environment environment) throws IOException { public void start(ChannelSession cs, Environment environment) throws IOException {
try { try {
SshdPlugin.instance.getLogger().info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command); SshdPlugin.instance.getLogger()
.info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
} catch (Exception e) { } 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());
@@ -60,8 +64,6 @@ public class ConsoleCommandFactory implements CommandFactory {
} }
@Override @Override
public void destroy() { public void destroy(ChannelSession cn) {}
}
}
}
} }

View File

@@ -54,20 +54,29 @@ public class ConsoleLogFormatter extends Formatter {
// ORIGINAL CODE FROM org.bukkit.craftbukkit.command.ColouredConsoleSender // ORIGINAL CODE FROM org.bukkit.craftbukkit.command.ColouredConsoleSender
final Map<ChatColor, String> replacements = new EnumMap<>(ChatColor.class); final Map<ChatColor, String> replacements = new EnumMap<>(ChatColor.class);
replacements.put(ChatColor.BLACK, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).boldOff().toString()); replacements
replacements.put(ChatColor.DARK_BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString()); .put(ChatColor.BLACK, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).boldOff().toString());
replacements.put(ChatColor.DARK_GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).boldOff().toString()); replacements
replacements.put(ChatColor.DARK_AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).boldOff().toString()); .put(ChatColor.DARK_BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString());
replacements.put(ChatColor.DARK_RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).boldOff().toString()); replacements.put(ChatColor.DARK_GREEN,
replacements.put(ChatColor.DARK_PURPLE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).boldOff().toString()); Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).boldOff().toString());
replacements.put(ChatColor.GOLD, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).boldOff().toString()); replacements
.put(ChatColor.DARK_AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).boldOff().toString());
replacements
.put(ChatColor.DARK_RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).boldOff().toString());
replacements.put(ChatColor.DARK_PURPLE,
Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).boldOff().toString());
replacements
.put(ChatColor.GOLD, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).boldOff().toString());
replacements.put(ChatColor.GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).boldOff().toString()); replacements.put(ChatColor.GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).boldOff().toString());
replacements.put(ChatColor.DARK_GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).bold().toString()); replacements
.put(ChatColor.DARK_GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).bold().toString());
replacements.put(ChatColor.BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).bold().toString()); replacements.put(ChatColor.BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).bold().toString());
replacements.put(ChatColor.GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).bold().toString()); replacements.put(ChatColor.GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).bold().toString());
replacements.put(ChatColor.AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).bold().toString()); replacements.put(ChatColor.AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).bold().toString());
replacements.put(ChatColor.RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).bold().toString()); replacements.put(ChatColor.RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).bold().toString());
replacements.put(ChatColor.LIGHT_PURPLE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).bold().toString()); replacements.put(ChatColor.LIGHT_PURPLE,
Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).bold().toString());
replacements.put(ChatColor.YELLOW, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).bold().toString()); replacements.put(ChatColor.YELLOW, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).bold().toString());
replacements.put(ChatColor.WHITE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).bold().toString()); replacements.put(ChatColor.WHITE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).bold().toString());
replacements.put(ChatColor.MAGIC, Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString()); replacements.put(ChatColor.MAGIC, Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString());

View File

@@ -1,11 +1,20 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import com.ryanmichela.sshd.ConsoleCommandCompleter;
import com.ryanmichela.sshd.ConsoleLogFormatter;
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.implementations.SSHDCommandSender;
import jline.console.ConsoleReader; import jline.console.ConsoleReader;
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;
import org.apache.sshd.server.Command; import org.apache.sshd.server.shell.ShellFactory;
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.Environment;
import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.ExitCallback;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -16,19 +25,16 @@ import java.io.OutputStream;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.StreamHandler; import java.util.logging.StreamHandler;
public class ConsoleShellFactory implements Factory<Command> { public class ConsoleShellFactory implements ShellFactory {
static SSHDCommandSender sshdCommandSender = new SSHDCommandSender(); static SSHDCommandSender sshdCommandSender = new SSHDCommandSender();
public Command get() { public Command createShell(ChannelSession cs) {
return this.create();
}
public Command create() {
return new ConsoleShell(); return new ConsoleShell();
} }
public static class ConsoleShell implements Command, Runnable { public static class ConsoleShell implements Command, Runnable {
private InputStream in; private InputStream in;
private OutputStream out; private OutputStream out;
private OutputStream err; private OutputStream err;
@@ -71,56 +77,76 @@ public class ConsoleShellFactory implements Factory<Command> {
this.callback = callback; this.callback = callback;
} }
public void start(Environment env) throws IOException { @Override
try { public void start(ChannelSession cs, Environment env) throws IOException
consoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal()); {
consoleReader.setExpandEvents(true); try
consoleReader.addCompleter(new ConsoleCommandCompleter()); {
consoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal());
consoleReader.setExpandEvents(true);
consoleReader.addCompleter(new ConsoleCommandCompleter());
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), consoleReader); StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), consoleReader);
streamHandlerAppender = new StreamHandlerAppender(streamHandler); streamHandlerAppender = new StreamHandlerAppender(streamHandler);
((Logger) LogManager.getRootLogger()).addAppender(streamHandlerAppender); ((Logger)LogManager.getRootLogger()).addAppender(streamHandlerAppender);
environment = env; environment = env;
thread = new Thread(this, "SSHD ConsoleShell " + env.getEnv().get(Environment.ENV_USER)); thread = new Thread(this, "SSHD ConsoleShell " + env.getEnv().get(Environment.ENV_USER));
thread.start(); thread.start();
} catch (Exception e) { }
throw new IOException("Error starting shell", e); catch (Exception e)
} {
throw new IOException("Error starting shell", e);
}
} }
public void destroy() { @Override
((Logger) LogManager.getRootLogger()).removeAppender(streamHandlerAppender); public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(streamHandlerAppender); }
}
public void run() { public void run()
try { {
try
{
if (!SshdPlugin.instance.getConfig().getString("mode").equals("RPC")) if (!SshdPlugin.instance.getConfig().getString("mode").equals("RPC"))
printPreamble(consoleReader); printPreamble(consoleReader);
while (true) { while (true)
{
String command = consoleReader.readLine("\r>", null); String command = consoleReader.readLine("\r>", null);
if (command == null) continue; if (command == null)
if (command.equals("exit")) break; continue;
Bukkit.getScheduler().runTask(SshdPlugin.instance, () -> { if (command.equals("exit") || command.equals("quit"))
if (SshdPlugin.instance.getConfig().getString("mode").equals("RPC") && command.startsWith("rpc")) { break;
//NO ECHO NO PREAMBLE AND SHIT Bukkit.getScheduler().runTask(
String cmd = command.substring("rpc".length() + 1, command.length()); SshdPlugin.instance, () ->
Bukkit.dispatchCommand(sshdCommandSender, cmd); {
} else { if (SshdPlugin.instance.getConfig().getString("mode").equals("RPC") && command.startsWith("rpc"))
SshdPlugin.instance.getLogger().info("<" + environment.getEnv().get(Environment.ENV_USER) + "> " + command); {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); // NO ECHO NO PREAMBLE AND SHIT
} String cmd = command.substring("rpc".length() + 1, command.length());
}); Bukkit.dispatchCommand(sshdCommandSender, cmd);
}
else
{
SshdPlugin.instance.getLogger().info("<" + environment.getEnv().get(Environment.ENV_USER) + "> "
+ command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}
});
} }
} catch (IOException e) { }
catch (IOException e)
{
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e); SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
} finally { }
finally
{
callback.onExit(0); callback.onExit(0);
} }
} }
private void printPreamble(ConsoleReader consoleReader) throws IOException { private void printPreamble(ConsoleReader consoleReader) throws IOException
{
consoleReader.println(" _____ _____ _ _ _____" + "\r"); consoleReader.println(" _____ _____ _ _ _____" + "\r");
consoleReader.println(" / ____/ ____| | | | __ \\" + "\r"); consoleReader.println(" / ____/ ____| | | | __ \\" + "\r");
consoleReader.println("| (___| (___ | |__| | | | |" + "\r"); consoleReader.println("| (___| (___ | |__| | | | |" + "\r");
@@ -133,5 +159,5 @@ public class ConsoleShellFactory implements Factory<Command> {
consoleReader.println("Type 'exit' to exit the shell." + "\r"); consoleReader.println("Type 'exit' to exit the shell." + "\r");
consoleReader.println("===============================================" + "\r"); consoleReader.println("===============================================" + "\r");
} }
} }
} }

View File

@@ -1,5 +1,7 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import org.apache.sshd.common.SshException;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@@ -7,6 +9,7 @@ import java.io.OutputStream;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public class FlushyOutputStream extends OutputStream { public class FlushyOutputStream extends OutputStream {
private OutputStream base; private OutputStream base;
private boolean isClosed = false; private boolean isClosed = false;
@@ -16,23 +19,27 @@ public class FlushyOutputStream extends OutputStream {
@Override @Override
public void write(int b) throws IOException { public void write(int b) throws IOException {
if(isClosed) return; if (isClosed) return;
base.write(b); base.write(b);
base.flush(); base.flush();
} }
@Override @Override
public void write(byte[] b) throws IOException { public void write(byte[] b) throws IOException {
if(isClosed) return; if (isClosed) return;
base.write(b); base.write(b);
base.flush(); base.flush();
} }
@Override @Override
public void write(byte[] b, int off, int len) throws IOException { public void write(byte[] b, int off, int len) throws IOException {
if(isClosed) return; if (isClosed) return;
base.write(b, off, len); try {
base.flush(); base.write(b, off, len);
base.flush();
} catch (SshException e) {
if (!e.getMessage().contains("channel already closed")) throw e;
}
} }
@Override @Override

View File

@@ -1,8 +1,7 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import org.apache.sshd.common.SshException;
import jline.console.ConsoleReader; import jline.console.ConsoleReader;
import org.apache.sshd.common.SshException;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@@ -12,6 +11,7 @@ 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) {

View File

@@ -1,97 +0,0 @@
package com.ryanmichela.sshd;
import org.apache.commons.codec.binary.Base64;
import java.io.Reader;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
/**
* Copyright 2013 Ryan Michela
*/
public class PemDecoder extends java.io.BufferedReader {
private static final String BEGIN = "^-+\\s*BEGIN.+";
private static final String END = "^-+\\s*END.+";
private static final String COMMENT = "Comment:";
public PemDecoder(Reader in) {
super(in);
}
public PublicKey getPemBytes() throws Exception {
StringBuilder b64 = new StringBuilder();
String line = readLine();
if (!line.matches(BEGIN)) {
return null;
}
for(line = readLine(); line != null; line = readLine()) {
if (!line.matches(END) && !line.startsWith(COMMENT)) {
b64.append(line.trim());
}
}
return decodePublicKey(b64.toString());
}
private byte[] bytes;
private int pos;
private PublicKey decodePublicKey(String keyLine) throws Exception {
bytes = null;
pos = 0;
// look for the Base64 encoded part of the line to decode
// both ssh-rsa and ssh-dss begin with "AAAA" due to the length bytes
for (String part : keyLine.split(" ")) {
if (part.startsWith("AAAA")) {
bytes = Base64.decodeBase64(part.getBytes());
break;
}
}
if (bytes == null) {
throw new IllegalArgumentException("no Base64 part to decode");
}
String type = decodeType();
if (type.equals("ssh-rsa")) {
BigInteger e = decodeBigInt();
BigInteger m = decodeBigInt();
RSAPublicKeySpec spec = new RSAPublicKeySpec(m, e);
return KeyFactory.getInstance("RSA").generatePublic(spec);
} else if (type.equals("ssh-dss")) {
BigInteger p = decodeBigInt();
BigInteger q = decodeBigInt();
BigInteger g = decodeBigInt();
BigInteger y = decodeBigInt();
DSAPublicKeySpec spec = new DSAPublicKeySpec(y, p, q, g);
return KeyFactory.getInstance("DSA").generatePublic(spec);
} else {
throw new IllegalArgumentException("unknown type " + type);
}
}
private String decodeType() {
int len = decodeInt();
String type = new String(bytes, pos, len);
pos += len;
return type;
}
private int decodeInt() {
return ((bytes[pos++] & 0xFF) << 24) | ((bytes[pos++] & 0xFF) << 16)
| ((bytes[pos++] & 0xFF) << 8) | (bytes[pos++] & 0xFF);
}
private BigInteger decodeBigInt() {
int len = decodeInt();
byte[] bigIntBytes = new byte[len];
System.arraycopy(bytes, pos, bigIntBytes, 0, len);
pos += len;
return new BigInteger(bigIntBytes);
}
}

View File

@@ -1,50 +1,85 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
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.PublicKeyEntryResolver;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator; import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession; import org.apache.sshd.server.session.ServerSession;
import java.io.File; import java.io.File;
import java.util.List;
import java.io.FileReader; import java.io.FileReader;
import java.security.PublicKey; import java.security.PublicKey;
/** /**
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public class PublicKeyAuthenticator implements PublickeyAuthenticator { public class PublicKeyAuthenticator implements PublickeyAuthenticator
private File authorizedKeysDir; {
public PublicKeyAuthenticator(File authorizedKeysDir) { private File authorizedKeysDir;
this.authorizedKeysDir = authorizedKeysDir;
}
@Override public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
public boolean authenticate(String username, PublicKey key, ServerSession session) {
byte[] keyBytes = key.getEncoded();
File keyFile = new File(authorizedKeysDir, username);
if (keyFile.exists()) { @Override public boolean authenticate(String username, PublicKey key, ServerSession session)
try { {
byte[] keyBytes = key.getEncoded();
File keyFile = new File(authorizedKeysDir, username);
FileReader fr = new FileReader(keyFile); if (keyFile.exists())
PemDecoder pd = new PemDecoder(fr); {
PublicKey k = pd.getPemBytes(); try
pd.close(); {
List<AuthorizedKeyEntry> pklist = AuthorizedKeyEntry.readAuthorizedKeys(keyFile.toPath());
PublickeyAuthenticator auth = PublickeyAuthenticator.fromAuthorizedEntries(username, session, pklist,
PublicKeyEntryResolver.IGNORING);
if (k != null) { boolean accepted = auth.authenticate(username, key, session);
if (ArrayUtils.isEquals(key.getEncoded(), k.getEncoded())) {
return true; if (accepted)
} {
} else { SshdPlugin.instance.getLogger().info(
SshdPlugin.instance.getLogger().severe("Failed to parse PEM file. " + keyFile.getAbsolutePath()); username + " successfully authenticated via SSH session using key file " + keyFile.getAbsolutePath());
}
else
{
SshdPlugin.instance.getLogger().info(
username + " failed authentication via SSH session using key file " + keyFile.getAbsolutePath());
}
return accepted;
/*
FileReader fr = new FileReader(keyFile);
PemDecoder pd = new PemDecoder(fr);
PublicKey k = pd.getPemBytes();
pd.close();
if (k != null)
{
if (ArrayUtils.isEquals(key.getEncoded(), k.getEncoded()))
{
return true;
}
}
else
{
SshdPlugin.instance.getLogger().severe("Failed to parse PEM file. " + keyFile.getAbsolutePath());
} }
} catch (Exception e) { */
SshdPlugin.instance.getLogger().severe("Failed to process public key " + keyFile.getAbsolutePath() + ". " + e.getMessage()); }
} catch (Exception e)
} else { {
SshdPlugin.instance.getLogger().warning("Could not locate public key for " + username + ". Make sure the user's key is named the same as their user name without a file extension."); SshdPlugin.instance.getLogger().severe("Failed to process public key " + keyFile.getAbsolutePath() + " " + e.getMessage());
} }
}
else
{
SshdPlugin.instance.getLogger().warning("Could not locate public key for " + username
+ ". Make sure the user's key is named the same as their user name "
+ "without a file extension.");
}
return false; return false;
} }
} }

View File

@@ -8,6 +8,7 @@ import java.lang.reflect.Modifier;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public class ReflectionUtil { public class ReflectionUtil {
public static void setProtectedValue(Object o, String field, Object newValue) { public static void setProtectedValue(Object o, String field, Object newValue) {
setProtectedValue(o.getClass(), o, field, newValue); setProtectedValue(o.getClass(), o, field, newValue);
} }
@@ -36,9 +37,9 @@ public class ReflectionUtil {
public static <T> T getProtectedValue(Object obj, String fieldName) { public static <T> T getProtectedValue(Object obj, String fieldName) {
try { try {
Class c = obj.getClass(); Class c = obj.getClass();
while(c != Object.class) { while (c != Object.class) {
Field[] fields = c.getDeclaredFields(); Field[] fields = c.getDeclaredFields();
for(Field f : fields) { for (Field f : fields) {
if (f.getName() == fieldName) { if (f.getName() == fieldName) {
f.setAccessible(true); f.setAccessible(true);
return (T) f.get(obj); return (T) f.get(obj);
@@ -76,7 +77,7 @@ public class ReflectionUtil {
public static Object invokeProtectedMethod(Class c, Object o, String method, Object... args) { public static Object invokeProtectedMethod(Class c, Object o, String method, Object... args) {
try { try {
Class[] pTypes = new Class[args.length]; Class[] pTypes = new Class[args.length];
for(int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Integer) { if (args[i] instanceof Integer) {
pTypes[i] = int.class; pTypes[i] = int.class;
} else { } else {
@@ -87,8 +88,7 @@ public class ReflectionUtil {
Method m = c.getDeclaredMethod(method, pTypes); Method m = c.getDeclaredMethod(method, pTypes);
m.setAccessible(true); m.setAccessible(true);
return m.invoke(o, args); return m.invoke(o, args);
} } catch (Exception ex) {
catch (Exception ex) {
System.out.println("*** " + c.getName() + "." + method + "(): " + ex); System.out.println("*** " + c.getName() + "." + method + "(): " + ex);
return null; return null;
} }

View File

@@ -6,6 +6,8 @@ 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 org.bukkit.plugin.java.JavaPlugin;
import com.ryanmichela.sshd.ConsoleShellFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
@@ -15,64 +17,72 @@ import java.util.logging.Level;
/** /**
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public class SshdPlugin extends JavaPlugin { public
private SshServer sshd; class SshdPlugin extends JavaPlugin
public static SshdPlugin instance; {
@Override private SshServer sshd;
public void onLoad() { public static SshdPlugin instance;
saveDefaultConfig();
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
if (!authorizedKeys.exists()) {
authorizedKeys.mkdirs();
}
// Don't go any lower than INFO or SSHD will cause a stack overflow exception. @Override public void onLoad()
// SSHD will log that it wrote bites to the output stream, which writes {
// bytes to the output stream - ad nauseaum. saveDefaultConfig();
getLogger().setLevel(Level.INFO); File authorizedKeys = new File(getDataFolder(), "authorized_keys");
} if (!authorizedKeys.exists())
{
authorizedKeys.mkdirs();
}
@Override // Don't go any lower than INFO or SSHD will cause a stack overflow exception.
public void onEnable() { // SSHD will log that it wrote bites to the output stream, which writes
instance = this; // bytes to the output stream - ad nauseaum.
getLogger().setLevel(Level.INFO);
}
sshd = SshServer.setUpDefaultServer(); @Override public void onEnable()
sshd.setPort(getConfig().getInt("port", 22)); {
String host = getConfig().getString("listenAddress", "all"); instance = this;
sshd.setHost(host.equals("all") ? null : host);
File hostKey = new File(getDataFolder(), "hostkey"); sshd = SshServer.setUpDefaultServer();
File authorizedKeys = new File(getDataFolder(), "authorized_keys"); sshd.setPort(getConfig().getInt("port", 22));
String host = getConfig().getString("listenAddress", "all");
sshd.setHost(host.equals("all") ? null : host);
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey)); File hostKey = new File(getDataFolder(), "hostkey");
sshd.setShellFactory(new ConsoleShellFactory()); File authorizedKeys = new File(getDataFolder(), "authorized_keys");
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
if (getConfig().getBoolean("enableSFTP")) { sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey.toPath()));
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory())); sshd.setShellFactory(new ConsoleShellFactory());
sshd.setFileSystemFactory(new VirtualFileSystemFactory( sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
FileSystems.getDefault().getPath( sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
getDataFolder().getAbsolutePath()
).getParent().getParent()
));
}
sshd.setCommandFactory(new ConsoleCommandFactory()); if (getConfig().getBoolean("enableSFTP"))
try { {
sshd.start(); sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
} catch (IOException e) { sshd.setFileSystemFactory(
getLogger().log(Level.SEVERE, "Failed to start SSH server! ", e); new VirtualFileSystemFactory(FileSystems.getDefault().getPath(getDataFolder().getAbsolutePath()).getParent().getParent()));
} }
}
@Override sshd.setCommandFactory(new ConsoleCommandFactory());
public void onDisable() { try
try { {
sshd.stop(); sshd.start();
} catch (Exception e) { }
// do nothing catch (IOException e)
} {
} getLogger().log(Level.SEVERE, "Failed to start SSH server! ", e);
}
}
@Override public void onDisable()
{
try
{
sshd.stop();
}
catch (Exception e)
{
// do nothing
}
}
} }

View File

@@ -14,6 +14,7 @@ import java.util.logging.StreamHandler;
* Copyright 2014 Ryan Michela * Copyright 2014 Ryan Michela
*/ */
public class StreamHandlerAppender implements Appender { public class StreamHandlerAppender implements Appender {
private StreamHandler streamHandler; private StreamHandler streamHandler;
private UUID uuid; private UUID uuid;
@@ -26,13 +27,13 @@ public class StreamHandlerAppender implements Appender {
public void append(LogEvent logEvent) { public void append(LogEvent logEvent) {
java.util.logging.Level level; java.util.logging.Level level;
if(logEvent.getLevel().equals(org.apache.logging.log4j.Level.DEBUG)) { if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.DEBUG)) {
level = java.util.logging.Level.FINE; level = java.util.logging.Level.FINE;
} else if(logEvent.getLevel().equals(org.apache.logging.log4j.Level.INFO)) { } else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.INFO)) {
level = java.util.logging.Level.INFO; level = java.util.logging.Level.INFO;
} else if(logEvent.getLevel().equals(org.apache.logging.log4j.Level.WARN)) { } else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.WARN)) {
level = java.util.logging.Level.WARNING; level = java.util.logging.Level.WARNING;
} else if(logEvent.getLevel().equals(org.apache.logging.log4j.Level.ERROR)) { } else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.ERROR)) {
level = java.util.logging.Level.SEVERE; level = java.util.logging.Level.SEVERE;
} else { } else {
level = java.util.logging.Level.INFO; level = java.util.logging.Level.INFO;

View File

@@ -6,11 +6,13 @@ import java.util.concurrent.ExecutionException;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
public abstract class Waitable<T> implements Runnable { public abstract class Waitable<T> implements Runnable {
private enum Status { private enum Status {
WAITING, WAITING,
RUNNING, RUNNING,
FINISHED, FINISHED,
} }
Throwable t = null; Throwable t = null;
T value = null; T value = null;
Status status = Status.WAITING; Status status = Status.WAITING;

View File

@@ -1,6 +1,5 @@
package com.ryanmichela.sshd.implementations; package com.ryanmichela.sshd.implementations;
import com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
@@ -16,6 +15,8 @@ import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.ryanmichela.sshd.ConsoleShellFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set; import java.util.Set;
@@ -44,7 +45,7 @@ public class SSHDCommandSender implements ConsoleCommandSender, CommandSender {
} }
public String getName() { public String getName() {
return "SSHD CONSOLE"; return "Console";
} }
public boolean isOp() { public boolean isOp() {

View File

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